From a73e7c7886dfbc6db1e167f36fd430782dbab662 Mon Sep 17 00:00:00 2001 From: Geoff Franks Date: Tue, 1 Apr 2025 19:04:14 +0000 Subject: [PATCH] Convert to use nats-binary blob --- packages/nats-server/packaging | 17 +- packages/nats-server/spec | 65 +- src/code.cloudfoundry.org/go.mod | 6 - src/code.cloudfoundry.org/go.sum | 17 - .../nats-server/nats-server.go | 8 - .../integration/helpers/nats_runner.go | 2 +- .../vendor/github.com/google/go-tpm/LICENSE | 202 - .../google/go-tpm/legacy/tpm2/README.md | 35 - .../google/go-tpm/legacy/tpm2/constants.go | 575 - .../google/go-tpm/legacy/tpm2/error.go | 362 - .../google/go-tpm/legacy/tpm2/kdf.go | 116 - .../google/go-tpm/legacy/tpm2/open_other.go | 57 - .../google/go-tpm/legacy/tpm2/open_windows.go | 39 - .../google/go-tpm/legacy/tpm2/structures.go | 1112 - .../google/go-tpm/legacy/tpm2/tpm2.go | 2326 -- .../google/go-tpm/tpmutil/encoding.go | 211 - .../google/go-tpm/tpmutil/poll_other.go | 10 - .../google/go-tpm/tpmutil/poll_unix.go | 32 - .../github.com/google/go-tpm/tpmutil/run.go | 113 - .../google/go-tpm/tpmutil/run_other.go | 111 - .../google/go-tpm/tpmutil/run_windows.go | 84 - .../google/go-tpm/tpmutil/structures.go | 195 - .../google/go-tpm/tpmutil/tbs/tbs_windows.go | 267 - .../compress/internal/race/norace.go | 13 - .../klauspost/compress/internal/race/race.go | 26 - .../klauspost/compress/s2/.gitignore | 15 - .../github.com/klauspost/compress/s2/LICENSE | 28 - .../klauspost/compress/s2/README.md | 1120 - .../klauspost/compress/s2/decode.go | 443 - .../klauspost/compress/s2/decode_amd64.s | 568 - .../klauspost/compress/s2/decode_arm64.s | 574 - .../klauspost/compress/s2/decode_asm.go | 17 - .../klauspost/compress/s2/decode_other.go | 288 - .../github.com/klauspost/compress/s2/dict.go | 350 - .../klauspost/compress/s2/encode.go | 414 - .../klauspost/compress/s2/encode_all.go | 1480 -- .../klauspost/compress/s2/encode_amd64.go | 317 - .../klauspost/compress/s2/encode_best.go | 796 - .../klauspost/compress/s2/encode_better.go | 1510 -- .../klauspost/compress/s2/encode_go.go | 741 - .../compress/s2/encodeblock_amd64.go | 228 - .../klauspost/compress/s2/encodeblock_amd64.s | 21303 ---------------- .../github.com/klauspost/compress/s2/index.go | 602 - .../klauspost/compress/s2/lz4convert.go | 585 - .../klauspost/compress/s2/lz4sconvert.go | 467 - .../klauspost/compress/s2/reader.go | 1075 - .../github.com/klauspost/compress/s2/s2.go | 151 - .../klauspost/compress/s2/writer.go | 1064 - .../github.com/minio/highwayhash/.gitignore | 1 - .../minio/highwayhash/.golangci.yml | 28 - .../github.com/minio/highwayhash/LICENSE | 202 - .../github.com/minio/highwayhash/README.md | 99 - .../minio/highwayhash/highwayhash.go | 225 - .../minio/highwayhash/highwayhashAVX2_amd64.s | 248 - .../minio/highwayhash/highwayhashSVE_arm64.s | 132 - .../minio/highwayhash/highwayhash_amd64.go | 70 - .../minio/highwayhash/highwayhash_amd64.s | 294 - .../minio/highwayhash/highwayhash_arm64.go | 81 - .../minio/highwayhash/highwayhash_arm64.s | 324 - .../minio/highwayhash/highwayhash_generic.go | 338 - .../minio/highwayhash/highwayhash_ppc64le.go | 36 - .../minio/highwayhash/highwayhash_ppc64le.s | 182 - .../minio/highwayhash/highwayhash_ref.go | 29 - .../vendor/github.com/nats-io/jwt/v2/LICENSE | 201 - .../vendor/github.com/nats-io/jwt/v2/Makefile | 18 - .../nats-io/jwt/v2/account_claims.go | 482 - .../nats-io/jwt/v2/activation_claims.go | 182 - .../nats-io/jwt/v2/authorization_claims.go | 255 - .../github.com/nats-io/jwt/v2/claims.go | 299 - .../github.com/nats-io/jwt/v2/creds_utils.go | 266 - .../github.com/nats-io/jwt/v2/decoder.go | 164 - .../nats-io/jwt/v2/decoder_account.go | 87 - .../nats-io/jwt/v2/decoder_activation.go | 77 - .../nats-io/jwt/v2/decoder_authorization.go | 36 - .../nats-io/jwt/v2/decoder_operator.go | 73 - .../github.com/nats-io/jwt/v2/decoder_user.go | 81 - .../github.com/nats-io/jwt/v2/exports.go | 317 - .../github.com/nats-io/jwt/v2/genericlaims.go | 161 - .../github.com/nats-io/jwt/v2/header.go | 78 - .../github.com/nats-io/jwt/v2/imports.go | 172 - .../nats-io/jwt/v2/operator_claims.go | 257 - .../nats-io/jwt/v2/revocation_list.go | 84 - .../github.com/nats-io/jwt/v2/signingkeys.go | 213 - .../vendor/github.com/nats-io/jwt/v2/types.go | 495 - .../github.com/nats-io/jwt/v2/user_claims.go | 162 - .../github.com/nats-io/jwt/v2/validation.go | 118 - .../nats-io/nats-server/v2/.coveralls.yml | 1 - .../nats-io/nats-server/v2/.gitignore | 55 - .../nats-io/nats-server/v2/.golangci.yml | 71 - .../nats-server/v2/.goreleaser-nightly.yml | 40 - .../nats-io/nats-server/v2/.goreleaser.yml | 95 - .../nats-io/nats-server/v2/.travis.yml | 55 - .../nats-io/nats-server/v2/AMBASSADORS.md | 5 - .../nats-io/nats-server/v2/CODE-OF-CONDUCT.md | 3 - .../nats-io/nats-server/v2/CONTRIBUTING.md | 45 - .../nats-io/nats-server/v2/DEPENDENCIES.md | 19 - .../nats-io/nats-server/v2/GOVERNANCE.md | 3 - .../github.com/nats-io/nats-server/v2/LICENSE | 201 - .../nats-io/nats-server/v2/MAINTAINERS.md | 10 - .../nats-io/nats-server/v2/README.md | 76 - .../github.com/nats-io/nats-server/v2/TODO.md | 54 - .../nats-io/nats-server/v2/conf/fuzz.go | 24 - .../nats-io/nats-server/v2/conf/lex.go | 1319 - .../nats-io/nats-server/v2/conf/parse.go | 497 - .../nats-io/nats-server/v2/conf/simple.conf | 6 - .../nats-server/v2/internal/fastrand/LICENSE | 27 - .../v2/internal/fastrand/fastrand.go | 23 - .../nats-server/v2/internal/ldap/dn.go | 305 - .../nats-io/nats-server/v2/locksordering.txt | 32 - .../nats-io/nats-server/v2/logger/log.go | 405 - .../nats-io/nats-server/v2/logger/syslog.go | 132 - .../nats-server/v2/logger/syslog_windows.go | 112 - .../github.com/nats-io/nats-server/v2/main.go | 140 - .../nats-server/v2/server/README-MQTT.md | 595 - .../nats-io/nats-server/v2/server/README.md | 17 - .../nats-io/nats-server/v2/server/accounts.go | 4655 ---- .../nats-io/nats-server/v2/server/auth.go | 1535 -- .../nats-server/v2/server/auth_callout.go | 480 - .../nats-server/v2/server/avl/seqset.go | 678 - .../nats-server/v2/server/certidp/certidp.go | 312 - .../nats-server/v2/server/certidp/messages.go | 106 - .../v2/server/certidp/ocsp_responder.go | 84 - .../v2/server/certstore/certstore.go | 104 - .../v2/server/certstore/certstore_other.go | 45 - .../v2/server/certstore/certstore_windows.go | 965 - .../nats-server/v2/server/certstore/errors.go | 79 - .../nats-server/v2/server/ciphersuites.go | 103 - .../nats-io/nats-server/v2/server/client.go | 6263 ----- .../nats-io/nats-server/v2/server/const.go | 240 - .../nats-io/nats-server/v2/server/consumer.go | 6118 ----- .../nats-io/nats-server/v2/server/dirstore.go | 724 - .../nats-server/v2/server/disk_avail.go | 37 - .../v2/server/disk_avail_netbsd.go | 21 - .../v2/server/disk_avail_openbsd.go | 37 - .../nats-server/v2/server/disk_avail_wasm.go | 20 - .../v2/server/disk_avail_windows.go | 21 - .../nats-io/nats-server/v2/server/errors.go | 396 - .../nats-io/nats-server/v2/server/errors.json | 1662 -- .../nats-io/nats-server/v2/server/events.go | 3241 --- .../nats-server/v2/server/filestore.go | 10857 -------- .../nats-io/nats-server/v2/server/fuzz.go | 47 - .../nats-io/nats-server/v2/server/gateway.go | 3377 --- .../nats-io/nats-server/v2/server/gsl/gsl.go | 532 - .../nats-io/nats-server/v2/server/ipqueue.go | 286 - .../nats-server/v2/server/jetstream.go | 3046 --- .../nats-server/v2/server/jetstream_api.go | 5076 ---- .../v2/server/jetstream_cluster.go | 9309 ------- .../nats-server/v2/server/jetstream_errors.go | 102 - .../v2/server/jetstream_errors_generated.go | 2628 -- .../nats-server/v2/server/jetstream_events.go | 345 - .../v2/server/jetstream_versioning.go | 179 - .../nats-io/nats-server/v2/server/jwt.go | 228 - .../nats-io/nats-server/v2/server/leafnode.go | 3190 --- .../nats-io/nats-server/v2/server/log.go | 283 - .../nats-io/nats-server/v2/server/memstore.go | 2274 -- .../nats-io/nats-server/v2/server/monitor.go | 4014 --- .../v2/server/monitor_sort_opts.go | 156 - .../nats-io/nats-server/v2/server/mqtt.go | 5789 ----- .../nats-io/nats-server/v2/server/msgtrace.go | 846 - .../nats-io/nats-server/v2/server/nkey.go | 47 - .../nats-io/nats-server/v2/server/ocsp.go | 992 - .../nats-server/v2/server/ocsp_peer.go | 405 - .../v2/server/ocsp_responsecache.go | 636 - .../nats-io/nats-server/v2/server/opts.go | 6019 ----- .../nats-io/nats-server/v2/server/parser.go | 1309 - .../nats-io/nats-server/v2/server/proto.go | 269 - .../nats-server/v2/server/pse/freebsd.txt | 30 - .../nats-server/v2/server/pse/pse_darwin.go | 86 - .../v2/server/pse/pse_dragonfly.go | 36 - .../nats-server/v2/server/pse/pse_freebsd.go | 85 - .../v2/server/pse/pse_freebsd_amd64.go | 91 - .../nats-server/v2/server/pse/pse_linux.go | 126 - .../nats-server/v2/server/pse/pse_netbsd.go | 36 - .../nats-server/v2/server/pse/pse_openbsd.go | 36 - .../nats-server/v2/server/pse/pse_rumprun.go | 25 - .../nats-server/v2/server/pse/pse_solaris.go | 23 - .../nats-server/v2/server/pse/pse_wasm.go | 25 - .../nats-server/v2/server/pse/pse_windows.go | 296 - .../nats-server/v2/server/pse/pse_zos.go | 25 - .../nats-io/nats-server/v2/server/raft.go | 4369 ---- .../nats-server/v2/server/rate_counter.go | 65 - .../nats-io/nats-server/v2/server/reload.go | 2478 -- .../nats-io/nats-server/v2/server/ring.go | 77 - .../nats-io/nats-server/v2/server/route.go | 3225 --- .../nats-io/nats-server/v2/server/sendq.go | 118 - .../nats-io/nats-server/v2/server/server.go | 4593 ---- .../nats-io/nats-server/v2/server/service.go | 28 - .../nats-server/v2/server/service_windows.go | 138 - .../nats-io/nats-server/v2/server/signal.go | 207 - .../nats-server/v2/server/signal_wasm.go | 24 - .../nats-server/v2/server/signal_windows.go | 110 - .../nats-io/nats-server/v2/server/store.go | 797 - .../nats-io/nats-server/v2/server/stream.go | 6553 ----- .../nats-server/v2/server/stree/dump.go | 70 - .../nats-server/v2/server/stree/leaf.go | 51 - .../nats-server/v2/server/stree/node.go | 53 - .../nats-server/v2/server/stree/node10.go | 106 - .../nats-server/v2/server/stree/node16.go | 104 - .../nats-server/v2/server/stree/node256.go | 80 - .../nats-server/v2/server/stree/node4.go | 99 - .../nats-server/v2/server/stree/node48.go | 110 - .../nats-server/v2/server/stree/parts.go | 141 - .../nats-server/v2/server/stree/stree.go | 420 - .../nats-server/v2/server/stree/util.go | 57 - .../v2/server/subject_transform.go | 632 - .../nats-io/nats-server/v2/server/sublist.go | 1786 -- .../nats-server/v2/server/sysmem/mem_bsd.go | 20 - .../v2/server/sysmem/mem_darwin.go | 20 - .../nats-server/v2/server/sysmem/mem_linux.go | 27 - .../nats-server/v2/server/sysmem/mem_wasm.go | 21 - .../v2/server/sysmem/mem_windows.go | 51 - .../nats-server/v2/server/sysmem/mem_zos.go | 21 - .../nats-server/v2/server/sysmem/sysctl.go | 33 - .../nats-io/nats-server/v2/server/thw/thw.go | 257 - .../v2/server/tpm/js_ek_tpm_other.go | 23 - .../v2/server/tpm/js_ek_tpm_windows.go | 281 - .../nats-io/nats-server/v2/server/util.go | 342 - .../nats-server/v2/server/websocket.go | 1529 -- .../vendor/go.uber.org/automaxprocs/LICENSE | 19 - .../automaxprocs/internal/cgroups/cgroup.go | 79 - .../automaxprocs/internal/cgroups/cgroups.go | 118 - .../automaxprocs/internal/cgroups/cgroups2.go | 176 - .../automaxprocs/internal/cgroups/doc.go | 23 - .../automaxprocs/internal/cgroups/errors.go | 52 - .../internal/cgroups/mountpoint.go | 171 - .../automaxprocs/internal/cgroups/subsys.go | 103 - .../internal/runtime/cpu_quota_linux.go | 75 - .../internal/runtime/cpu_quota_unsupported.go | 31 - .../automaxprocs/internal/runtime/runtime.go | 40 - .../automaxprocs/maxprocs/maxprocs.go | 139 - .../automaxprocs/maxprocs/version.go | 24 - .../chacha20poly1305/chacha20poly1305.go | 98 - .../chacha20poly1305_amd64.go | 86 - .../chacha20poly1305/chacha20poly1305_amd64.s | 9762 ------- .../chacha20poly1305_generic.go | 81 - .../chacha20poly1305_noasm.go | 15 - .../chacha20poly1305/xchacha20poly1305.go | 86 - .../golang.org/x/crypto/cryptobyte/asn1.go | 825 - .../x/crypto/cryptobyte/asn1/asn1.go | 46 - .../golang.org/x/crypto/cryptobyte/builder.go | 350 - .../golang.org/x/crypto/cryptobyte/string.go | 183 - .../vendor/golang.org/x/crypto/ocsp/ocsp.go | 793 - .../vendor/golang.org/x/time/LICENSE | 27 - .../vendor/golang.org/x/time/PATENTS | 22 - .../vendor/golang.org/x/time/rate/rate.go | 427 - .../golang.org/x/time/rate/sometimes.go | 67 - src/code.cloudfoundry.org/vendor/modules.txt | 42 - 247 files changed, 8 insertions(+), 185226 deletions(-) delete mode 100644 src/code.cloudfoundry.org/nats-server/nats-server.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/LICENSE delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/README.md delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/constants.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/error.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/kdf.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/open_other.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/open_windows.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/structures.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/tpm2.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/encoding.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/poll_other.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/poll_unix.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/run.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/run_other.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/run_windows.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/structures.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/tbs/tbs_windows.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/internal/race/norace.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/internal/race/race.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/.gitignore delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/LICENSE delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/README.md delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_amd64.s delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_arm64.s delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_asm.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_other.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/dict.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_all.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_amd64.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_best.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_better.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_go.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encodeblock_amd64.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encodeblock_amd64.s delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/index.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/lz4convert.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/lz4sconvert.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/reader.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/s2.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/writer.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/.gitignore delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/.golangci.yml delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/LICENSE delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/README.md delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhashAVX2_amd64.s delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhashSVE_arm64.s delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_amd64.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_amd64.s delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_arm64.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_arm64.s delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_generic.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_ppc64le.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_ppc64le.s delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_ref.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/LICENSE delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/Makefile delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/account_claims.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/activation_claims.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/authorization_claims.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/claims.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/creds_utils.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_account.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_activation.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_authorization.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_operator.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_user.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/exports.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/genericlaims.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/header.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/imports.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/operator_claims.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/revocation_list.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/signingkeys.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/types.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/user_claims.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/validation.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.coveralls.yml delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.gitignore delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.golangci.yml delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.goreleaser-nightly.yml delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.goreleaser.yml delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.travis.yml delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/AMBASSADORS.md delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/CODE-OF-CONDUCT.md delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/CONTRIBUTING.md delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/DEPENDENCIES.md delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/GOVERNANCE.md delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/LICENSE delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/MAINTAINERS.md delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/README.md delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/TODO.md delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/fuzz.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/lex.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/parse.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/simple.conf delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/internal/fastrand/LICENSE delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/internal/fastrand/fastrand.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/internal/ldap/dn.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/locksordering.txt delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/logger/log.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/logger/syslog.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/logger/syslog_windows.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/main.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/README-MQTT.md delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/README.md delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/accounts.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/auth.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/auth_callout.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/avl/seqset.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certidp/certidp.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certidp/messages.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certidp/ocsp_responder.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/certstore.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/certstore_other.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/certstore_windows.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/errors.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ciphersuites.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/client.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/const.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/consumer.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/dirstore.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_netbsd.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_openbsd.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_wasm.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_windows.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/errors.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/errors.json delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/events.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/filestore.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/fuzz.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/gateway.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/gsl/gsl.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ipqueue.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_api.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_errors.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_errors_generated.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_events.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_versioning.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jwt.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/leafnode.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/log.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/memstore.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/monitor.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/monitor_sort_opts.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/mqtt.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/msgtrace.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/nkey.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ocsp.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ocsp_peer.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ocsp_responsecache.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/opts.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/parser.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/proto.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/freebsd.txt delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_darwin.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_dragonfly.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_freebsd.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_freebsd_amd64.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_linux.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_netbsd.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_openbsd.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_rumprun.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_solaris.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_wasm.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_windows.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_zos.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/raft.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/rate_counter.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/reload.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ring.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/route.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sendq.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/server.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/service.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/service_windows.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/signal.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/signal_wasm.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/signal_windows.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/store.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stream.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/dump.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/leaf.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node10.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node16.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node256.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node4.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node48.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/parts.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/util.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/subject_transform.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sublist.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_bsd.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_darwin.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_linux.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_wasm.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_windows.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_zos.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/sysctl.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/thw/thw.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/tpm/js_ek_tpm_other.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/tpm/js_ek_tpm_windows.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/util.go delete mode 100644 src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/websocket.go delete mode 100644 src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/LICENSE delete mode 100644 src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroup.go delete mode 100644 src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups.go delete mode 100644 src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups2.go delete mode 100644 src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/doc.go delete mode 100644 src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/errors.go delete mode 100644 src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/mountpoint.go delete mode 100644 src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/subsys.go delete mode 100644 src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go delete mode 100644 src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go delete mode 100644 src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/runtime/runtime.go delete mode 100644 src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/maxprocs/maxprocs.go delete mode 100644 src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/maxprocs/version.go delete mode 100644 src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305.go delete mode 100644 src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.go delete mode 100644 src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.s delete mode 100644 src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_generic.go delete mode 100644 src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_noasm.go delete mode 100644 src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/xchacha20poly1305.go delete mode 100644 src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/asn1.go delete mode 100644 src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/asn1/asn1.go delete mode 100644 src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/builder.go delete mode 100644 src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/string.go delete mode 100644 src/code.cloudfoundry.org/vendor/golang.org/x/crypto/ocsp/ocsp.go delete mode 100644 src/code.cloudfoundry.org/vendor/golang.org/x/time/LICENSE delete mode 100644 src/code.cloudfoundry.org/vendor/golang.org/x/time/PATENTS delete mode 100644 src/code.cloudfoundry.org/vendor/golang.org/x/time/rate/rate.go delete mode 100644 src/code.cloudfoundry.org/vendor/golang.org/x/time/rate/sometimes.go diff --git a/packages/nats-server/packaging b/packages/nats-server/packaging index 8b7e5dd8..bfc45c78 100755 --- a/packages/nats-server/packaging +++ b/packages/nats-server/packaging @@ -1,16 +1,9 @@ #!/usr/bin/env bash -set -ex +set -e -mkdir -p ${BOSH_INSTALL_TARGET}/src -mv * ${BOSH_INSTALL_TARGET}/src -mv ${BOSH_INSTALL_TARGET}/src . +mkdir -p ${BOSH_INSTALL_TARGET}/bin -export GOBIN=${BOSH_INSTALL_TARGET}/bin -mkdir -p "${GOBIN}" - -source /var/vcap/packages/golang-*-linux/bosh/compile.env - -pushd "src/code.cloudfoundry.org" - go build -o "${GOBIN}/nats-server" github.com/nats-io/nats-server/v2 -popd +tar xzf nats-server-gh-release/nats-server-v*-linux-amd64.tar.gz +cp nats-server-v*-linux-amd64/nats-server ${BOSH_INSTALL_TARGET}/bin/nats-server +chmod +x ${BOSH_INSTALL_TARGET}/bin/nats-server diff --git a/packages/nats-server/spec b/packages/nats-server/spec index fce3bbe3..06045bc9 100644 --- a/packages/nats-server/spec +++ b/packages/nats-server/spec @@ -1,68 +1,7 @@ --- name: nats-server -dependencies: - - golang-1.24-linux +dependencies: [] files: - - code.cloudfoundry.org/go.mod - - code.cloudfoundry.org/go.sum - - code.cloudfoundry.org/vendor/modules.txt - - code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/tbs/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/klauspost/compress/flate/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/klauspost/compress/internal/le/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/klauspost/compress/internal/race/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/*.s # gosub - - code.cloudfoundry.org/vendor/github.com/minio/highwayhash/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/minio/highwayhash/*.s # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/internal/fastrand/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/internal/ldap/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/logger/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/avl/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certidp/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/gsl/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/thw/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/tpm/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nkeys/*.go # gosub - - code.cloudfoundry.org/vendor/github.com/nats-io/nuid/*.go # gosub - - code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/*.go # gosub - - code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/runtime/*.go # gosub - - code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/maxprocs/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/bcrypt/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/blake2b/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/blake2b/*.s # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/blowfish/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20/*.s # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/*.s # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/asn1/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/curve25519/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/internal/alias/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/internal/poly1305/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/internal/poly1305/*.s # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/nacl/box/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/nacl/secretbox/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/ocsp/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/salsa20/salsa/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/crypto/salsa20/salsa/*.s # gosub - - code.cloudfoundry.org/vendor/golang.org/x/sys/cpu/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/sys/cpu/*.s # gosub - - code.cloudfoundry.org/vendor/golang.org/x/sys/windows/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/sys/windows/registry/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/sys/windows/svc/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/sys/windows/svc/eventlog/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/sys/windows/svc/mgr/*.go # gosub - - code.cloudfoundry.org/vendor/golang.org/x/time/rate/*.go # gosub +- nats-server-gh-release/nats-server-v*-linux-amd64.tar.gz diff --git a/src/code.cloudfoundry.org/go.mod b/src/code.cloudfoundry.org/go.mod index 1f661da1..9d788c9e 100644 --- a/src/code.cloudfoundry.org/go.mod +++ b/src/code.cloudfoundry.org/go.mod @@ -12,7 +12,6 @@ require ( code.cloudfoundry.org/lager/v3 v3.32.0 code.cloudfoundry.org/tlsconfig v0.23.0 github.com/nats-io/gnatsd v1.4.1 - github.com/nats-io/nats-server/v2 v2.11.0 github.com/nats-io/nats.go v1.40.1 github.com/onsi/ginkgo/v2 v2.23.3 github.com/onsi/gomega v1.36.3 @@ -25,24 +24,19 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/go-tpm v0.9.3 // indirect github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect github.com/klauspost/compress v1.18.0 // indirect - github.com/minio/highwayhash v1.0.3 // indirect github.com/nats-io/go-nats v1.7.2 // indirect - github.com/nats-io/jwt/v2 v2.7.3 // indirect github.com/nats-io/nkeys v0.4.10 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/square/certstrap v1.3.0 // indirect go.step.sm/crypto v0.60.0 // indirect - go.uber.org/automaxprocs v1.6.0 // indirect golang.org/x/crypto v0.36.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect - golang.org/x/time v0.11.0 // indirect golang.org/x/tools v0.31.0 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/src/code.cloudfoundry.org/go.sum b/src/code.cloudfoundry.org/go.sum index 9f751128..556d84c9 100644 --- a/src/code.cloudfoundry.org/go.sum +++ b/src/code.cloudfoundry.org/go.sum @@ -613,8 +613,6 @@ github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3 github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op h1:+OSa/t11TFhqfrX0EOSqQBDJ0YlpmK0rDSiB19dg9M0= -github.com/antithesishq/antithesis-sdk-go v0.4.3-default-no-op/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= @@ -750,8 +748,6 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc= -github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -833,16 +829,10 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= -github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= -github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/nats-io/gnatsd v1.4.1 h1:RconcfDeWpKCD6QIIwiVFcvForlXpWeJP7i5/lDLy44= github.com/nats-io/gnatsd v1.4.1/go.mod h1:nqco77VO78hLCJpIcVfygDP2rPGfsEHkGTUk94uh5DQ= github.com/nats-io/go-nats v1.7.2 h1:cJujlwCYR8iMz5ofZSD/p2WLW8FabhkQ2lIEVbSvNSA= github.com/nats-io/go-nats v1.7.2/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0= -github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE= -github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4= -github.com/nats-io/nats-server/v2 v2.11.0 h1:fdwAT1d6DZW/4LUz5rkvQUe5leGEwjjOQYntzVRKvjE= -github.com/nats-io/nats-server/v2 v2.11.0/go.mod h1:leXySghbdtXSUmWem8K9McnJ6xbJOb0t9+NQ5HTRZjI= github.com/nats-io/nats.go v1.40.1 h1:MLjDkdsbGUeCMKFyCFoLnNn/HDTqcgVa3EQm+pMNDPk= github.com/nats-io/nats.go v1.40.1/go.mod h1:wV73x0FSI/orHPSYoyMeJB+KajMDoWyXmFaRrrYaaTo= github.com/nats-io/nkeys v0.4.10 h1:glmRrpCmYLHByYcePvnTBEAwawwapjCPMjy2huw20wc= @@ -902,8 +892,6 @@ github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= @@ -959,8 +947,6 @@ go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.step.sm/crypto v0.60.0 h1:UgSw8DFG5xUOGB3GUID17UA32G4j1iNQ4qoMhBmsVFw= go.step.sm/crypto v0.60.0/go.mod h1:Ep83Lv818L4gV0vhFTdPWRKnL6/5fRMpi8SaoP5ArSw= -go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= -go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1229,7 +1215,6 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1268,8 +1253,6 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/src/code.cloudfoundry.org/nats-server/nats-server.go b/src/code.cloudfoundry.org/nats-server/nats-server.go deleted file mode 100644 index f538d2c5..00000000 --- a/src/code.cloudfoundry.org/nats-server/nats-server.go +++ /dev/null @@ -1,8 +0,0 @@ -//go:build nats_server -// +build nats_server - -package nats_server - -import ( - _ "github.com/nats-io/nats-server/v2" -) diff --git a/src/code.cloudfoundry.org/nats-v2-migrate/integration/helpers/nats_runner.go b/src/code.cloudfoundry.org/nats-v2-migrate/integration/helpers/nats_runner.go index d56f953d..cf0fd61a 100644 --- a/src/code.cloudfoundry.org/nats-v2-migrate/integration/helpers/nats_runner.go +++ b/src/code.cloudfoundry.org/nats-v2-migrate/integration/helpers/nats_runner.go @@ -59,7 +59,7 @@ func (runner *NATSRunner) Start(version ...string) { Expect(err).NotTo(HaveOccurred()) cmd = exec.Command(gnatsdBin, "-p", strconv.Itoa(runner.port)) } else { - natsServerBin, err := gexec.Build("github.com/nats-io/nats-server/v2", "-buildvcs=false") + natsServerBin, err := exec.LookPath("nats-server") Expect(err).NotTo(HaveOccurred()) cmd = exec.Command(natsServerBin, "-p", strconv.Itoa(runner.port)) } diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/LICENSE b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/LICENSE deleted file mode 100644 index d6456956..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/README.md b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/README.md deleted file mode 100644 index 4d0ff8be..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# TPM 2.0 client library - -## Tests - -This library contains unit tests in `github.com/google/go-tpm/tpm2`, which just -tests that various encoding and error checking functions work correctly. It also -contains more comprehensive integration tests in -`github.com/google/go-tpm/tpm2/test`, which run actual commands on a TPM. - -By default, these integration tests are run against the -[`go-tpm-tools`](https://github.com/google/go-tpm-tools) -simulator, which is baesed on the -[Microsoft Reference TPM2 code](https://github.com/microsoft/ms-tpm-20-ref). To -run both the unit and integration tests, run (in this directory) -```bash -go test . ./test -``` - -These integration tests can also be run against a real TPM device. This is -slightly more complex as the tests often need to be built as a normal user and -then executed as root. For example, -```bash -# Build the test binary without running it -go test -c github.com/google/go-tpm/tpm2/test -# Execute the test binary as root -sudo ./test.test --tpm-path=/dev/tpmrm0 -``` -On Linux, The `--tpm-path` causes the integration tests to be run against a -real TPM located at that path (usually `/dev/tpmrm0` or `/dev/tpm0`). On Windows, the story is similar, execept that -the `--use-tbs` flag is used instead. - -Tip: if your TPM host is remote and you don't want to install Go on it, this -same two-step process can be used. The test binary can be copied to a remote -host and run without extra installation (as the test binary has very few -*runtime* dependancies). diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/constants.go b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/constants.go deleted file mode 100644 index 2b0de544..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/constants.go +++ /dev/null @@ -1,575 +0,0 @@ -// Copyright (c) 2018, Google LLC All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tpm2 - -import ( - "crypto" - "crypto/elliptic" - "fmt" - "strings" - - // Register the relevant hash implementations to prevent a runtime failure. - _ "crypto/sha1" - _ "crypto/sha256" - _ "crypto/sha512" - - "github.com/google/go-tpm/tpmutil" -) - -var hashInfo = []struct { - alg Algorithm - hash crypto.Hash -}{ - {AlgSHA1, crypto.SHA1}, - {AlgSHA256, crypto.SHA256}, - {AlgSHA384, crypto.SHA384}, - {AlgSHA512, crypto.SHA512}, - {AlgSHA3_256, crypto.SHA3_256}, - {AlgSHA3_384, crypto.SHA3_384}, - {AlgSHA3_512, crypto.SHA3_512}, -} - -// MAX_DIGEST_BUFFER is the maximum size of []byte request or response fields. -// Typically used for chunking of big blobs of data (such as for hashing or -// encryption). -const maxDigestBuffer = 1024 - -// Algorithm represents a TPM_ALG_ID value. -type Algorithm uint16 - -// HashToAlgorithm looks up the TPM2 algorithm corresponding to the provided crypto.Hash -func HashToAlgorithm(hash crypto.Hash) (Algorithm, error) { - for _, info := range hashInfo { - if info.hash == hash { - return info.alg, nil - } - } - return AlgUnknown, fmt.Errorf("go hash algorithm #%d has no TPM2 algorithm", hash) -} - -// IsNull returns true if a is AlgNull or zero (unset). -func (a Algorithm) IsNull() bool { - return a == AlgNull || a == AlgUnknown -} - -// UsesCount returns true if a signature algorithm uses count value. -func (a Algorithm) UsesCount() bool { - return a == AlgECDAA -} - -// UsesHash returns true if the algorithm requires the use of a hash. -func (a Algorithm) UsesHash() bool { - return a == AlgOAEP -} - -// Hash returns a crypto.Hash based on the given TPM_ALG_ID. -// An error is returned if the given algorithm is not a hash algorithm or is not available. -func (a Algorithm) Hash() (crypto.Hash, error) { - for _, info := range hashInfo { - if info.alg == a { - if !info.hash.Available() { - return crypto.Hash(0), fmt.Errorf("go hash algorithm #%d not available", info.hash) - } - return info.hash, nil - } - } - return crypto.Hash(0), fmt.Errorf("hash algorithm not supported: 0x%x", a) -} - -func (a Algorithm) String() string { - var s strings.Builder - var err error - switch a { - case AlgUnknown: - _, err = s.WriteString("AlgUnknown") - case AlgRSA: - _, err = s.WriteString("RSA") - case AlgSHA1: - _, err = s.WriteString("SHA1") - case AlgHMAC: - _, err = s.WriteString("HMAC") - case AlgAES: - _, err = s.WriteString("AES") - case AlgKeyedHash: - _, err = s.WriteString("KeyedHash") - case AlgXOR: - _, err = s.WriteString("XOR") - case AlgSHA256: - _, err = s.WriteString("SHA256") - case AlgSHA384: - _, err = s.WriteString("SHA384") - case AlgSHA512: - _, err = s.WriteString("SHA512") - case AlgNull: - _, err = s.WriteString("AlgNull") - case AlgRSASSA: - _, err = s.WriteString("RSASSA") - case AlgRSAES: - _, err = s.WriteString("RSAES") - case AlgRSAPSS: - _, err = s.WriteString("RSAPSS") - case AlgOAEP: - _, err = s.WriteString("OAEP") - case AlgECDSA: - _, err = s.WriteString("ECDSA") - case AlgECDH: - _, err = s.WriteString("ECDH") - case AlgECDAA: - _, err = s.WriteString("ECDAA") - case AlgKDF2: - _, err = s.WriteString("KDF2") - case AlgECC: - _, err = s.WriteString("ECC") - case AlgSymCipher: - _, err = s.WriteString("SymCipher") - case AlgSHA3_256: - _, err = s.WriteString("SHA3_256") - case AlgSHA3_384: - _, err = s.WriteString("SHA3_384") - case AlgSHA3_512: - _, err = s.WriteString("SHA3_512") - case AlgCTR: - _, err = s.WriteString("CTR") - case AlgOFB: - _, err = s.WriteString("OFB") - case AlgCBC: - _, err = s.WriteString("CBC") - case AlgCFB: - _, err = s.WriteString("CFB") - case AlgECB: - _, err = s.WriteString("ECB") - default: - return fmt.Sprintf("Alg?<%d>", int(a)) - } - if err != nil { - return fmt.Sprintf("Writing to string builder failed: %v", err) - } - return s.String() -} - -// Supported Algorithms. -const ( - AlgUnknown Algorithm = 0x0000 - AlgRSA Algorithm = 0x0001 - AlgSHA1 Algorithm = 0x0004 - AlgHMAC Algorithm = 0x0005 - AlgAES Algorithm = 0x0006 - AlgKeyedHash Algorithm = 0x0008 - AlgXOR Algorithm = 0x000A - AlgSHA256 Algorithm = 0x000B - AlgSHA384 Algorithm = 0x000C - AlgSHA512 Algorithm = 0x000D - AlgNull Algorithm = 0x0010 - AlgRSASSA Algorithm = 0x0014 - AlgRSAES Algorithm = 0x0015 - AlgRSAPSS Algorithm = 0x0016 - AlgOAEP Algorithm = 0x0017 - AlgECDSA Algorithm = 0x0018 - AlgECDH Algorithm = 0x0019 - AlgECDAA Algorithm = 0x001A - AlgKDF2 Algorithm = 0x0021 - AlgECC Algorithm = 0x0023 - AlgSymCipher Algorithm = 0x0025 - AlgSHA3_256 Algorithm = 0x0027 - AlgSHA3_384 Algorithm = 0x0028 - AlgSHA3_512 Algorithm = 0x0029 - AlgCTR Algorithm = 0x0040 - AlgOFB Algorithm = 0x0041 - AlgCBC Algorithm = 0x0042 - AlgCFB Algorithm = 0x0043 - AlgECB Algorithm = 0x0044 -) - -// HandleType defines a type of handle. -type HandleType uint8 - -// Supported handle types -const ( - HandleTypePCR HandleType = 0x00 - HandleTypeNVIndex HandleType = 0x01 - HandleTypeHMACSession HandleType = 0x02 - HandleTypeLoadedSession HandleType = 0x02 - HandleTypePolicySession HandleType = 0x03 - HandleTypeSavedSession HandleType = 0x03 - HandleTypePermanent HandleType = 0x40 - HandleTypeTransient HandleType = 0x80 - HandleTypePersistent HandleType = 0x81 -) - -// SessionType defines the type of session created in StartAuthSession. -type SessionType uint8 - -// Supported session types. -const ( - SessionHMAC SessionType = 0x00 - SessionPolicy SessionType = 0x01 - SessionTrial SessionType = 0x03 -) - -// SessionAttributes represents an attribute of a session. -type SessionAttributes byte - -// Session Attributes (Structures 8.4 TPMA_SESSION) -const ( - AttrContinueSession SessionAttributes = 1 << iota - AttrAuditExclusive - AttrAuditReset - _ // bit 3 reserved - _ // bit 4 reserved - AttrDecrypt - AttrEcrypt - AttrAudit -) - -// EmptyAuth represents the empty authorization value. -var EmptyAuth []byte - -// KeyProp is a bitmask used in Attributes field of key templates. Individual -// flags should be OR-ed to form a full mask. -type KeyProp uint32 - -// Key properties. -const ( - FlagFixedTPM KeyProp = 0x00000002 - FlagStClear KeyProp = 0x00000004 - FlagFixedParent KeyProp = 0x00000010 - FlagSensitiveDataOrigin KeyProp = 0x00000020 - FlagUserWithAuth KeyProp = 0x00000040 - FlagAdminWithPolicy KeyProp = 0x00000080 - FlagNoDA KeyProp = 0x00000400 - FlagRestricted KeyProp = 0x00010000 - FlagDecrypt KeyProp = 0x00020000 - FlagSign KeyProp = 0x00040000 - - FlagSealDefault = FlagFixedTPM | FlagFixedParent - FlagSignerDefault = FlagSign | FlagRestricted | FlagFixedTPM | - FlagFixedParent | FlagSensitiveDataOrigin | FlagUserWithAuth - FlagStorageDefault = FlagDecrypt | FlagRestricted | FlagFixedTPM | - FlagFixedParent | FlagSensitiveDataOrigin | FlagUserWithAuth -) - -// TPMProp represents a Property Tag (TPM_PT) used with calls to GetCapability(CapabilityTPMProperties). -type TPMProp uint32 - -// TPM Capability Properties, see TPM 2.0 Spec, Rev 1.38, Table 23. -// Fixed TPM Properties (PT_FIXED) -const ( - FamilyIndicator TPMProp = 0x100 + iota - SpecLevel - SpecRevision - SpecDayOfYear - SpecYear - Manufacturer - VendorString1 - VendorString2 - VendorString3 - VendorString4 - VendorTPMType - FirmwareVersion1 - FirmwareVersion2 - InputMaxBufferSize - TransientObjectsMin - PersistentObjectsMin - LoadedObjectsMin - ActiveSessionsMax - PCRCount - PCRSelectMin - ContextGapMax - _ // (PT_FIXED + 21) is skipped - NVCountersMax - NVIndexMax - MemoryMethod - ClockUpdate - ContextHash - ContextSym - ContextSymSize - OrderlyCount - CommandMaxSize - ResponseMaxSize - DigestMaxSize - ObjectContextMaxSize - SessionContextMaxSize - PSFamilyIndicator - PSSpecLevel - PSSpecRevision - PSSpecDayOfYear - PSSpecYear - SplitSigningMax - TotalCommands - LibraryCommands - VendorCommands - NVMaxBufferSize - TPMModes - CapabilityMaxBufferSize -) - -// Variable TPM Properties (PT_VAR) -const ( - TPMAPermanent TPMProp = 0x200 + iota - TPMAStartupClear - HRNVIndex - HRLoaded - HRLoadedAvail - HRActive - HRActiveAvail - HRTransientAvail - CurrentPersistent - AvailPersistent - NVCounters - NVCountersAvail - AlgorithmSet - LoadedCurves - LockoutCounter - MaxAuthFail - LockoutInterval - LockoutRecovery - NVWriteRecovery - AuditCounter0 - AuditCounter1 -) - -// Allowed ranges of different kinds of Handles (TPM_HANDLE) -// These constants have type TPMProp for backwards compatibility. -const ( - PCRFirst TPMProp = 0x00000000 - HMACSessionFirst TPMProp = 0x02000000 - LoadedSessionFirst TPMProp = 0x02000000 - PolicySessionFirst TPMProp = 0x03000000 - ActiveSessionFirst TPMProp = 0x03000000 - TransientFirst TPMProp = 0x80000000 - PersistentFirst TPMProp = 0x81000000 - PersistentLast TPMProp = 0x81FFFFFF - PlatformPersistent TPMProp = 0x81800000 - NVIndexFirst TPMProp = 0x01000000 - NVIndexLast TPMProp = 0x01FFFFFF - PermanentFirst TPMProp = 0x40000000 - PermanentLast TPMProp = 0x4000010F -) - -// Reserved Handles. -const ( - HandleOwner tpmutil.Handle = 0x40000001 + iota - HandleRevoke - HandleTransport - HandleOperator - HandleAdmin - HandleEK - HandleNull - HandleUnassigned - HandlePasswordSession - HandleLockout - HandleEndorsement - HandlePlatform -) - -// Capability identifies some TPM property or state type. -type Capability uint32 - -// TPM Capabilities. -const ( - CapabilityAlgs Capability = iota - CapabilityHandles - CapabilityCommands - CapabilityPPCommands - CapabilityAuditCommands - CapabilityPCRs - CapabilityTPMProperties - CapabilityPCRProperties - CapabilityECCCurves - CapabilityAuthPolicies -) - -// TPM Structure Tags. Tags are used to disambiguate structures, similar to Alg -// values: tag value defines what kind of data lives in a nested field. -const ( - TagNull tpmutil.Tag = 0x8000 - TagNoSessions tpmutil.Tag = 0x8001 - TagSessions tpmutil.Tag = 0x8002 - TagAttestCertify tpmutil.Tag = 0x8017 - TagAttestQuote tpmutil.Tag = 0x8018 - TagAttestCreation tpmutil.Tag = 0x801a - TagAuthSecret tpmutil.Tag = 0x8023 - TagHashCheck tpmutil.Tag = 0x8024 - TagAuthSigned tpmutil.Tag = 0x8025 -) - -// StartupType instructs the TPM on how to handle its state during Shutdown or -// Startup. -type StartupType uint16 - -// Startup types -const ( - StartupClear StartupType = iota - StartupState -) - -// EllipticCurve identifies specific EC curves. -type EllipticCurve uint16 - -// ECC curves supported by TPM 2.0 spec. -const ( - CurveNISTP192 = EllipticCurve(iota + 1) - CurveNISTP224 - CurveNISTP256 - CurveNISTP384 - CurveNISTP521 - - CurveBNP256 = EllipticCurve(iota + 10) - CurveBNP638 - - CurveSM2P256 = EllipticCurve(0x0020) -) - -var toGoCurve = map[EllipticCurve]elliptic.Curve{ - CurveNISTP224: elliptic.P224(), - CurveNISTP256: elliptic.P256(), - CurveNISTP384: elliptic.P384(), - CurveNISTP521: elliptic.P521(), -} - -// Supported TPM operations. -const ( - CmdNVUndefineSpaceSpecial tpmutil.Command = 0x0000011F - CmdEvictControl tpmutil.Command = 0x00000120 - CmdUndefineSpace tpmutil.Command = 0x00000122 - CmdClear tpmutil.Command = 0x00000126 - CmdHierarchyChangeAuth tpmutil.Command = 0x00000129 - CmdDefineSpace tpmutil.Command = 0x0000012A - CmdCreatePrimary tpmutil.Command = 0x00000131 - CmdIncrementNVCounter tpmutil.Command = 0x00000134 - CmdWriteNV tpmutil.Command = 0x00000137 - CmdWriteLockNV tpmutil.Command = 0x00000138 - CmdDictionaryAttackLockReset tpmutil.Command = 0x00000139 - CmdDictionaryAttackParameters tpmutil.Command = 0x0000013A - CmdPCREvent tpmutil.Command = 0x0000013C - CmdPCRReset tpmutil.Command = 0x0000013D - CmdSequenceComplete tpmutil.Command = 0x0000013E - CmdStartup tpmutil.Command = 0x00000144 - CmdShutdown tpmutil.Command = 0x00000145 - CmdActivateCredential tpmutil.Command = 0x00000147 - CmdCertify tpmutil.Command = 0x00000148 - CmdCertifyCreation tpmutil.Command = 0x0000014A - CmdReadNV tpmutil.Command = 0x0000014E - CmdReadLockNV tpmutil.Command = 0x0000014F - CmdPolicySecret tpmutil.Command = 0x00000151 - CmdCreate tpmutil.Command = 0x00000153 - CmdECDHZGen tpmutil.Command = 0x00000154 - CmdImport tpmutil.Command = 0x00000156 - CmdLoad tpmutil.Command = 0x00000157 - CmdQuote tpmutil.Command = 0x00000158 - CmdRSADecrypt tpmutil.Command = 0x00000159 - CmdSequenceUpdate tpmutil.Command = 0x0000015C - CmdSign tpmutil.Command = 0x0000015D - CmdUnseal tpmutil.Command = 0x0000015E - CmdPolicySigned tpmutil.Command = 0x00000160 - CmdContextLoad tpmutil.Command = 0x00000161 - CmdContextSave tpmutil.Command = 0x00000162 - CmdECDHKeyGen tpmutil.Command = 0x00000163 - CmdEncryptDecrypt tpmutil.Command = 0x00000164 - CmdFlushContext tpmutil.Command = 0x00000165 - CmdLoadExternal tpmutil.Command = 0x00000167 - CmdMakeCredential tpmutil.Command = 0x00000168 - CmdReadPublicNV tpmutil.Command = 0x00000169 - CmdPolicyCommandCode tpmutil.Command = 0x0000016C - CmdPolicyOr tpmutil.Command = 0x00000171 - CmdReadPublic tpmutil.Command = 0x00000173 - CmdRSAEncrypt tpmutil.Command = 0x00000174 - CmdStartAuthSession tpmutil.Command = 0x00000176 - CmdGetCapability tpmutil.Command = 0x0000017A - CmdGetRandom tpmutil.Command = 0x0000017B - CmdHash tpmutil.Command = 0x0000017D - CmdPCRRead tpmutil.Command = 0x0000017E - CmdPolicyPCR tpmutil.Command = 0x0000017F - CmdReadClock tpmutil.Command = 0x00000181 - CmdPCRExtend tpmutil.Command = 0x00000182 - CmdEventSequenceComplete tpmutil.Command = 0x00000185 - CmdHashSequenceStart tpmutil.Command = 0x00000186 - CmdPolicyGetDigest tpmutil.Command = 0x00000189 - CmdPolicyPassword tpmutil.Command = 0x0000018C - CmdEncryptDecrypt2 tpmutil.Command = 0x00000193 -) - -// Regular TPM 2.0 devices use 24-bit mask (3 bytes) for PCR selection. -const sizeOfPCRSelect = 3 - -const defaultRSAExponent = 1<<16 + 1 - -// NVAttr is a bitmask used in Attributes field of NV indexes. Individual -// flags should be OR-ed to form a full mask. -type NVAttr uint32 - -// NV Attributes -const ( - AttrPPWrite NVAttr = 0x00000001 - AttrOwnerWrite NVAttr = 0x00000002 - AttrAuthWrite NVAttr = 0x00000004 - AttrPolicyWrite NVAttr = 0x00000008 - AttrPolicyDelete NVAttr = 0x00000400 - AttrWriteLocked NVAttr = 0x00000800 - AttrWriteAll NVAttr = 0x00001000 - AttrWriteDefine NVAttr = 0x00002000 - AttrWriteSTClear NVAttr = 0x00004000 - AttrGlobalLock NVAttr = 0x00008000 - AttrPPRead NVAttr = 0x00010000 - AttrOwnerRead NVAttr = 0x00020000 - AttrAuthRead NVAttr = 0x00040000 - AttrPolicyRead NVAttr = 0x00080000 - AttrNoDA NVAttr = 0x02000000 - AttrOrderly NVAttr = 0x04000000 - AttrClearSTClear NVAttr = 0x08000000 - AttrReadLocked NVAttr = 0x10000000 - AttrWritten NVAttr = 0x20000000 - AttrPlatformCreate NVAttr = 0x40000000 - AttrReadSTClear NVAttr = 0x80000000 -) - -var permMap = map[NVAttr]string{ - AttrPPWrite: "PPWrite", - AttrOwnerWrite: "OwnerWrite", - AttrAuthWrite: "AuthWrite", - AttrPolicyWrite: "PolicyWrite", - AttrPolicyDelete: "PolicyDelete", - AttrWriteLocked: "WriteLocked", - AttrWriteAll: "WriteAll", - AttrWriteDefine: "WriteDefine", - AttrWriteSTClear: "WriteSTClear", - AttrGlobalLock: "GlobalLock", - AttrPPRead: "PPRead", - AttrOwnerRead: "OwnerRead", - AttrAuthRead: "AuthRead", - AttrPolicyRead: "PolicyRead", - AttrNoDA: "No Do", - AttrOrderly: "Oderly", - AttrClearSTClear: "ClearSTClear", - AttrReadLocked: "ReadLocked", - AttrWritten: "Writte", - AttrPlatformCreate: "PlatformCreate", - AttrReadSTClear: "ReadSTClear", -} - -// String returns a textual representation of the set of NVAttr -func (p NVAttr) String() string { - var retString strings.Builder - for iterator, item := range permMap { - if (p & iterator) != 0 { - retString.WriteString(item + " + ") - } - } - if retString.String() == "" { - return "Permission/s not found" - } - return strings.TrimSuffix(retString.String(), " + ") - -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/error.go b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/error.go deleted file mode 100644 index e1983356..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/error.go +++ /dev/null @@ -1,362 +0,0 @@ -package tpm2 - -import ( - "fmt" - - "github.com/google/go-tpm/tpmutil" -) - -type ( - // RCFmt0 holds Format 0 error codes - RCFmt0 uint8 - - // RCFmt1 holds Format 1 error codes - RCFmt1 uint8 - - // RCWarn holds error codes used in warnings - RCWarn uint8 - - // RCIndex is used to reference arguments, handles and sessions in errors - RCIndex uint8 -) - -// Format 0 error codes. -const ( - RCInitialize RCFmt0 = 0x00 - RCFailure RCFmt0 = 0x01 - RCSequence RCFmt0 = 0x03 - RCPrivate RCFmt0 = 0x0B - RCHMAC RCFmt0 = 0x19 - RCDisabled RCFmt0 = 0x20 - RCExclusive RCFmt0 = 0x21 - RCAuthType RCFmt0 = 0x24 - RCAuthMissing RCFmt0 = 0x25 - RCPolicy RCFmt0 = 0x26 - RCPCR RCFmt0 = 0x27 - RCPCRChanged RCFmt0 = 0x28 - RCUpgrade RCFmt0 = 0x2D - RCTooManyContexts RCFmt0 = 0x2E - RCAuthUnavailable RCFmt0 = 0x2F - RCReboot RCFmt0 = 0x30 - RCUnbalanced RCFmt0 = 0x31 - RCCommandSize RCFmt0 = 0x42 - RCCommandCode RCFmt0 = 0x43 - RCAuthSize RCFmt0 = 0x44 - RCAuthContext RCFmt0 = 0x45 - RCNVRange RCFmt0 = 0x46 - RCNVSize RCFmt0 = 0x47 - RCNVLocked RCFmt0 = 0x48 - RCNVAuthorization RCFmt0 = 0x49 - RCNVUninitialized RCFmt0 = 0x4A - RCNVSpace RCFmt0 = 0x4B - RCNVDefined RCFmt0 = 0x4C - RCBadContext RCFmt0 = 0x50 - RCCPHash RCFmt0 = 0x51 - RCParent RCFmt0 = 0x52 - RCNeedsTest RCFmt0 = 0x53 - RCNoResult RCFmt0 = 0x54 - RCSensitive RCFmt0 = 0x55 -) - -var fmt0Msg = map[RCFmt0]string{ - RCInitialize: "TPM not initialized by TPM2_Startup or already initialized", - RCFailure: "commands not being accepted because of a TPM failure", - RCSequence: "improper use of a sequence handle", - RCPrivate: "not currently used", - RCHMAC: "not currently used", - RCDisabled: "the command is disabled", - RCExclusive: "command failed because audit sequence required exclusivity", - RCAuthType: "authorization handle is not correct for command", - RCAuthMissing: "5 command requires an authorization session for handle and it is not present", - RCPolicy: "policy failure in math operation or an invalid authPolicy value", - RCPCR: "PCR check fail", - RCPCRChanged: "PCR have changed since checked", - RCUpgrade: "TPM is in field upgrade mode unless called via TPM2_FieldUpgradeData(), then it is not in field upgrade mode", - RCTooManyContexts: "context ID counter is at maximum", - RCAuthUnavailable: "authValue or authPolicy is not available for selected entity", - RCReboot: "a _TPM_Init and Startup(CLEAR) is required before the TPM can resume operation", - RCUnbalanced: "the protection algorithms (hash and symmetric) are not reasonably balanced; the digest size of the hash must be larger than the key size of the symmetric algorithm", - RCCommandSize: "command commandSize value is inconsistent with contents of the command buffer; either the size is not the same as the octets loaded by the hardware interface layer or the value is not large enough to hold a command header", - RCCommandCode: "command code not supported", - RCAuthSize: "the value of authorizationSize is out of range or the number of octets in the Authorization Area is greater than required", - RCAuthContext: "use of an authorization session with a context command or another command that cannot have an authorization session", - RCNVRange: "NV offset+size is out of range", - RCNVSize: "Requested allocation size is larger than allowed", - RCNVLocked: "NV access locked", - RCNVAuthorization: "NV access authorization fails in command actions", - RCNVUninitialized: "an NV Index is used before being initialized or the state saved by TPM2_Shutdown(STATE) could not be restored", - RCNVSpace: "insufficient space for NV allocation", - RCNVDefined: "NV Index or persistent object already defined", - RCBadContext: "context in TPM2_ContextLoad() is not valid", - RCCPHash: "cpHash value already set or not correct for use", - RCParent: "handle for parent is not a valid parent", - RCNeedsTest: "some function needs testing", - RCNoResult: "returned when an internal function cannot process a request due to an unspecified problem; this code is usually related to invalid parameters that are not properly filtered by the input unmarshaling code", - RCSensitive: "the sensitive area did not unmarshal correctly after decryption", -} - -// Format 1 error codes. -const ( - RCAsymmetric = 0x01 - RCAttributes = 0x02 - RCHash = 0x03 - RCValue = 0x04 - RCHierarchy = 0x05 - RCKeySize = 0x07 - RCMGF = 0x08 - RCMode = 0x09 - RCType = 0x0A - RCHandle = 0x0B - RCKDF = 0x0C - RCRange = 0x0D - RCAuthFail = 0x0E - RCNonce = 0x0F - RCPP = 0x10 - RCScheme = 0x12 - RCSize = 0x15 - RCSymmetric = 0x16 - RCTag = 0x17 - RCSelector = 0x18 - RCInsufficient = 0x1A - RCSignature = 0x1B - RCKey = 0x1C - RCPolicyFail = 0x1D - RCIntegrity = 0x1F - RCTicket = 0x20 - RCReservedBits = 0x21 - RCBadAuth = 0x22 - RCExpired = 0x23 - RCPolicyCC = 0x24 - RCBinding = 0x25 - RCCurve = 0x26 - RCECCPoint = 0x27 -) - -var fmt1Msg = map[RCFmt1]string{ - RCAsymmetric: "asymmetric algorithm not supported or not correct", - RCAttributes: "inconsistent attributes", - RCHash: "hash algorithm not supported or not appropriate", - RCValue: "value is out of range or is not correct for the context", - RCHierarchy: "hierarchy is not enabled or is not correct for the use", - RCKeySize: "key size is not supported", - RCMGF: "mask generation function not supported", - RCMode: "mode of operation not supported", - RCType: "the type of the value is not appropriate for the use", - RCHandle: "the handle is not correct for the use", - RCKDF: "unsupported key derivation function or function not appropriate for use", - RCRange: "value was out of allowed range", - RCAuthFail: "the authorization HMAC check failed and DA counter incremented", - RCNonce: "invalid nonce size or nonce value mismatch", - RCPP: "authorization requires assertion of PP", - RCScheme: "unsupported or incompatible scheme", - RCSize: "structure is the wrong size", - RCSymmetric: "unsupported symmetric algorithm or key size, or not appropriate for instance", - RCTag: "incorrect structure tag", - RCSelector: "union selector is incorrect", - RCInsufficient: "the TPM was unable to unmarshal a value because there were not enough octets in the input buffer", - RCSignature: "the signature is not valid", - RCKey: "key fields are not compatible with the selected use", - RCPolicyFail: "a policy check failed", - RCIntegrity: "integrity check failed", - RCTicket: "invalid ticket", - RCReservedBits: "reserved bits not set to zero as required", - RCBadAuth: "authorization failure without DA implications", - RCExpired: "the policy has expired", - RCPolicyCC: "the commandCode in the policy is not the commandCode of the command or the command code in a policy command references a command that is not implemented", - RCBinding: "public and sensitive portions of an object are not cryptographically bound", - RCCurve: "curve not supported", - RCECCPoint: "point is not on the required curve", -} - -// Warning codes. -const ( - RCContextGap RCWarn = 0x01 - RCObjectMemory RCWarn = 0x02 - RCSessionMemory RCWarn = 0x03 - RCMemory RCWarn = 0x04 - RCSessionHandles RCWarn = 0x05 - RCObjectHandles RCWarn = 0x06 - RCLocality RCWarn = 0x07 - RCYielded RCWarn = 0x08 - RCCanceled RCWarn = 0x09 - RCTesting RCWarn = 0x0A - RCReferenceH0 RCWarn = 0x10 - RCReferenceH1 RCWarn = 0x11 - RCReferenceH2 RCWarn = 0x12 - RCReferenceH3 RCWarn = 0x13 - RCReferenceH4 RCWarn = 0x14 - RCReferenceH5 RCWarn = 0x15 - RCReferenceH6 RCWarn = 0x16 - RCReferenceS0 RCWarn = 0x18 - RCReferenceS1 RCWarn = 0x19 - RCReferenceS2 RCWarn = 0x1A - RCReferenceS3 RCWarn = 0x1B - RCReferenceS4 RCWarn = 0x1C - RCReferenceS5 RCWarn = 0x1D - RCReferenceS6 RCWarn = 0x1E - RCNVRate RCWarn = 0x20 - RCLockout RCWarn = 0x21 - RCRetry RCWarn = 0x22 - RCNVUnavailable RCWarn = 0x23 -) - -var warnMsg = map[RCWarn]string{ - RCContextGap: "gap for context ID is too large", - RCObjectMemory: "out of memory for object contexts", - RCSessionMemory: "out of memory for session contexts", - RCMemory: "out of shared object/session memory or need space for internal operations", - RCSessionHandles: "out of session handles", - RCObjectHandles: "out of object handles", - RCLocality: "bad locality", - RCYielded: "the TPM has suspended operation on the command; forward progress was made and the command may be retried", - RCCanceled: "the command was canceled", - RCTesting: "TPM is performing self-tests", - RCReferenceH0: "the 1st handle in the handle area references a transient object or session that is not loaded", - RCReferenceH1: "the 2nd handle in the handle area references a transient object or session that is not loaded", - RCReferenceH2: "the 3rd handle in the handle area references a transient object or session that is not loaded", - RCReferenceH3: "the 4th handle in the handle area references a transient object or session that is not loaded", - RCReferenceH4: "the 5th handle in the handle area references a transient object or session that is not loaded", - RCReferenceH5: "the 6th handle in the handle area references a transient object or session that is not loaded", - RCReferenceH6: "the 7th handle in the handle area references a transient object or session that is not loaded", - RCReferenceS0: "the 1st authorization session handle references a session that is not loaded", - RCReferenceS1: "the 2nd authorization session handle references a session that is not loaded", - RCReferenceS2: "the 3rd authorization session handle references a session that is not loaded", - RCReferenceS3: "the 4th authorization session handle references a session that is not loaded", - RCReferenceS4: "the 5th authorization session handle references a session that is not loaded", - RCReferenceS5: "the 6th authorization session handle references a session that is not loaded", - RCReferenceS6: "the 7th authorization session handle references a session that is not loaded", - RCNVRate: "the TPM is rate-limiting accesses to prevent wearout of NV", - RCLockout: "authorizations for objects subject to DA protection are not allowed at this time because the TPM is in DA lockout mode", - RCRetry: "the TPM was not able to start the command", - RCNVUnavailable: "the command may require writing of NV and NV is not current accessible", -} - -// Indexes for arguments, handles and sessions. -const ( - RC1 RCIndex = iota + 1 - RC2 - RC3 - RC4 - RC5 - RC6 - RC7 - RC8 - RC9 - RCA - RCB - RCC - RCD - RCE - RCF -) - -const unknownCode = "unknown error code" - -// Error is returned for all Format 0 errors from the TPM. It is used for general -// errors not specific to a parameter, handle or session. -type Error struct { - Code RCFmt0 -} - -func (e Error) Error() string { - msg := fmt0Msg[e.Code] - if msg == "" { - msg = unknownCode - } - return fmt.Sprintf("error code 0x%x : %s", e.Code, msg) -} - -// VendorError represents a vendor-specific error response. These types of responses -// are not decoded and Code contains the complete response code. -type VendorError struct { - Code uint32 -} - -func (e VendorError) Error() string { - return fmt.Sprintf("vendor error code 0x%x", e.Code) -} - -// Warning is typically used to report transient errors. -type Warning struct { - Code RCWarn -} - -func (w Warning) Error() string { - msg := warnMsg[w.Code] - if msg == "" { - msg = unknownCode - } - return fmt.Sprintf("warning code 0x%x : %s", w.Code, msg) -} - -// ParameterError describes an error related to a parameter, and the parameter number. -type ParameterError struct { - Code RCFmt1 - Parameter RCIndex -} - -func (e ParameterError) Error() string { - msg := fmt1Msg[e.Code] - if msg == "" { - msg = unknownCode - } - return fmt.Sprintf("parameter %d, error code 0x%x : %s", e.Parameter, e.Code, msg) -} - -// HandleError describes an error related to a handle, and the handle number. -type HandleError struct { - Code RCFmt1 - Handle RCIndex -} - -func (e HandleError) Error() string { - msg := fmt1Msg[e.Code] - if msg == "" { - msg = unknownCode - } - return fmt.Sprintf("handle %d, error code 0x%x : %s", e.Handle, e.Code, msg) -} - -// SessionError describes an error related to a session, and the session number. -type SessionError struct { - Code RCFmt1 - Session RCIndex -} - -func (e SessionError) Error() string { - msg := fmt1Msg[e.Code] - if msg == "" { - msg = unknownCode - } - return fmt.Sprintf("session %d, error code 0x%x : %s", e.Session, e.Code, msg) -} - -// Decode a TPM2 response code and return the appropriate error. Logic -// according to the "Response Code Evaluation" chart in Part 1 of the TPM 2.0 -// spec. -func decodeResponse(code tpmutil.ResponseCode) error { - if code == tpmutil.RCSuccess { - return nil - } - if code&0x180 == 0 { // Bits 7:8 == 0 is a TPM1 error - return fmt.Errorf("response status 0x%x", code) - } - if code&0x80 == 0 { // Bit 7 unset - if code&0x400 > 0 { // Bit 10 set, vendor specific code - return VendorError{uint32(code)} - } - if code&0x800 > 0 { // Bit 11 set, warning with code in bit 0:6 - return Warning{RCWarn(code & 0x7f)} - } - // error with code in bit 0:6 - return Error{RCFmt0(code & 0x7f)} - } - if code&0x40 > 0 { // Bit 6 set, code in 0:5, parameter number in 8:11 - return ParameterError{RCFmt1(code & 0x3f), RCIndex((code & 0xf00) >> 8)} - } - if code&0x800 == 0 { // Bit 11 unset, code in 0:5, handle in 8:10 - return HandleError{RCFmt1(code & 0x3f), RCIndex((code & 0x700) >> 8)} - } - // Code in 0:5, Session in 8:10 - return SessionError{RCFmt1(code & 0x3f), RCIndex((code & 0x700) >> 8)} -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/kdf.go b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/kdf.go deleted file mode 100644 index 3a22e8be..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/kdf.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) 2018, Google LLC All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tpm2 - -import ( - "crypto" - "crypto/hmac" - "encoding/binary" - "hash" -) - -// KDFa implements TPM 2.0's default key derivation function, as defined in -// section 11.4.9.2 of the TPM revision 2 specification part 1. -// See: https://trustedcomputinggroup.org/resource/tpm-library-specification/ -// The key & label parameters must not be zero length. -// The label parameter is a non-null-terminated string. -// The contextU & contextV parameters are optional. -// Deprecated: Use KDFaHash. -func KDFa(hashAlg Algorithm, key []byte, label string, contextU, contextV []byte, bits int) ([]byte, error) { - h, err := hashAlg.Hash() - if err != nil { - return nil, err - } - return KDFaHash(h, key, label, contextU, contextV, bits), nil -} - -// KDFe implements TPM 2.0's ECDH key derivation function, as defined in -// section 11.4.9.3 of the TPM revision 2 specification part 1. -// See: https://trustedcomputinggroup.org/resource/tpm-library-specification/ -// The z parameter is the x coordinate of one party's private ECC key multiplied -// by the other party's public ECC point. -// The use parameter is a non-null-terminated string. -// The partyUInfo and partyVInfo are the x coordinates of the initiator's and -// Deprecated: Use KDFeHash. -func KDFe(hashAlg Algorithm, z []byte, use string, partyUInfo, partyVInfo []byte, bits int) ([]byte, error) { - h, err := hashAlg.Hash() - if err != nil { - return nil, err - } - return KDFeHash(h, z, use, partyUInfo, partyVInfo, bits), nil -} - -// KDFaHash implements TPM 2.0's default key derivation function, as defined in -// section 11.4.9.2 of the TPM revision 2 specification part 1. -// See: https://trustedcomputinggroup.org/resource/tpm-library-specification/ -// The key & label parameters must not be zero length. -// The label parameter is a non-null-terminated string. -// The contextU & contextV parameters are optional. -func KDFaHash(h crypto.Hash, key []byte, label string, contextU, contextV []byte, bits int) []byte { - mac := hmac.New(h.New, key) - - out := kdf(mac, bits, func() { - mac.Write([]byte(label)) - mac.Write([]byte{0}) // Terminating null character for C-string. - mac.Write(contextU) - mac.Write(contextV) - binary.Write(mac, binary.BigEndian, uint32(bits)) - }) - return out -} - -// KDFeHash implements TPM 2.0's ECDH key derivation function, as defined in -// section 11.4.9.3 of the TPM revision 2 specification part 1. -// See: https://trustedcomputinggroup.org/resource/tpm-library-specification/ -// The z parameter is the x coordinate of one party's private ECC key multiplied -// by the other party's public ECC point. -// The use parameter is a non-null-terminated string. -// The partyUInfo and partyVInfo are the x coordinates of the initiator's and -// the responder's ECC points, respectively. -func KDFeHash(h crypto.Hash, z []byte, use string, partyUInfo, partyVInfo []byte, bits int) []byte { - hash := h.New() - - out := kdf(hash, bits, func() { - hash.Write(z) - hash.Write([]byte(use)) - hash.Write([]byte{0}) // Terminating null character for C-string. - hash.Write(partyUInfo) - hash.Write(partyVInfo) - }) - return out -} - -func kdf(h hash.Hash, bits int, update func()) []byte { - bytes := (bits + 7) / 8 - out := []byte{} - - for counter := 1; len(out) < bytes; counter++ { - h.Reset() - binary.Write(h, binary.BigEndian, uint32(counter)) - update() - - out = h.Sum(out) - } - // out's length is a multiple of hash size, so there will be excess - // bytes if bytes isn't a multiple of hash size. - out = out[:bytes] - - // As mentioned in the KDFa and KDFe specs mentioned above, - // the unused bits of the most significant octet are masked off. - if maskBits := uint8(bits % 8); maskBits > 0 { - out[0] &= (1 << maskBits) - 1 - } - return out -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/open_other.go b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/open_other.go deleted file mode 100644 index 7d6d9a31..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/open_other.go +++ /dev/null @@ -1,57 +0,0 @@ -//go:build !windows - -// Copyright (c) 2019, Google LLC All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tpm2 - -import ( - "errors" - "fmt" - "io" - "os" - - "github.com/google/go-tpm/tpmutil" -) - -// OpenTPM opens a channel to the TPM at the given path. If the file is a -// device, then it treats it like a normal TPM device, and if the file is a -// Unix domain socket, then it opens a connection to the socket. -// -// This function may also be invoked with no paths, as tpm2.OpenTPM(). In this -// case, the default paths on Linux (/dev/tpmrm0 then /dev/tpm0), will be used. -func OpenTPM(path ...string) (tpm io.ReadWriteCloser, err error) { - switch len(path) { - case 0: - tpm, err = tpmutil.OpenTPM("/dev/tpmrm0") - if errors.Is(err, os.ErrNotExist) { - tpm, err = tpmutil.OpenTPM("/dev/tpm0") - } - case 1: - tpm, err = tpmutil.OpenTPM(path[0]) - default: - return nil, errors.New("cannot specify multiple paths to tpm2.OpenTPM") - } - if err != nil { - return nil, err - } - - // Make sure this is a TPM 2.0 - _, err = GetManufacturer(tpm) - if err != nil { - tpm.Close() - return nil, fmt.Errorf("open %s: device is not a TPM 2.0", path) - } - return tpm, nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/open_windows.go b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/open_windows.go deleted file mode 100644 index ad37a602..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/open_windows.go +++ /dev/null @@ -1,39 +0,0 @@ -//go:build windows - -// Copyright (c) 2018, Google LLC All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tpm2 - -import ( - "fmt" - "io" - - "github.com/google/go-tpm/tpmutil" - "github.com/google/go-tpm/tpmutil/tbs" -) - -// OpenTPM opens a channel to the TPM. -func OpenTPM() (io.ReadWriteCloser, error) { - info, err := tbs.GetDeviceInfo() - if err != nil { - return nil, err - } - - if info.TPMVersion != tbs.TPMVersion20 { - return nil, fmt.Errorf("openTPM: device is not a TPM 2.0") - } - - return tpmutil.OpenTPM() -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/structures.go b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/structures.go deleted file mode 100644 index 6df9f7f0..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/structures.go +++ /dev/null @@ -1,1112 +0,0 @@ -// Copyright (c) 2018, Google LLC All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tpm2 - -import ( - "bytes" - "crypto" - "crypto/ecdsa" - "crypto/rsa" - "encoding/binary" - "errors" - "fmt" - "math/big" - "reflect" - - "github.com/google/go-tpm/tpmutil" -) - -// NVPublic contains the public area of an NV index. -type NVPublic struct { - NVIndex tpmutil.Handle - NameAlg Algorithm - Attributes NVAttr - AuthPolicy tpmutil.U16Bytes - DataSize uint16 -} - -type tpmsSensitiveCreate struct { - UserAuth tpmutil.U16Bytes - Data tpmutil.U16Bytes -} - -// PCRSelection contains a slice of PCR indexes and a hash algorithm used in -// them. -type PCRSelection struct { - Hash Algorithm - PCRs []int -} - -type tpmsPCRSelection struct { - Hash Algorithm - Size byte - PCRs tpmutil.RawBytes -} - -// Public contains the public area of an object. -type Public struct { - Type Algorithm - NameAlg Algorithm - Attributes KeyProp - AuthPolicy tpmutil.U16Bytes - - // Exactly one of the following fields should be set - // When encoding/decoding, one will be picked based on Type. - - // RSAParameters contains both [rsa]parameters and [rsa]unique. - RSAParameters *RSAParams - // ECCParameters contains both [ecc]parameters and [ecc]unique. - ECCParameters *ECCParams - // SymCipherParameters contains both [sym]parameters and [sym]unique. - SymCipherParameters *SymCipherParams - // KeyedHashParameters contains both [keyedHash]parameters and [keyedHash]unique. - KeyedHashParameters *KeyedHashParams -} - -// Encode serializes a Public structure in TPM wire format. -func (p Public) Encode() ([]byte, error) { - head, err := tpmutil.Pack(p.Type, p.NameAlg, p.Attributes, p.AuthPolicy) - if err != nil { - return nil, fmt.Errorf("encoding Type, NameAlg, Attributes, AuthPolicy: %v", err) - } - var params []byte - switch p.Type { - case AlgRSA: - params, err = p.RSAParameters.encode() - case AlgKeyedHash: - params, err = p.KeyedHashParameters.encode() - case AlgECC: - params, err = p.ECCParameters.encode() - case AlgSymCipher: - params, err = p.SymCipherParameters.encode() - default: - err = fmt.Errorf("unsupported type in TPMT_PUBLIC: 0x%x", p.Type) - } - if err != nil { - return nil, fmt.Errorf("encoding RSAParameters, ECCParameters, SymCipherParameters or KeyedHash: %v", err) - } - return concat(head, params) -} - -// Key returns the (public) key from the public area of an object. -func (p Public) Key() (crypto.PublicKey, error) { - var pubKey crypto.PublicKey - switch p.Type { - case AlgRSA: - // Endianness of big.Int.Bytes/SetBytes and modulus in the TPM is the same - // (big-endian). - pubKey = &rsa.PublicKey{N: p.RSAParameters.Modulus(), E: int(p.RSAParameters.Exponent())} - case AlgECC: - curve, ok := toGoCurve[p.ECCParameters.CurveID] - if !ok { - return nil, fmt.Errorf("can't map TPM EC curve ID 0x%x to Go elliptic.Curve value", p.ECCParameters.CurveID) - } - pubKey = &ecdsa.PublicKey{ - X: p.ECCParameters.Point.X(), - Y: p.ECCParameters.Point.Y(), - Curve: curve, - } - default: - return nil, fmt.Errorf("unsupported public key type 0x%x", p.Type) - } - return pubKey, nil -} - -// Name computes the Digest-based Name from the public area of an object. -func (p Public) Name() (Name, error) { - pubEncoded, err := p.Encode() - if err != nil { - return Name{}, err - } - hash, err := p.NameAlg.Hash() - if err != nil { - return Name{}, err - } - nameHash := hash.New() - nameHash.Write(pubEncoded) - return Name{ - Digest: &HashValue{ - Alg: p.NameAlg, - Value: nameHash.Sum(nil), - }, - }, nil -} - -// MatchesTemplate checks if the Public area has the same algorithms and -// parameters as the provided template. Note that this does not necessarily -// mean that the key was created from this template, as the Unique field is -// both provided in the template and overridden in the key creation process. -func (p Public) MatchesTemplate(template Public) bool { - if p.Type != template.Type || - p.NameAlg != template.NameAlg || - p.Attributes != template.Attributes || - !bytes.Equal(p.AuthPolicy, template.AuthPolicy) { - return false - } - switch p.Type { - case AlgRSA: - return p.RSAParameters.matchesTemplate(template.RSAParameters) - case AlgECC: - return p.ECCParameters.matchesTemplate(template.ECCParameters) - case AlgSymCipher: - return p.SymCipherParameters.matchesTemplate(template.SymCipherParameters) - case AlgKeyedHash: - return p.KeyedHashParameters.matchesTemplate(template.KeyedHashParameters) - default: - return true - } -} - -// DecodePublic decodes a TPMT_PUBLIC message. No error is returned if -// the input has extra trailing data. -func DecodePublic(buf []byte) (Public, error) { - in := bytes.NewBuffer(buf) - var pub Public - var err error - if err = tpmutil.UnpackBuf(in, &pub.Type, &pub.NameAlg, &pub.Attributes, &pub.AuthPolicy); err != nil { - return pub, fmt.Errorf("decoding TPMT_PUBLIC: %v", err) - } - - switch pub.Type { - case AlgRSA: - pub.RSAParameters, err = decodeRSAParams(in) - case AlgECC: - pub.ECCParameters, err = decodeECCParams(in) - case AlgSymCipher: - pub.SymCipherParameters, err = decodeSymCipherParams(in) - case AlgKeyedHash: - pub.KeyedHashParameters, err = decodeKeyedHashParams(in) - default: - err = fmt.Errorf("unsupported type in TPMT_PUBLIC: 0x%x", pub.Type) - } - return pub, err -} - -// RSAParams represents parameters of an RSA key pair: -// both the TPMS_RSA_PARMS and the TPM2B_PUBLIC_KEY_RSA. -// -// Symmetric and Sign may be nil, depending on key Attributes in Public. -// -// ExponentRaw and ModulusRaw are the actual data encoded in the template, which -// is useful for templates that differ in zero-padding, for example. -type RSAParams struct { - Symmetric *SymScheme - Sign *SigScheme - KeyBits uint16 - ExponentRaw uint32 - ModulusRaw tpmutil.U16Bytes -} - -// Exponent returns the RSA exponent value represented by ExponentRaw, handling -// the fact that an exponent of 0 represents a value of 65537 (2^16 + 1). -func (p *RSAParams) Exponent() uint32 { - if p.ExponentRaw == 0 { - return defaultRSAExponent - } - return p.ExponentRaw -} - -// Modulus returns the RSA modulus value represented by ModulusRaw, handling the -// fact that the same modulus value can have multiple different representations. -func (p *RSAParams) Modulus() *big.Int { - return new(big.Int).SetBytes(p.ModulusRaw) -} - -func (p *RSAParams) matchesTemplate(t *RSAParams) bool { - return reflect.DeepEqual(p.Symmetric, t.Symmetric) && - reflect.DeepEqual(p.Sign, t.Sign) && - p.KeyBits == t.KeyBits && p.ExponentRaw == t.ExponentRaw -} - -func (p *RSAParams) encode() ([]byte, error) { - if p == nil { - return nil, nil - } - sym, err := p.Symmetric.encode() - if err != nil { - return nil, fmt.Errorf("encoding Symmetric: %v", err) - } - sig, err := p.Sign.encode() - if err != nil { - return nil, fmt.Errorf("encoding Sign: %v", err) - } - rest, err := tpmutil.Pack(p.KeyBits, p.ExponentRaw, p.ModulusRaw) - if err != nil { - return nil, fmt.Errorf("encoding KeyBits, Exponent, Modulus: %v", err) - } - return concat(sym, sig, rest) -} - -func decodeRSAParams(in *bytes.Buffer) (*RSAParams, error) { - var params RSAParams - var err error - - if params.Symmetric, err = decodeSymScheme(in); err != nil { - return nil, fmt.Errorf("decoding Symmetric: %v", err) - } - if params.Sign, err = decodeSigScheme(in); err != nil { - return nil, fmt.Errorf("decoding Sign: %v", err) - } - if err := tpmutil.UnpackBuf(in, ¶ms.KeyBits, ¶ms.ExponentRaw, ¶ms.ModulusRaw); err != nil { - return nil, fmt.Errorf("decoding KeyBits, Exponent, Modulus: %v", err) - } - return ¶ms, nil -} - -// ECCParams represents parameters of an ECC key pair: -// both the TPMS_ECC_PARMS and the TPMS_ECC_POINT. -// -// Symmetric, Sign and KDF may be nil, depending on key Attributes in Public. -type ECCParams struct { - Symmetric *SymScheme - Sign *SigScheme - CurveID EllipticCurve - KDF *KDFScheme - Point ECPoint -} - -// ECPoint represents a ECC coordinates for a point using byte buffers. -type ECPoint struct { - XRaw, YRaw tpmutil.U16Bytes -} - -// X returns the X Point value reprsented by XRaw. -func (p ECPoint) X() *big.Int { - return new(big.Int).SetBytes(p.XRaw) -} - -// Y returns the Y Point value reprsented by YRaw. -func (p ECPoint) Y() *big.Int { - return new(big.Int).SetBytes(p.YRaw) -} - -func (p *ECCParams) matchesTemplate(t *ECCParams) bool { - return reflect.DeepEqual(p.Symmetric, t.Symmetric) && - reflect.DeepEqual(p.Sign, t.Sign) && - p.CurveID == t.CurveID && reflect.DeepEqual(p.KDF, t.KDF) -} - -func (p *ECCParams) encode() ([]byte, error) { - if p == nil { - return nil, nil - } - sym, err := p.Symmetric.encode() - if err != nil { - return nil, fmt.Errorf("encoding Symmetric: %v", err) - } - sig, err := p.Sign.encode() - if err != nil { - return nil, fmt.Errorf("encoding Sign: %v", err) - } - curve, err := tpmutil.Pack(p.CurveID) - if err != nil { - return nil, fmt.Errorf("encoding CurveID: %v", err) - } - kdf, err := p.KDF.encode() - if err != nil { - return nil, fmt.Errorf("encoding KDF: %v", err) - } - point, err := tpmutil.Pack(p.Point.XRaw, p.Point.YRaw) - if err != nil { - return nil, fmt.Errorf("encoding Point: %v", err) - } - return concat(sym, sig, curve, kdf, point) -} - -func decodeECCParams(in *bytes.Buffer) (*ECCParams, error) { - var params ECCParams - var err error - - if params.Symmetric, err = decodeSymScheme(in); err != nil { - return nil, fmt.Errorf("decoding Symmetric: %v", err) - } - if params.Sign, err = decodeSigScheme(in); err != nil { - return nil, fmt.Errorf("decoding Sign: %v", err) - } - if err := tpmutil.UnpackBuf(in, ¶ms.CurveID); err != nil { - return nil, fmt.Errorf("decoding CurveID: %v", err) - } - if params.KDF, err = decodeKDFScheme(in); err != nil { - return nil, fmt.Errorf("decoding KDF: %v", err) - } - if err := tpmutil.UnpackBuf(in, ¶ms.Point.XRaw, ¶ms.Point.YRaw); err != nil { - return nil, fmt.Errorf("decoding Point: %v", err) - } - return ¶ms, nil -} - -// SymCipherParams represents parameters of a symmetric block cipher TPM object: -// both the TPMS_SYMCIPHER_PARMS and the TPM2B_DIGEST (hash of the key). -type SymCipherParams struct { - Symmetric *SymScheme - Unique tpmutil.U16Bytes -} - -func (p *SymCipherParams) matchesTemplate(t *SymCipherParams) bool { - return reflect.DeepEqual(p.Symmetric, t.Symmetric) -} - -func (p *SymCipherParams) encode() ([]byte, error) { - sym, err := p.Symmetric.encode() - if err != nil { - return nil, fmt.Errorf("encoding Symmetric: %v", err) - } - unique, err := tpmutil.Pack(p.Unique) - if err != nil { - return nil, fmt.Errorf("encoding Unique: %v", err) - } - return concat(sym, unique) -} - -func decodeSymCipherParams(in *bytes.Buffer) (*SymCipherParams, error) { - var params SymCipherParams - var err error - - if params.Symmetric, err = decodeSymScheme(in); err != nil { - return nil, fmt.Errorf("decoding Symmetric: %v", err) - } - if err := tpmutil.UnpackBuf(in, ¶ms.Unique); err != nil { - return nil, fmt.Errorf("decoding Unique: %v", err) - } - return ¶ms, nil -} - -// KeyedHashParams represents parameters of a keyed hash TPM object: -// both the TPMS_KEYEDHASH_PARMS and the TPM2B_DIGEST (hash of the key). -type KeyedHashParams struct { - Alg Algorithm - Hash Algorithm - KDF Algorithm - Unique tpmutil.U16Bytes -} - -func (p *KeyedHashParams) matchesTemplate(t *KeyedHashParams) bool { - if p.Alg != t.Alg { - return false - } - switch p.Alg { - case AlgHMAC: - return p.Hash == t.Hash - case AlgXOR: - return p.Hash == t.Hash && p.KDF == t.KDF - default: - return true - } -} - -func (p *KeyedHashParams) encode() ([]byte, error) { - if p == nil { - return tpmutil.Pack(AlgNull, tpmutil.U16Bytes(nil)) - } - var params []byte - var err error - switch p.Alg { - case AlgNull: - params, err = tpmutil.Pack(p.Alg) - case AlgHMAC: - params, err = tpmutil.Pack(p.Alg, p.Hash) - case AlgXOR: - params, err = tpmutil.Pack(p.Alg, p.Hash, p.KDF) - default: - err = fmt.Errorf("unsupported KeyedHash Algorithm: 0x%x", p.Alg) - } - if err != nil { - return nil, fmt.Errorf("encoding Alg Params: %v", err) - } - unique, err := tpmutil.Pack(p.Unique) - if err != nil { - return nil, fmt.Errorf("encoding Unique: %v", err) - } - return concat(params, unique) -} - -func decodeKeyedHashParams(in *bytes.Buffer) (*KeyedHashParams, error) { - var p KeyedHashParams - var err error - if err = tpmutil.UnpackBuf(in, &p.Alg); err != nil { - return nil, fmt.Errorf("decoding Alg: %v", err) - } - switch p.Alg { - case AlgNull: - err = nil - case AlgHMAC: - err = tpmutil.UnpackBuf(in, &p.Hash) - case AlgXOR: - err = tpmutil.UnpackBuf(in, &p.Hash, &p.KDF) - default: - err = fmt.Errorf("unsupported KeyedHash Algorithm: 0x%x", p.Alg) - } - if err != nil { - return nil, fmt.Errorf("decoding Alg Params: %v", err) - } - if err = tpmutil.UnpackBuf(in, &p.Unique); err != nil { - return nil, fmt.Errorf("decoding Unique: %v", err) - } - return &p, nil -} - -// SymScheme represents a symmetric encryption scheme. -// Known in the specification by TPMT_SYM_DEF_OBJECT. -type SymScheme struct { - Alg Algorithm - KeyBits uint16 - Mode Algorithm -} - -func (s *SymScheme) encode() ([]byte, error) { - if s == nil || s.Alg.IsNull() { - return tpmutil.Pack(AlgNull) - } - return tpmutil.Pack(s.Alg, s.KeyBits, s.Mode) -} - -func decodeSymScheme(in *bytes.Buffer) (*SymScheme, error) { - var scheme SymScheme - if err := tpmutil.UnpackBuf(in, &scheme.Alg); err != nil { - return nil, fmt.Errorf("decoding Alg: %v", err) - } - if scheme.Alg == AlgNull { - return nil, nil - } - if err := tpmutil.UnpackBuf(in, &scheme.KeyBits, &scheme.Mode); err != nil { - return nil, fmt.Errorf("decoding KeyBits, Mode: %v", err) - } - return &scheme, nil -} - -// AsymScheme represents am asymmetric encryption scheme. -type AsymScheme struct { - Alg Algorithm - Hash Algorithm -} - -func (s *AsymScheme) encode() ([]byte, error) { - if s == nil || s.Alg.IsNull() { - return tpmutil.Pack(AlgNull) - } - if s.Alg.UsesHash() { - return tpmutil.Pack(s.Alg, s.Hash) - } - return tpmutil.Pack(s.Alg) -} - -// SigScheme represents a signing scheme. -type SigScheme struct { - Alg Algorithm - Hash Algorithm - Count uint32 -} - -func (s *SigScheme) encode() ([]byte, error) { - if s == nil || s.Alg.IsNull() { - return tpmutil.Pack(AlgNull) - } - if s.Alg.UsesCount() { - return tpmutil.Pack(s.Alg, s.Hash, s.Count) - } - return tpmutil.Pack(s.Alg, s.Hash) -} - -func decodeSigScheme(in *bytes.Buffer) (*SigScheme, error) { - var scheme SigScheme - if err := tpmutil.UnpackBuf(in, &scheme.Alg); err != nil { - return nil, fmt.Errorf("decoding Alg: %v", err) - } - if scheme.Alg == AlgNull { - return nil, nil - } - if err := tpmutil.UnpackBuf(in, &scheme.Hash); err != nil { - return nil, fmt.Errorf("decoding Hash: %v", err) - } - if scheme.Alg.UsesCount() { - if err := tpmutil.UnpackBuf(in, &scheme.Count); err != nil { - return nil, fmt.Errorf("decoding Count: %v", err) - } - } - return &scheme, nil -} - -// KDFScheme represents a KDF (Key Derivation Function) scheme. -type KDFScheme struct { - Alg Algorithm - Hash Algorithm -} - -func (s *KDFScheme) encode() ([]byte, error) { - if s == nil || s.Alg.IsNull() { - return tpmutil.Pack(AlgNull) - } - return tpmutil.Pack(s.Alg, s.Hash) -} - -func decodeKDFScheme(in *bytes.Buffer) (*KDFScheme, error) { - var scheme KDFScheme - if err := tpmutil.UnpackBuf(in, &scheme.Alg); err != nil { - return nil, fmt.Errorf("decoding Alg: %v", err) - } - if scheme.Alg == AlgNull { - return nil, nil - } - if err := tpmutil.UnpackBuf(in, &scheme.Hash); err != nil { - return nil, fmt.Errorf("decoding Hash: %v", err) - } - return &scheme, nil -} - -// Signature combines all possible signatures from RSA and ECC keys. Only one -// of RSA or ECC will be populated. -type Signature struct { - Alg Algorithm - RSA *SignatureRSA - ECC *SignatureECC -} - -// Encode serializes a Signature structure in TPM wire format. -func (s Signature) Encode() ([]byte, error) { - head, err := tpmutil.Pack(s.Alg) - if err != nil { - return nil, fmt.Errorf("encoding Alg: %v", err) - } - var signature []byte - switch s.Alg { - case AlgRSASSA, AlgRSAPSS: - if signature, err = tpmutil.Pack(s.RSA); err != nil { - return nil, fmt.Errorf("encoding RSA: %v", err) - } - case AlgECDSA: - signature, err = tpmutil.Pack(s.ECC.HashAlg, tpmutil.U16Bytes(s.ECC.R.Bytes()), tpmutil.U16Bytes(s.ECC.S.Bytes())) - if err != nil { - return nil, fmt.Errorf("encoding ECC: %v", err) - } - } - return concat(head, signature) -} - -// DecodeSignature decodes a serialized TPMT_SIGNATURE structure. -func DecodeSignature(in *bytes.Buffer) (*Signature, error) { - var sig Signature - if err := tpmutil.UnpackBuf(in, &sig.Alg); err != nil { - return nil, fmt.Errorf("decoding Alg: %v", err) - } - switch sig.Alg { - case AlgRSASSA, AlgRSAPSS: - sig.RSA = new(SignatureRSA) - if err := tpmutil.UnpackBuf(in, sig.RSA); err != nil { - return nil, fmt.Errorf("decoding RSA: %v", err) - } - case AlgECDSA: - sig.ECC = new(SignatureECC) - var r, s tpmutil.U16Bytes - if err := tpmutil.UnpackBuf(in, &sig.ECC.HashAlg, &r, &s); err != nil { - return nil, fmt.Errorf("decoding ECC: %v", err) - } - sig.ECC.R = big.NewInt(0).SetBytes(r) - sig.ECC.S = big.NewInt(0).SetBytes(s) - default: - return nil, fmt.Errorf("unsupported signature algorithm 0x%x", sig.Alg) - } - return &sig, nil -} - -// SignatureRSA is an RSA-specific signature value. -type SignatureRSA struct { - HashAlg Algorithm - Signature tpmutil.U16Bytes -} - -// SignatureECC is an ECC-specific signature value. -type SignatureECC struct { - HashAlg Algorithm - R *big.Int - S *big.Int -} - -// Private contains private section of a TPM key. -type Private struct { - Type Algorithm - AuthValue tpmutil.U16Bytes - SeedValue tpmutil.U16Bytes - Sensitive tpmutil.U16Bytes -} - -// Encode serializes a Private structure in TPM wire format. -func (p Private) Encode() ([]byte, error) { - if p.Type.IsNull() { - return nil, nil - } - return tpmutil.Pack(p) -} - -// AttestationData contains data attested by TPM commands (like Certify). -type AttestationData struct { - Magic uint32 - Type tpmutil.Tag - QualifiedSigner Name - ExtraData tpmutil.U16Bytes - ClockInfo ClockInfo - FirmwareVersion uint64 - AttestedCertifyInfo *CertifyInfo - AttestedQuoteInfo *QuoteInfo - AttestedCreationInfo *CreationInfo -} - -// DecodeAttestationData decode a TPMS_ATTEST message. No error is returned if -// the input has extra trailing data. -func DecodeAttestationData(in []byte) (*AttestationData, error) { - buf := bytes.NewBuffer(in) - - var ad AttestationData - if err := tpmutil.UnpackBuf(buf, &ad.Magic, &ad.Type); err != nil { - return nil, fmt.Errorf("decoding Magic/Type: %v", err) - } - // All attestation structures have the magic prefix - // TPMS_GENERATED_VALUE to symbolize they were created by - // the TPM when signed with an AK. - if ad.Magic != 0xff544347 { - return nil, fmt.Errorf("incorrect magic value: %x", ad.Magic) - } - - n, err := DecodeName(buf) - if err != nil { - return nil, fmt.Errorf("decoding QualifiedSigner: %v", err) - } - ad.QualifiedSigner = *n - if err := tpmutil.UnpackBuf(buf, &ad.ExtraData, &ad.ClockInfo, &ad.FirmwareVersion); err != nil { - return nil, fmt.Errorf("decoding ExtraData/ClockInfo/FirmwareVersion: %v", err) - } - - // The spec specifies several other types of attestation data. We only need - // parsing of Certify & Creation attestation data for now. If you need - // support for other attestation types, add them here. - switch ad.Type { - case TagAttestCertify: - if ad.AttestedCertifyInfo, err = decodeCertifyInfo(buf); err != nil { - return nil, fmt.Errorf("decoding AttestedCertifyInfo: %v", err) - } - case TagAttestCreation: - if ad.AttestedCreationInfo, err = decodeCreationInfo(buf); err != nil { - return nil, fmt.Errorf("decoding AttestedCreationInfo: %v", err) - } - case TagAttestQuote: - if ad.AttestedQuoteInfo, err = decodeQuoteInfo(buf); err != nil { - return nil, fmt.Errorf("decoding AttestedQuoteInfo: %v", err) - } - default: - return nil, fmt.Errorf("only Quote, Certify & Creation attestation structures are supported, got type 0x%x", ad.Type) - } - - return &ad, nil -} - -// Encode serializes an AttestationData structure in TPM wire format. -func (ad AttestationData) Encode() ([]byte, error) { - head, err := tpmutil.Pack(ad.Magic, ad.Type) - if err != nil { - return nil, fmt.Errorf("encoding Magic, Type: %v", err) - } - signer, err := ad.QualifiedSigner.Encode() - if err != nil { - return nil, fmt.Errorf("encoding QualifiedSigner: %v", err) - } - tail, err := tpmutil.Pack(ad.ExtraData, ad.ClockInfo, ad.FirmwareVersion) - if err != nil { - return nil, fmt.Errorf("encoding ExtraData, ClockInfo, FirmwareVersion: %v", err) - } - - var info []byte - switch ad.Type { - case TagAttestCertify: - if info, err = ad.AttestedCertifyInfo.encode(); err != nil { - return nil, fmt.Errorf("encoding AttestedCertifyInfo: %v", err) - } - case TagAttestCreation: - if info, err = ad.AttestedCreationInfo.encode(); err != nil { - return nil, fmt.Errorf("encoding AttestedCreationInfo: %v", err) - } - case TagAttestQuote: - if info, err = ad.AttestedQuoteInfo.encode(); err != nil { - return nil, fmt.Errorf("encoding AttestedQuoteInfo: %v", err) - } - default: - return nil, fmt.Errorf("only Quote, Certify & Creation attestation structures are supported, got type 0x%x", ad.Type) - } - - return concat(head, signer, tail, info) -} - -// CreationInfo contains Creation-specific data for TPMS_ATTEST. -type CreationInfo struct { - Name Name - // Most TPM2B_Digest structures contain a TPMU_HA structure - // and get parsed to HashValue. This is never the case for the - // digest in TPMS_CREATION_INFO. - OpaqueDigest tpmutil.U16Bytes -} - -func decodeCreationInfo(in *bytes.Buffer) (*CreationInfo, error) { - var ci CreationInfo - - n, err := DecodeName(in) - if err != nil { - return nil, fmt.Errorf("decoding Name: %v", err) - } - ci.Name = *n - - if err := tpmutil.UnpackBuf(in, &ci.OpaqueDigest); err != nil { - return nil, fmt.Errorf("decoding Digest: %v", err) - } - - return &ci, nil -} - -func (ci CreationInfo) encode() ([]byte, error) { - n, err := ci.Name.Encode() - if err != nil { - return nil, fmt.Errorf("encoding Name: %v", err) - } - - d, err := tpmutil.Pack(ci.OpaqueDigest) - if err != nil { - return nil, fmt.Errorf("encoding Digest: %v", err) - } - - return concat(n, d) -} - -// CertifyInfo contains Certify-specific data for TPMS_ATTEST. -type CertifyInfo struct { - Name Name - QualifiedName Name -} - -func decodeCertifyInfo(in *bytes.Buffer) (*CertifyInfo, error) { - var ci CertifyInfo - - n, err := DecodeName(in) - if err != nil { - return nil, fmt.Errorf("decoding Name: %v", err) - } - ci.Name = *n - - n, err = DecodeName(in) - if err != nil { - return nil, fmt.Errorf("decoding QualifiedName: %v", err) - } - ci.QualifiedName = *n - - return &ci, nil -} - -func (ci CertifyInfo) encode() ([]byte, error) { - n, err := ci.Name.Encode() - if err != nil { - return nil, fmt.Errorf("encoding Name: %v", err) - } - qn, err := ci.QualifiedName.Encode() - if err != nil { - return nil, fmt.Errorf("encoding QualifiedName: %v", err) - } - return concat(n, qn) -} - -// QuoteInfo represents a TPMS_QUOTE_INFO structure. -type QuoteInfo struct { - PCRSelection PCRSelection - PCRDigest tpmutil.U16Bytes -} - -func decodeQuoteInfo(in *bytes.Buffer) (*QuoteInfo, error) { - var out QuoteInfo - sel, err := decodeOneTPMLPCRSelection(in) - if err != nil { - return nil, fmt.Errorf("decoding PCRSelection: %v", err) - } - out.PCRSelection = sel - - if err := tpmutil.UnpackBuf(in, &out.PCRDigest); err != nil { - return nil, fmt.Errorf("decoding PCRDigest: %v", err) - } - return &out, nil -} - -func (qi QuoteInfo) encode() ([]byte, error) { - sel, err := encodeTPMLPCRSelection(qi.PCRSelection) - if err != nil { - return nil, fmt.Errorf("encoding PCRSelection: %v", err) - } - - digest, err := tpmutil.Pack(qi.PCRDigest) - if err != nil { - return nil, fmt.Errorf("encoding PCRDigest: %v", err) - } - - return concat(sel, digest) -} - -// IDObject represents an encrypted credential bound to a TPM object. -type IDObject struct { - IntegrityHMAC tpmutil.U16Bytes - // EncIdentity is packed raw, as the bytes representing the size - // of the credential value are present within the encrypted blob. - EncIdentity tpmutil.RawBytes -} - -// CreationData describes the attributes and environment for an object created -// on the TPM. This structure encodes/decodes to/from TPMS_CREATION_DATA. -type CreationData struct { - PCRSelection PCRSelection - PCRDigest tpmutil.U16Bytes - Locality byte - ParentNameAlg Algorithm - ParentName Name - ParentQualifiedName Name - OutsideInfo tpmutil.U16Bytes -} - -// EncodeCreationData encodes byte array to TPMS_CREATION_DATA message. -func (cd *CreationData) EncodeCreationData() ([]byte, error) { - sel, err := encodeTPMLPCRSelection(cd.PCRSelection) - if err != nil { - return nil, fmt.Errorf("encoding PCRSelection: %v", err) - } - d, err := tpmutil.Pack(cd.PCRDigest, cd.Locality, cd.ParentNameAlg) - if err != nil { - return nil, fmt.Errorf("encoding PCRDigest, Locality, ParentNameAlg: %v", err) - } - pn, err := cd.ParentName.Encode() - if err != nil { - return nil, fmt.Errorf("encoding ParentName: %v", err) - } - pqn, err := cd.ParentQualifiedName.Encode() - if err != nil { - return nil, fmt.Errorf("encoding ParentQualifiedName: %v", err) - } - o, err := tpmutil.Pack(cd.OutsideInfo) - if err != nil { - return nil, fmt.Errorf("encoding OutsideInfo: %v", err) - } - return concat(sel, d, pn, pqn, o) -} - -// DecodeCreationData decodes a TPMS_CREATION_DATA message. No error is -// returned if the input has extra trailing data. -func DecodeCreationData(buf []byte) (*CreationData, error) { - in := bytes.NewBuffer(buf) - var out CreationData - - sel, err := decodeOneTPMLPCRSelection(in) - if err != nil { - return nil, fmt.Errorf("decodeOneTPMLPCRSelection returned error %v", err) - } - out.PCRSelection = sel - - if err := tpmutil.UnpackBuf(in, &out.PCRDigest, &out.Locality, &out.ParentNameAlg); err != nil { - return nil, fmt.Errorf("decoding PCRDigest, Locality, ParentNameAlg: %v", err) - } - - n, err := DecodeName(in) - if err != nil { - return nil, fmt.Errorf("decoding ParentName: %v", err) - } - out.ParentName = *n - if n, err = DecodeName(in); err != nil { - return nil, fmt.Errorf("decoding ParentQualifiedName: %v", err) - } - out.ParentQualifiedName = *n - - if err := tpmutil.UnpackBuf(in, &out.OutsideInfo); err != nil { - return nil, fmt.Errorf("decoding OutsideInfo: %v", err) - } - - return &out, nil -} - -// Name represents a TPM2B_NAME, a name for TPM entities. Only one of -// Handle or Digest should be set. -type Name struct { - Handle *tpmutil.Handle - Digest *HashValue -} - -// DecodeName deserializes a Name hash from the TPM wire format. -func DecodeName(in *bytes.Buffer) (*Name, error) { - var nameBuf tpmutil.U16Bytes - if err := tpmutil.UnpackBuf(in, &nameBuf); err != nil { - return nil, err - } - - name := new(Name) - switch len(nameBuf) { - case 0: - // No name is present. - case 4: - name.Handle = new(tpmutil.Handle) - if err := tpmutil.UnpackBuf(bytes.NewBuffer(nameBuf), name.Handle); err != nil { - return nil, fmt.Errorf("decoding Handle: %v", err) - } - default: - var err error - name.Digest, err = decodeHashValue(bytes.NewBuffer(nameBuf)) - if err != nil { - return nil, fmt.Errorf("decoding Digest: %v", err) - } - } - return name, nil -} - -// Encode serializes a Name hash into the TPM wire format. -func (n Name) Encode() ([]byte, error) { - var buf []byte - var err error - switch { - case n.Handle != nil: - if buf, err = tpmutil.Pack(*n.Handle); err != nil { - return nil, fmt.Errorf("encoding Handle: %v", err) - } - case n.Digest != nil: - if buf, err = n.Digest.Encode(); err != nil { - return nil, fmt.Errorf("encoding Digest: %v", err) - } - default: - // Name is empty, which is valid. - } - return tpmutil.Pack(tpmutil.U16Bytes(buf)) -} - -// MatchesPublic compares Digest in Name against given Public structure. Note: -// this only works for regular Names, not Qualified Names. -func (n Name) MatchesPublic(p Public) (bool, error) { - if n.Digest == nil { - return false, errors.New("Name doesn't have a Digest, can't compare to Public") - } - expected, err := p.Name() - if err != nil { - return false, err - } - // No secrets, so no constant-time comparison - return bytes.Equal(expected.Digest.Value, n.Digest.Value), nil -} - -// HashValue is an algorithm-specific hash value. -type HashValue struct { - Alg Algorithm - Value tpmutil.U16Bytes -} - -func decodeHashValue(in *bytes.Buffer) (*HashValue, error) { - var hv HashValue - if err := tpmutil.UnpackBuf(in, &hv.Alg); err != nil { - return nil, fmt.Errorf("decoding Alg: %v", err) - } - hfn, err := hv.Alg.Hash() - if err != nil { - return nil, err - } - hv.Value = make(tpmutil.U16Bytes, hfn.Size()) - if _, err := in.Read(hv.Value); err != nil { - return nil, fmt.Errorf("decoding Value: %v", err) - } - return &hv, nil -} - -// Encode represents the given hash value as a TPMT_HA structure. -func (hv HashValue) Encode() ([]byte, error) { - return tpmutil.Pack(hv.Alg, tpmutil.RawBytes(hv.Value)) -} - -// ClockInfo contains TPM state info included in AttestationData. -type ClockInfo struct { - Clock uint64 - ResetCount uint32 - RestartCount uint32 - Safe byte -} - -// AlgorithmAttributes represents a TPMA_ALGORITHM value. -type AlgorithmAttributes uint32 - -// AlgorithmDescription represents a TPMS_ALGORITHM_DESCRIPTION structure. -type AlgorithmDescription struct { - ID Algorithm - Attributes AlgorithmAttributes -} - -// TaggedProperty represents a TPMS_TAGGED_PROPERTY structure. -type TaggedProperty struct { - Tag TPMProp - Value uint32 -} - -// Ticket represents evidence the TPM previously processed -// information. -type Ticket struct { - Type tpmutil.Tag - Hierarchy tpmutil.Handle - Digest tpmutil.U16Bytes -} - -// AuthCommand represents a TPMS_AUTH_COMMAND. This structure encapsulates parameters -// which authorize the use of a given handle or parameter. -type AuthCommand struct { - Session tpmutil.Handle - Nonce tpmutil.U16Bytes - Attributes SessionAttributes - Auth tpmutil.U16Bytes -} - -// TPMLDigest represents the TPML_Digest structure -// It is used to convey a list of digest values. -// This type is used in TPM2_PolicyOR() and in TPM2_PCR_Read() -type TPMLDigest struct { - Digests []tpmutil.U16Bytes -} - -// Encode converts the TPMLDigest structure into a byte slice -func (list *TPMLDigest) Encode() ([]byte, error) { - res, err := tpmutil.Pack(uint32(len(list.Digests))) - if err != nil { - return nil, err - } - for _, item := range list.Digests { - b, err := tpmutil.Pack(item) - if err != nil { - return nil, err - } - res = append(res, b...) - - } - return res, nil -} - -// DecodeTPMLDigest decodes a TPML_Digest part of a message. -func DecodeTPMLDigest(buf []byte) (*TPMLDigest, error) { - in := bytes.NewBuffer(buf) - var tpmld TPMLDigest - var count uint32 - if err := binary.Read(in, binary.BigEndian, &count); err != nil { - return nil, fmt.Errorf("decoding TPML_Digest: %v", err) - } - for in.Len() > 0 { - var hash tpmutil.U16Bytes - if err := hash.TPMUnmarshal(in); err != nil { - return nil, err - } - tpmld.Digests = append(tpmld.Digests, hash) - } - if count != uint32(len(tpmld.Digests)) { - return nil, fmt.Errorf("expected size and read size does not match") - } - return &tpmld, nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/tpm2.go b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/tpm2.go deleted file mode 100644 index 18d5a960..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/legacy/tpm2/tpm2.go +++ /dev/null @@ -1,2326 +0,0 @@ -// Copyright (c) 2018, Google LLC All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package tpm2 supports direct communication with a TPM 2.0 device under Linux. -package tpm2 - -import ( - "bytes" - "crypto" - "fmt" - "io" - - "github.com/google/go-tpm/tpmutil" -) - -// GetRandom gets random bytes from the TPM. -func GetRandom(rw io.ReadWriter, size uint16) ([]byte, error) { - resp, err := runCommand(rw, TagNoSessions, CmdGetRandom, size) - if err != nil { - return nil, err - } - - var randBytes tpmutil.U16Bytes - if _, err := tpmutil.Unpack(resp, &randBytes); err != nil { - return nil, err - } - return randBytes, nil -} - -// FlushContext removes an object or session under handle to be removed from -// the TPM. This must be called for any loaded handle to avoid out-of-memory -// errors in TPM. -func FlushContext(rw io.ReadWriter, handle tpmutil.Handle) error { - _, err := runCommand(rw, TagNoSessions, CmdFlushContext, handle) - return err -} - -func encodeTPMLPCRSelection(sel ...PCRSelection) ([]byte, error) { - if len(sel) == 0 { - return tpmutil.Pack(uint32(0)) - } - - // PCR selection is a variable-size bitmask, where position of a set bit is - // the selected PCR index. - // Size of the bitmask in bytes is pre-pended. It should be at least - // sizeOfPCRSelect. - // - // For example, selecting PCRs 3 and 9 looks like: - // size(3) mask mask mask - // 00000011 00000000 00000001 00000100 - var retBytes []byte - for _, s := range sel { - if len(s.PCRs) == 0 { - return tpmutil.Pack(uint32(0)) - } - - ts := tpmsPCRSelection{ - Hash: s.Hash, - Size: sizeOfPCRSelect, - PCRs: make(tpmutil.RawBytes, sizeOfPCRSelect), - } - - // s[i].PCRs parameter is indexes of PCRs, convert that to set bits. - for _, n := range s.PCRs { - if n >= 8*sizeOfPCRSelect { - return nil, fmt.Errorf("PCR index %d is out of range (exceeds maximum value %d)", n, 8*sizeOfPCRSelect-1) - } - byteNum := n / 8 - bytePos := byte(1 << byte(n%8)) - ts.PCRs[byteNum] |= bytePos - } - - tmpBytes, err := tpmutil.Pack(ts) - if err != nil { - return nil, err - } - - retBytes = append(retBytes, tmpBytes...) - } - tmpSize, err := tpmutil.Pack(uint32(len(sel))) - if err != nil { - return nil, err - } - retBytes = append(tmpSize, retBytes...) - - return retBytes, nil -} - -func decodeTPMLPCRSelection(buf *bytes.Buffer) ([]PCRSelection, error) { - var count uint32 - var sel []PCRSelection - - // This unpacks buffer which is of type TPMLPCRSelection - // and returns the count of TPMSPCRSelections. - if err := tpmutil.UnpackBuf(buf, &count); err != nil { - return sel, err - } - - var ts tpmsPCRSelection - for i := 0; i < int(count); i++ { - var s PCRSelection - if err := tpmutil.UnpackBuf(buf, &ts.Hash, &ts.Size); err != nil { - return sel, err - } - ts.PCRs = make(tpmutil.RawBytes, ts.Size) - if _, err := buf.Read(ts.PCRs); err != nil { - return sel, err - } - s.Hash = ts.Hash - for j := 0; j < int(ts.Size); j++ { - for k := 0; k < 8; k++ { - set := ts.PCRs[j] & byte(1< 0, nil - case CapabilityAlgs: - var numAlgs uint32 - if err := tpmutil.UnpackBuf(buf, &numAlgs); err != nil { - return nil, false, fmt.Errorf("could not unpack algorithm count: %v", err) - } - - var algs []interface{} - for i := 0; i < int(numAlgs); i++ { - var alg AlgorithmDescription - if err := tpmutil.UnpackBuf(buf, &alg); err != nil { - return nil, false, fmt.Errorf("could not unpack algorithm description: %v", err) - } - algs = append(algs, alg) - } - return algs, moreData > 0, nil - case CapabilityTPMProperties: - var numProps uint32 - if err := tpmutil.UnpackBuf(buf, &numProps); err != nil { - return nil, false, fmt.Errorf("could not unpack fixed properties count: %v", err) - } - - var props []interface{} - for i := 0; i < int(numProps); i++ { - var prop TaggedProperty - if err := tpmutil.UnpackBuf(buf, &prop); err != nil { - return nil, false, fmt.Errorf("could not unpack tagged property: %v", err) - } - props = append(props, prop) - } - return props, moreData > 0, nil - - case CapabilityPCRs: - var pcrss []interface{} - pcrs, err := decodeTPMLPCRSelection(buf) - if err != nil { - return nil, false, fmt.Errorf("could not unpack pcr selection: %v", err) - } - for i := 0; i < len(pcrs); i++ { - pcrss = append(pcrss, pcrs[i]) - } - - return pcrss, moreData > 0, nil - - default: - return nil, false, fmt.Errorf("unsupported capability %v", capReported) - } -} - -// GetCapability returns various information about the TPM state. -// -// Currently only CapabilityHandles (list active handles) and CapabilityAlgs -// (list supported algorithms) are supported. CapabilityHandles will return -// a []tpmutil.Handle for vals, CapabilityAlgs will return -// []AlgorithmDescription. -// -// moreData is true if the TPM indicated that more data is available. Follow -// the spec for the capability in question on how to query for more data. -func GetCapability(rw io.ReadWriter, capa Capability, count, property uint32) (vals []interface{}, moreData bool, err error) { - resp, err := runCommand(rw, TagNoSessions, CmdGetCapability, capa, property, count) - if err != nil { - return nil, false, err - } - return decodeGetCapability(resp) -} - -// GetManufacturer returns the manufacturer ID -func GetManufacturer(rw io.ReadWriter) ([]byte, error) { - caps, _, err := GetCapability(rw, CapabilityTPMProperties, 1, uint32(Manufacturer)) - if err != nil { - return nil, err - } - - prop := caps[0].(TaggedProperty) - return tpmutil.Pack(prop.Value) -} - -func encodeAuthArea(sections ...AuthCommand) ([]byte, error) { - var res tpmutil.RawBytes - for _, s := range sections { - buf, err := tpmutil.Pack(s) - if err != nil { - return nil, err - } - res = append(res, buf...) - } - - size, err := tpmutil.Pack(uint32(len(res))) - if err != nil { - return nil, err - } - - return concat(size, res) -} - -func encodePCREvent(pcr tpmutil.Handle, eventData []byte) ([]byte, error) { - ha, err := tpmutil.Pack(pcr) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: EmptyAuth}) - if err != nil { - return nil, err - } - event, err := tpmutil.Pack(tpmutil.U16Bytes(eventData)) - if err != nil { - return nil, err - } - return concat(ha, auth, event) -} - -// PCREvent writes an update to the specified PCR. -func PCREvent(rw io.ReadWriter, pcr tpmutil.Handle, eventData []byte) error { - Cmd, err := encodePCREvent(pcr, eventData) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdPCREvent, tpmutil.RawBytes(Cmd)) - return err -} - -func encodeSensitiveArea(s tpmsSensitiveCreate) ([]byte, error) { - // TPMS_SENSITIVE_CREATE - buf, err := tpmutil.Pack(s) - if err != nil { - return nil, err - } - // TPM2B_SENSITIVE_CREATE - return tpmutil.Pack(tpmutil.U16Bytes(buf)) -} - -// encodeCreate works for both TPM2_Create and TPM2_CreatePrimary. -func encodeCreate(owner tpmutil.Handle, sel PCRSelection, auth AuthCommand, ownerPassword string, sensitiveData []byte, pub Public, outsideInfo []byte) ([]byte, error) { - parent, err := tpmutil.Pack(owner) - if err != nil { - return nil, err - } - encodedAuth, err := encodeAuthArea(auth) - if err != nil { - return nil, err - } - inSensitive, err := encodeSensitiveArea(tpmsSensitiveCreate{ - UserAuth: []byte(ownerPassword), - Data: sensitiveData, - }) - if err != nil { - return nil, err - } - inPublic, err := pub.Encode() - if err != nil { - return nil, err - } - publicBlob, err := tpmutil.Pack(tpmutil.U16Bytes(inPublic)) - if err != nil { - return nil, err - } - outsideInfoBlob, err := tpmutil.Pack(tpmutil.U16Bytes(outsideInfo)) - if err != nil { - return nil, err - } - creationPCR, err := encodeTPMLPCRSelection(sel) - if err != nil { - return nil, err - } - return concat( - parent, - encodedAuth, - inSensitive, - publicBlob, - outsideInfoBlob, - creationPCR, - ) -} - -func decodeCreatePrimary(in []byte) (handle tpmutil.Handle, public, creationData, creationHash tpmutil.U16Bytes, ticket Ticket, creationName tpmutil.U16Bytes, err error) { - var paramSize uint32 - - buf := bytes.NewBuffer(in) - // Handle and auth data. - if err := tpmutil.UnpackBuf(buf, &handle, ¶mSize); err != nil { - return 0, nil, nil, nil, Ticket{}, nil, fmt.Errorf("decoding handle, paramSize: %v", err) - } - - if err := tpmutil.UnpackBuf(buf, &public, &creationData, &creationHash, &ticket, &creationName); err != nil { - return 0, nil, nil, nil, Ticket{}, nil, fmt.Errorf("decoding public, creationData, creationHash, ticket, creationName: %v", err) - } - - if _, err := DecodeCreationData(creationData); err != nil { - return 0, nil, nil, nil, Ticket{}, nil, fmt.Errorf("parsing CreationData: %v", err) - } - return handle, public, creationData, creationHash, ticket, creationName, err -} - -// CreatePrimary initializes the primary key in a given hierarchy. -// The second return value is the public part of the generated key. -func CreatePrimary(rw io.ReadWriter, owner tpmutil.Handle, sel PCRSelection, parentPassword, ownerPassword string, p Public) (tpmutil.Handle, crypto.PublicKey, error) { - hnd, public, _, _, _, _, err := CreatePrimaryEx(rw, owner, sel, parentPassword, ownerPassword, p) - if err != nil { - return 0, nil, err - } - - pub, err := DecodePublic(public) - if err != nil { - return 0, nil, fmt.Errorf("parsing public: %v", err) - } - - pubKey, err := pub.Key() - if err != nil { - return 0, nil, fmt.Errorf("extracting cryto.PublicKey from Public part of primary key: %v", err) - } - - return hnd, pubKey, err -} - -// CreatePrimaryEx initializes the primary key in a given hierarchy. -// This function differs from CreatePrimary in that all response elements -// are returned, and they are returned in relatively raw form. -func CreatePrimaryEx(rw io.ReadWriter, owner tpmutil.Handle, sel PCRSelection, parentPassword, ownerPassword string, pub Public) (keyHandle tpmutil.Handle, public, creationData, creationHash []byte, ticket Ticket, creationName []byte, err error) { - auth := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(parentPassword)} - Cmd, err := encodeCreate(owner, sel, auth, ownerPassword, nil /*inSensitive*/, pub, nil /*OutsideInfo*/) - if err != nil { - return 0, nil, nil, nil, Ticket{}, nil, err - } - resp, err := runCommand(rw, TagSessions, CmdCreatePrimary, tpmutil.RawBytes(Cmd)) - if err != nil { - return 0, nil, nil, nil, Ticket{}, nil, err - } - - return decodeCreatePrimary(resp) -} - -// CreatePrimaryRawTemplate is CreatePrimary, but with the public template -// (TPMT_PUBLIC) provided pre-encoded. This is commonly used with key templates -// stored in NV RAM. -func CreatePrimaryRawTemplate(rw io.ReadWriter, owner tpmutil.Handle, sel PCRSelection, parentPassword, ownerPassword string, public []byte) (tpmutil.Handle, crypto.PublicKey, error) { - pub, err := DecodePublic(public) - if err != nil { - return 0, nil, fmt.Errorf("parsing input template: %v", err) - } - return CreatePrimary(rw, owner, sel, parentPassword, ownerPassword, pub) -} - -func decodeReadPublic(in []byte) (Public, []byte, []byte, error) { - var resp struct { - Public tpmutil.U16Bytes - Name tpmutil.U16Bytes - QualifiedName tpmutil.U16Bytes - } - if _, err := tpmutil.Unpack(in, &resp); err != nil { - return Public{}, nil, nil, err - } - pub, err := DecodePublic(resp.Public) - if err != nil { - return Public{}, nil, nil, err - } - return pub, resp.Name, resp.QualifiedName, nil -} - -// ReadPublic reads the public part of the object under handle. -// Returns the public data, name and qualified name. -func ReadPublic(rw io.ReadWriter, handle tpmutil.Handle) (Public, []byte, []byte, error) { - resp, err := runCommand(rw, TagNoSessions, CmdReadPublic, handle) - if err != nil { - return Public{}, nil, nil, err - } - - return decodeReadPublic(resp) -} - -func decodeCreate(in []byte) (private, public, creationData, creationHash tpmutil.U16Bytes, creationTicket Ticket, err error) { - buf := bytes.NewBuffer(in) - var paramSize uint32 - if err := tpmutil.UnpackBuf(buf, ¶mSize, &private, &public, &creationData, &creationHash, &creationTicket); err != nil { - return nil, nil, nil, nil, Ticket{}, fmt.Errorf("decoding Handle, Private, Public, CreationData, CreationHash, CreationTicket: %v", err) - } - if err != nil { - return nil, nil, nil, nil, Ticket{}, fmt.Errorf("decoding CreationTicket: %v", err) - } - if _, err := DecodeCreationData(creationData); err != nil { - return nil, nil, nil, nil, Ticket{}, fmt.Errorf("decoding CreationData: %v", err) - } - return private, public, creationData, creationHash, creationTicket, nil -} - -func create(rw io.ReadWriter, parentHandle tpmutil.Handle, auth AuthCommand, objectPassword string, sensitiveData []byte, pub Public, pcrSelection PCRSelection, outsideInfo []byte) (private, public, creationData, creationHash []byte, creationTicket Ticket, err error) { - cmd, err := encodeCreate(parentHandle, pcrSelection, auth, objectPassword, sensitiveData, pub, outsideInfo) - if err != nil { - return nil, nil, nil, nil, Ticket{}, err - } - resp, err := runCommand(rw, TagSessions, CmdCreate, tpmutil.RawBytes(cmd)) - if err != nil { - return nil, nil, nil, nil, Ticket{}, err - } - return decodeCreate(resp) -} - -// CreateKey creates a new key pair under the owner handle. -// Returns private key and public key blobs as well as the -// creation data, a hash of said data and the creation ticket. -func CreateKey(rw io.ReadWriter, owner tpmutil.Handle, sel PCRSelection, parentPassword, ownerPassword string, pub Public) (private, public, creationData, creationHash []byte, creationTicket Ticket, err error) { - auth := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(parentPassword)} - return create(rw, owner, auth, ownerPassword, nil /*inSensitive*/, pub, sel, nil /*OutsideInfo*/) -} - -// CreateKeyUsingAuth creates a new key pair under the owner handle using the -// provided AuthCommand. Returns private key and public key blobs as well as -// the creation data, a hash of said data, and the creation ticket. -func CreateKeyUsingAuth(rw io.ReadWriter, owner tpmutil.Handle, sel PCRSelection, auth AuthCommand, ownerPassword string, pub Public) (private, public, creationData, creationHash []byte, creationTicket Ticket, err error) { - return create(rw, owner, auth, ownerPassword, nil /*inSensitive*/, pub, sel, nil /*OutsideInfo*/) -} - -// CreateKeyWithSensitive is very similar to CreateKey, except -// that it can take in a piece of sensitive data. -func CreateKeyWithSensitive(rw io.ReadWriter, owner tpmutil.Handle, sel PCRSelection, parentPassword, ownerPassword string, pub Public, sensitive []byte) (private, public, creationData, creationHash []byte, creationTicket Ticket, err error) { - auth := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(parentPassword)} - return create(rw, owner, auth, ownerPassword, sensitive, pub, sel, nil /*OutsideInfo*/) -} - -// CreateKeyWithOutsideInfo is very similar to CreateKey, except -// that it returns the outside information. -func CreateKeyWithOutsideInfo(rw io.ReadWriter, owner tpmutil.Handle, sel PCRSelection, parentPassword, ownerPassword string, pub Public, outsideInfo []byte) (private, public, creationData, creationHash []byte, creationTicket Ticket, err error) { - auth := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(parentPassword)} - return create(rw, owner, auth, ownerPassword, nil /*inSensitive*/, pub, sel, outsideInfo) -} - -// Seal creates a data blob object that seals the sensitive data under a parent and with a -// password and auth policy. Access to the parent must be available with a simple password. -// Returns private and public portions of the created object. -func Seal(rw io.ReadWriter, parentHandle tpmutil.Handle, parentPassword, objectPassword string, objectAuthPolicy []byte, sensitiveData []byte) ([]byte, []byte, error) { - inPublic := Public{ - Type: AlgKeyedHash, - NameAlg: AlgSHA256, - Attributes: FlagFixedTPM | FlagFixedParent, - AuthPolicy: objectAuthPolicy, - } - auth := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(parentPassword)} - private, public, _, _, _, err := create(rw, parentHandle, auth, objectPassword, sensitiveData, inPublic, PCRSelection{}, nil /*OutsideInfo*/) - if err != nil { - return nil, nil, err - } - return private, public, nil -} - -func encodeImport(parentHandle tpmutil.Handle, auth AuthCommand, publicBlob, privateBlob, symSeed, encryptionKey tpmutil.U16Bytes, sym *SymScheme) ([]byte, error) { - ph, err := tpmutil.Pack(parentHandle) - if err != nil { - return nil, err - } - encodedAuth, err := encodeAuthArea(auth) - if err != nil { - return nil, err - } - data, err := tpmutil.Pack(encryptionKey, publicBlob, privateBlob, symSeed) - if err != nil { - return nil, err - } - encodedScheme, err := sym.encode() - if err != nil { - return nil, err - } - - return concat(ph, encodedAuth, data, encodedScheme) -} - -func decodeImport(resp []byte) ([]byte, error) { - var paramSize uint32 - var outPrivate tpmutil.U16Bytes - _, err := tpmutil.Unpack(resp, ¶mSize, &outPrivate) - return outPrivate, err -} - -// Import allows a user to import a key created on a different computer -// or in a different TPM. The publicBlob and privateBlob must always be -// provided. symSeed should be non-nil iff an "outer wrapper" is used. Both of -// encryptionKey and sym should be non-nil iff an "inner wrapper" is used. -func Import(rw io.ReadWriter, parentHandle tpmutil.Handle, auth AuthCommand, publicBlob, privateBlob, symSeed, encryptionKey []byte, sym *SymScheme) ([]byte, error) { - Cmd, err := encodeImport(parentHandle, auth, publicBlob, privateBlob, symSeed, encryptionKey, sym) - if err != nil { - return nil, err - } - resp, err := runCommand(rw, TagSessions, CmdImport, tpmutil.RawBytes(Cmd)) - if err != nil { - return nil, err - } - return decodeImport(resp) -} - -func encodeLoad(parentHandle tpmutil.Handle, auth AuthCommand, publicBlob, privateBlob tpmutil.U16Bytes) ([]byte, error) { - ah, err := tpmutil.Pack(parentHandle) - if err != nil { - return nil, err - } - encodedAuth, err := encodeAuthArea(auth) - if err != nil { - return nil, err - } - params, err := tpmutil.Pack(privateBlob, publicBlob) - if err != nil { - return nil, err - } - return concat(ah, encodedAuth, params) -} - -func decodeLoad(in []byte) (tpmutil.Handle, []byte, error) { - var handle tpmutil.Handle - var paramSize uint32 - var name tpmutil.U16Bytes - - if _, err := tpmutil.Unpack(in, &handle, ¶mSize, &name); err != nil { - return 0, nil, err - } - - // Re-encode the name as a TPM2B_NAME so it can be parsed by DecodeName(). - b := &bytes.Buffer{} - if err := name.TPMMarshal(b); err != nil { - return 0, nil, err - } - return handle, b.Bytes(), nil -} - -// Load loads public/private blobs into an object in the TPM. -// Returns loaded object handle and its name. -func Load(rw io.ReadWriter, parentHandle tpmutil.Handle, parentAuth string, publicBlob, privateBlob []byte) (tpmutil.Handle, []byte, error) { - auth := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(parentAuth)} - return LoadUsingAuth(rw, parentHandle, auth, publicBlob, privateBlob) -} - -// LoadUsingAuth loads public/private blobs into an object in the TPM using the -// provided AuthCommand. Returns loaded object handle and its name. -func LoadUsingAuth(rw io.ReadWriter, parentHandle tpmutil.Handle, auth AuthCommand, publicBlob, privateBlob []byte) (tpmutil.Handle, []byte, error) { - Cmd, err := encodeLoad(parentHandle, auth, publicBlob, privateBlob) - if err != nil { - return 0, nil, err - } - resp, err := runCommand(rw, TagSessions, CmdLoad, tpmutil.RawBytes(Cmd)) - if err != nil { - return 0, nil, err - } - return decodeLoad(resp) -} - -func encodeLoadExternal(pub Public, private Private, hierarchy tpmutil.Handle) ([]byte, error) { - privateBlob, err := private.Encode() - if err != nil { - return nil, err - } - publicBlob, err := pub.Encode() - if err != nil { - return nil, err - } - - return tpmutil.Pack(tpmutil.U16Bytes(privateBlob), tpmutil.U16Bytes(publicBlob), hierarchy) -} - -func decodeLoadExternal(in []byte) (tpmutil.Handle, []byte, error) { - var handle tpmutil.Handle - var name tpmutil.U16Bytes - - if _, err := tpmutil.Unpack(in, &handle, &name); err != nil { - return 0, nil, err - } - return handle, name, nil -} - -// LoadExternal loads a public (and optionally a private) key into an object in -// the TPM. Returns loaded object handle and its name. -func LoadExternal(rw io.ReadWriter, pub Public, private Private, hierarchy tpmutil.Handle) (tpmutil.Handle, []byte, error) { - Cmd, err := encodeLoadExternal(pub, private, hierarchy) - if err != nil { - return 0, nil, err - } - resp, err := runCommand(rw, TagNoSessions, CmdLoadExternal, tpmutil.RawBytes(Cmd)) - if err != nil { - return 0, nil, err - } - handle, name, err := decodeLoadExternal(resp) - if err != nil { - return 0, nil, err - } - return handle, name, nil -} - -// PolicyPassword sets password authorization requirement on the object. -func PolicyPassword(rw io.ReadWriter, handle tpmutil.Handle) error { - _, err := runCommand(rw, TagNoSessions, CmdPolicyPassword, handle) - return err -} - -func encodePolicySecret(entityHandle tpmutil.Handle, entityAuth AuthCommand, policyHandle tpmutil.Handle, policyNonce, cpHash, policyRef tpmutil.U16Bytes, expiry int32) ([]byte, error) { - auth, err := encodeAuthArea(entityAuth) - if err != nil { - return nil, err - } - handles, err := tpmutil.Pack(entityHandle, policyHandle) - if err != nil { - return nil, err - } - params, err := tpmutil.Pack(policyNonce, cpHash, policyRef, expiry) - if err != nil { - return nil, err - } - return concat(handles, auth, params) -} - -func decodePolicySecret(in []byte) ([]byte, *Ticket, error) { - buf := bytes.NewBuffer(in) - - var paramSize uint32 - var timeout tpmutil.U16Bytes - if err := tpmutil.UnpackBuf(buf, ¶mSize, &timeout); err != nil { - return nil, nil, fmt.Errorf("decoding timeout: %v", err) - } - var t Ticket - if err := tpmutil.UnpackBuf(buf, &t); err != nil { - return nil, nil, fmt.Errorf("decoding ticket: %v", err) - } - return timeout, &t, nil -} - -// PolicySecret sets a secret authorization requirement on the provided entity. -func PolicySecret(rw io.ReadWriter, entityHandle tpmutil.Handle, entityAuth AuthCommand, policyHandle tpmutil.Handle, policyNonce, cpHash, policyRef []byte, expiry int32) ([]byte, *Ticket, error) { - Cmd, err := encodePolicySecret(entityHandle, entityAuth, policyHandle, policyNonce, cpHash, policyRef, expiry) - if err != nil { - return nil, nil, err - } - resp, err := runCommand(rw, TagSessions, CmdPolicySecret, tpmutil.RawBytes(Cmd)) - if err != nil { - return nil, nil, err - } - return decodePolicySecret(resp) -} - -func encodePolicySigned(validationKeyHandle tpmutil.Handle, policyHandle tpmutil.Handle, policyNonce, cpHash, policyRef tpmutil.U16Bytes, expiry int32, auth []byte) ([]byte, error) { - handles, err := tpmutil.Pack(validationKeyHandle, policyHandle) - if err != nil { - return nil, err - } - params, err := tpmutil.Pack(policyNonce, cpHash, policyRef, expiry, auth) - if err != nil { - return nil, err - } - return concat(handles, params) -} - -func decodePolicySigned(in []byte) ([]byte, *Ticket, error) { - buf := bytes.NewBuffer(in) - - var timeout tpmutil.U16Bytes - if err := tpmutil.UnpackBuf(buf, &timeout); err != nil { - return nil, nil, fmt.Errorf("decoding timeout: %v", err) - } - var t Ticket - if err := tpmutil.UnpackBuf(buf, &t); err != nil { - return nil, nil, fmt.Errorf("decoding ticket: %v", err) - } - return timeout, &t, nil -} - -// PolicySigned sets a signed authorization requirement on the provided policy. -func PolicySigned(rw io.ReadWriter, validationKeyHandle tpmutil.Handle, policyHandle tpmutil.Handle, policyNonce, cpHash, policyRef []byte, expiry int32, signedAuth []byte) ([]byte, *Ticket, error) { - Cmd, err := encodePolicySigned(validationKeyHandle, policyHandle, policyNonce, cpHash, policyRef, expiry, signedAuth) - if err != nil { - return nil, nil, err - } - resp, err := runCommand(rw, TagNoSessions, CmdPolicySigned, tpmutil.RawBytes(Cmd)) - if err != nil { - return nil, nil, err - } - return decodePolicySigned(resp) -} - -func encodePolicyPCR(session tpmutil.Handle, expectedDigest tpmutil.U16Bytes, sel PCRSelection) ([]byte, error) { - params, err := tpmutil.Pack(session, expectedDigest) - if err != nil { - return nil, err - } - pcrs, err := encodeTPMLPCRSelection(sel) - if err != nil { - return nil, err - } - return concat(params, pcrs) -} - -// PolicyPCR sets PCR state binding for authorization on a session. -// -// expectedDigest is optional. When specified, it's compared against the digest -// of PCRs matched by sel. -// -// Note that expectedDigest must be a *digest* of the expected PCR value. You -// must compute the digest manually. ReadPCR returns raw PCR values, not their -// digests. -// If you wish to select multiple PCRs, concatenate their values before -// computing the digest. See "TPM 2.0 Part 1, Selecting Multiple PCR". -func PolicyPCR(rw io.ReadWriter, session tpmutil.Handle, expectedDigest []byte, sel PCRSelection) error { - Cmd, err := encodePolicyPCR(session, expectedDigest, sel) - if err != nil { - return err - } - _, err = runCommand(rw, TagNoSessions, CmdPolicyPCR, tpmutil.RawBytes(Cmd)) - return err -} - -// PolicyOr compares PolicySession→Digest against the list of provided values. -// If the current Session→Digest does not match any value in the list, -// the TPM shall return TPM_RC_VALUE. Otherwise, the TPM will reset policySession→Digest -// to a Zero Digest. Then policySession→Digest is extended by the concatenation of -// TPM_CC_PolicyOR and the concatenation of all of the digests. -func PolicyOr(rw io.ReadWriter, session tpmutil.Handle, digests TPMLDigest) error { - d, err := digests.Encode() - if err != nil { - return err - } - data, err := tpmutil.Pack(session, d) - if err != nil { - return err - } - _, err = runCommand(rw, TagNoSessions, CmdPolicyOr, data) - return err -} - -// PolicyGetDigest returns the current policyDigest of the session. -func PolicyGetDigest(rw io.ReadWriter, handle tpmutil.Handle) ([]byte, error) { - resp, err := runCommand(rw, TagNoSessions, CmdPolicyGetDigest, handle) - if err != nil { - return nil, err - } - - var digest tpmutil.U16Bytes - _, err = tpmutil.Unpack(resp, &digest) - return digest, err -} - -func encodeStartAuthSession(tpmKey, bindKey tpmutil.Handle, nonceCaller, secret tpmutil.U16Bytes, se SessionType, sym, hashAlg Algorithm) ([]byte, error) { - ha, err := tpmutil.Pack(tpmKey, bindKey) - if err != nil { - return nil, err - } - params, err := tpmutil.Pack(nonceCaller, secret, se, sym, hashAlg) - if err != nil { - return nil, err - } - return concat(ha, params) -} - -func decodeStartAuthSession(in []byte) (tpmutil.Handle, []byte, error) { - var handle tpmutil.Handle - var nonce tpmutil.U16Bytes - if _, err := tpmutil.Unpack(in, &handle, &nonce); err != nil { - return 0, nil, err - } - return handle, nonce, nil -} - -// StartAuthSession initializes a session object. -// Returns session handle and the initial nonce from the TPM. -func StartAuthSession(rw io.ReadWriter, tpmKey, bindKey tpmutil.Handle, nonceCaller, secret []byte, se SessionType, sym, hashAlg Algorithm) (tpmutil.Handle, []byte, error) { - Cmd, err := encodeStartAuthSession(tpmKey, bindKey, nonceCaller, secret, se, sym, hashAlg) - if err != nil { - return 0, nil, err - } - resp, err := runCommand(rw, TagNoSessions, CmdStartAuthSession, tpmutil.RawBytes(Cmd)) - if err != nil { - return 0, nil, err - } - return decodeStartAuthSession(resp) -} - -func encodeUnseal(sessionHandle, itemHandle tpmutil.Handle, password string) ([]byte, error) { - ha, err := tpmutil.Pack(itemHandle) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(AuthCommand{Session: sessionHandle, Attributes: AttrContinueSession, Auth: []byte(password)}) - if err != nil { - return nil, err - } - return concat(ha, auth) -} - -func decodeUnseal(in []byte) ([]byte, error) { - var paramSize uint32 - var unsealed tpmutil.U16Bytes - - if _, err := tpmutil.Unpack(in, ¶mSize, &unsealed); err != nil { - return nil, err - } - return unsealed, nil -} - -// Unseal returns the data for a loaded sealed object. -func Unseal(rw io.ReadWriter, itemHandle tpmutil.Handle, password string) ([]byte, error) { - return UnsealWithSession(rw, HandlePasswordSession, itemHandle, password) -} - -// UnsealWithSession returns the data for a loaded sealed object. -func UnsealWithSession(rw io.ReadWriter, sessionHandle, itemHandle tpmutil.Handle, password string) ([]byte, error) { - Cmd, err := encodeUnseal(sessionHandle, itemHandle, password) - if err != nil { - return nil, err - } - resp, err := runCommand(rw, TagSessions, CmdUnseal, tpmutil.RawBytes(Cmd)) - if err != nil { - return nil, err - } - return decodeUnseal(resp) -} - -func encodeQuote(signingHandle tpmutil.Handle, signerAuth string, toQuote tpmutil.U16Bytes, sel PCRSelection, sigAlg Algorithm) ([]byte, error) { - ha, err := tpmutil.Pack(signingHandle) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(signerAuth)}) - if err != nil { - return nil, err - } - params, err := tpmutil.Pack(toQuote, sigAlg) - if err != nil { - return nil, err - } - pcrs, err := encodeTPMLPCRSelection(sel) - if err != nil { - return nil, err - } - return concat(ha, auth, params, pcrs) -} - -func decodeQuote(in []byte) ([]byte, []byte, error) { - buf := bytes.NewBuffer(in) - var paramSize uint32 - if err := tpmutil.UnpackBuf(buf, ¶mSize); err != nil { - return nil, nil, err - } - buf.Truncate(int(paramSize)) - var attest tpmutil.U16Bytes - if err := tpmutil.UnpackBuf(buf, &attest); err != nil { - return nil, nil, err - } - return attest, buf.Bytes(), nil -} - -// Quote returns a quote of PCR values. A quote is a signature of the PCR -// values, created using a signing TPM key. -// -// Returns attestation data and the decoded signature. -func Quote(rw io.ReadWriter, signingHandle tpmutil.Handle, signerAuth, unused string, toQuote []byte, sel PCRSelection, sigAlg Algorithm) ([]byte, *Signature, error) { - // TODO: Remove "unused" parameter on next breaking change. - attest, sigRaw, err := QuoteRaw(rw, signingHandle, signerAuth, unused, toQuote, sel, sigAlg) - if err != nil { - return nil, nil, err - } - sig, err := DecodeSignature(bytes.NewBuffer(sigRaw)) - if err != nil { - return nil, nil, err - } - return attest, sig, nil -} - -// QuoteRaw is very similar to Quote, except that it will return -// the raw signature in a byte array without decoding. -func QuoteRaw(rw io.ReadWriter, signingHandle tpmutil.Handle, signerAuth, _ string, toQuote []byte, sel PCRSelection, sigAlg Algorithm) ([]byte, []byte, error) { - // TODO: Remove "unused" parameter on next breaking change. - Cmd, err := encodeQuote(signingHandle, signerAuth, toQuote, sel, sigAlg) - if err != nil { - return nil, nil, err - } - resp, err := runCommand(rw, TagSessions, CmdQuote, tpmutil.RawBytes(Cmd)) - if err != nil { - return nil, nil, err - } - return decodeQuote(resp) -} - -func encodeActivateCredential(auth []AuthCommand, activeHandle tpmutil.Handle, keyHandle tpmutil.Handle, credBlob, secret tpmutil.U16Bytes) ([]byte, error) { - ha, err := tpmutil.Pack(activeHandle, keyHandle) - if err != nil { - return nil, err - } - a, err := encodeAuthArea(auth...) - if err != nil { - return nil, err - } - params, err := tpmutil.Pack(credBlob, secret) - if err != nil { - return nil, err - } - return concat(ha, a, params) -} - -func decodeActivateCredential(in []byte) ([]byte, error) { - var paramSize uint32 - var certInfo tpmutil.U16Bytes - - if _, err := tpmutil.Unpack(in, ¶mSize, &certInfo); err != nil { - return nil, err - } - return certInfo, nil -} - -// ActivateCredential associates an object with a credential. -// Returns decrypted certificate information. -func ActivateCredential(rw io.ReadWriter, activeHandle, keyHandle tpmutil.Handle, activePassword, protectorPassword string, credBlob, secret []byte) ([]byte, error) { - return ActivateCredentialUsingAuth(rw, []AuthCommand{ - {Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(activePassword)}, - {Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(protectorPassword)}, - }, activeHandle, keyHandle, credBlob, secret) -} - -// ActivateCredentialUsingAuth associates an object with a credential, using the -// given set of authorizations. Two authorization must be provided. -// Returns decrypted certificate information. -func ActivateCredentialUsingAuth(rw io.ReadWriter, auth []AuthCommand, activeHandle, keyHandle tpmutil.Handle, credBlob, secret []byte) ([]byte, error) { - if len(auth) != 2 { - return nil, fmt.Errorf("len(auth) = %d, want 2", len(auth)) - } - - Cmd, err := encodeActivateCredential(auth, activeHandle, keyHandle, credBlob, secret) - if err != nil { - return nil, err - } - resp, err := runCommand(rw, TagSessions, CmdActivateCredential, tpmutil.RawBytes(Cmd)) - if err != nil { - return nil, err - } - return decodeActivateCredential(resp) -} - -func encodeMakeCredential(protectorHandle tpmutil.Handle, credential, activeName tpmutil.U16Bytes) ([]byte, error) { - ha, err := tpmutil.Pack(protectorHandle) - if err != nil { - return nil, err - } - params, err := tpmutil.Pack(credential, activeName) - if err != nil { - return nil, err - } - return concat(ha, params) -} - -func decodeMakeCredential(in []byte) ([]byte, []byte, error) { - var credBlob, encryptedSecret tpmutil.U16Bytes - - if _, err := tpmutil.Unpack(in, &credBlob, &encryptedSecret); err != nil { - return nil, nil, err - } - return credBlob, encryptedSecret, nil -} - -// MakeCredential creates an encrypted credential for use in MakeCredential. -// Returns encrypted credential and wrapped secret used to encrypt it. -func MakeCredential(rw io.ReadWriter, protectorHandle tpmutil.Handle, credential, activeName []byte) ([]byte, []byte, error) { - Cmd, err := encodeMakeCredential(protectorHandle, credential, activeName) - if err != nil { - return nil, nil, err - } - resp, err := runCommand(rw, TagNoSessions, CmdMakeCredential, tpmutil.RawBytes(Cmd)) - if err != nil { - return nil, nil, err - } - return decodeMakeCredential(resp) -} - -func encodeEvictControl(ownerAuth string, owner, objectHandle, persistentHandle tpmutil.Handle) ([]byte, error) { - ha, err := tpmutil.Pack(owner, objectHandle) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(ownerAuth)}) - if err != nil { - return nil, err - } - params, err := tpmutil.Pack(persistentHandle) - if err != nil { - return nil, err - } - return concat(ha, auth, params) -} - -// EvictControl toggles persistence of an object within the TPM. -func EvictControl(rw io.ReadWriter, ownerAuth string, owner, objectHandle, persistentHandle tpmutil.Handle) error { - Cmd, err := encodeEvictControl(ownerAuth, owner, objectHandle, persistentHandle) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdEvictControl, tpmutil.RawBytes(Cmd)) - return err -} - -func encodeClear(handle tpmutil.Handle, auth AuthCommand) ([]byte, error) { - ah, err := tpmutil.Pack(handle) - if err != nil { - return nil, err - } - encodedAuth, err := encodeAuthArea(auth) - if err != nil { - return nil, err - } - return concat(ah, encodedAuth) -} - -// Clear clears lockout, endorsement and owner hierarchy authorization values -func Clear(rw io.ReadWriter, handle tpmutil.Handle, auth AuthCommand) error { - Cmd, err := encodeClear(handle, auth) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdClear, tpmutil.RawBytes(Cmd)) - return err -} - -func encodeHierarchyChangeAuth(handle tpmutil.Handle, auth AuthCommand, newAuth string) ([]byte, error) { - ah, err := tpmutil.Pack(handle) - if err != nil { - return nil, err - } - encodedAuth, err := encodeAuthArea(auth) - if err != nil { - return nil, err - } - param, err := tpmutil.Pack(tpmutil.U16Bytes(newAuth)) - if err != nil { - return nil, err - } - return concat(ah, encodedAuth, param) -} - -// HierarchyChangeAuth changes the authorization values for a hierarchy or for the lockout authority -func HierarchyChangeAuth(rw io.ReadWriter, handle tpmutil.Handle, auth AuthCommand, newAuth string) error { - Cmd, err := encodeHierarchyChangeAuth(handle, auth, newAuth) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdHierarchyChangeAuth, tpmutil.RawBytes(Cmd)) - return err -} - -// ContextSave returns an encrypted version of the session, object or sequence -// context for storage outside of the TPM. The handle references context to -// store. -func ContextSave(rw io.ReadWriter, handle tpmutil.Handle) ([]byte, error) { - return runCommand(rw, TagNoSessions, CmdContextSave, handle) -} - -// ContextLoad reloads context data created by ContextSave. -func ContextLoad(rw io.ReadWriter, saveArea []byte) (tpmutil.Handle, error) { - resp, err := runCommand(rw, TagNoSessions, CmdContextLoad, tpmutil.RawBytes(saveArea)) - if err != nil { - return 0, err - } - var handle tpmutil.Handle - _, err = tpmutil.Unpack(resp, &handle) - return handle, err -} - -func encodeIncrementNV(handle tpmutil.Handle, authString string) ([]byte, error) { - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(authString)}) - if err != nil { - return nil, err - } - out, err := tpmutil.Pack(handle, handle) - if err != nil { - return nil, err - } - return concat(out, auth) -} - -// NVIncrement increments a counter in NVRAM. -func NVIncrement(rw io.ReadWriter, handle tpmutil.Handle, authString string) error { - Cmd, err := encodeIncrementNV(handle, authString) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdIncrementNVCounter, tpmutil.RawBytes(Cmd)) - return err -} - -// NVUndefineSpace removes an index from TPM's NV storage. -func NVUndefineSpace(rw io.ReadWriter, ownerAuth string, owner, index tpmutil.Handle) error { - authArea := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(ownerAuth)} - return NVUndefineSpaceEx(rw, owner, index, authArea) -} - -// NVUndefineSpaceEx removes an index from NVRAM. Unlike, NVUndefineSpace(), custom command -// authorization can be provided. -func NVUndefineSpaceEx(rw io.ReadWriter, owner, index tpmutil.Handle, authArea AuthCommand) error { - out, err := tpmutil.Pack(owner, index) - if err != nil { - return err - } - auth, err := encodeAuthArea(authArea) - if err != nil { - return err - } - cmd, err := concat(out, auth) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdUndefineSpace, tpmutil.RawBytes(cmd)) - return err -} - -// NVUndefineSpaceSpecial This command allows removal of a platform-created NV Index that has TPMA_NV_POLICY_DELETE SET. -// The policy to authorize NV index access needs to be created with PolicyCommandCode(rw, sessionHandle, CmdNVUndefineSpaceSpecial) function -// nvAuthCmd takes the session handle for the policy and the AuthValue (which can be emptyAuth) for the authorization. -// platformAuth takes either a sessionHandle for the platform policy or HandlePasswordSession and the platformAuth value for authorization. -func NVUndefineSpaceSpecial(rw io.ReadWriter, nvIndex tpmutil.Handle, nvAuth, platformAuth AuthCommand) error { - authBytes, err := encodeAuthArea(nvAuth, platformAuth) - if err != nil { - return err - } - auth, err := tpmutil.Pack(authBytes) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdNVUndefineSpaceSpecial, nvIndex, HandlePlatform, tpmutil.RawBytes(auth)) - return err -} - -// NVDefineSpace creates an index in TPM's NV storage. -func NVDefineSpace(rw io.ReadWriter, owner, handle tpmutil.Handle, ownerAuth, authString string, policy []byte, attributes NVAttr, dataSize uint16) error { - nvPub := NVPublic{ - NVIndex: handle, - NameAlg: AlgSHA1, - Attributes: attributes, - AuthPolicy: policy, - DataSize: dataSize, - } - authArea := AuthCommand{ - Session: HandlePasswordSession, - Attributes: AttrContinueSession, - Auth: []byte(ownerAuth), - } - return NVDefineSpaceEx(rw, owner, authString, nvPub, authArea) -} - -// NVDefineSpaceEx accepts NVPublic structure and AuthCommand, allowing more flexibility. -func NVDefineSpaceEx(rw io.ReadWriter, owner tpmutil.Handle, authVal string, pubInfo NVPublic, authArea AuthCommand) error { - ha, err := tpmutil.Pack(owner) - if err != nil { - return err - } - auth, err := encodeAuthArea(authArea) - if err != nil { - return err - } - publicInfo, err := tpmutil.Pack(pubInfo) - if err != nil { - return err - } - params, err := tpmutil.Pack(tpmutil.U16Bytes(authVal), tpmutil.U16Bytes(publicInfo)) - if err != nil { - return err - } - cmd, err := concat(ha, auth, params) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdDefineSpace, tpmutil.RawBytes(cmd)) - return err -} - -// NVWrite writes data into the TPM's NV storage. -func NVWrite(rw io.ReadWriter, authHandle, nvIndex tpmutil.Handle, authString string, data tpmutil.U16Bytes, offset uint16) error { - auth := AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(authString)} - return NVWriteEx(rw, authHandle, nvIndex, auth, data, offset) -} - -// NVWriteEx does the same as NVWrite with the exception of letting the user take care of the AuthCommand before calling the function. -// This allows more flexibility and does not limit the AuthCommand to PasswordSession. -func NVWriteEx(rw io.ReadWriter, authHandle, nvIndex tpmutil.Handle, authArea AuthCommand, data tpmutil.U16Bytes, offset uint16) error { - h, err := tpmutil.Pack(authHandle, nvIndex) - if err != nil { - return err - } - authEnc, err := encodeAuthArea(authArea) - if err != nil { - return err - } - - d, err := tpmutil.Pack(data, offset) - if err != nil { - return err - } - - b, err := concat(h, authEnc, d) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdWriteNV, tpmutil.RawBytes(b)) - return err -} - -func encodeLockNV(owner, handle tpmutil.Handle, authString string) ([]byte, error) { - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(authString)}) - if err != nil { - return nil, err - } - out, err := tpmutil.Pack(owner, handle) - if err != nil { - return nil, err - } - return concat(out, auth) -} - -// NVWriteLock inhibits further writes on the given NV index if at least one of -// the AttrWriteSTClear or AttrWriteDefine bits is set. -// -// AttrWriteSTClear causes the index to be locked until the TPM is restarted -// (see the Startup function). -// -// AttrWriteDefine causes the index to be locked permanently if data has been -// written to the index; otherwise the lock is removed on startup. -// -// NVWriteLock returns an error if neither bit is set. -// -// It is not an error to call NVWriteLock for an index that is already locked -// for writing. -func NVWriteLock(rw io.ReadWriter, owner, handle tpmutil.Handle, authString string) error { - Cmd, err := encodeLockNV(owner, handle, authString) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdWriteLockNV, tpmutil.RawBytes(Cmd)) - return err -} - -func decodeNVReadPublic(in []byte) (NVPublic, error) { - var pub NVPublic - var buf tpmutil.U16Bytes - if _, err := tpmutil.Unpack(in, &buf); err != nil { - return pub, err - } - _, err := tpmutil.Unpack(buf, &pub) - return pub, err -} - -// NVReadPublic reads the public data of an NV index. -func NVReadPublic(rw io.ReadWriter, index tpmutil.Handle) (NVPublic, error) { - // Read public area to determine data size. - resp, err := runCommand(rw, TagNoSessions, CmdReadPublicNV, index) - if err != nil { - return NVPublic{}, err - } - return decodeNVReadPublic(resp) -} - -func decodeNVRead(in []byte) ([]byte, error) { - var paramSize uint32 - var data tpmutil.U16Bytes - if _, err := tpmutil.Unpack(in, ¶mSize, &data); err != nil { - return nil, err - } - return data, nil -} - -func encodeNVRead(nvIndex, authHandle tpmutil.Handle, password string, offset, dataSize uint16) ([]byte, error) { - handles, err := tpmutil.Pack(authHandle, nvIndex) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(password)}) - if err != nil { - return nil, err - } - - params, err := tpmutil.Pack(dataSize, offset) - if err != nil { - return nil, err - } - - return concat(handles, auth, params) -} - -// NVRead reads a full data blob from an NV index. This function is -// deprecated; use NVReadEx instead. -func NVRead(rw io.ReadWriter, index tpmutil.Handle) ([]byte, error) { - return NVReadEx(rw, index, index, "", 0) -} - -// NVReadEx reads a full data blob from an NV index, using the given -// authorization handle. NVRead commands are done in blocks of blockSize. -// If blockSize is 0, the TPM is queried for TPM_PT_NV_BUFFER_MAX, and that -// value is used. -func NVReadEx(rw io.ReadWriter, index, authHandle tpmutil.Handle, password string, blockSize int) ([]byte, error) { - if blockSize == 0 { - readBuff, _, err := GetCapability(rw, CapabilityTPMProperties, 1, uint32(NVMaxBufferSize)) - if err != nil { - return nil, fmt.Errorf("GetCapability for TPM_PT_NV_BUFFER_MAX failed: %v", err) - } - if len(readBuff) != 1 { - return nil, fmt.Errorf("could not determine NVRAM read/write buffer size") - } - rb, ok := readBuff[0].(TaggedProperty) - if !ok { - return nil, fmt.Errorf("GetCapability returned unexpected type: %T, expected TaggedProperty", readBuff[0]) - } - blockSize = int(rb.Value) - } - - // Read public area to determine data size. - pub, err := NVReadPublic(rw, index) - if err != nil { - return nil, fmt.Errorf("decoding NV_ReadPublic response: %v", err) - } - - // Read the NVRAM area in blocks. - outBuff := make([]byte, 0, int(pub.DataSize)) - for len(outBuff) < int(pub.DataSize) { - readSize := blockSize - if readSize > (int(pub.DataSize) - len(outBuff)) { - readSize = int(pub.DataSize) - len(outBuff) - } - - Cmd, err := encodeNVRead(index, authHandle, password, uint16(len(outBuff)), uint16(readSize)) - if err != nil { - return nil, fmt.Errorf("building NV_Read command: %v", err) - } - resp, err := runCommand(rw, TagSessions, CmdReadNV, tpmutil.RawBytes(Cmd)) - if err != nil { - return nil, fmt.Errorf("running NV_Read command (cursor=%d,size=%d): %v", len(outBuff), readSize, err) - } - data, err := decodeNVRead(resp) - if err != nil { - return nil, fmt.Errorf("decoding NV_Read command: %v", err) - } - outBuff = append(outBuff, data...) - } - return outBuff, nil -} - -// NVReadLock inhibits further reads of the given NV index if AttrReadSTClear -// is set. After the TPM is restarted the index can be read again (see the -// Startup function). -// -// NVReadLock returns an error if the AttrReadSTClear bit is not set. -// -// It is not an error to call NVReadLock for an index that is already locked -// for reading. -func NVReadLock(rw io.ReadWriter, owner, handle tpmutil.Handle, authString string) error { - Cmd, err := encodeLockNV(owner, handle, authString) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdReadLockNV, tpmutil.RawBytes(Cmd)) - return err -} - -// decodeHash unpacks a successful response to TPM2_Hash, returning the computed digest and -// validation ticket. -func decodeHash(resp []byte) ([]byte, *Ticket, error) { - var digest tpmutil.U16Bytes - var validation Ticket - - buf := bytes.NewBuffer(resp) - if err := tpmutil.UnpackBuf(buf, &digest, &validation); err != nil { - return nil, nil, err - } - return digest, &validation, nil -} - -// Hash computes a hash of data in buf using TPM2_Hash, returning the computed -// digest and validation ticket. The validation ticket serves as confirmation -// from the TPM that the data in buf did not begin with TPM_GENERATED_VALUE. -// NOTE: TPM2_Hash can only accept data up to MAX_DIGEST_BUFFER in size, which -// is implementation-dependent, but guaranteed to be at least 1024 octets. -func Hash(rw io.ReadWriter, alg Algorithm, buf tpmutil.U16Bytes, hierarchy tpmutil.Handle) (digest []byte, validation *Ticket, err error) { - resp, err := runCommand(rw, TagNoSessions, CmdHash, buf, alg, hierarchy) - if err != nil { - return nil, nil, err - } - return decodeHash(resp) -} - -// HashSequenceStart starts a hash or an event sequence. If hashAlg is an -// implemented hash, then a hash sequence is started. If hashAlg is -// TPM_ALG_NULL, then an event sequence is started. -func HashSequenceStart(rw io.ReadWriter, sequenceAuth string, hashAlg Algorithm) (seqHandle tpmutil.Handle, err error) { - resp, err := runCommand(rw, TagNoSessions, CmdHashSequenceStart, tpmutil.U16Bytes(sequenceAuth), hashAlg) - if err != nil { - return 0, err - } - var handle tpmutil.Handle - _, err = tpmutil.Unpack(resp, &handle) - return handle, err -} - -func encodeSequenceUpdate(sequenceAuth string, seqHandle tpmutil.Handle, buf tpmutil.U16Bytes) ([]byte, error) { - ha, err := tpmutil.Pack(seqHandle) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(sequenceAuth)}) - if err != nil { - return nil, err - } - params, err := tpmutil.Pack(buf) - if err != nil { - return nil, err - } - return concat(ha, auth, params) -} - -// SequenceUpdate is used to add data to a hash or HMAC sequence. -func SequenceUpdate(rw io.ReadWriter, sequenceAuth string, seqHandle tpmutil.Handle, buffer []byte) error { - cmd, err := encodeSequenceUpdate(sequenceAuth, seqHandle, buffer) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdSequenceUpdate, tpmutil.RawBytes(cmd)) - return err -} - -func decodeSequenceComplete(resp []byte) ([]byte, *Ticket, error) { - var digest tpmutil.U16Bytes - var validation Ticket - var paramSize uint32 - - if _, err := tpmutil.Unpack(resp, ¶mSize, &digest, &validation); err != nil { - return nil, nil, err - } - return digest, &validation, nil -} - -func encodeSequenceComplete(sequenceAuth string, seqHandle, hierarchy tpmutil.Handle, buf tpmutil.U16Bytes) ([]byte, error) { - ha, err := tpmutil.Pack(seqHandle) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(sequenceAuth)}) - if err != nil { - return nil, err - } - params, err := tpmutil.Pack(buf, hierarchy) - if err != nil { - return nil, err - } - return concat(ha, auth, params) -} - -// SequenceComplete adds the last part of data, if any, to a hash/HMAC sequence -// and returns the result. -func SequenceComplete(rw io.ReadWriter, sequenceAuth string, seqHandle, hierarchy tpmutil.Handle, buffer []byte) (digest []byte, validation *Ticket, err error) { - cmd, err := encodeSequenceComplete(sequenceAuth, seqHandle, hierarchy, buffer) - if err != nil { - return nil, nil, err - } - resp, err := runCommand(rw, TagSessions, CmdSequenceComplete, tpmutil.RawBytes(cmd)) - if err != nil { - return nil, nil, err - } - return decodeSequenceComplete(resp) -} - -func encodeEventSequenceComplete(auths []AuthCommand, pcrHandle, seqHandle tpmutil.Handle, buf tpmutil.U16Bytes) ([]byte, error) { - ha, err := tpmutil.Pack(pcrHandle, seqHandle) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(auths...) - if err != nil { - return nil, err - } - params, err := tpmutil.Pack(buf) - if err != nil { - return nil, err - } - return concat(ha, auth, params) -} - -func decodeEventSequenceComplete(resp []byte) ([]*HashValue, error) { - var paramSize uint32 - var hashCount uint32 - var err error - - buf := bytes.NewBuffer(resp) - if err := tpmutil.UnpackBuf(buf, ¶mSize, &hashCount); err != nil { - return nil, err - } - - buf.Truncate(int(paramSize)) - digests := make([]*HashValue, hashCount) - for i := uint32(0); i < hashCount; i++ { - if digests[i], err = decodeHashValue(buf); err != nil { - return nil, err - } - } - - return digests, nil -} - -// EventSequenceComplete adds the last part of data, if any, to an Event -// Sequence and returns the result in a digest list. If pcrHandle references a -// PCR and not AlgNull, then the returned digest list is processed in the same -// manner as the digest list input parameter to PCRExtend() with the pcrHandle -// in each bank extended with the associated digest value. -func EventSequenceComplete(rw io.ReadWriter, pcrAuth, sequenceAuth string, pcrHandle, seqHandle tpmutil.Handle, buffer []byte) (digests []*HashValue, err error) { - auth := []AuthCommand{ - {Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(pcrAuth)}, - {Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(sequenceAuth)}, - } - cmd, err := encodeEventSequenceComplete(auth, pcrHandle, seqHandle, buffer) - if err != nil { - return nil, err - } - resp, err := runCommand(rw, TagSessions, CmdEventSequenceComplete, tpmutil.RawBytes(cmd)) - if err != nil { - return nil, err - } - return decodeEventSequenceComplete(resp) -} - -// Startup initializes a TPM (usually done by the OS). -func Startup(rw io.ReadWriter, typ StartupType) error { - _, err := runCommand(rw, TagNoSessions, CmdStartup, typ) - return err -} - -// Shutdown shuts down a TPM (usually done by the OS). -func Shutdown(rw io.ReadWriter, typ StartupType) error { - _, err := runCommand(rw, TagNoSessions, CmdShutdown, typ) - return err -} - -// nullTicket is a hard-coded null ticket of type TPMT_TK_HASHCHECK. -// It is for Sign commands that do not require the TPM to verify that the digest -// is not from data that started with TPM_GENERATED_VALUE. -var nullTicket = Ticket{ - Type: TagHashCheck, - Hierarchy: HandleNull, - Digest: tpmutil.U16Bytes{}, -} - -func encodeSign(sessionHandle, key tpmutil.Handle, password string, digest tpmutil.U16Bytes, sigScheme *SigScheme, validation *Ticket) ([]byte, error) { - ha, err := tpmutil.Pack(key) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(AuthCommand{Session: sessionHandle, Attributes: AttrContinueSession, Auth: []byte(password)}) - if err != nil { - return nil, err - } - d, err := tpmutil.Pack(digest) - if err != nil { - return nil, err - } - s, err := sigScheme.encode() - if err != nil { - return nil, err - } - if validation == nil { - validation = &nullTicket - } - v, err := tpmutil.Pack(validation) - if err != nil { - return nil, err - } - - return concat(ha, auth, d, s, v) -} - -func decodeSign(buf []byte) (*Signature, error) { - in := bytes.NewBuffer(buf) - var paramSize uint32 - if err := tpmutil.UnpackBuf(in, ¶mSize); err != nil { - return nil, err - } - return DecodeSignature(in) -} - -// SignWithSession computes a signature for digest using a given loaded key. Signature -// algorithm depends on the key type. Used for keys with non-password authorization policies. -// If 'key' references a Restricted Decryption key, 'validation' must be a valid hash verification -// ticket from the TPM, which can be obtained by using Hash() to hash the data with the TPM. -// If 'validation' is nil, a NULL ticket is passed to TPM2_Sign. -func SignWithSession(rw io.ReadWriter, sessionHandle, key tpmutil.Handle, password string, digest []byte, validation *Ticket, sigScheme *SigScheme) (*Signature, error) { - Cmd, err := encodeSign(sessionHandle, key, password, digest, sigScheme, validation) - if err != nil { - return nil, err - } - resp, err := runCommand(rw, TagSessions, CmdSign, tpmutil.RawBytes(Cmd)) - if err != nil { - return nil, err - } - return decodeSign(resp) -} - -// Sign computes a signature for digest using a given loaded key. Signature -// algorithm depends on the key type. -// If 'key' references a Restricted Decryption key, 'validation' must be a valid hash verification -// ticket from the TPM, which can be obtained by using Hash() to hash the data with the TPM. -// If 'validation' is nil, a NULL ticket is passed to TPM2_Sign. -func Sign(rw io.ReadWriter, key tpmutil.Handle, password string, digest []byte, validation *Ticket, sigScheme *SigScheme) (*Signature, error) { - return SignWithSession(rw, HandlePasswordSession, key, password, digest, validation, sigScheme) -} - -func encodeCertify(objectAuth, signerAuth string, object, signer tpmutil.Handle, qualifyingData tpmutil.U16Bytes) ([]byte, error) { - ha, err := tpmutil.Pack(object, signer) - if err != nil { - return nil, err - } - - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(objectAuth)}, AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(signerAuth)}) - if err != nil { - return nil, err - } - - scheme := SigScheme{Alg: AlgRSASSA, Hash: AlgSHA256} - // Use signing key's scheme. - s, err := scheme.encode() - if err != nil { - return nil, err - } - data, err := tpmutil.Pack(qualifyingData) - if err != nil { - return nil, err - } - return concat(ha, auth, data, s) -} - -// This function differs from encodeCertify in that it takes the scheme to be used as an additional argument. -func encodeCertifyEx(objectAuth, signerAuth string, object, signer tpmutil.Handle, qualifyingData tpmutil.U16Bytes, scheme SigScheme) ([]byte, error) { - ha, err := tpmutil.Pack(object, signer) - if err != nil { - return nil, err - } - - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(objectAuth)}, AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(signerAuth)}) - if err != nil { - return nil, err - } - - s, err := scheme.encode() - if err != nil { - return nil, err - } - data, err := tpmutil.Pack(qualifyingData) - if err != nil { - return nil, err - } - return concat(ha, auth, data, s) -} - -func decodeCertify(resp []byte) ([]byte, []byte, error) { - var paramSize uint32 - var attest tpmutil.U16Bytes - - buf := bytes.NewBuffer(resp) - if err := tpmutil.UnpackBuf(buf, ¶mSize); err != nil { - return nil, nil, err - } - buf.Truncate(int(paramSize)) - if err := tpmutil.UnpackBuf(buf, &attest); err != nil { - return nil, nil, err - } - return attest, buf.Bytes(), nil -} - -// Certify generates a signature of a loaded TPM object with a signing key -// signer. This function calls encodeCertify which makes use of the hardcoded -// signing scheme {AlgRSASSA, AlgSHA256}. Returned values are: attestation data (TPMS_ATTEST), -// signature and error, if any. -func Certify(rw io.ReadWriter, objectAuth, signerAuth string, object, signer tpmutil.Handle, qualifyingData []byte) ([]byte, []byte, error) { - cmd, err := encodeCertify(objectAuth, signerAuth, object, signer, qualifyingData) - if err != nil { - return nil, nil, err - } - resp, err := runCommand(rw, TagSessions, CmdCertify, tpmutil.RawBytes(cmd)) - if err != nil { - return nil, nil, err - } - return decodeCertify(resp) -} - -// CertifyEx generates a signature of a loaded TPM object with a signing key -// signer. This function differs from Certify in that it takes the scheme -// to be used as an additional argument and calls encodeCertifyEx instead -// of encodeCertify. Returned values are: attestation data (TPMS_ATTEST), -// signature and error, if any. -func CertifyEx(rw io.ReadWriter, objectAuth, signerAuth string, object, signer tpmutil.Handle, qualifyingData []byte, scheme SigScheme) ([]byte, []byte, error) { - cmd, err := encodeCertifyEx(objectAuth, signerAuth, object, signer, qualifyingData, scheme) - if err != nil { - return nil, nil, err - } - resp, err := runCommand(rw, TagSessions, CmdCertify, tpmutil.RawBytes(cmd)) - if err != nil { - return nil, nil, err - } - return decodeCertify(resp) -} - -func encodeCertifyCreation(objectAuth string, object, signer tpmutil.Handle, qualifyingData, creationHash tpmutil.U16Bytes, scheme SigScheme, ticket Ticket) ([]byte, error) { - handles, err := tpmutil.Pack(signer, object) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(objectAuth)}) - if err != nil { - return nil, err - } - s, err := scheme.encode() - if err != nil { - return nil, err - } - params, err := tpmutil.Pack(qualifyingData, creationHash, tpmutil.RawBytes(s), ticket) - if err != nil { - return nil, err - } - return concat(handles, auth, params) -} - -// CertifyCreation generates a signature of a newly-created & -// loaded TPM object, using signer as the signing key. -func CertifyCreation(rw io.ReadWriter, objectAuth string, object, signer tpmutil.Handle, qualifyingData, creationHash []byte, sigScheme SigScheme, creationTicket Ticket) (attestation, signature []byte, err error) { - Cmd, err := encodeCertifyCreation(objectAuth, object, signer, qualifyingData, creationHash, sigScheme, creationTicket) - if err != nil { - return nil, nil, err - } - resp, err := runCommand(rw, TagSessions, CmdCertifyCreation, tpmutil.RawBytes(Cmd)) - if err != nil { - return nil, nil, err - } - return decodeCertify(resp) -} - -func runCommand(rw io.ReadWriter, tag tpmutil.Tag, Cmd tpmutil.Command, in ...interface{}) ([]byte, error) { - resp, code, err := tpmutil.RunCommand(rw, tag, Cmd, in...) - if err != nil { - return nil, err - } - if code != tpmutil.RCSuccess { - return nil, decodeResponse(code) - } - return resp, decodeResponse(code) -} - -// concat is a helper for encoding functions that separately encode handle, -// auth and param areas. A nil error is always returned, so that callers can -// simply return concat(a, b, c). -func concat(chunks ...[]byte) ([]byte, error) { - return bytes.Join(chunks, nil), nil -} - -func encodePCRExtend(pcr tpmutil.Handle, hashAlg Algorithm, hash tpmutil.RawBytes, password string) ([]byte, error) { - ha, err := tpmutil.Pack(pcr) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(password)}) - if err != nil { - return nil, err - } - pcrCount := uint32(1) - extend, err := tpmutil.Pack(pcrCount, hashAlg, hash) - if err != nil { - return nil, err - } - return concat(ha, auth, extend) -} - -// PCRExtend extends a value into the selected PCR -func PCRExtend(rw io.ReadWriter, pcr tpmutil.Handle, hashAlg Algorithm, hash []byte, password string) error { - Cmd, err := encodePCRExtend(pcr, hashAlg, hash, password) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdPCRExtend, tpmutil.RawBytes(Cmd)) - return err -} - -// ReadPCR reads the value of the given PCR. -func ReadPCR(rw io.ReadWriter, pcr int, hashAlg Algorithm) ([]byte, error) { - pcrSelection := PCRSelection{ - Hash: hashAlg, - PCRs: []int{pcr}, - } - pcrVals, err := ReadPCRs(rw, pcrSelection) - if err != nil { - return nil, fmt.Errorf("unable to read PCRs from TPM: %v", err) - } - pcrVal, present := pcrVals[pcr] - if !present { - return nil, fmt.Errorf("PCR %d value missing from response", pcr) - } - return pcrVal, nil -} - -func encodePCRReset(pcr tpmutil.Handle) ([]byte, error) { - ha, err := tpmutil.Pack(pcr) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: EmptyAuth}) - if err != nil { - return nil, err - } - return concat(ha, auth) -} - -// PCRReset resets the value of the given PCR. Usually, only PCR 16 (Debug) and -// PCR 23 (Application) are resettable on the default locality. -func PCRReset(rw io.ReadWriter, pcr tpmutil.Handle) error { - Cmd, err := encodePCRReset(pcr) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdPCRReset, tpmutil.RawBytes(Cmd)) - return err -} - -// EncryptSymmetric encrypts data using a symmetric key. -// -// WARNING: This command performs low-level cryptographic operations. -// Secure use of this command is subtle and requires careful analysis. -// Please consult with experts in cryptography for how to use it securely. -// -// The iv is the initialization vector. The iv must not be empty and its size depends on the -// details of the symmetric encryption scheme. -// -// The data may be longer than block size, EncryptSymmetric will chain -// multiple TPM calls to encrypt the entire blob. -// -// Key handle should point at SymCipher object which is a child of the key (and -// not e.g. RSA key itself). -func EncryptSymmetric(rw io.ReadWriteCloser, keyAuth string, key tpmutil.Handle, iv, data []byte) ([]byte, error) { - return encryptDecryptSymmetric(rw, keyAuth, key, iv, data, false) -} - -// DecryptSymmetric decrypts data using a symmetric key. -// -// WARNING: This command performs low-level cryptographic operations. -// Secure use of this command is subtle and requires careful analysis. -// Please consult with experts in cryptography for how to use it securely. -// -// The iv is the initialization vector. The iv must not be empty and its size -// depends on the details of the symmetric encryption scheme. -// -// The data may be longer than block size, DecryptSymmetric will chain multiple -// TPM calls to decrypt the entire blob. -// -// Key handle should point at SymCipher object which is a child of the key (and -// not e.g. RSA key itself). -func DecryptSymmetric(rw io.ReadWriteCloser, keyAuth string, key tpmutil.Handle, iv, data []byte) ([]byte, error) { - return encryptDecryptSymmetric(rw, keyAuth, key, iv, data, true) -} - -func encodeEncryptDecrypt(keyAuth string, key tpmutil.Handle, iv, data tpmutil.U16Bytes, decrypt bool) ([]byte, error) { - ha, err := tpmutil.Pack(key) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(keyAuth)}) - if err != nil { - return nil, err - } - // Use encryption key's mode. - params, err := tpmutil.Pack(decrypt, AlgNull, iv, data) - if err != nil { - return nil, err - } - return concat(ha, auth, params) -} - -func encodeEncryptDecrypt2(keyAuth string, key tpmutil.Handle, iv, data tpmutil.U16Bytes, decrypt bool) ([]byte, error) { - ha, err := tpmutil.Pack(key) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(keyAuth)}) - if err != nil { - return nil, err - } - // Use encryption key's mode. - params, err := tpmutil.Pack(data, decrypt, AlgNull, iv) - if err != nil { - return nil, err - } - return concat(ha, auth, params) -} - -func decodeEncryptDecrypt(resp []byte) ([]byte, []byte, error) { - var paramSize uint32 - var out, nextIV tpmutil.U16Bytes - if _, err := tpmutil.Unpack(resp, ¶mSize, &out, &nextIV); err != nil { - return nil, nil, err - } - return out, nextIV, nil -} - -func encryptDecryptBlockSymmetric(rw io.ReadWriteCloser, keyAuth string, key tpmutil.Handle, iv, data []byte, decrypt bool) ([]byte, []byte, error) { - Cmd, err := encodeEncryptDecrypt2(keyAuth, key, iv, data, decrypt) - if err != nil { - return nil, nil, err - } - resp, err := runCommand(rw, TagSessions, CmdEncryptDecrypt2, tpmutil.RawBytes(Cmd)) - if err != nil { - fmt0Err, ok := err.(Error) - if ok && fmt0Err.Code == RCCommandCode { - // If TPM2_EncryptDecrypt2 is not supported, fall back to - // TPM2_EncryptDecrypt. - Cmd, _ := encodeEncryptDecrypt(keyAuth, key, iv, data, decrypt) - resp, err = runCommand(rw, TagSessions, CmdEncryptDecrypt, tpmutil.RawBytes(Cmd)) - if err != nil { - return nil, nil, err - } - } - } - if err != nil { - return nil, nil, err - } - return decodeEncryptDecrypt(resp) -} - -func encryptDecryptSymmetric(rw io.ReadWriteCloser, keyAuth string, key tpmutil.Handle, iv, data []byte, decrypt bool) ([]byte, error) { - var out, block []byte - var err error - - for rest := data; len(rest) > 0; { - if len(rest) > maxDigestBuffer { - block, rest = rest[:maxDigestBuffer], rest[maxDigestBuffer:] - } else { - block, rest = rest, nil - } - block, iv, err = encryptDecryptBlockSymmetric(rw, keyAuth, key, iv, block, decrypt) - if err != nil { - return nil, err - } - out = append(out, block...) - } - - return out, nil -} - -func encodeRSAEncrypt(key tpmutil.Handle, message tpmutil.U16Bytes, scheme *AsymScheme, label string) ([]byte, error) { - ha, err := tpmutil.Pack(key) - if err != nil { - return nil, err - } - m, err := tpmutil.Pack(message) - if err != nil { - return nil, err - } - s, err := scheme.encode() - if err != nil { - return nil, err - } - if label != "" { - label += "\x00" - } - l, err := tpmutil.Pack(tpmutil.U16Bytes(label)) - if err != nil { - return nil, err - } - return concat(ha, m, s, l) -} - -func decodeRSAEncrypt(resp []byte) ([]byte, error) { - var out tpmutil.U16Bytes - _, err := tpmutil.Unpack(resp, &out) - return out, err -} - -// RSAEncrypt performs RSA encryption in the TPM according to RFC 3447. The key must be -// a (public) key loaded into the TPM beforehand. Note that when using OAEP with a label, -// a null byte is appended to the label and the null byte is included in the padding -// scheme. -func RSAEncrypt(rw io.ReadWriter, key tpmutil.Handle, message []byte, scheme *AsymScheme, label string) ([]byte, error) { - Cmd, err := encodeRSAEncrypt(key, message, scheme, label) - if err != nil { - return nil, err - } - resp, err := runCommand(rw, TagNoSessions, CmdRSAEncrypt, tpmutil.RawBytes(Cmd)) - if err != nil { - return nil, err - } - return decodeRSAEncrypt(resp) -} - -func encodeRSADecrypt(sessionHandle, key tpmutil.Handle, password string, message tpmutil.U16Bytes, scheme *AsymScheme, label string) ([]byte, error) { - ha, err := tpmutil.Pack(key) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(AuthCommand{Session: sessionHandle, Attributes: AttrContinueSession, Auth: []byte(password)}) - if err != nil { - return nil, err - } - m, err := tpmutil.Pack(message) - if err != nil { - return nil, err - } - s, err := scheme.encode() - if err != nil { - return nil, err - } - if label != "" { - label += "\x00" - } - l, err := tpmutil.Pack(tpmutil.U16Bytes(label)) - if err != nil { - return nil, err - } - return concat(ha, auth, m, s, l) -} - -func decodeRSADecrypt(resp []byte) ([]byte, error) { - var out tpmutil.U16Bytes - var paramSize uint32 - _, err := tpmutil.Unpack(resp, ¶mSize, &out) - return out, err -} - -// RSADecrypt performs RSA decryption in the TPM according to RFC 3447. The key must be -// a private RSA key in the TPM with FlagDecrypt set. Note that when using OAEP with a -// label, a null byte is appended to the label and the null byte is included in the -// padding scheme. -func RSADecrypt(rw io.ReadWriter, key tpmutil.Handle, password string, message []byte, scheme *AsymScheme, label string) ([]byte, error) { - return RSADecryptWithSession(rw, HandlePasswordSession, key, password, message, scheme, label) -} - -// RSADecryptWithSession performs RSA decryption in the TPM according to RFC 3447. The key must be -// a private RSA key in the TPM with FlagDecrypt set. Note that when using OAEP with a -// label, a null byte is appended to the label and the null byte is included in the -// padding scheme. -func RSADecryptWithSession(rw io.ReadWriter, sessionHandle, key tpmutil.Handle, password string, message []byte, scheme *AsymScheme, label string) ([]byte, error) { - Cmd, err := encodeRSADecrypt(sessionHandle, key, password, message, scheme, label) - if err != nil { - return nil, err - } - resp, err := runCommand(rw, TagSessions, CmdRSADecrypt, tpmutil.RawBytes(Cmd)) - if err != nil { - return nil, err - } - return decodeRSADecrypt(resp) -} - -func encodeECDHKeyGen(key tpmutil.Handle) ([]byte, error) { - return tpmutil.Pack(key) -} - -func decodeECDHKeyGen(resp []byte) (*ECPoint, *ECPoint, error) { - // Unpack z and pub as TPM2B_ECC_POINT, which is a TPMS_ECC_POINT with a total size prepended. - var z2B, pub2B tpmutil.U16Bytes - _, err := tpmutil.Unpack(resp, &z2B, &pub2B) - if err != nil { - return nil, nil, err - } - var zPoint, pubPoint ECPoint - _, err = tpmutil.Unpack(z2B, &zPoint.XRaw, &zPoint.YRaw) - if err != nil { - return nil, nil, err - } - _, err = tpmutil.Unpack(pub2B, &pubPoint.XRaw, &pubPoint.YRaw) - if err != nil { - return nil, nil, err - } - return &zPoint, &pubPoint, nil -} - -// ECDHKeyGen generates an ephemeral ECC key, calculates the ECDH point multiplcation of the -// ephemeral private key and a loaded public key, and returns the public ephemeral point along with -// the coordinates of the resulting point. -func ECDHKeyGen(rw io.ReadWriter, key tpmutil.Handle) (zPoint, pubPoint *ECPoint, err error) { - Cmd, err := encodeECDHKeyGen(key) - if err != nil { - return nil, nil, err - } - resp, err := runCommand(rw, TagNoSessions, CmdECDHKeyGen, tpmutil.RawBytes(Cmd)) - if err != nil { - return nil, nil, err - } - return decodeECDHKeyGen(resp) -} - -func encodeECDHZGen(key tpmutil.Handle, password string, inPoint ECPoint) ([]byte, error) { - ha, err := tpmutil.Pack(key) - if err != nil { - return nil, err - } - auth, err := encodeAuthArea(AuthCommand{Session: HandlePasswordSession, Attributes: AttrContinueSession, Auth: []byte(password)}) - if err != nil { - return nil, err - } - p, err := tpmutil.Pack(inPoint) - if err != nil { - return nil, err - } - // Pack the TPMS_ECC_POINT as a TPM2B_ECC_POINT. - p2B, err := tpmutil.Pack(tpmutil.U16Bytes(p)) - if err != nil { - return nil, err - } - return concat(ha, auth, p2B) -} - -func decodeECDHZGen(resp []byte) (*ECPoint, error) { - var paramSize uint32 - // Unpack a TPM2B_ECC_POINT, which is a TPMS_ECC_POINT with a total size prepended. - var z2B tpmutil.U16Bytes - _, err := tpmutil.Unpack(resp, ¶mSize, &z2B) - if err != nil { - return nil, err - } - var zPoint ECPoint - _, err = tpmutil.Unpack(z2B, &zPoint.XRaw, &zPoint.YRaw) - if err != nil { - return nil, err - } - return &zPoint, nil -} - -// ECDHZGen performs ECDH point multiplication between a private key held in the TPM and a given -// public point, returning the coordinates of the resulting point. The key must have FlagDecrypt -// set. -func ECDHZGen(rw io.ReadWriter, key tpmutil.Handle, password string, inPoint ECPoint) (zPoint *ECPoint, err error) { - Cmd, err := encodeECDHZGen(key, password, inPoint) - if err != nil { - return nil, err - } - resp, err := runCommand(rw, TagSessions, CmdECDHZGen, tpmutil.RawBytes(Cmd)) - if err != nil { - return nil, err - } - return decodeECDHZGen(resp) -} - -// DictionaryAttackLockReset cancels the effect of a TPM lockout due to a number -// of successive authorization failures, by setting the lockout counter to zero. -// The command requires Lockout Authorization and only one lockoutAuth authorization -// failure is allowed for this command during a lockoutRecovery interval. -// Lockout Authorization value by default is empty and can be changed via -// a call to HierarchyChangeAuth(HandleLockout). -func DictionaryAttackLockReset(rw io.ReadWriter, auth AuthCommand) error { - ha, err := tpmutil.Pack(HandleLockout) - if err != nil { - return err - } - encodedAuth, err := encodeAuthArea(auth) - if err != nil { - return err - } - Cmd, err := concat(ha, encodedAuth) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdDictionaryAttackLockReset, tpmutil.RawBytes(Cmd)) - return err -} - -// DictionaryAttackParameters changes the lockout parameters. -// The command requires Lockout Authorization and has same authorization policy -// as in DictionaryAttackLockReset. -func DictionaryAttackParameters(rw io.ReadWriter, auth AuthCommand, maxTries, recoveryTime, lockoutRecovery uint32) error { - ha, err := tpmutil.Pack(HandleLockout) - if err != nil { - return err - } - encodedAuth, err := encodeAuthArea(auth) - if err != nil { - return err - } - params, err := tpmutil.Pack(maxTries, recoveryTime, lockoutRecovery) - if err != nil { - return err - } - Cmd, err := concat(ha, encodedAuth, params) - if err != nil { - return err - } - _, err = runCommand(rw, TagSessions, CmdDictionaryAttackParameters, tpmutil.RawBytes(Cmd)) - return err -} - -// PolicyCommandCode indicates that the authorization will be limited to a specific command code -func PolicyCommandCode(rw io.ReadWriter, session tpmutil.Handle, cc tpmutil.Command) error { - data, err := tpmutil.Pack(session, cc) - if err != nil { - return err - } - _, err = runCommand(rw, TagNoSessions, CmdPolicyCommandCode, data) - return err -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/encoding.go b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/encoding.go deleted file mode 100644 index 5983cc21..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/encoding.go +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) 2018, Google LLC All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tpmutil - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "io" - "reflect" -) - -var ( - selfMarshalerType = reflect.TypeOf((*SelfMarshaler)(nil)).Elem() - handlesAreaType = reflect.TypeOf((*[]Handle)(nil)) -) - -// packWithHeader takes a header and a sequence of elements that are either of -// fixed length or slices of fixed-length types and packs them into a single -// byte array using binary.Write. It updates the CommandHeader to have the right -// length. -func packWithHeader(ch commandHeader, cmd ...interface{}) ([]byte, error) { - hdrSize := binary.Size(ch) - body, err := Pack(cmd...) - if err != nil { - return nil, fmt.Errorf("couldn't pack message body: %v", err) - } - bodySize := len(body) - ch.Size = uint32(hdrSize + bodySize) - header, err := Pack(ch) - if err != nil { - return nil, fmt.Errorf("couldn't pack message header: %v", err) - } - return append(header, body...), nil -} - -// Pack encodes a set of elements into a single byte array, using -// encoding/binary. This means that all the elements must be encodeable -// according to the rules of encoding/binary. -// -// It has one difference from encoding/binary: it encodes byte slices with a -// prepended length, to match how the TPM encodes variable-length arrays. If -// you wish to add a byte slice without length prefix, use RawBytes. -func Pack(elts ...interface{}) ([]byte, error) { - buf := new(bytes.Buffer) - if err := packType(buf, elts...); err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -// tryMarshal attempts to use a TPMMarshal() method defined on the type -// to pack v into buf. True is returned if the method exists and the -// marshal was attempted. -func tryMarshal(buf io.Writer, v reflect.Value) (bool, error) { - t := v.Type() - if t.Implements(selfMarshalerType) { - if v.Kind() == reflect.Ptr && v.IsNil() { - return true, fmt.Errorf("cannot call TPMMarshal on a nil pointer of type %T", v) - } - return true, v.Interface().(SelfMarshaler).TPMMarshal(buf) - } - - // We might have a non-pointer struct field, but we dont have a - // pointer with which to implement the interface. - // If the pointer of the type implements the interface, we should be - // able to construct a value to call TPMMarshal() with. - // TODO(awly): Try and avoid blowing away private data by using Addr() instead of Set() - if reflect.PtrTo(t).Implements(selfMarshalerType) { - tmp := reflect.New(t) - tmp.Elem().Set(v) - return true, tmp.Interface().(SelfMarshaler).TPMMarshal(buf) - } - - return false, nil -} - -func packValue(buf io.Writer, v reflect.Value) error { - if v.Type() == handlesAreaType { - v = v.Convert(reflect.TypeOf((*handleList)(nil))) - } - if canMarshal, err := tryMarshal(buf, v); canMarshal { - return err - } - - switch v.Kind() { - case reflect.Ptr: - if v.IsNil() { - return fmt.Errorf("cannot pack nil %s", v.Type().String()) - } - return packValue(buf, v.Elem()) - case reflect.Struct: - for i := 0; i < v.NumField(); i++ { - f := v.Field(i) - if err := packValue(buf, f); err != nil { - return err - } - } - default: - return binary.Write(buf, binary.BigEndian, v.Interface()) - } - return nil -} - -func packType(buf io.Writer, elts ...interface{}) error { - for _, e := range elts { - if err := packValue(buf, reflect.ValueOf(e)); err != nil { - return err - } - } - - return nil -} - -// tryUnmarshal attempts to use TPMUnmarshal() to perform the -// unpack, if the given value implements SelfMarshaler. -// True is returned if v implements SelfMarshaler & TPMUnmarshal -// was called, along with an error returned from TPMUnmarshal. -func tryUnmarshal(buf io.Reader, v reflect.Value) (bool, error) { - t := v.Type() - if t.Implements(selfMarshalerType) { - if v.Kind() == reflect.Ptr && v.IsNil() { - return true, fmt.Errorf("cannot call TPMUnmarshal on a nil pointer") - } - return true, v.Interface().(SelfMarshaler).TPMUnmarshal(buf) - } - - // We might have a non-pointer struct field, which is addressable, - // If the pointer of the type implements the interface, and the - // value is addressable, we should be able to call TPMUnmarshal(). - if v.CanAddr() && reflect.PtrTo(t).Implements(selfMarshalerType) { - return true, v.Addr().Interface().(SelfMarshaler).TPMUnmarshal(buf) - } - - return false, nil -} - -// Unpack is a convenience wrapper around UnpackBuf. Unpack returns the number -// of bytes read from b to fill elts and error, if any. -func Unpack(b []byte, elts ...interface{}) (int, error) { - buf := bytes.NewBuffer(b) - err := UnpackBuf(buf, elts...) - read := len(b) - buf.Len() - return read, err -} - -func unpackValue(buf io.Reader, v reflect.Value) error { - if v.Type() == handlesAreaType { - v = v.Convert(reflect.TypeOf((*handleList)(nil))) - } - if didUnmarshal, err := tryUnmarshal(buf, v); didUnmarshal { - return err - } - - switch v.Kind() { - case reflect.Ptr: - if v.IsNil() { - return fmt.Errorf("cannot unpack nil %s", v.Type().String()) - } - return unpackValue(buf, v.Elem()) - case reflect.Struct: - for i := 0; i < v.NumField(); i++ { - f := v.Field(i) - if err := unpackValue(buf, f); err != nil { - return err - } - } - return nil - default: - // binary.Read can only set pointer values, so we need to take the address. - if !v.CanAddr() { - return fmt.Errorf("cannot unpack unaddressable leaf type %q", v.Type().String()) - } - return binary.Read(buf, binary.BigEndian, v.Addr().Interface()) - } -} - -// UnpackBuf recursively unpacks types from a reader just as encoding/binary -// does under binary.BigEndian, but with one difference: it unpacks a byte -// slice by first reading an integer with lengthPrefixSize bytes, then reading -// that many bytes. It assumes that incoming values are pointers to values so -// that, e.g., underlying slices can be resized as needed. -func UnpackBuf(buf io.Reader, elts ...interface{}) error { - for _, e := range elts { - v := reflect.ValueOf(e) - if v.Kind() != reflect.Ptr { - return fmt.Errorf("non-pointer value %q passed to UnpackBuf", v.Type().String()) - } - if v.IsNil() { - return errors.New("nil pointer passed to UnpackBuf") - } - - if err := unpackValue(buf, v); err != nil { - return err - } - } - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/poll_other.go b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/poll_other.go deleted file mode 100644 index ba7e062e..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/poll_other.go +++ /dev/null @@ -1,10 +0,0 @@ -//go:build !linux && !darwin - -package tpmutil - -import ( - "os" -) - -// Not implemented on Windows. -func poll(_ *os.File) error { return nil } diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/poll_unix.go b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/poll_unix.go deleted file mode 100644 index 89d85d38..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/poll_unix.go +++ /dev/null @@ -1,32 +0,0 @@ -//go:build linux || darwin - -package tpmutil - -import ( - "fmt" - "os" - - "golang.org/x/sys/unix" -) - -// poll blocks until the file descriptor is ready for reading or an error occurs. -func poll(f *os.File) error { - var ( - fds = []unix.PollFd{{ - Fd: int32(f.Fd()), - Events: 0x1, // POLLIN - }} - timeout = -1 // Indefinite timeout - ) - - if _, err := unix.Poll(fds, timeout); err != nil { - return err - } - - // Revents is filled in by the kernel. - // If the expected event happened, Revents should match Events. - if fds[0].Revents != fds[0].Events { - return fmt.Errorf("unexpected poll Revents 0x%x", fds[0].Revents) - } - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/run.go b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/run.go deleted file mode 100644 index c07e3aba..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/run.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) 2018, Google LLC All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package tpmutil provides common utility functions for both TPM 1.2 and TPM -// 2.0 devices. -package tpmutil - -import ( - "errors" - "io" - "os" - "time" -) - -// maxTPMResponse is the largest possible response from the TPM. We need to know -// this because we don't always know the length of the TPM response, and -// /dev/tpm insists on giving it all back in a single value rather than -// returning a header and a body in separate responses. -const maxTPMResponse = 4096 - -// RunCommandRaw executes the given raw command and returns the raw response. -// Does not check the response code except to execute retry logic. -func RunCommandRaw(rw io.ReadWriter, inb []byte) ([]byte, error) { - if rw == nil { - return nil, errors.New("nil TPM handle") - } - - // f(t) = (2^t)ms, up to 2s - var backoffFac uint - var rh responseHeader - var outb []byte - - for { - if _, err := rw.Write(inb); err != nil { - return nil, err - } - - // If the TPM is a real device, it may not be ready for reading - // immediately after writing the command. Wait until the file - // descriptor is ready to be read from. - if f, ok := rw.(*os.File); ok { - if err := poll(f); err != nil { - return nil, err - } - } - - outb = make([]byte, maxTPMResponse) - outlen, err := rw.Read(outb) - if err != nil { - return nil, err - } - // Resize the buffer to match the amount read from the TPM. - outb = outb[:outlen] - - _, err = Unpack(outb, &rh) - if err != nil { - return nil, err - } - - // If TPM is busy, retry the command after waiting a few ms. - if rh.Res == RCRetry { - if backoffFac < 11 { - dur := (1 << backoffFac) * time.Millisecond - time.Sleep(dur) - backoffFac++ - } else { - return nil, err - } - } else { - break - } - } - - return outb, nil -} - -// RunCommand executes cmd with given tag and arguments. Returns TPM response -// body (without response header) and response code from the header. Returned -// error may be nil if response code is not RCSuccess; caller should check -// both. -func RunCommand(rw io.ReadWriter, tag Tag, cmd Command, in ...interface{}) ([]byte, ResponseCode, error) { - inb, err := packWithHeader(commandHeader{tag, 0, cmd}, in...) - if err != nil { - return nil, 0, err - } - - outb, err := RunCommandRaw(rw, inb) - if err != nil { - return nil, 0, err - } - - var rh responseHeader - read, err := Unpack(outb, &rh) - if err != nil { - return nil, 0, err - } - if rh.Res != RCSuccess { - return nil, rh.Res, nil - } - - return outb[read:], rh.Res, nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/run_other.go b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/run_other.go deleted file mode 100644 index 2a142d39..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/run_other.go +++ /dev/null @@ -1,111 +0,0 @@ -//go:build !windows - -// Copyright (c) 2018, Google LLC All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tpmutil - -import ( - "fmt" - "io" - "net" - "os" -) - -// OpenTPM opens a channel to the TPM at the given path. If the file is a -// device, then it treats it like a normal TPM device, and if the file is a -// Unix domain socket, then it opens a connection to the socket. -func OpenTPM(path string) (io.ReadWriteCloser, error) { - // If it's a regular file, then open it - var rwc io.ReadWriteCloser - fi, err := os.Stat(path) - if err != nil { - return nil, err - } - - if fi.Mode()&os.ModeDevice != 0 { - var f *os.File - f, err = os.OpenFile(path, os.O_RDWR, 0600) - if err != nil { - return nil, err - } - rwc = io.ReadWriteCloser(f) - } else if fi.Mode()&os.ModeSocket != 0 { - rwc = NewEmulatorReadWriteCloser(path) - } else { - return nil, fmt.Errorf("unsupported TPM file mode %s", fi.Mode().String()) - } - - return rwc, nil -} - -// dialer abstracts the net.Dial call so test code can provide its own net.Conn -// implementation. -type dialer func(network, path string) (net.Conn, error) - -// EmulatorReadWriteCloser manages connections with a TPM emulator over a Unix -// domain socket. These emulators often operate in a write/read/disconnect -// sequence, so the Write method always connects, and the Read method always -// closes. EmulatorReadWriteCloser is not thread safe. -type EmulatorReadWriteCloser struct { - path string - conn net.Conn - dialer dialer -} - -// NewEmulatorReadWriteCloser stores information about a Unix domain socket to -// write to and read from. -func NewEmulatorReadWriteCloser(path string) *EmulatorReadWriteCloser { - return &EmulatorReadWriteCloser{ - path: path, - dialer: net.Dial, - } -} - -// Read implements io.Reader by reading from the Unix domain socket and closing -// it. -func (erw *EmulatorReadWriteCloser) Read(p []byte) (int, error) { - // Read is always the second operation in a Write/Read sequence. - if erw.conn == nil { - return 0, fmt.Errorf("must call Write then Read in an alternating sequence") - } - n, err := erw.conn.Read(p) - erw.conn.Close() - erw.conn = nil - return n, err -} - -// Write implements io.Writer by connecting to the Unix domain socket and -// writing. -func (erw *EmulatorReadWriteCloser) Write(p []byte) (int, error) { - if erw.conn != nil { - return 0, fmt.Errorf("must call Write then Read in an alternating sequence") - } - var err error - erw.conn, err = erw.dialer("unix", erw.path) - if err != nil { - return 0, err - } - return erw.conn.Write(p) -} - -// Close implements io.Closer by closing the Unix domain socket if one is open. -func (erw *EmulatorReadWriteCloser) Close() error { - if erw.conn == nil { - return fmt.Errorf("cannot call Close when no connection is open") - } - err := erw.conn.Close() - erw.conn = nil - return err -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/run_windows.go b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/run_windows.go deleted file mode 100644 index f355b810..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/run_windows.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) 2018, Google LLC All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tpmutil - -import ( - "io" - - "github.com/google/go-tpm/tpmutil/tbs" -) - -// winTPMBuffer is a ReadWriteCloser to access the TPM in Windows. -type winTPMBuffer struct { - context tbs.Context - outBuffer []byte -} - -// Executes the TPM command specified by commandBuffer (at Normal Priority), returning the number -// of bytes in the command and any error code returned by executing the TPM command. Command -// response can be read by calling Read(). -func (rwc *winTPMBuffer) Write(commandBuffer []byte) (int, error) { - // TPM spec defines longest possible response to be maxTPMResponse. - rwc.outBuffer = rwc.outBuffer[:maxTPMResponse] - - outBufferLen, err := rwc.context.SubmitCommand( - tbs.NormalPriority, - commandBuffer, - rwc.outBuffer, - ) - - if err != nil { - rwc.outBuffer = rwc.outBuffer[:0] - return 0, err - } - // Shrink outBuffer so it is length of response. - rwc.outBuffer = rwc.outBuffer[:outBufferLen] - return len(commandBuffer), nil -} - -// Provides TPM response from the command called in the last Write call. -func (rwc *winTPMBuffer) Read(responseBuffer []byte) (int, error) { - if len(rwc.outBuffer) == 0 { - return 0, io.EOF - } - lenCopied := copy(responseBuffer, rwc.outBuffer) - // Cut out the piece of slice which was just read out, maintaining original slice capacity. - rwc.outBuffer = append(rwc.outBuffer[:0], rwc.outBuffer[lenCopied:]...) - return lenCopied, nil -} - -func (rwc *winTPMBuffer) Close() error { - return rwc.context.Close() -} - -// OpenTPM creates a new instance of a ReadWriteCloser which can interact with a -// Windows TPM. -func OpenTPM() (io.ReadWriteCloser, error) { - tpmContext, err := tbs.CreateContext(tbs.TPMVersion20, tbs.IncludeTPM12|tbs.IncludeTPM20) - rwc := &winTPMBuffer{ - context: tpmContext, - outBuffer: make([]byte, 0, maxTPMResponse), - } - return rwc, err -} - -// FromContext creates a new instance of a ReadWriteCloser which can -// interact with a Windows TPM, using the specified TBS handle. -func FromContext(ctx tbs.Context) io.ReadWriteCloser { - return &winTPMBuffer{ - context: ctx, - outBuffer: make([]byte, 0, maxTPMResponse), - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/structures.go b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/structures.go deleted file mode 100644 index 893b6b6d..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/structures.go +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright (c) 2018, Google LLC All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tpmutil - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" -) - -// maxBytesBufferSize sets a sane upper bound on the size of a U32Bytes -// buffer. This limit exists to prevent a maliciously large size prefix -// from resulting in a massive memory allocation, potentially causing -// an OOM condition on the system. -// We expect no buffer from a TPM to approach 1Mb in size. -const maxBytesBufferSize uint32 = 1024 * 1024 // 1Mb. - -// RawBytes is for Pack and RunCommand arguments that are already encoded. -// Compared to []byte, RawBytes will not be prepended with slice length during -// encoding. -type RawBytes []byte - -// U16Bytes is a byte slice with a 16-bit header -type U16Bytes []byte - -// TPMMarshal packs U16Bytes -func (b *U16Bytes) TPMMarshal(out io.Writer) error { - size := len([]byte(*b)) - if err := binary.Write(out, binary.BigEndian, uint16(size)); err != nil { - return err - } - - n, err := out.Write(*b) - if err != nil { - return err - } - if n != size { - return fmt.Errorf("unable to write all contents of U16Bytes") - } - return nil -} - -// TPMUnmarshal unpacks a U16Bytes -func (b *U16Bytes) TPMUnmarshal(in io.Reader) error { - var tmpSize uint16 - if err := binary.Read(in, binary.BigEndian, &tmpSize); err != nil { - return err - } - size := int(tmpSize) - - if len(*b) >= size { - *b = (*b)[:size] - } else { - *b = append(*b, make([]byte, size-len(*b))...) - } - - n, err := in.Read(*b) - if err != nil { - return err - } - if n != size { - return io.ErrUnexpectedEOF - } - return nil -} - -// U32Bytes is a byte slice with a 32-bit header -type U32Bytes []byte - -// TPMMarshal packs U32Bytes -func (b *U32Bytes) TPMMarshal(out io.Writer) error { - size := len([]byte(*b)) - if err := binary.Write(out, binary.BigEndian, uint32(size)); err != nil { - return err - } - - n, err := out.Write(*b) - if err != nil { - return err - } - if n != size { - return fmt.Errorf("unable to write all contents of U32Bytes") - } - return nil -} - -// TPMUnmarshal unpacks a U32Bytes -func (b *U32Bytes) TPMUnmarshal(in io.Reader) error { - var tmpSize uint32 - if err := binary.Read(in, binary.BigEndian, &tmpSize); err != nil { - return err - } - - if tmpSize > maxBytesBufferSize { - return bytes.ErrTooLarge - } - // We can now safely cast to an int on 32-bit or 64-bit machines - size := int(tmpSize) - - if len(*b) >= size { - *b = (*b)[:size] - } else { - *b = append(*b, make([]byte, size-len(*b))...) - } - - n, err := in.Read(*b) - if err != nil { - return err - } - if n != size { - return fmt.Errorf("unable to read all contents in to U32Bytes") - } - return nil -} - -// Tag is a command tag. -type Tag uint16 - -// Command is an identifier of a TPM command. -type Command uint32 - -// A commandHeader is the header for a TPM command. -type commandHeader struct { - Tag Tag - Size uint32 - Cmd Command -} - -// ResponseCode is a response code returned by TPM. -type ResponseCode uint32 - -// RCSuccess is response code for successful command. Identical for TPM 1.2 and -// 2.0. -const RCSuccess ResponseCode = 0x000 - -// RCRetry is response code for TPM is busy. -const RCRetry ResponseCode = 0x922 - -// A responseHeader is a header for TPM responses. -type responseHeader struct { - Tag Tag - Size uint32 - Res ResponseCode -} - -// A Handle is a reference to a TPM object. -type Handle uint32 - -// HandleValue returns the handle value. This behavior is intended to satisfy -// an interface that can be implemented by other, more complex types as well. -func (h Handle) HandleValue() uint32 { - return uint32(h) -} - -type handleList []Handle - -func (l *handleList) TPMMarshal(_ io.Writer) error { - return fmt.Errorf("TPMMarhsal on []Handle is not supported yet") -} - -func (l *handleList) TPMUnmarshal(in io.Reader) error { - var numHandles uint16 - if err := binary.Read(in, binary.BigEndian, &numHandles); err != nil { - return err - } - - // Make len(e) match size exactly. - size := int(numHandles) - if len(*l) >= size { - *l = (*l)[:size] - } else { - *l = append(*l, make([]Handle, size-len(*l))...) - } - return binary.Read(in, binary.BigEndian, *l) -} - -// SelfMarshaler allows custom types to override default encoding/decoding -// behavior in Pack, Unpack and UnpackBuf. -type SelfMarshaler interface { - TPMMarshal(out io.Writer) error - TPMUnmarshal(in io.Reader) error -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/tbs/tbs_windows.go b/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/tbs/tbs_windows.go deleted file mode 100644 index b23bf96a..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/google/go-tpm/tpmutil/tbs/tbs_windows.go +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright (c) 2018, Google LLC All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package tbs provides an low-level interface directly mapping to Windows -// Tbs.dll system library commands: -// https://docs.microsoft.com/en-us/windows/desktop/TBS/tpm-base-services-portal -// Public field descriptions contain links to the high-level Windows documentation. -package tbs - -import ( - "fmt" - "syscall" - "unsafe" -) - -// Context references the current TPM context -type Context uintptr - -// Version of TPM being used by the application. -type Version uint32 - -// Flag indicates TPM versions that are supported by the application. -type Flag uint32 - -// CommandPriority is used to determine which pending command to submit whenever the TPM is free. -type CommandPriority uint32 - -// Command parameters: -// https://github.com/tpn/winsdk-10/blob/master/Include/10.0.10240.0/shared/tbs.h -const ( - // https://docs.microsoft.com/en-us/windows/desktop/api/Tbs/ns-tbs-tdtbs_context_params2 - // OR flags to use multiple. - RequestRaw Flag = 1 << iota // Add flag to request raw context - IncludeTPM12 // Add flag to support TPM 1.2 - IncludeTPM20 // Add flag to support TPM 2 - - TPMVersion12 Version = 1 // For TPM 1.2 applications - TPMVersion20 Version = 2 // For TPM 2 applications or applications using multiple TPM versions - - // https://docs.microsoft.com/en-us/windows/desktop/tbs/command-scheduling - // https://docs.microsoft.com/en-us/windows/desktop/api/Tbs/nf-tbs-tbsip_submit_command#parameters - LowPriority CommandPriority = 100 // For low priority application use - NormalPriority CommandPriority = 200 // For normal priority application use - HighPriority CommandPriority = 300 // For high priority application use - SystemPriority CommandPriority = 400 // For system tasks that access the TPM - - commandLocalityZero uint32 = 0 // Windows currently only supports TBS_COMMAND_LOCALITY_ZERO. -) - -// Error is the return type of all functions in this package. -type Error uint32 - -func (err Error) Error() string { - if description, ok := errorDescriptions[err]; ok { - return fmt.Sprintf("TBS Error 0x%X: %s", uint32(err), description) - } - return fmt.Sprintf("Unrecognized TBS Error 0x%X", uint32(err)) -} - -func getError(err uintptr) error { - // tbs.dll uses 0x0 as the return value for success. - if err == 0 { - return nil - } - return Error(err) -} - -// TBS Return Codes: -// https://docs.microsoft.com/en-us/windows/desktop/TBS/tbs-return-codes -const ( - ErrInternalError Error = 0x80284001 - ErrBadParameter Error = 0x80284002 - ErrInvalidOutputPointer Error = 0x80284003 - ErrInvalidContext Error = 0x80284004 - ErrInsufficientBuffer Error = 0x80284005 - ErrIOError Error = 0x80284006 - ErrInvalidContextParam Error = 0x80284007 - ErrServiceNotRunning Error = 0x80284008 - ErrTooManyTBSContexts Error = 0x80284009 - ErrTooManyResources Error = 0x8028400A - ErrServiceStartPending Error = 0x8028400B - ErrPPINotSupported Error = 0x8028400C - ErrCommandCanceled Error = 0x8028400D - ErrBufferTooLarge Error = 0x8028400E - ErrTPMNotFound Error = 0x8028400F - ErrServiceDisabled Error = 0x80284010 - ErrNoEventLog Error = 0x80284011 - ErrAccessDenied Error = 0x80284012 - ErrProvisioningNotAllowed Error = 0x80284013 - ErrPPIFunctionUnsupported Error = 0x80284014 - ErrOwnerauthNotFound Error = 0x80284015 -) - -var errorDescriptions = map[Error]string{ - ErrInternalError: "An internal software error occurred.", - ErrBadParameter: "One or more parameter values are not valid.", - ErrInvalidOutputPointer: "A specified output pointer is bad.", - ErrInvalidContext: "The specified context handle does not refer to a valid context.", - ErrInsufficientBuffer: "The specified output buffer is too small.", - ErrIOError: "An error occurred while communicating with the TPM.", - ErrInvalidContextParam: "A context parameter that is not valid was passed when attempting to create a TBS context.", - ErrServiceNotRunning: "The TBS service is not running and could not be started.", - ErrTooManyTBSContexts: "A new context could not be created because there are too many open contexts.", - ErrTooManyResources: "A new virtual resource could not be created because there are too many open virtual resources.", - ErrServiceStartPending: "The TBS service has been started but is not yet running.", - ErrPPINotSupported: "The physical presence interface is not supported.", - ErrCommandCanceled: "The command was canceled.", - ErrBufferTooLarge: "The input or output buffer is too large.", - ErrTPMNotFound: "A compatible Trusted Platform Module (TPM) Security Device cannot be found on this computer.", - ErrServiceDisabled: "The TBS service has been disabled.", - ErrNoEventLog: "The TBS event log is not available.", - ErrAccessDenied: "The caller does not have the appropriate rights to perform the requested operation.", - ErrProvisioningNotAllowed: "The TPM provisioning action is not allowed by the specified flags.", - ErrPPIFunctionUnsupported: "The Physical Presence Interface of this firmware does not support the requested method.", - ErrOwnerauthNotFound: "The requested TPM OwnerAuth value was not found.", -} - -// Tbs.dll provides an API for making calls to the TPM: -// https://docs.microsoft.com/en-us/windows/desktop/TBS/tpm-base-services-portal -var ( - tbsDLL = syscall.NewLazyDLL("Tbs.dll") - tbsGetDeviceInfo = tbsDLL.NewProc("Tbsi_GetDeviceInfo") - tbsCreateContext = tbsDLL.NewProc("Tbsi_Context_Create") - tbsContextClose = tbsDLL.NewProc("Tbsip_Context_Close") - tbsSubmitCommand = tbsDLL.NewProc("Tbsip_Submit_Command") - tbsGetTCGLog = tbsDLL.NewProc("Tbsi_Get_TCG_Log") -) - -// Returns the address of the beginning of a slice or 0 for a nil slice. -func sliceAddress(s []byte) uintptr { - if len(s) == 0 { - return 0 - } - return uintptr(unsafe.Pointer(&(s[0]))) -} - -// DeviceInfo is TPM_DEVICE_INFO from tbs.h -type DeviceInfo struct { - StructVersion uint32 - TPMVersion Version - TPMInterfaceType uint32 - TPMImpRevision uint32 -} - -// GetDeviceInfo gets the DeviceInfo of the current TPM: -// https://docs.microsoft.com/en-us/windows/win32/api/tbs/nf-tbs-tbsi_getdeviceinfo -func GetDeviceInfo() (*DeviceInfo, error) { - info := DeviceInfo{} - // TBS_RESULT Tbsi_GetDeviceInfo( - // UINT32 Size, - // PVOID Info - // ); - if err := tbsGetDeviceInfo.Find(); err != nil { - return nil, err - } - result, _, _ := tbsGetDeviceInfo.Call( - unsafe.Sizeof(info), - uintptr(unsafe.Pointer(&info)), - ) - return &info, getError(result) -} - -// CreateContext creates a new TPM context: -// https://docs.microsoft.com/en-us/windows/desktop/api/Tbs/nf-tbs-tbsi_context_create -func CreateContext(version Version, flag Flag) (Context, error) { - var context Context - params := struct { - Version - Flag - }{version, flag} - // TBS_RESULT Tbsi_Context_Create( - // _In_ PCTBS_CONTEXT_PARAMS pContextParams, - // _Out_ PTBS_HCONTEXT *phContext - // ); - if err := tbsCreateContext.Find(); err != nil { - return context, err - } - result, _, _ := tbsCreateContext.Call( - uintptr(unsafe.Pointer(¶ms)), - uintptr(unsafe.Pointer(&context)), - ) - return context, getError(result) -} - -// Close closes an existing TPM context: -// https://docs.microsoft.com/en-us/windows/desktop/api/Tbs/nf-tbs-tbsip_context_close -func (context Context) Close() error { - // TBS_RESULT Tbsip_Context_Close( - // _In_ TBS_HCONTEXT hContext - // ); - if err := tbsContextClose.Find(); err != nil { - return err - } - result, _, _ := tbsContextClose.Call(uintptr(context)) - return getError(result) -} - -// SubmitCommand sends commandBuffer to the TPM, returning the number of bytes -// written to responseBuffer. ErrInsufficientBuffer is returned if the -// responseBuffer is too short. ErrInvalidOutputPointer is returned if the -// responseBuffer is nil. On failure, the returned length is unspecified. -// https://docs.microsoft.com/en-us/windows/desktop/api/Tbs/nf-tbs-tbsip_submit_command -func (context Context) SubmitCommand( - priority CommandPriority, - commandBuffer []byte, - responseBuffer []byte, -) (uint32, error) { - responseBufferLen := uint32(len(responseBuffer)) - - // TBS_RESULT Tbsip_Submit_Command( - // _In_ TBS_HCONTEXT hContext, - // _In_ TBS_COMMAND_LOCALITY Locality, - // _In_ TBS_COMMAND_PRIORITY Priority, - // _In_ const PCBYTE *pabCommand, - // _In_ UINT32 cbCommand, - // _Out_ PBYTE *pabResult, - // _Inout_ UINT32 *pcbOutput - // ); - if err := tbsSubmitCommand.Find(); err != nil { - return 0, err - } - result, _, _ := tbsSubmitCommand.Call( - uintptr(context), - uintptr(commandLocalityZero), - uintptr(priority), - sliceAddress(commandBuffer), - uintptr(len(commandBuffer)), - sliceAddress(responseBuffer), - uintptr(unsafe.Pointer(&responseBufferLen)), - ) - return responseBufferLen, getError(result) -} - -// GetTCGLog gets the system event log, returning the number of bytes written -// to logBuffer. If logBuffer is nil, the size of the TCG log is returned. -// ErrInsufficientBuffer is returned if the logBuffer is too short. On failure, -// the returned length is unspecified. -// https://docs.microsoft.com/en-us/windows/desktop/api/Tbs/nf-tbs-tbsi_get_tcg_log -func (context Context) GetTCGLog(logBuffer []byte) (uint32, error) { - logBufferLen := uint32(len(logBuffer)) - - // TBS_RESULT Tbsi_Get_TCG_Log( - // TBS_HCONTEXT hContext, - // PBYTE pOutputBuf, - // PUINT32 pOutputBufLen - // ); - if err := tbsGetTCGLog.Find(); err != nil { - return 0, err - } - result, _, _ := tbsGetTCGLog.Call( - uintptr(context), - sliceAddress(logBuffer), - uintptr(unsafe.Pointer(&logBufferLen)), - ) - return logBufferLen, getError(result) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/internal/race/norace.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/internal/race/norace.go deleted file mode 100644 index affbbbb5..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/internal/race/norace.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !race - -package race - -func ReadSlice[T any](s []T) { -} - -func WriteSlice[T any](s []T) { -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/internal/race/race.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/internal/race/race.go deleted file mode 100644 index f5e240dc..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/internal/race/race.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build race - -package race - -import ( - "runtime" - "unsafe" -) - -func ReadSlice[T any](s []T) { - if len(s) == 0 { - return - } - runtime.RaceReadRange(unsafe.Pointer(&s[0]), len(s)*int(unsafe.Sizeof(s[0]))) -} - -func WriteSlice[T any](s []T) { - if len(s) == 0 { - return - } - runtime.RaceWriteRange(unsafe.Pointer(&s[0]), len(s)*int(unsafe.Sizeof(s[0]))) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/.gitignore b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/.gitignore deleted file mode 100644 index 3a89c6e3..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -testdata/bench - -# These explicitly listed benchmark data files are for an obsolete version of -# snappy_test.go. -testdata/alice29.txt -testdata/asyoulik.txt -testdata/fireworks.jpeg -testdata/geo.protodata -testdata/html -testdata/html_x_4 -testdata/kppkn.gtb -testdata/lcet10.txt -testdata/paper-100k.pdf -testdata/plrabn12.txt -testdata/urls.10K diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/LICENSE b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/LICENSE deleted file mode 100644 index 1d2d645b..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2011 The Snappy-Go Authors. All rights reserved. -Copyright (c) 2019 Klaus Post. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/README.md b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/README.md deleted file mode 100644 index 1d9220cb..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/README.md +++ /dev/null @@ -1,1120 +0,0 @@ -# S2 Compression - -S2 is an extension of [Snappy](https://github.com/google/snappy). - -S2 is aimed for high throughput, which is why it features concurrent compression for bigger payloads. - -Decoding is compatible with Snappy compressed content, but content compressed with S2 cannot be decompressed by Snappy. -This means that S2 can seamlessly replace Snappy without converting compressed content. - -S2 can produce Snappy compatible output, faster and better than Snappy. -If you want full benefit of the changes you should use s2 without Snappy compatibility. - -S2 is designed to have high throughput on content that cannot be compressed. -This is important, so you don't have to worry about spending CPU cycles on already compressed data. - -## Benefits over Snappy - -* Better compression -* Adjustable compression (3 levels) -* Concurrent stream compression -* Faster decompression, even for Snappy compatible content -* Concurrent Snappy/S2 stream decompression -* Skip forward in compressed stream -* Random seeking with indexes -* Compatible with reading Snappy compressed content -* Smaller block size overhead on incompressible blocks -* Block concatenation -* Block Dictionary support -* Uncompressed stream mode -* Automatic stream size padding -* Snappy compatible block compression - -## Drawbacks over Snappy - -* Not optimized for 32 bit systems -* Streams use slightly more memory due to larger blocks and concurrency (configurable) - -# Usage - -Installation: `go get -u github.com/klauspost/compress/s2` - -Full package documentation: - -[![godoc][1]][2] - -[1]: https://godoc.org/github.com/klauspost/compress?status.svg -[2]: https://godoc.org/github.com/klauspost/compress/s2 - -## Compression - -```Go -func EncodeStream(src io.Reader, dst io.Writer) error { - enc := s2.NewWriter(dst) - _, err := io.Copy(enc, src) - if err != nil { - enc.Close() - return err - } - // Blocks until compression is done. - return enc.Close() -} -``` - -You should always call `enc.Close()`, otherwise you will leak resources and your encode will be incomplete. - -For the best throughput, you should attempt to reuse the `Writer` using the `Reset()` method. - -The Writer in S2 is always buffered, therefore `NewBufferedWriter` in Snappy can be replaced with `NewWriter` in S2. -It is possible to flush any buffered data using the `Flush()` method. -This will block until all data sent to the encoder has been written to the output. - -S2 also supports the `io.ReaderFrom` interface, which will consume all input from a reader. - -As a final method to compress data, if you have a single block of data you would like to have encoded as a stream, -a slightly more efficient method is to use the `EncodeBuffer` method. -This will take ownership of the buffer until the stream is closed. - -```Go -func EncodeStream(src []byte, dst io.Writer) error { - enc := s2.NewWriter(dst) - // The encoder owns the buffer until Flush or Close is called. - err := enc.EncodeBuffer(src) - if err != nil { - enc.Close() - return err - } - // Blocks until compression is done. - return enc.Close() -} -``` - -Each call to `EncodeBuffer` will result in discrete blocks being created without buffering, -so it should only be used a single time per stream. -If you need to write several blocks, you should use the regular io.Writer interface. - - -## Decompression - -```Go -func DecodeStream(src io.Reader, dst io.Writer) error { - dec := s2.NewReader(src) - _, err := io.Copy(dst, dec) - return err -} -``` - -Similar to the Writer, a Reader can be reused using the `Reset` method. - -For the best possible throughput, there is a `EncodeBuffer(buf []byte)` function available. -However, it requires that the provided buffer isn't used after it is handed over to S2 and until the stream is flushed or closed. - -For smaller data blocks, there is also a non-streaming interface: `Encode()`, `EncodeBetter()` and `Decode()`. -Do however note that these functions (similar to Snappy) does not provide validation of data, -so data corruption may be undetected. Stream encoding provides CRC checks of data. - -It is possible to efficiently skip forward in a compressed stream using the `Skip()` method. -For big skips the decompressor is able to skip blocks without decompressing them. - -## Single Blocks - -Similar to Snappy S2 offers single block compression. -Blocks do not offer the same flexibility and safety as streams, -but may be preferable for very small payloads, less than 100K. - -Using a simple `dst := s2.Encode(nil, src)` will compress `src` and return the compressed result. -It is possible to provide a destination buffer. -If the buffer has a capacity of `s2.MaxEncodedLen(len(src))` it will be used. -If not a new will be allocated. - -Alternatively `EncodeBetter`/`EncodeBest` can also be used for better, but slightly slower compression. - -Similarly to decompress a block you can use `dst, err := s2.Decode(nil, src)`. -Again an optional destination buffer can be supplied. -The `s2.DecodedLen(src)` can be used to get the minimum capacity needed. -If that is not satisfied a new buffer will be allocated. - -Block function always operate on a single goroutine since it should only be used for small payloads. - -# Commandline tools - -Some very simply commandline tools are provided; `s2c` for compression and `s2d` for decompression. - -Binaries can be downloaded on the [Releases Page](https://github.com/klauspost/compress/releases). - -Installing then requires Go to be installed. To install them, use: - -`go install github.com/klauspost/compress/s2/cmd/s2c@latest && go install github.com/klauspost/compress/s2/cmd/s2d@latest` - -To build binaries to the current folder use: - -`go build github.com/klauspost/compress/s2/cmd/s2c && go build github.com/klauspost/compress/s2/cmd/s2d` - - -## s2c - -``` -Usage: s2c [options] file1 file2 - -Compresses all files supplied as input separately. -Output files are written as 'filename.ext.s2' or 'filename.ext.snappy'. -By default output files will be overwritten. -Use - as the only file name to read from stdin and write to stdout. - -Wildcards are accepted: testdir/*.txt will compress all files in testdir ending with .txt -Directories can be wildcards as well. testdir/*/*.txt will match testdir/subdir/b.txt - -File names beginning with 'http://' and 'https://' will be downloaded and compressed. -Only http response code 200 is accepted. - -Options: - -bench int - Run benchmark n times. No output will be written - -blocksize string - Max block size. Examples: 64K, 256K, 1M, 4M. Must be power of two and <= 4MB (default "4M") - -c Write all output to stdout. Multiple input files will be concatenated - -cpu int - Compress using this amount of threads (default 32) - -faster - Compress faster, but with a minor compression loss - -help - Display help - -index - Add seek index (default true) - -o string - Write output to another file. Single input file only - -pad string - Pad size to a multiple of this value, Examples: 500, 64K, 256K, 1M, 4M, etc (default "1") - -q Don't write any output to terminal, except errors - -rm - Delete source file(s) after successful compression - -safe - Do not overwrite output files - -slower - Compress more, but a lot slower - -snappy - Generate Snappy compatible output stream - -verify - Verify written files - -``` - -## s2d - -``` -Usage: s2d [options] file1 file2 - -Decompresses all files supplied as input. Input files must end with '.s2' or '.snappy'. -Output file names have the extension removed. By default output files will be overwritten. -Use - as the only file name to read from stdin and write to stdout. - -Wildcards are accepted: testdir/*.txt will compress all files in testdir ending with .txt -Directories can be wildcards as well. testdir/*/*.txt will match testdir/subdir/b.txt - -File names beginning with 'http://' and 'https://' will be downloaded and decompressed. -Extensions on downloaded files are ignored. Only http response code 200 is accepted. - -Options: - -bench int - Run benchmark n times. No output will be written - -c Write all output to stdout. Multiple input files will be concatenated - -help - Display help - -o string - Write output to another file. Single input file only - -offset string - Start at offset. Examples: 92, 64K, 256K, 1M, 4M. Requires Index - -q Don't write any output to terminal, except errors - -rm - Delete source file(s) after successful decompression - -safe - Do not overwrite output files - -tail string - Return last of compressed file. Examples: 92, 64K, 256K, 1M, 4M. Requires Index - -verify - Verify files, but do not write output -``` - -## s2sx: self-extracting archives - -s2sx allows creating self-extracting archives with no dependencies. - -By default, executables are created for the same platforms as the host os, -but this can be overridden with `-os` and `-arch` parameters. - -Extracted files have 0666 permissions, except when untar option used. - -``` -Usage: s2sx [options] file1 file2 - -Compresses all files supplied as input separately. -If files have '.s2' extension they are assumed to be compressed already. -Output files are written as 'filename.s2sx' and with '.exe' for windows targets. -If output is big, an additional file with ".more" is written. This must be included as well. -By default output files will be overwritten. - -Wildcards are accepted: testdir/*.txt will compress all files in testdir ending with .txt -Directories can be wildcards as well. testdir/*/*.txt will match testdir/subdir/b.txt - -Options: - -arch string - Destination architecture (default "amd64") - -c Write all output to stdout. Multiple input files will be concatenated - -cpu int - Compress using this amount of threads (default 32) - -help - Display help - -max string - Maximum executable size. Rest will be written to another file. (default "1G") - -os string - Destination operating system (default "windows") - -q Don't write any output to terminal, except errors - -rm - Delete source file(s) after successful compression - -safe - Do not overwrite output files - -untar - Untar on destination -``` - -Available platforms are: - - * darwin-amd64 - * darwin-arm64 - * linux-amd64 - * linux-arm - * linux-arm64 - * linux-mips64 - * linux-ppc64le - * windows-386 - * windows-amd64 - -By default, there is a size limit of 1GB for the output executable. - -When this is exceeded the remaining file content is written to a file called -output+`.more`. This file must be included for a successful extraction and -placed alongside the executable for a successful extraction. - -This file *must* have the same name as the executable, so if the executable is renamed, -so must the `.more` file. - -This functionality is disabled with stdin/stdout. - -### Self-extracting TAR files - -If you wrap a TAR file you can specify `-untar` to make it untar on the destination host. - -Files are extracted to the current folder with the path specified in the tar file. - -Note that tar files are not validated before they are wrapped. - -For security reasons files that move below the root folder are not allowed. - -# Performance - -This section will focus on comparisons to Snappy. -This package is solely aimed at replacing Snappy as a high speed compression package. -If you are mainly looking for better compression [zstandard](https://github.com/klauspost/compress/tree/master/zstd#zstd) -gives better compression, but typically at speeds slightly below "better" mode in this package. - -Compression is increased compared to Snappy, mostly around 5-20% and the throughput is typically 25-40% increased (single threaded) compared to the Snappy Go implementation. - -Streams are concurrently compressed. The stream will be distributed among all available CPU cores for the best possible throughput. - -A "better" compression mode is also available. This allows to trade a bit of speed for a minor compression gain. -The content compressed in this mode is fully compatible with the standard decoder. - -Snappy vs S2 **compression** speed on 16 core (32 thread) computer, using all threads and a single thread (1 CPU): - -| File | S2 Speed | S2 Throughput | S2 % smaller | S2 "better" | "better" throughput | "better" % smaller | -|---------------------------------------------------------------------------------------------------------|----------|---------------|--------------|-------------|---------------------|--------------------| -| [rawstudio-mint14.tar](https://files.klauspost.com/compress/rawstudio-mint14.7z) | 16.33x | 10556 MB/s | 8.0% | 6.04x | 5252 MB/s | 14.7% | -| (1 CPU) | 1.08x | 940 MB/s | - | 0.46x | 400 MB/s | - | -| [github-june-2days-2019.json](https://files.klauspost.com/compress/github-june-2days-2019.json.zst) | 16.51x | 15224 MB/s | 31.70% | 9.47x | 8734 MB/s | 37.71% | -| (1 CPU) | 1.26x | 1157 MB/s | - | 0.60x | 556 MB/s | - | -| [github-ranks-backup.bin](https://files.klauspost.com/compress/github-ranks-backup.bin.zst) | 15.14x | 12598 MB/s | -5.76% | 6.23x | 5675 MB/s | 3.62% | -| (1 CPU) | 1.02x | 932 MB/s | - | 0.47x | 432 MB/s | - | -| [consensus.db.10gb](https://files.klauspost.com/compress/consensus.db.10gb.zst) | 11.21x | 12116 MB/s | 15.95% | 3.24x | 3500 MB/s | 18.00% | -| (1 CPU) | 1.05x | 1135 MB/s | - | 0.27x | 292 MB/s | - | -| [apache.log](https://files.klauspost.com/compress/apache.log.zst) | 8.55x | 16673 MB/s | 20.54% | 5.85x | 11420 MB/s | 24.97% | -| (1 CPU) | 1.91x | 1771 MB/s | - | 0.53x | 1041 MB/s | - | -| [gob-stream](https://files.klauspost.com/compress/gob-stream.7z) | 15.76x | 14357 MB/s | 24.01% | 8.67x | 7891 MB/s | 33.68% | -| (1 CPU) | 1.17x | 1064 MB/s | - | 0.65x | 595 MB/s | - | -| [10gb.tar](http://mattmahoney.net/dc/10gb.html) | 13.33x | 9835 MB/s | 2.34% | 6.85x | 4863 MB/s | 9.96% | -| (1 CPU) | 0.97x | 689 MB/s | - | 0.55x | 387 MB/s | - | -| sharnd.out.2gb | 9.11x | 13213 MB/s | 0.01% | 1.49x | 9184 MB/s | 0.01% | -| (1 CPU) | 0.88x | 5418 MB/s | - | 0.77x | 5417 MB/s | - | -| [sofia-air-quality-dataset csv](https://files.klauspost.com/compress/sofia-air-quality-dataset.tar.zst) | 22.00x | 11477 MB/s | 18.73% | 11.15x | 5817 MB/s | 27.88% | -| (1 CPU) | 1.23x | 642 MB/s | - | 0.71x | 642 MB/s | - | -| [silesia.tar](http://sun.aei.polsl.pl/~sdeor/corpus/silesia.zip) | 11.23x | 6520 MB/s | 5.9% | 5.35x | 3109 MB/s | 15.88% | -| (1 CPU) | 1.05x | 607 MB/s | - | 0.52x | 304 MB/s | - | -| [enwik9](https://files.klauspost.com/compress/enwik9.zst) | 19.28x | 8440 MB/s | 4.04% | 9.31x | 4076 MB/s | 18.04% | -| (1 CPU) | 1.12x | 488 MB/s | - | 0.57x | 250 MB/s | - | - -### Legend - -* `S2 Speed`: Speed of S2 compared to Snappy, using 16 cores and 1 core. -* `S2 Throughput`: Throughput of S2 in MB/s. -* `S2 % smaller`: How many percent of the Snappy output size is S2 better. -* `S2 "better"`: Speed when enabling "better" compression mode in S2 compared to Snappy. -* `"better" throughput`: Speed when enabling "better" compression mode in S2 compared to Snappy. -* `"better" % smaller`: How many percent of the Snappy output size is S2 better when using "better" compression. - -There is a good speedup across the board when using a single thread and a significant speedup when using multiple threads. - -Machine generated data gets by far the biggest compression boost, with size being reduced by up to 35% of Snappy size. - -The "better" compression mode sees a good improvement in all cases, but usually at a performance cost. - -Incompressible content (`sharnd.out.2gb`, 2GB random data) sees the smallest speedup. -This is likely dominated by synchronization overhead, which is confirmed by the fact that single threaded performance is higher (see above). - -## Decompression - -S2 attempts to create content that is also fast to decompress, except in "better" mode where the smallest representation is used. - -S2 vs Snappy **decompression** speed. Both operating on single core: - -| File | S2 Throughput | vs. Snappy | Better Throughput | vs. Snappy | -|-----------------------------------------------------------------------------------------------------|---------------|------------|-------------------|------------| -| [rawstudio-mint14.tar](https://files.klauspost.com/compress/rawstudio-mint14.7z) | 2117 MB/s | 1.14x | 1738 MB/s | 0.94x | -| [github-june-2days-2019.json](https://files.klauspost.com/compress/github-june-2days-2019.json.zst) | 2401 MB/s | 1.25x | 2307 MB/s | 1.20x | -| [github-ranks-backup.bin](https://files.klauspost.com/compress/github-ranks-backup.bin.zst) | 2075 MB/s | 0.98x | 1764 MB/s | 0.83x | -| [consensus.db.10gb](https://files.klauspost.com/compress/consensus.db.10gb.zst) | 2967 MB/s | 1.05x | 2885 MB/s | 1.02x | -| [adresser.json](https://files.klauspost.com/compress/adresser.json.zst) | 4141 MB/s | 1.07x | 4184 MB/s | 1.08x | -| [gob-stream](https://files.klauspost.com/compress/gob-stream.7z) | 2264 MB/s | 1.12x | 2185 MB/s | 1.08x | -| [10gb.tar](http://mattmahoney.net/dc/10gb.html) | 1525 MB/s | 1.03x | 1347 MB/s | 0.91x | -| sharnd.out.2gb | 3813 MB/s | 0.79x | 3900 MB/s | 0.81x | -| [enwik9](http://mattmahoney.net/dc/textdata.html) | 1246 MB/s | 1.29x | 967 MB/s | 1.00x | -| [silesia.tar](http://sun.aei.polsl.pl/~sdeor/corpus/silesia.zip) | 1433 MB/s | 1.12x | 1203 MB/s | 0.94x | -| [enwik10](https://encode.su/threads/3315-enwik10-benchmark-results) | 1284 MB/s | 1.32x | 1010 MB/s | 1.04x | - -### Legend - -* `S2 Throughput`: Decompression speed of S2 encoded content. -* `Better Throughput`: Decompression speed of S2 "better" encoded content. -* `vs Snappy`: Decompression speed of S2 "better" mode compared to Snappy and absolute speed. - - -While the decompression code hasn't changed, there is a significant speedup in decompression speed. -S2 prefers longer matches and will typically only find matches that are 6 bytes or longer. -While this reduces compression a bit, it improves decompression speed. - -The "better" compression mode will actively look for shorter matches, which is why it has a decompression speed quite similar to Snappy. - -Without assembly decompression is also very fast; single goroutine decompression speed. No assembly: - -| File | S2 Throughput | S2 throughput | -|--------------------------------|---------------|---------------| -| consensus.db.10gb.s2 | 1.84x | 2289.8 MB/s | -| 10gb.tar.s2 | 1.30x | 867.07 MB/s | -| rawstudio-mint14.tar.s2 | 1.66x | 1329.65 MB/s | -| github-june-2days-2019.json.s2 | 2.36x | 1831.59 MB/s | -| github-ranks-backup.bin.s2 | 1.73x | 1390.7 MB/s | -| enwik9.s2 | 1.67x | 681.53 MB/s | -| adresser.json.s2 | 3.41x | 4230.53 MB/s | -| silesia.tar.s2 | 1.52x | 811.58 | - -Even though S2 typically compresses better than Snappy, decompression speed is always better. - -### Concurrent Stream Decompression - -For full stream decompression S2 offers a [DecodeConcurrent](https://pkg.go.dev/github.com/klauspost/compress/s2#Reader.DecodeConcurrent) -that will decode a full stream using multiple goroutines. - -Example scaling, AMD Ryzen 3950X, 16 cores, decompression using `s2d -bench=3 `, best of 3: - -| Input | `-cpu=1` | `-cpu=2` | `-cpu=4` | `-cpu=8` | `-cpu=16` | -|-------------------------------------------|------------|------------|------------|------------|-------------| -| enwik10.snappy | 1098.6MB/s | 1819.8MB/s | 3625.6MB/s | 6910.6MB/s | 10818.2MB/s | -| enwik10.s2 | 1303.5MB/s | 2606.1MB/s | 4847.9MB/s | 8878.4MB/s | 9592.1MB/s | -| sofia-air-quality-dataset.tar.snappy | 1302.0MB/s | 2165.0MB/s | 4244.5MB/s | 8241.0MB/s | 12920.5MB/s | -| sofia-air-quality-dataset.tar.s2 | 1399.2MB/s | 2463.2MB/s | 5196.5MB/s | 9639.8MB/s | 11439.5MB/s | -| sofia-air-quality-dataset.tar.s2 (no asm) | 837.5MB/s | 1652.6MB/s | 3183.6MB/s | 5945.0MB/s | 9620.7MB/s | - -Scaling can be expected to be pretty linear until memory bandwidth is saturated. - -For now the DecodeConcurrent can only be used for full streams without seeking or combining with regular reads. - -## Block compression - - -When compressing blocks no concurrent compression is performed just as Snappy. -This is because blocks are for smaller payloads and generally will not benefit from concurrent compression. - -An important change is that incompressible blocks will not be more than at most 10 bytes bigger than the input. -In rare, worst case scenario Snappy blocks could be significantly bigger than the input. - -### Mixed content blocks - -The most reliable is a wide dataset. -For this we use [`webdevdata.org-2015-01-07-subset`](https://files.klauspost.com/compress/webdevdata.org-2015-01-07-4GB-subset.7z), -53927 files, total input size: 4,014,735,833 bytes. Single goroutine used. - -| * | Input | Output | Reduction | MB/s | -|-------------------|------------|------------|------------|------------| -| S2 | 4014735833 | 1059723369 | 73.60% | **936.73** | -| S2 Better | 4014735833 | 961580539 | 76.05% | 451.10 | -| S2 Best | 4014735833 | 899182886 | **77.60%** | 46.84 | -| Snappy | 4014735833 | 1128706759 | 71.89% | 790.15 | -| S2, Snappy Output | 4014735833 | 1093823291 | 72.75% | 936.60 | -| LZ4 | 4014735833 | 1063768713 | 73.50% | 452.02 | - -S2 delivers both the best single threaded throughput with regular mode and the best compression rate with "best". -"Better" mode provides the same compression speed as LZ4 with better compression ratio. - -When outputting Snappy compatible output it still delivers better throughput (150MB/s more) and better compression. - -As can be seen from the other benchmarks decompression should also be easier on the S2 generated output. - -Though they cannot be compared due to different decompression speeds here are the speed/size comparisons for -other Go compressors: - -| * | Input | Output | Reduction | MB/s | -|-------------------|------------|------------|-----------|--------| -| Zstd Fastest (Go) | 4014735833 | 794608518 | 80.21% | 236.04 | -| Zstd Best (Go) | 4014735833 | 704603356 | 82.45% | 35.63 | -| Deflate (Go) l1 | 4014735833 | 871294239 | 78.30% | 214.04 | -| Deflate (Go) l9 | 4014735833 | 730389060 | 81.81% | 41.17 | - -### Standard block compression - -Benchmarking single block performance is subject to a lot more variation since it only tests a limited number of file patterns. -So individual benchmarks should only be seen as a guideline and the overall picture is more important. - -These micro-benchmarks are with data in cache and trained branch predictors. For a more realistic benchmark see the mixed content above. - -Block compression. Parallel benchmark running on 16 cores, 16 goroutines. - -AMD64 assembly is use for both S2 and Snappy. - -| Absolute Perf | Snappy size | S2 Size | Snappy Speed | S2 Speed | Snappy dec | S2 dec | -|-----------------------|-------------|---------|--------------|-------------|-------------|-------------| -| html | 22843 | 20868 | 16246 MB/s | 18617 MB/s | 40972 MB/s | 49263 MB/s | -| urls.10K | 335492 | 286541 | 7943 MB/s | 10201 MB/s | 22523 MB/s | 26484 MB/s | -| fireworks.jpeg | 123034 | 123100 | 349544 MB/s | 303228 MB/s | 718321 MB/s | 827552 MB/s | -| fireworks.jpeg (200B) | 146 | 155 | 8869 MB/s | 20180 MB/s | 33691 MB/s | 52421 MB/s | -| paper-100k.pdf | 85304 | 84202 | 167546 MB/s | 112988 MB/s | 326905 MB/s | 291944 MB/s | -| html_x_4 | 92234 | 20870 | 15194 MB/s | 54457 MB/s | 30843 MB/s | 32217 MB/s | -| alice29.txt | 88034 | 85934 | 5936 MB/s | 6540 MB/s | 12882 MB/s | 20044 MB/s | -| asyoulik.txt | 77503 | 79575 | 5517 MB/s | 6657 MB/s | 12735 MB/s | 22806 MB/s | -| lcet10.txt | 234661 | 220383 | 6235 MB/s | 6303 MB/s | 14519 MB/s | 18697 MB/s | -| plrabn12.txt | 319267 | 318196 | 5159 MB/s | 6074 MB/s | 11923 MB/s | 19901 MB/s | -| geo.protodata | 23335 | 18606 | 21220 MB/s | 25432 MB/s | 56271 MB/s | 62540 MB/s | -| kppkn.gtb | 69526 | 65019 | 9732 MB/s | 8905 MB/s | 18491 MB/s | 18969 MB/s | -| alice29.txt (128B) | 80 | 82 | 6691 MB/s | 17179 MB/s | 31883 MB/s | 38874 MB/s | -| alice29.txt (1000B) | 774 | 774 | 12204 MB/s | 13273 MB/s | 48056 MB/s | 52341 MB/s | -| alice29.txt (10000B) | 6648 | 6933 | 10044 MB/s | 12824 MB/s | 32378 MB/s | 46322 MB/s | -| alice29.txt (20000B) | 12686 | 13516 | 7733 MB/s | 12160 MB/s | 30566 MB/s | 58969 MB/s | - - -Speed is generally at or above Snappy. Small blocks gets a significant speedup, although at the expense of size. - -Decompression speed is better than Snappy, except in one case. - -Since payloads are very small the variance in terms of size is rather big, so they should only be seen as a general guideline. - -Size is on average around Snappy, but varies on content type. -In cases where compression is worse, it usually is compensated by a speed boost. - - -### Better compression - -Benchmarking single block performance is subject to a lot more variation since it only tests a limited number of file patterns. -So individual benchmarks should only be seen as a guideline and the overall picture is more important. - -| Absolute Perf | Snappy size | Better Size | Snappy Speed | Better Speed | Snappy dec | Better dec | -|-----------------------|-------------|-------------|--------------|--------------|-------------|-------------| -| html | 22843 | 18972 | 16246 MB/s | 8621 MB/s | 40972 MB/s | 40292 MB/s | -| urls.10K | 335492 | 248079 | 7943 MB/s | 5104 MB/s | 22523 MB/s | 20981 MB/s | -| fireworks.jpeg | 123034 | 123100 | 349544 MB/s | 84429 MB/s | 718321 MB/s | 823698 MB/s | -| fireworks.jpeg (200B) | 146 | 149 | 8869 MB/s | 7125 MB/s | 33691 MB/s | 30101 MB/s | -| paper-100k.pdf | 85304 | 82887 | 167546 MB/s | 11087 MB/s | 326905 MB/s | 198869 MB/s | -| html_x_4 | 92234 | 18982 | 15194 MB/s | 29316 MB/s | 30843 MB/s | 30937 MB/s | -| alice29.txt | 88034 | 71611 | 5936 MB/s | 3709 MB/s | 12882 MB/s | 16611 MB/s | -| asyoulik.txt | 77503 | 65941 | 5517 MB/s | 3380 MB/s | 12735 MB/s | 14975 MB/s | -| lcet10.txt | 234661 | 184939 | 6235 MB/s | 3537 MB/s | 14519 MB/s | 16634 MB/s | -| plrabn12.txt | 319267 | 264990 | 5159 MB/s | 2960 MB/s | 11923 MB/s | 13382 MB/s | -| geo.protodata | 23335 | 17689 | 21220 MB/s | 10859 MB/s | 56271 MB/s | 57961 MB/s | -| kppkn.gtb | 69526 | 55398 | 9732 MB/s | 5206 MB/s | 18491 MB/s | 16524 MB/s | -| alice29.txt (128B) | 80 | 78 | 6691 MB/s | 7422 MB/s | 31883 MB/s | 34225 MB/s | -| alice29.txt (1000B) | 774 | 746 | 12204 MB/s | 5734 MB/s | 48056 MB/s | 42068 MB/s | -| alice29.txt (10000B) | 6648 | 6218 | 10044 MB/s | 6055 MB/s | 32378 MB/s | 28813 MB/s | -| alice29.txt (20000B) | 12686 | 11492 | 7733 MB/s | 3143 MB/s | 30566 MB/s | 27315 MB/s | - - -Except for the mostly incompressible JPEG image compression is better and usually in the -double digits in terms of percentage reduction over Snappy. - -The PDF sample shows a significant slowdown compared to Snappy, as this mode tries harder -to compress the data. Very small blocks are also not favorable for better compression, so throughput is way down. - -This mode aims to provide better compression at the expense of performance and achieves that -without a huge performance penalty, except on very small blocks. - -Decompression speed suffers a little compared to the regular S2 mode, -but still manages to be close to Snappy in spite of increased compression. - -# Best compression mode - -S2 offers a "best" compression mode. - -This will compress as much as possible with little regard to CPU usage. - -Mainly for offline compression, but where decompression speed should still -be high and compatible with other S2 compressed data. - -Some examples compared on 16 core CPU, amd64 assembly used: - -``` -* enwik10 -Default... 10000000000 -> 4759950115 [47.60%]; 1.03s, 9263.0MB/s -Better... 10000000000 -> 4084706676 [40.85%]; 2.16s, 4415.4MB/s -Best... 10000000000 -> 3615520079 [36.16%]; 42.259s, 225.7MB/s - -* github-june-2days-2019.json -Default... 6273951764 -> 1041700255 [16.60%]; 431ms, 13882.3MB/s -Better... 6273951764 -> 945841238 [15.08%]; 547ms, 10938.4MB/s -Best... 6273951764 -> 826392576 [13.17%]; 9.455s, 632.8MB/s - -* nyc-taxi-data-10M.csv -Default... 3325605752 -> 1093516949 [32.88%]; 324ms, 9788.7MB/s -Better... 3325605752 -> 885394158 [26.62%]; 491ms, 6459.4MB/s -Best... 3325605752 -> 773681257 [23.26%]; 8.29s, 412.0MB/s - -* 10gb.tar -Default... 10065157632 -> 5915541066 [58.77%]; 1.028s, 9337.4MB/s -Better... 10065157632 -> 5453844650 [54.19%]; 1.597s, 4862.7MB/s -Best... 10065157632 -> 5192495021 [51.59%]; 32.78s, 308.2MB/ - -* consensus.db.10gb -Default... 10737418240 -> 4549762344 [42.37%]; 882ms, 12118.4MB/s -Better... 10737418240 -> 4438535064 [41.34%]; 1.533s, 3500.9MB/s -Best... 10737418240 -> 4210602774 [39.21%]; 42.96s, 254.4MB/s -``` - -Decompression speed should be around the same as using the 'better' compression mode. - -## Dictionaries - -*Note: S2 dictionary compression is currently at an early implementation stage, with no assembly for -neither encoding nor decoding. Performance improvements can be expected in the future.* - -Adding dictionaries allow providing a custom dictionary that will serve as lookup in the beginning of blocks. - -The same dictionary *must* be used for both encoding and decoding. -S2 does not keep track of whether the same dictionary is used, -and using the wrong dictionary will most often not result in an error when decompressing. - -Blocks encoded *without* dictionaries can be decompressed seamlessly *with* a dictionary. -This means it is possible to switch from an encoding without dictionaries to an encoding with dictionaries -and treat the blocks similarly. - -Similar to [zStandard dictionaries](https://github.com/facebook/zstd#the-case-for-small-data-compression), -the same usage scenario applies to S2 dictionaries. - -> Training works if there is some correlation in a family of small data samples. The more data-specific a dictionary is, the more efficient it is (there is no universal dictionary). Hence, deploying one dictionary per type of data will provide the greatest benefits. Dictionary gains are mostly effective in the first few KB. Then, the compression algorithm will gradually use previously decoded content to better compress the rest of the file. - -S2 further limits the dictionary to only be enabled on the first 64KB of a block. -This will remove any negative (speed) impacts of the dictionaries on bigger blocks. - -### Compression - -Using the [github_users_sample_set](https://github.com/facebook/zstd/releases/download/v1.1.3/github_users_sample_set.tar.zst) -and a 64KB dictionary trained with zStandard the following sizes can be achieved. - -| | Default | Better | Best | -|--------------------|------------------|------------------|-----------------------| -| Without Dictionary | 3362023 (44.92%) | 3083163 (41.19%) | 3057944 (40.86%) | -| With Dictionary | 921524 (12.31%) | 873154 (11.67%) | 785503 bytes (10.49%) | - -So for highly repetitive content, this case provides an almost 3x reduction in size. - -For less uniform data we will use the Go source code tree. -Compressing First 64KB of all `.go` files in `go/src`, Go 1.19.5, 8912 files, 51253563 bytes input: - -| | Default | Better | Best | -|--------------------|-------------------|-------------------|-------------------| -| Without Dictionary | 22955767 (44.79%) | 20189613 (39.39% | 19482828 (38.01%) | -| With Dictionary | 19654568 (38.35%) | 16289357 (31.78%) | 15184589 (29.63%) | -| Saving/file | 362 bytes | 428 bytes | 472 bytes | - - -### Creating Dictionaries - -There are no tools to create dictionaries in S2. -However, there are multiple ways to create a useful dictionary: - -#### Using a Sample File - -If your input is very uniform, you can just use a sample file as the dictionary. - -For example in the `github_users_sample_set` above, the average compression only goes up from -10.49% to 11.48% by using the first file as dictionary compared to using a dedicated dictionary. - -```Go - // Read a sample - sample, err := os.ReadFile("sample.json") - - // Create a dictionary. - dict := s2.MakeDict(sample, nil) - - // b := dict.Bytes() will provide a dictionary that can be saved - // and reloaded with s2.NewDict(b). - - // To encode: - encoded := dict.Encode(nil, file) - - // To decode: - decoded, err := dict.Decode(nil, file) -``` - -#### Using Zstandard - -Zstandard dictionaries can easily be converted to S2 dictionaries. - -This can be helpful to generate dictionaries for files that don't have a fixed structure. - - -Example, with training set files placed in `./training-set`: - -`λ zstd -r --train-fastcover training-set/* --maxdict=65536 -o name.dict` - -This will create a dictionary of 64KB, that can be converted to a dictionary like this: - -```Go - // Decode the Zstandard dictionary. - insp, err := zstd.InspectDictionary(zdict) - if err != nil { - panic(err) - } - - // We are only interested in the contents. - // Assume that files start with "// Copyright (c) 2023". - // Search for the longest match for that. - // This may save a few bytes. - dict := s2.MakeDict(insp.Content(), []byte("// Copyright (c) 2023")) - - // b := dict.Bytes() will provide a dictionary that can be saved - // and reloaded with s2.NewDict(b). - - // We can now encode using this dictionary - encodedWithDict := dict.Encode(nil, payload) - - // To decode content: - decoded, err := dict.Decode(nil, encodedWithDict) -``` - -It is recommended to save the dictionary returned by ` b:= dict.Bytes()`, since that will contain only the S2 dictionary. - -This dictionary can later be loaded using `s2.NewDict(b)`. The dictionary then no longer requires `zstd` to be initialized. - -Also note how `s2.MakeDict` allows you to search for a common starting sequence of your files. -This can be omitted, at the expense of a few bytes. - -# Snappy Compatibility - -S2 now offers full compatibility with Snappy. - -This means that the efficient encoders of S2 can be used to generate fully Snappy compatible output. - -There is a [snappy](https://github.com/klauspost/compress/tree/master/snappy) package that can be used by -simply changing imports from `github.com/golang/snappy` to `github.com/klauspost/compress/snappy`. -This uses "better" mode for all operations. -If you would like more control, you can use the s2 package as described below: - -## Blocks - -Snappy compatible blocks can be generated with the S2 encoder. -Compression and speed is typically a bit better `MaxEncodedLen` is also smaller for smaller memory usage. Replace - -| Snappy | S2 replacement | -|---------------------------|-----------------------| -| snappy.Encode(...) | s2.EncodeSnappy(...) | -| snappy.MaxEncodedLen(...) | s2.MaxEncodedLen(...) | - -`s2.EncodeSnappy` can be replaced with `s2.EncodeSnappyBetter` or `s2.EncodeSnappyBest` to get more efficiently compressed snappy compatible output. - -`s2.ConcatBlocks` is compatible with snappy blocks. - -Comparison of [`webdevdata.org-2015-01-07-subset`](https://files.klauspost.com/compress/webdevdata.org-2015-01-07-4GB-subset.7z), -53927 files, total input size: 4,014,735,833 bytes. amd64, single goroutine used: - -| Encoder | Size | MB/s | Reduction | -|-----------------------|------------|------------|------------| -| snappy.Encode | 1128706759 | 725.59 | 71.89% | -| s2.EncodeSnappy | 1093823291 | **899.16** | 72.75% | -| s2.EncodeSnappyBetter | 1001158548 | 578.49 | 75.06% | -| s2.EncodeSnappyBest | 944507998 | 66.00 | **76.47%** | - -## Streams - -For streams, replace `enc = snappy.NewBufferedWriter(w)` with `enc = s2.NewWriter(w, s2.WriterSnappyCompat())`. -All other options are available, but note that block size limit is different for snappy. - -Comparison of different streams, AMD Ryzen 3950x, 16 cores. Size and throughput: - -| File | snappy.NewWriter | S2 Snappy | S2 Snappy, Better | S2 Snappy, Best | -|-----------------------------|--------------------------|---------------------------|--------------------------|-------------------------| -| nyc-taxi-data-10M.csv | 1316042016 - 539.47MB/s | 1307003093 - 10132.73MB/s | 1174534014 - 5002.44MB/s | 1115904679 - 177.97MB/s | -| enwik10 (xml) | 5088294643 - 451.13MB/s | 5175840939 - 9440.69MB/s | 4560784526 - 4487.21MB/s | 4340299103 - 158.92MB/s | -| 10gb.tar (mixed) | 6056946612 - 729.73MB/s | 6208571995 - 9978.05MB/s | 5741646126 - 4919.98MB/s | 5548973895 - 180.44MB/s | -| github-june-2days-2019.json | 1525176492 - 933.00MB/s | 1476519054 - 13150.12MB/s | 1400547532 - 5803.40MB/s | 1321887137 - 204.29MB/s | -| consensus.db.10gb (db) | 5412897703 - 1102.14MB/s | 5354073487 - 13562.91MB/s | 5335069899 - 5294.73MB/s | 5201000954 - 175.72MB/s | - -# Decompression - -All decompression functions map directly to equivalent s2 functions. - -| Snappy | S2 replacement | -|------------------------|--------------------| -| snappy.Decode(...) | s2.Decode(...) | -| snappy.DecodedLen(...) | s2.DecodedLen(...) | -| snappy.NewReader(...) | s2.NewReader(...) | - -Features like [quick forward skipping without decompression](https://pkg.go.dev/github.com/klauspost/compress/s2#Reader.Skip) -are also available for Snappy streams. - -If you know you are only decompressing snappy streams, setting [`ReaderMaxBlockSize(64<<10)`](https://pkg.go.dev/github.com/klauspost/compress/s2#ReaderMaxBlockSize) -on your Reader will reduce memory consumption. - -# Concatenating blocks and streams. - -Concatenating streams will concatenate the output of both without recompressing them. -While this is inefficient in terms of compression it might be usable in certain scenarios. -The 10 byte 'stream identifier' of the second stream can optionally be stripped, but it is not a requirement. - -Blocks can be concatenated using the `ConcatBlocks` function. - -Snappy blocks/streams can safely be concatenated with S2 blocks and streams. -Streams with indexes (see below) will currently not work on concatenated streams. - -# Stream Seek Index - -S2 and Snappy streams can have indexes. These indexes will allow random seeking within the compressed data. - -The index can either be appended to the stream as a skippable block or returned for separate storage. - -When the index is appended to a stream it will be skipped by regular decoders, -so the output remains compatible with other decoders. - -## Creating an Index - -To automatically add an index to a stream, add `WriterAddIndex()` option to your writer. -Then the index will be added to the stream when `Close()` is called. - -``` - // Add Index to stream... - enc := s2.NewWriter(w, s2.WriterAddIndex()) - io.Copy(enc, r) - enc.Close() -``` - -If you want to store the index separately, you can use `CloseIndex()` instead of the regular `Close()`. -This will return the index. Note that `CloseIndex()` should only be called once, and you shouldn't call `Close()`. - -``` - // Get index for separate storage... - enc := s2.NewWriter(w) - io.Copy(enc, r) - index, err := enc.CloseIndex() -``` - -The `index` can then be used needing to read from the stream. -This means the index can be used without needing to seek to the end of the stream -or for manually forwarding streams. See below. - -Finally, an existing S2/Snappy stream can be indexed using the `s2.IndexStream(r io.Reader)` function. - -## Using Indexes - -To use indexes there is a `ReadSeeker(random bool, index []byte) (*ReadSeeker, error)` function available. - -Calling ReadSeeker will return an [io.ReadSeeker](https://pkg.go.dev/io#ReadSeeker) compatible version of the reader. - -If 'random' is specified the returned io.Seeker can be used for random seeking, otherwise only forward seeking is supported. -Enabling random seeking requires the original input to support the [io.Seeker](https://pkg.go.dev/io#Seeker) interface. - -``` - dec := s2.NewReader(r) - rs, err := dec.ReadSeeker(false, nil) - rs.Seek(wantOffset, io.SeekStart) -``` - -Get a seeker to seek forward. Since no index is provided, the index is read from the stream. -This requires that an index was added and that `r` supports the [io.Seeker](https://pkg.go.dev/io#Seeker) interface. - -A custom index can be specified which will be used if supplied. -When using a custom index, it will not be read from the input stream. - -``` - dec := s2.NewReader(r) - rs, err := dec.ReadSeeker(false, index) - rs.Seek(wantOffset, io.SeekStart) -``` - -This will read the index from `index`. Since we specify non-random (forward only) seeking `r` does not have to be an io.Seeker - -``` - dec := s2.NewReader(r) - rs, err := dec.ReadSeeker(true, index) - rs.Seek(wantOffset, io.SeekStart) -``` - -Finally, since we specify that we want to do random seeking `r` must be an io.Seeker. - -The returned [ReadSeeker](https://pkg.go.dev/github.com/klauspost/compress/s2#ReadSeeker) contains a shallow reference to the existing Reader, -meaning changes performed to one is reflected in the other. - -To check if a stream contains an index at the end, the `(*Index).LoadStream(rs io.ReadSeeker) error` can be used. - -## Manually Forwarding Streams - -Indexes can also be read outside the decoder using the [Index](https://pkg.go.dev/github.com/klauspost/compress/s2#Index) type. -This can be used for parsing indexes, either separate or in streams. - -In some cases it may not be possible to serve a seekable stream. -This can for instance be an HTTP stream, where the Range request -is sent at the start of the stream. - -With a little bit of extra code it is still possible to use indexes -to forward to specific offset with a single forward skip. - -It is possible to load the index manually like this: -``` - var index s2.Index - _, err = index.Load(idxBytes) -``` - -This can be used to figure out how much to offset the compressed stream: - -``` - compressedOffset, uncompressedOffset, err := index.Find(wantOffset) -``` - -The `compressedOffset` is the number of bytes that should be skipped -from the beginning of the compressed file. - -The `uncompressedOffset` will then be offset of the uncompressed bytes returned -when decoding from that position. This will always be <= wantOffset. - -When creating a decoder it must be specified that it should *not* expect a stream identifier -at the beginning of the stream. Assuming the io.Reader `r` has been forwarded to `compressedOffset` -we create the decoder like this: - -``` - dec := s2.NewReader(r, s2.ReaderIgnoreStreamIdentifier()) -``` - -We are not completely done. We still need to forward the stream the uncompressed bytes we didn't want. -This is done using the regular "Skip" function: - -``` - err = dec.Skip(wantOffset - uncompressedOffset) -``` - -This will ensure that we are at exactly the offset we want, and reading from `dec` will start at the requested offset. - -# Compact storage - -For compact storage [RemoveIndexHeaders](https://pkg.go.dev/github.com/klauspost/compress/s2#RemoveIndexHeaders) can be used to remove any redundant info from -a serialized index. If you remove the header it must be restored before [Loading](https://pkg.go.dev/github.com/klauspost/compress/s2#Index.Load). - -This is expected to save 20 bytes. These can be restored using [RestoreIndexHeaders](https://pkg.go.dev/github.com/klauspost/compress/s2#RestoreIndexHeaders). This removes a layer of security, but is the most compact representation. Returns nil if headers contains errors. - -## Index Format: - -Each block is structured as a snappy skippable block, with the chunk ID 0x99. - -The block can be read from the front, but contains information so it can be read from the back as well. - -Numbers are stored as fixed size little endian values or [zigzag encoded](https://developers.google.com/protocol-buffers/docs/encoding#signed_integers) [base 128 varints](https://developers.google.com/protocol-buffers/docs/encoding), -with un-encoded value length of 64 bits, unless other limits are specified. - -| Content | Format | -|--------------------------------------|-------------------------------------------------------------------------------------------------------------------------------| -| ID, `[1]byte` | Always 0x99. | -| Data Length, `[3]byte` | 3 byte little-endian length of the chunk in bytes, following this. | -| Header `[6]byte` | Header, must be `[115, 50, 105, 100, 120, 0]` or in text: "s2idx\x00". | -| UncompressedSize, Varint | Total Uncompressed size. | -| CompressedSize, Varint | Total Compressed size if known. Should be -1 if unknown. | -| EstBlockSize, Varint | Block Size, used for guessing uncompressed offsets. Must be >= 0. | -| Entries, Varint | Number of Entries in index, must be < 65536 and >=0. | -| HasUncompressedOffsets `byte` | 0 if no uncompressed offsets are present, 1 if present. Other values are invalid. | -| UncompressedOffsets, [Entries]VarInt | Uncompressed offsets. See below how to decode. | -| CompressedOffsets, [Entries]VarInt | Compressed offsets. See below how to decode. | -| Block Size, `[4]byte` | Little Endian total encoded size (including header and trailer). Can be used for searching backwards to start of block. | -| Trailer `[6]byte` | Trailer, must be `[0, 120, 100, 105, 50, 115]` or in text: "\x00xdi2s". Can be used for identifying block from end of stream. | - -For regular streams the uncompressed offsets are fully predictable, -so `HasUncompressedOffsets` allows to specify that compressed blocks all have -exactly `EstBlockSize` bytes of uncompressed content. - -Entries *must* be in order, starting with the lowest offset, -and there *must* be no uncompressed offset duplicates. -Entries *may* point to the start of a skippable block, -but it is then not allowed to also have an entry for the next block since -that would give an uncompressed offset duplicate. - -There is no requirement for all blocks to be represented in the index. -In fact there is a maximum of 65536 block entries in an index. - -The writer can use any method to reduce the number of entries. -An implicit block start at 0,0 can be assumed. - -### Decoding entries: - -``` -// Read Uncompressed entries. -// Each assumes EstBlockSize delta from previous. -for each entry { - uOff = 0 - if HasUncompressedOffsets == 1 { - uOff = ReadVarInt // Read value from stream - } - - // Except for the first entry, use previous values. - if entryNum == 0 { - entry[entryNum].UncompressedOffset = uOff - continue - } - - // Uncompressed uses previous offset and adds EstBlockSize - entry[entryNum].UncompressedOffset = entry[entryNum-1].UncompressedOffset + EstBlockSize + uOff -} - - -// Guess that the first block will be 50% of uncompressed size. -// Integer truncating division must be used. -CompressGuess := EstBlockSize / 2 - -// Read Compressed entries. -// Each assumes CompressGuess delta from previous. -// CompressGuess is adjusted for each value. -for each entry { - cOff = ReadVarInt // Read value from stream - - // Except for the first entry, use previous values. - if entryNum == 0 { - entry[entryNum].CompressedOffset = cOff - continue - } - - // Compressed uses previous and our estimate. - entry[entryNum].CompressedOffset = entry[entryNum-1].CompressedOffset + CompressGuess + cOff - - // Adjust compressed offset for next loop, integer truncating division must be used. - CompressGuess += cOff/2 -} -``` - -To decode from any given uncompressed offset `(wantOffset)`: - -* Iterate entries until `entry[n].UncompressedOffset > wantOffset`. -* Start decoding from `entry[n-1].CompressedOffset`. -* Discard `entry[n-1].UncompressedOffset - wantOffset` bytes from the decoded stream. - -See [using indexes](https://github.com/klauspost/compress/tree/master/s2#using-indexes) for functions that perform the operations with a simpler interface. - - -# Format Extensions - -* Frame [Stream identifier](https://github.com/google/snappy/blob/master/framing_format.txt#L68) changed from `sNaPpY` to `S2sTwO`. -* [Framed compressed blocks](https://github.com/google/snappy/blob/master/format_description.txt) can be up to 4MB (up from 64KB). -* Compressed blocks can have an offset of `0`, which indicates to repeat the last seen offset. - -Repeat offsets must be encoded as a [2.2.1. Copy with 1-byte offset (01)](https://github.com/google/snappy/blob/master/format_description.txt#L89), where the offset is 0. - -The length is specified by reading the 3-bit length specified in the tag and decode using this table: - -| Length | Actual Length | -|--------|----------------------| -| 0 | 4 | -| 1 | 5 | -| 2 | 6 | -| 3 | 7 | -| 4 | 8 | -| 5 | 8 + read 1 byte | -| 6 | 260 + read 2 bytes | -| 7 | 65540 + read 3 bytes | - -This allows any repeat offset + length to be represented by 2 to 5 bytes. -It also allows to emit matches longer than 64 bytes with one copy + one repeat instead of several 64 byte copies. - -Lengths are stored as little endian values. - -The first copy of a block cannot be a repeat offset and the offset is reset on every block in streams. - -Default streaming block size is 1MB. - -# Dictionary Encoding - -Adding dictionaries allow providing a custom dictionary that will serve as lookup in the beginning of blocks. - -A dictionary provides an initial repeat value that can be used to point to a common header. - -Other than that the dictionary contains values that can be used as back-references. - -Often used data should be placed at the *end* of the dictionary since offsets < 2048 bytes will be smaller. - -## Format - -Dictionary *content* must at least 16 bytes and less or equal to 64KiB (65536 bytes). - -Encoding: `[repeat value (uvarint)][dictionary content...]` - -Before the dictionary content, an unsigned base-128 (uvarint) encoded value specifying the initial repeat offset. -This value is an offset into the dictionary content and not a back-reference offset, -so setting this to 0 will make the repeat value point to the first value of the dictionary. - -The value must be less than the dictionary length-8 - -## Encoding - -From the decoder point of view the dictionary content is seen as preceding the encoded content. - -`[dictionary content][decoded output]` - -Backreferences to the dictionary are encoded as ordinary backreferences that have an offset before the start of the decoded block. - -Matches copying from the dictionary are **not** allowed to cross from the dictionary into the decoded data. -However, if a copy ends at the end of the dictionary the next repeat will point to the start of the decoded buffer, which is allowed. - -The first match can be a repeat value, which will use the repeat offset stored in the dictionary. - -When 64KB (65536 bytes) has been en/decoded it is no longer allowed to reference the dictionary, -neither by a copy nor repeat operations. -If the boundary is crossed while copying from the dictionary, the operation should complete, -but the next instruction is not allowed to reference the dictionary. - -Valid blocks encoded *without* a dictionary can be decoded with any dictionary. -There are no checks whether the supplied dictionary is the correct for a block. -Because of this there is no overhead by using a dictionary. - -## Example - -This is the dictionary content. Elements are separated by `[]`. - -Dictionary: `[0x0a][Yesterday 25 bananas were added to Benjamins brown bag]`. - -Initial repeat offset is set at 10, which is the letter `2`. - -Encoded `[LIT "10"][REPEAT len=10][LIT "hich"][MATCH off=50 len=6][MATCH off=31 len=6][MATCH off=61 len=10]` - -Decoded: `[10][ bananas w][hich][ were ][brown ][were added]` - -Output: `10 bananas which were brown were added` - - -## Streams - -For streams each block can use the dictionary. - -The dictionary cannot not currently be provided on the stream. - - -# LICENSE - -This code is based on the [Snappy-Go](https://github.com/golang/snappy) implementation. - -Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode.go deleted file mode 100644 index 264ffd0a..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode.go +++ /dev/null @@ -1,443 +0,0 @@ -// Copyright 2011 The Snappy-Go Authors. All rights reserved. -// Copyright (c) 2019 Klaus Post. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package s2 - -import ( - "encoding/binary" - "errors" - "fmt" - "strconv" - - "github.com/klauspost/compress/internal/race" -) - -var ( - // ErrCorrupt reports that the input is invalid. - ErrCorrupt = errors.New("s2: corrupt input") - // ErrCRC reports that the input failed CRC validation (streams only) - ErrCRC = errors.New("s2: corrupt input, crc mismatch") - // ErrTooLarge reports that the uncompressed length is too large. - ErrTooLarge = errors.New("s2: decoded block is too large") - // ErrUnsupported reports that the input isn't supported. - ErrUnsupported = errors.New("s2: unsupported input") -) - -// DecodedLen returns the length of the decoded block. -func DecodedLen(src []byte) (int, error) { - v, _, err := decodedLen(src) - return v, err -} - -// decodedLen returns the length of the decoded block and the number of bytes -// that the length header occupied. -func decodedLen(src []byte) (blockLen, headerLen int, err error) { - v, n := binary.Uvarint(src) - if n <= 0 || v > 0xffffffff { - return 0, 0, ErrCorrupt - } - - const wordSize = 32 << (^uint(0) >> 32 & 1) - if wordSize == 32 && v > 0x7fffffff { - return 0, 0, ErrTooLarge - } - return int(v), n, nil -} - -const ( - decodeErrCodeCorrupt = 1 -) - -// Decode returns the decoded form of src. The returned slice may be a sub- -// slice of dst if dst was large enough to hold the entire decoded block. -// Otherwise, a newly allocated slice will be returned. -// -// The dst and src must not overlap. It is valid to pass a nil dst. -func Decode(dst, src []byte) ([]byte, error) { - dLen, s, err := decodedLen(src) - if err != nil { - return nil, err - } - if dLen <= cap(dst) { - dst = dst[:dLen] - } else { - dst = make([]byte, dLen) - } - - race.WriteSlice(dst) - race.ReadSlice(src[s:]) - - if s2Decode(dst, src[s:]) != 0 { - return nil, ErrCorrupt - } - return dst, nil -} - -// s2DecodeDict writes the decoding of src to dst. It assumes that the varint-encoded -// length of the decompressed bytes has already been read, and that len(dst) -// equals that length. -// -// It returns 0 on success or a decodeErrCodeXxx error code on failure. -func s2DecodeDict(dst, src []byte, dict *Dict) int { - if dict == nil { - return s2Decode(dst, src) - } - const debug = false - const debugErrs = debug - - if debug { - fmt.Println("Starting decode, dst len:", len(dst)) - } - var d, s, length int - offset := len(dict.dict) - dict.repeat - - // As long as we can read at least 5 bytes... - for s < len(src)-5 { - // Removing bounds checks is SLOWER, when if doing - // in := src[s:s+5] - // Checked on Go 1.18 - switch src[s] & 0x03 { - case tagLiteral: - x := uint32(src[s] >> 2) - switch { - case x < 60: - s++ - case x == 60: - s += 2 - x = uint32(src[s-1]) - case x == 61: - in := src[s : s+3] - x = uint32(in[1]) | uint32(in[2])<<8 - s += 3 - case x == 62: - in := src[s : s+4] - // Load as 32 bit and shift down. - x = uint32(in[0]) | uint32(in[1])<<8 | uint32(in[2])<<16 | uint32(in[3])<<24 - x >>= 8 - s += 4 - case x == 63: - in := src[s : s+5] - x = uint32(in[1]) | uint32(in[2])<<8 | uint32(in[3])<<16 | uint32(in[4])<<24 - s += 5 - } - length = int(x) + 1 - if debug { - fmt.Println("literals, length:", length, "d-after:", d+length) - } - if length > len(dst)-d || length > len(src)-s || (strconv.IntSize == 32 && length <= 0) { - if debugErrs { - fmt.Println("corrupt literal: length:", length, "d-left:", len(dst)-d, "src-left:", len(src)-s) - } - return decodeErrCodeCorrupt - } - - copy(dst[d:], src[s:s+length]) - d += length - s += length - continue - - case tagCopy1: - s += 2 - toffset := int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1])) - length = int(src[s-2]) >> 2 & 0x7 - if toffset == 0 { - if debug { - fmt.Print("(repeat) ") - } - // keep last offset - switch length { - case 5: - length = int(src[s]) + 4 - s += 1 - case 6: - in := src[s : s+2] - length = int(uint32(in[0])|(uint32(in[1])<<8)) + (1 << 8) - s += 2 - case 7: - in := src[s : s+3] - length = int((uint32(in[2])<<16)|(uint32(in[1])<<8)|uint32(in[0])) + (1 << 16) - s += 3 - default: // 0-> 4 - } - } else { - offset = toffset - } - length += 4 - case tagCopy2: - in := src[s : s+3] - offset = int(uint32(in[1]) | uint32(in[2])<<8) - length = 1 + int(in[0])>>2 - s += 3 - - case tagCopy4: - in := src[s : s+5] - offset = int(uint32(in[1]) | uint32(in[2])<<8 | uint32(in[3])<<16 | uint32(in[4])<<24) - length = 1 + int(in[0])>>2 - s += 5 - } - - if offset <= 0 || length > len(dst)-d { - if debugErrs { - fmt.Println("match error; offset:", offset, "length:", length, "dst-left:", len(dst)-d) - } - return decodeErrCodeCorrupt - } - - // copy from dict - if d < offset { - if d > MaxDictSrcOffset { - if debugErrs { - fmt.Println("dict after", MaxDictSrcOffset, "d:", d, "offset:", offset, "length:", length) - } - return decodeErrCodeCorrupt - } - startOff := len(dict.dict) - offset + d - if startOff < 0 || startOff+length > len(dict.dict) { - if debugErrs { - fmt.Printf("offset (%d) + length (%d) bigger than dict (%d)\n", offset, length, len(dict.dict)) - } - return decodeErrCodeCorrupt - } - if debug { - fmt.Println("dict copy, length:", length, "offset:", offset, "d-after:", d+length, "dict start offset:", startOff) - } - copy(dst[d:d+length], dict.dict[startOff:]) - d += length - continue - } - - if debug { - fmt.Println("copy, length:", length, "offset:", offset, "d-after:", d+length) - } - - // Copy from an earlier sub-slice of dst to a later sub-slice. - // If no overlap, use the built-in copy: - if offset > length { - copy(dst[d:d+length], dst[d-offset:]) - d += length - continue - } - - // Unlike the built-in copy function, this byte-by-byte copy always runs - // forwards, even if the slices overlap. Conceptually, this is: - // - // d += forwardCopy(dst[d:d+length], dst[d-offset:]) - // - // We align the slices into a and b and show the compiler they are the same size. - // This allows the loop to run without bounds checks. - a := dst[d : d+length] - b := dst[d-offset:] - b = b[:len(a)] - for i := range a { - a[i] = b[i] - } - d += length - } - - // Remaining with extra checks... - for s < len(src) { - switch src[s] & 0x03 { - case tagLiteral: - x := uint32(src[s] >> 2) - switch { - case x < 60: - s++ - case x == 60: - s += 2 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - if debugErrs { - fmt.Println("src went oob") - } - return decodeErrCodeCorrupt - } - x = uint32(src[s-1]) - case x == 61: - s += 3 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - if debugErrs { - fmt.Println("src went oob") - } - return decodeErrCodeCorrupt - } - x = uint32(src[s-2]) | uint32(src[s-1])<<8 - case x == 62: - s += 4 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - if debugErrs { - fmt.Println("src went oob") - } - return decodeErrCodeCorrupt - } - x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16 - case x == 63: - s += 5 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - if debugErrs { - fmt.Println("src went oob") - } - return decodeErrCodeCorrupt - } - x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24 - } - length = int(x) + 1 - if length > len(dst)-d || length > len(src)-s || (strconv.IntSize == 32 && length <= 0) { - if debugErrs { - fmt.Println("corrupt literal: length:", length, "d-left:", len(dst)-d, "src-left:", len(src)-s) - } - return decodeErrCodeCorrupt - } - if debug { - fmt.Println("literals, length:", length, "d-after:", d+length) - } - - copy(dst[d:], src[s:s+length]) - d += length - s += length - continue - - case tagCopy1: - s += 2 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - if debugErrs { - fmt.Println("src went oob") - } - return decodeErrCodeCorrupt - } - length = int(src[s-2]) >> 2 & 0x7 - toffset := int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1])) - if toffset == 0 { - if debug { - fmt.Print("(repeat) ") - } - // keep last offset - switch length { - case 5: - s += 1 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - if debugErrs { - fmt.Println("src went oob") - } - return decodeErrCodeCorrupt - } - length = int(uint32(src[s-1])) + 4 - case 6: - s += 2 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - if debugErrs { - fmt.Println("src went oob") - } - return decodeErrCodeCorrupt - } - length = int(uint32(src[s-2])|(uint32(src[s-1])<<8)) + (1 << 8) - case 7: - s += 3 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - if debugErrs { - fmt.Println("src went oob") - } - return decodeErrCodeCorrupt - } - length = int(uint32(src[s-3])|(uint32(src[s-2])<<8)|(uint32(src[s-1])<<16)) + (1 << 16) - default: // 0-> 4 - } - } else { - offset = toffset - } - length += 4 - case tagCopy2: - s += 3 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - if debugErrs { - fmt.Println("src went oob") - } - return decodeErrCodeCorrupt - } - length = 1 + int(src[s-3])>>2 - offset = int(uint32(src[s-2]) | uint32(src[s-1])<<8) - - case tagCopy4: - s += 5 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - if debugErrs { - fmt.Println("src went oob") - } - return decodeErrCodeCorrupt - } - length = 1 + int(src[s-5])>>2 - offset = int(uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24) - } - - if offset <= 0 || length > len(dst)-d { - if debugErrs { - fmt.Println("match error; offset:", offset, "length:", length, "dst-left:", len(dst)-d) - } - return decodeErrCodeCorrupt - } - - // copy from dict - if d < offset { - if d > MaxDictSrcOffset { - if debugErrs { - fmt.Println("dict after", MaxDictSrcOffset, "d:", d, "offset:", offset, "length:", length) - } - return decodeErrCodeCorrupt - } - rOff := len(dict.dict) - (offset - d) - if debug { - fmt.Println("starting dict entry from dict offset", len(dict.dict)-rOff) - } - if rOff+length > len(dict.dict) { - if debugErrs { - fmt.Println("err: END offset", rOff+length, "bigger than dict", len(dict.dict), "dict offset:", rOff, "length:", length) - } - return decodeErrCodeCorrupt - } - if rOff < 0 { - if debugErrs { - fmt.Println("err: START offset", rOff, "less than 0", len(dict.dict), "dict offset:", rOff, "length:", length) - } - return decodeErrCodeCorrupt - } - copy(dst[d:d+length], dict.dict[rOff:]) - d += length - continue - } - - if debug { - fmt.Println("copy, length:", length, "offset:", offset, "d-after:", d+length) - } - - // Copy from an earlier sub-slice of dst to a later sub-slice. - // If no overlap, use the built-in copy: - if offset > length { - copy(dst[d:d+length], dst[d-offset:]) - d += length - continue - } - - // Unlike the built-in copy function, this byte-by-byte copy always runs - // forwards, even if the slices overlap. Conceptually, this is: - // - // d += forwardCopy(dst[d:d+length], dst[d-offset:]) - // - // We align the slices into a and b and show the compiler they are the same size. - // This allows the loop to run without bounds checks. - a := dst[d : d+length] - b := dst[d-offset:] - b = b[:len(a)] - for i := range a { - a[i] = b[i] - } - d += length - } - - if d != len(dst) { - if debugErrs { - fmt.Println("wanted length", len(dst), "got", d) - } - return decodeErrCodeCorrupt - } - return 0 -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_amd64.s b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_amd64.s deleted file mode 100644 index 9b105e03..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_amd64.s +++ /dev/null @@ -1,568 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Copyright (c) 2019 Klaus Post. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !appengine -// +build gc -// +build !noasm - -#include "textflag.h" - -#define R_TMP0 AX -#define R_TMP1 BX -#define R_LEN CX -#define R_OFF DX -#define R_SRC SI -#define R_DST DI -#define R_DBASE R8 -#define R_DLEN R9 -#define R_DEND R10 -#define R_SBASE R11 -#define R_SLEN R12 -#define R_SEND R13 -#define R_TMP2 R14 -#define R_TMP3 R15 - -// The asm code generally follows the pure Go code in decode_other.go, except -// where marked with a "!!!". - -// func decode(dst, src []byte) int -// -// All local variables fit into registers. The non-zero stack size is only to -// spill registers and push args when issuing a CALL. The register allocation: -// - R_TMP0 scratch -// - R_TMP1 scratch -// - R_LEN length or x (shared) -// - R_OFF offset -// - R_SRC &src[s] -// - R_DST &dst[d] -// + R_DBASE dst_base -// + R_DLEN dst_len -// + R_DEND dst_base + dst_len -// + R_SBASE src_base -// + R_SLEN src_len -// + R_SEND src_base + src_len -// - R_TMP2 used by doCopy -// - R_TMP3 used by doCopy -// -// The registers R_DBASE-R_SEND (marked with a "+") are set at the start of the -// function, and after a CALL returns, and are not otherwise modified. -// -// The d variable is implicitly R_DST - R_DBASE, and len(dst)-d is R_DEND - R_DST. -// The s variable is implicitly R_SRC - R_SBASE, and len(src)-s is R_SEND - R_SRC. -TEXT ·s2Decode(SB), NOSPLIT, $48-56 - // Initialize R_SRC, R_DST and R_DBASE-R_SEND. - MOVQ dst_base+0(FP), R_DBASE - MOVQ dst_len+8(FP), R_DLEN - MOVQ R_DBASE, R_DST - MOVQ R_DBASE, R_DEND - ADDQ R_DLEN, R_DEND - MOVQ src_base+24(FP), R_SBASE - MOVQ src_len+32(FP), R_SLEN - MOVQ R_SBASE, R_SRC - MOVQ R_SBASE, R_SEND - ADDQ R_SLEN, R_SEND - XORQ R_OFF, R_OFF - -loop: - // for s < len(src) - CMPQ R_SRC, R_SEND - JEQ end - - // R_LEN = uint32(src[s]) - // - // switch src[s] & 0x03 - MOVBLZX (R_SRC), R_LEN - MOVL R_LEN, R_TMP1 - ANDL $3, R_TMP1 - CMPL R_TMP1, $1 - JAE tagCopy - - // ---------------------------------------- - // The code below handles literal tags. - - // case tagLiteral: - // x := uint32(src[s] >> 2) - // switch - SHRL $2, R_LEN - CMPL R_LEN, $60 - JAE tagLit60Plus - - // case x < 60: - // s++ - INCQ R_SRC - -doLit: - // This is the end of the inner "switch", when we have a literal tag. - // - // We assume that R_LEN == x and x fits in a uint32, where x is the variable - // used in the pure Go decode_other.go code. - - // length = int(x) + 1 - // - // Unlike the pure Go code, we don't need to check if length <= 0 because - // R_LEN can hold 64 bits, so the increment cannot overflow. - INCQ R_LEN - - // Prepare to check if copying length bytes will run past the end of dst or - // src. - // - // R_TMP0 = len(dst) - d - // R_TMP1 = len(src) - s - MOVQ R_DEND, R_TMP0 - SUBQ R_DST, R_TMP0 - MOVQ R_SEND, R_TMP1 - SUBQ R_SRC, R_TMP1 - - // !!! Try a faster technique for short (16 or fewer bytes) copies. - // - // if length > 16 || len(dst)-d < 16 || len(src)-s < 16 { - // goto callMemmove // Fall back on calling runtime·memmove. - // } - // - // The C++ snappy code calls this TryFastAppend. It also checks len(src)-s - // against 21 instead of 16, because it cannot assume that all of its input - // is contiguous in memory and so it needs to leave enough source bytes to - // read the next tag without refilling buffers, but Go's Decode assumes - // contiguousness (the src argument is a []byte). - CMPQ R_LEN, $16 - JGT callMemmove - CMPQ R_TMP0, $16 - JLT callMemmove - CMPQ R_TMP1, $16 - JLT callMemmove - - // !!! Implement the copy from src to dst as a 16-byte load and store. - // (Decode's documentation says that dst and src must not overlap.) - // - // This always copies 16 bytes, instead of only length bytes, but that's - // OK. If the input is a valid Snappy encoding then subsequent iterations - // will fix up the overrun. Otherwise, Decode returns a nil []byte (and a - // non-nil error), so the overrun will be ignored. - // - // Note that on amd64, it is legal and cheap to issue unaligned 8-byte or - // 16-byte loads and stores. This technique probably wouldn't be as - // effective on architectures that are fussier about alignment. - MOVOU 0(R_SRC), X0 - MOVOU X0, 0(R_DST) - - // d += length - // s += length - ADDQ R_LEN, R_DST - ADDQ R_LEN, R_SRC - JMP loop - -callMemmove: - // if length > len(dst)-d || length > len(src)-s { etc } - CMPQ R_LEN, R_TMP0 - JGT errCorrupt - CMPQ R_LEN, R_TMP1 - JGT errCorrupt - - // copy(dst[d:], src[s:s+length]) - // - // This means calling runtime·memmove(&dst[d], &src[s], length), so we push - // R_DST, R_SRC and R_LEN as arguments. Coincidentally, we also need to spill those - // three registers to the stack, to save local variables across the CALL. - MOVQ R_DST, 0(SP) - MOVQ R_SRC, 8(SP) - MOVQ R_LEN, 16(SP) - MOVQ R_DST, 24(SP) - MOVQ R_SRC, 32(SP) - MOVQ R_LEN, 40(SP) - MOVQ R_OFF, 48(SP) - CALL runtime·memmove(SB) - - // Restore local variables: unspill registers from the stack and - // re-calculate R_DBASE-R_SEND. - MOVQ 24(SP), R_DST - MOVQ 32(SP), R_SRC - MOVQ 40(SP), R_LEN - MOVQ 48(SP), R_OFF - MOVQ dst_base+0(FP), R_DBASE - MOVQ dst_len+8(FP), R_DLEN - MOVQ R_DBASE, R_DEND - ADDQ R_DLEN, R_DEND - MOVQ src_base+24(FP), R_SBASE - MOVQ src_len+32(FP), R_SLEN - MOVQ R_SBASE, R_SEND - ADDQ R_SLEN, R_SEND - - // d += length - // s += length - ADDQ R_LEN, R_DST - ADDQ R_LEN, R_SRC - JMP loop - -tagLit60Plus: - // !!! This fragment does the - // - // s += x - 58; if uint(s) > uint(len(src)) { etc } - // - // checks. In the asm version, we code it once instead of once per switch case. - ADDQ R_LEN, R_SRC - SUBQ $58, R_SRC - CMPQ R_SRC, R_SEND - JA errCorrupt - - // case x == 60: - CMPL R_LEN, $61 - JEQ tagLit61 - JA tagLit62Plus - - // x = uint32(src[s-1]) - MOVBLZX -1(R_SRC), R_LEN - JMP doLit - -tagLit61: - // case x == 61: - // x = uint32(src[s-2]) | uint32(src[s-1])<<8 - MOVWLZX -2(R_SRC), R_LEN - JMP doLit - -tagLit62Plus: - CMPL R_LEN, $62 - JA tagLit63 - - // case x == 62: - // x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16 - // We read one byte, safe to read one back, since we are just reading tag. - // x = binary.LittleEndian.Uint32(src[s-1:]) >> 8 - MOVL -4(R_SRC), R_LEN - SHRL $8, R_LEN - JMP doLit - -tagLit63: - // case x == 63: - // x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24 - MOVL -4(R_SRC), R_LEN - JMP doLit - -// The code above handles literal tags. -// ---------------------------------------- -// The code below handles copy tags. - -tagCopy4: - // case tagCopy4: - // s += 5 - ADDQ $5, R_SRC - - // if uint(s) > uint(len(src)) { etc } - CMPQ R_SRC, R_SEND - JA errCorrupt - - // length = 1 + int(src[s-5])>>2 - SHRQ $2, R_LEN - INCQ R_LEN - - // offset = int(uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24) - MOVLQZX -4(R_SRC), R_OFF - JMP doCopy - -tagCopy2: - // case tagCopy2: - // s += 3 - ADDQ $3, R_SRC - - // if uint(s) > uint(len(src)) { etc } - CMPQ R_SRC, R_SEND - JA errCorrupt - - // length = 1 + int(src[s-3])>>2 - SHRQ $2, R_LEN - INCQ R_LEN - - // offset = int(uint32(src[s-2]) | uint32(src[s-1])<<8) - MOVWQZX -2(R_SRC), R_OFF - JMP doCopy - -tagCopy: - // We have a copy tag. We assume that: - // - R_TMP1 == src[s] & 0x03 - // - R_LEN == src[s] - CMPQ R_TMP1, $2 - JEQ tagCopy2 - JA tagCopy4 - - // case tagCopy1: - // s += 2 - ADDQ $2, R_SRC - - // if uint(s) > uint(len(src)) { etc } - CMPQ R_SRC, R_SEND - JA errCorrupt - - // offset = int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1])) - // length = 4 + int(src[s-2])>>2&0x7 - MOVBQZX -1(R_SRC), R_TMP1 - MOVQ R_LEN, R_TMP0 - SHRQ $2, R_LEN - ANDQ $0xe0, R_TMP0 - ANDQ $7, R_LEN - SHLQ $3, R_TMP0 - ADDQ $4, R_LEN - ORQ R_TMP1, R_TMP0 - - // check if repeat code, ZF set by ORQ. - JZ repeatCode - - // This is a regular copy, transfer our temporary value to R_OFF (length) - MOVQ R_TMP0, R_OFF - JMP doCopy - -// This is a repeat code. -repeatCode: - // If length < 9, reuse last offset, with the length already calculated. - CMPQ R_LEN, $9 - JL doCopyRepeat - - // Read additional bytes for length. - JE repeatLen1 - - // Rare, so the extra branch shouldn't hurt too much. - CMPQ R_LEN, $10 - JE repeatLen2 - JMP repeatLen3 - -// Read repeat lengths. -repeatLen1: - // s ++ - ADDQ $1, R_SRC - - // if uint(s) > uint(len(src)) { etc } - CMPQ R_SRC, R_SEND - JA errCorrupt - - // length = src[s-1] + 8 - MOVBQZX -1(R_SRC), R_LEN - ADDL $8, R_LEN - JMP doCopyRepeat - -repeatLen2: - // s +=2 - ADDQ $2, R_SRC - - // if uint(s) > uint(len(src)) { etc } - CMPQ R_SRC, R_SEND - JA errCorrupt - - // length = uint32(src[s-2]) | (uint32(src[s-1])<<8) + (1 << 8) - MOVWQZX -2(R_SRC), R_LEN - ADDL $260, R_LEN - JMP doCopyRepeat - -repeatLen3: - // s +=3 - ADDQ $3, R_SRC - - // if uint(s) > uint(len(src)) { etc } - CMPQ R_SRC, R_SEND - JA errCorrupt - - // length = uint32(src[s-3]) | (uint32(src[s-2])<<8) | (uint32(src[s-1])<<16) + (1 << 16) - // Read one byte further back (just part of the tag, shifted out) - MOVL -4(R_SRC), R_LEN - SHRL $8, R_LEN - ADDL $65540, R_LEN - JMP doCopyRepeat - -doCopy: - // This is the end of the outer "switch", when we have a copy tag. - // - // We assume that: - // - R_LEN == length && R_LEN > 0 - // - R_OFF == offset - - // if d < offset { etc } - MOVQ R_DST, R_TMP1 - SUBQ R_DBASE, R_TMP1 - CMPQ R_TMP1, R_OFF - JLT errCorrupt - - // Repeat values can skip the test above, since any offset > 0 will be in dst. -doCopyRepeat: - // if offset <= 0 { etc } - CMPQ R_OFF, $0 - JLE errCorrupt - - // if length > len(dst)-d { etc } - MOVQ R_DEND, R_TMP1 - SUBQ R_DST, R_TMP1 - CMPQ R_LEN, R_TMP1 - JGT errCorrupt - - // forwardCopy(dst[d:d+length], dst[d-offset:]); d += length - // - // Set: - // - R_TMP2 = len(dst)-d - // - R_TMP3 = &dst[d-offset] - MOVQ R_DEND, R_TMP2 - SUBQ R_DST, R_TMP2 - MOVQ R_DST, R_TMP3 - SUBQ R_OFF, R_TMP3 - - // !!! Try a faster technique for short (16 or fewer bytes) forward copies. - // - // First, try using two 8-byte load/stores, similar to the doLit technique - // above. Even if dst[d:d+length] and dst[d-offset:] can overlap, this is - // still OK if offset >= 8. Note that this has to be two 8-byte load/stores - // and not one 16-byte load/store, and the first store has to be before the - // second load, due to the overlap if offset is in the range [8, 16). - // - // if length > 16 || offset < 8 || len(dst)-d < 16 { - // goto slowForwardCopy - // } - // copy 16 bytes - // d += length - CMPQ R_LEN, $16 - JGT slowForwardCopy - CMPQ R_OFF, $8 - JLT slowForwardCopy - CMPQ R_TMP2, $16 - JLT slowForwardCopy - MOVQ 0(R_TMP3), R_TMP0 - MOVQ R_TMP0, 0(R_DST) - MOVQ 8(R_TMP3), R_TMP1 - MOVQ R_TMP1, 8(R_DST) - ADDQ R_LEN, R_DST - JMP loop - -slowForwardCopy: - // !!! If the forward copy is longer than 16 bytes, or if offset < 8, we - // can still try 8-byte load stores, provided we can overrun up to 10 extra - // bytes. As above, the overrun will be fixed up by subsequent iterations - // of the outermost loop. - // - // The C++ snappy code calls this technique IncrementalCopyFastPath. Its - // commentary says: - // - // ---- - // - // The main part of this loop is a simple copy of eight bytes at a time - // until we've copied (at least) the requested amount of bytes. However, - // if d and d-offset are less than eight bytes apart (indicating a - // repeating pattern of length < 8), we first need to expand the pattern in - // order to get the correct results. For instance, if the buffer looks like - // this, with the eight-byte and patterns marked as - // intervals: - // - // abxxxxxxxxxxxx - // [------] d-offset - // [------] d - // - // a single eight-byte copy from to will repeat the pattern - // once, after which we can move two bytes without moving : - // - // ababxxxxxxxxxx - // [------] d-offset - // [------] d - // - // and repeat the exercise until the two no longer overlap. - // - // This allows us to do very well in the special case of one single byte - // repeated many times, without taking a big hit for more general cases. - // - // The worst case of extra writing past the end of the match occurs when - // offset == 1 and length == 1; the last copy will read from byte positions - // [0..7] and write to [4..11], whereas it was only supposed to write to - // position 1. Thus, ten excess bytes. - // - // ---- - // - // That "10 byte overrun" worst case is confirmed by Go's - // TestSlowForwardCopyOverrun, which also tests the fixUpSlowForwardCopy - // and finishSlowForwardCopy algorithm. - // - // if length > len(dst)-d-10 { - // goto verySlowForwardCopy - // } - SUBQ $10, R_TMP2 - CMPQ R_LEN, R_TMP2 - JGT verySlowForwardCopy - - // We want to keep the offset, so we use R_TMP2 from here. - MOVQ R_OFF, R_TMP2 - -makeOffsetAtLeast8: - // !!! As above, expand the pattern so that offset >= 8 and we can use - // 8-byte load/stores. - // - // for offset < 8 { - // copy 8 bytes from dst[d-offset:] to dst[d:] - // length -= offset - // d += offset - // offset += offset - // // The two previous lines together means that d-offset, and therefore - // // R_TMP3, is unchanged. - // } - CMPQ R_TMP2, $8 - JGE fixUpSlowForwardCopy - MOVQ (R_TMP3), R_TMP1 - MOVQ R_TMP1, (R_DST) - SUBQ R_TMP2, R_LEN - ADDQ R_TMP2, R_DST - ADDQ R_TMP2, R_TMP2 - JMP makeOffsetAtLeast8 - -fixUpSlowForwardCopy: - // !!! Add length (which might be negative now) to d (implied by R_DST being - // &dst[d]) so that d ends up at the right place when we jump back to the - // top of the loop. Before we do that, though, we save R_DST to R_TMP0 so that, if - // length is positive, copying the remaining length bytes will write to the - // right place. - MOVQ R_DST, R_TMP0 - ADDQ R_LEN, R_DST - -finishSlowForwardCopy: - // !!! Repeat 8-byte load/stores until length <= 0. Ending with a negative - // length means that we overrun, but as above, that will be fixed up by - // subsequent iterations of the outermost loop. - CMPQ R_LEN, $0 - JLE loop - MOVQ (R_TMP3), R_TMP1 - MOVQ R_TMP1, (R_TMP0) - ADDQ $8, R_TMP3 - ADDQ $8, R_TMP0 - SUBQ $8, R_LEN - JMP finishSlowForwardCopy - -verySlowForwardCopy: - // verySlowForwardCopy is a simple implementation of forward copy. In C - // parlance, this is a do/while loop instead of a while loop, since we know - // that length > 0. In Go syntax: - // - // for { - // dst[d] = dst[d - offset] - // d++ - // length-- - // if length == 0 { - // break - // } - // } - MOVB (R_TMP3), R_TMP1 - MOVB R_TMP1, (R_DST) - INCQ R_TMP3 - INCQ R_DST - DECQ R_LEN - JNZ verySlowForwardCopy - JMP loop - -// The code above handles copy tags. -// ---------------------------------------- - -end: - // This is the end of the "for s < len(src)". - // - // if d != len(dst) { etc } - CMPQ R_DST, R_DEND - JNE errCorrupt - - // return 0 - MOVQ $0, ret+48(FP) - RET - -errCorrupt: - // return decodeErrCodeCorrupt - MOVQ $1, ret+48(FP) - RET diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_arm64.s b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_arm64.s deleted file mode 100644 index 78e463f3..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_arm64.s +++ /dev/null @@ -1,574 +0,0 @@ -// Copyright 2020 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !appengine -// +build gc -// +build !noasm - -#include "textflag.h" - -#define R_TMP0 R2 -#define R_TMP1 R3 -#define R_LEN R4 -#define R_OFF R5 -#define R_SRC R6 -#define R_DST R7 -#define R_DBASE R8 -#define R_DLEN R9 -#define R_DEND R10 -#define R_SBASE R11 -#define R_SLEN R12 -#define R_SEND R13 -#define R_TMP2 R14 -#define R_TMP3 R15 - -// TEST_SRC will check if R_SRC is <= SRC_END -#define TEST_SRC() \ - CMP R_SEND, R_SRC \ - BGT errCorrupt - -// MOVD R_SRC, R_TMP1 -// SUB R_SBASE, R_TMP1, R_TMP1 -// CMP R_SLEN, R_TMP1 -// BGT errCorrupt - -// The asm code generally follows the pure Go code in decode_other.go, except -// where marked with a "!!!". - -// func decode(dst, src []byte) int -// -// All local variables fit into registers. The non-zero stack size is only to -// spill registers and push args when issuing a CALL. The register allocation: -// - R_TMP0 scratch -// - R_TMP1 scratch -// - R_LEN length or x -// - R_OFF offset -// - R_SRC &src[s] -// - R_DST &dst[d] -// + R_DBASE dst_base -// + R_DLEN dst_len -// + R_DEND dst_base + dst_len -// + R_SBASE src_base -// + R_SLEN src_len -// + R_SEND src_base + src_len -// - R_TMP2 used by doCopy -// - R_TMP3 used by doCopy -// -// The registers R_DBASE-R_SEND (marked with a "+") are set at the start of the -// function, and after a CALL returns, and are not otherwise modified. -// -// The d variable is implicitly R_DST - R_DBASE, and len(dst)-d is R_DEND - R_DST. -// The s variable is implicitly R_SRC - R_SBASE, and len(src)-s is R_SEND - R_SRC. -TEXT ·s2Decode(SB), NOSPLIT, $56-56 - // Initialize R_SRC, R_DST and R_DBASE-R_SEND. - MOVD dst_base+0(FP), R_DBASE - MOVD dst_len+8(FP), R_DLEN - MOVD R_DBASE, R_DST - MOVD R_DBASE, R_DEND - ADD R_DLEN, R_DEND, R_DEND - MOVD src_base+24(FP), R_SBASE - MOVD src_len+32(FP), R_SLEN - MOVD R_SBASE, R_SRC - MOVD R_SBASE, R_SEND - ADD R_SLEN, R_SEND, R_SEND - MOVD $0, R_OFF - -loop: - // for s < len(src) - CMP R_SEND, R_SRC - BEQ end - - // R_LEN = uint32(src[s]) - // - // switch src[s] & 0x03 - MOVBU (R_SRC), R_LEN - MOVW R_LEN, R_TMP1 - ANDW $3, R_TMP1 - MOVW $1, R1 - CMPW R1, R_TMP1 - BGE tagCopy - - // ---------------------------------------- - // The code below handles literal tags. - - // case tagLiteral: - // x := uint32(src[s] >> 2) - // switch - MOVW $60, R1 - LSRW $2, R_LEN, R_LEN - CMPW R_LEN, R1 - BLS tagLit60Plus - - // case x < 60: - // s++ - ADD $1, R_SRC, R_SRC - -doLit: - // This is the end of the inner "switch", when we have a literal tag. - // - // We assume that R_LEN == x and x fits in a uint32, where x is the variable - // used in the pure Go decode_other.go code. - - // length = int(x) + 1 - // - // Unlike the pure Go code, we don't need to check if length <= 0 because - // R_LEN can hold 64 bits, so the increment cannot overflow. - ADD $1, R_LEN, R_LEN - - // Prepare to check if copying length bytes will run past the end of dst or - // src. - // - // R_TMP0 = len(dst) - d - // R_TMP1 = len(src) - s - MOVD R_DEND, R_TMP0 - SUB R_DST, R_TMP0, R_TMP0 - MOVD R_SEND, R_TMP1 - SUB R_SRC, R_TMP1, R_TMP1 - - // !!! Try a faster technique for short (16 or fewer bytes) copies. - // - // if length > 16 || len(dst)-d < 16 || len(src)-s < 16 { - // goto callMemmove // Fall back on calling runtime·memmove. - // } - // - // The C++ snappy code calls this TryFastAppend. It also checks len(src)-s - // against 21 instead of 16, because it cannot assume that all of its input - // is contiguous in memory and so it needs to leave enough source bytes to - // read the next tag without refilling buffers, but Go's Decode assumes - // contiguousness (the src argument is a []byte). - CMP $16, R_LEN - BGT callMemmove - CMP $16, R_TMP0 - BLT callMemmove - CMP $16, R_TMP1 - BLT callMemmove - - // !!! Implement the copy from src to dst as a 16-byte load and store. - // (Decode's documentation says that dst and src must not overlap.) - // - // This always copies 16 bytes, instead of only length bytes, but that's - // OK. If the input is a valid Snappy encoding then subsequent iterations - // will fix up the overrun. Otherwise, Decode returns a nil []byte (and a - // non-nil error), so the overrun will be ignored. - // - // Note that on arm64, it is legal and cheap to issue unaligned 8-byte or - // 16-byte loads and stores. This technique probably wouldn't be as - // effective on architectures that are fussier about alignment. - LDP 0(R_SRC), (R_TMP2, R_TMP3) - STP (R_TMP2, R_TMP3), 0(R_DST) - - // d += length - // s += length - ADD R_LEN, R_DST, R_DST - ADD R_LEN, R_SRC, R_SRC - B loop - -callMemmove: - // if length > len(dst)-d || length > len(src)-s { etc } - CMP R_TMP0, R_LEN - BGT errCorrupt - CMP R_TMP1, R_LEN - BGT errCorrupt - - // copy(dst[d:], src[s:s+length]) - // - // This means calling runtime·memmove(&dst[d], &src[s], length), so we push - // R_DST, R_SRC and R_LEN as arguments. Coincidentally, we also need to spill those - // three registers to the stack, to save local variables across the CALL. - MOVD R_DST, 8(RSP) - MOVD R_SRC, 16(RSP) - MOVD R_LEN, 24(RSP) - MOVD R_DST, 32(RSP) - MOVD R_SRC, 40(RSP) - MOVD R_LEN, 48(RSP) - MOVD R_OFF, 56(RSP) - CALL runtime·memmove(SB) - - // Restore local variables: unspill registers from the stack and - // re-calculate R_DBASE-R_SEND. - MOVD 32(RSP), R_DST - MOVD 40(RSP), R_SRC - MOVD 48(RSP), R_LEN - MOVD 56(RSP), R_OFF - MOVD dst_base+0(FP), R_DBASE - MOVD dst_len+8(FP), R_DLEN - MOVD R_DBASE, R_DEND - ADD R_DLEN, R_DEND, R_DEND - MOVD src_base+24(FP), R_SBASE - MOVD src_len+32(FP), R_SLEN - MOVD R_SBASE, R_SEND - ADD R_SLEN, R_SEND, R_SEND - - // d += length - // s += length - ADD R_LEN, R_DST, R_DST - ADD R_LEN, R_SRC, R_SRC - B loop - -tagLit60Plus: - // !!! This fragment does the - // - // s += x - 58; if uint(s) > uint(len(src)) { etc } - // - // checks. In the asm version, we code it once instead of once per switch case. - ADD R_LEN, R_SRC, R_SRC - SUB $58, R_SRC, R_SRC - TEST_SRC() - - // case x == 60: - MOVW $61, R1 - CMPW R1, R_LEN - BEQ tagLit61 - BGT tagLit62Plus - - // x = uint32(src[s-1]) - MOVBU -1(R_SRC), R_LEN - B doLit - -tagLit61: - // case x == 61: - // x = uint32(src[s-2]) | uint32(src[s-1])<<8 - MOVHU -2(R_SRC), R_LEN - B doLit - -tagLit62Plus: - CMPW $62, R_LEN - BHI tagLit63 - - // case x == 62: - // x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16 - MOVHU -3(R_SRC), R_LEN - MOVBU -1(R_SRC), R_TMP1 - ORR R_TMP1<<16, R_LEN - B doLit - -tagLit63: - // case x == 63: - // x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24 - MOVWU -4(R_SRC), R_LEN - B doLit - - // The code above handles literal tags. - // ---------------------------------------- - // The code below handles copy tags. - -tagCopy4: - // case tagCopy4: - // s += 5 - ADD $5, R_SRC, R_SRC - - // if uint(s) > uint(len(src)) { etc } - MOVD R_SRC, R_TMP1 - SUB R_SBASE, R_TMP1, R_TMP1 - CMP R_SLEN, R_TMP1 - BGT errCorrupt - - // length = 1 + int(src[s-5])>>2 - MOVD $1, R1 - ADD R_LEN>>2, R1, R_LEN - - // offset = int(uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24) - MOVWU -4(R_SRC), R_OFF - B doCopy - -tagCopy2: - // case tagCopy2: - // s += 3 - ADD $3, R_SRC, R_SRC - - // if uint(s) > uint(len(src)) { etc } - TEST_SRC() - - // length = 1 + int(src[s-3])>>2 - MOVD $1, R1 - ADD R_LEN>>2, R1, R_LEN - - // offset = int(uint32(src[s-2]) | uint32(src[s-1])<<8) - MOVHU -2(R_SRC), R_OFF - B doCopy - -tagCopy: - // We have a copy tag. We assume that: - // - R_TMP1 == src[s] & 0x03 - // - R_LEN == src[s] - CMP $2, R_TMP1 - BEQ tagCopy2 - BGT tagCopy4 - - // case tagCopy1: - // s += 2 - ADD $2, R_SRC, R_SRC - - // if uint(s) > uint(len(src)) { etc } - TEST_SRC() - - // offset = int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1])) - // Calculate offset in R_TMP0 in case it is a repeat. - MOVD R_LEN, R_TMP0 - AND $0xe0, R_TMP0 - MOVBU -1(R_SRC), R_TMP1 - ORR R_TMP0<<3, R_TMP1, R_TMP0 - - // length = 4 + int(src[s-2])>>2&0x7 - MOVD $7, R1 - AND R_LEN>>2, R1, R_LEN - ADD $4, R_LEN, R_LEN - - // check if repeat code with offset 0. - CMP $0, R_TMP0 - BEQ repeatCode - - // This is a regular copy, transfer our temporary value to R_OFF (offset) - MOVD R_TMP0, R_OFF - B doCopy - - // This is a repeat code. -repeatCode: - // If length < 9, reuse last offset, with the length already calculated. - CMP $9, R_LEN - BLT doCopyRepeat - BEQ repeatLen1 - CMP $10, R_LEN - BEQ repeatLen2 - -repeatLen3: - // s +=3 - ADD $3, R_SRC, R_SRC - - // if uint(s) > uint(len(src)) { etc } - TEST_SRC() - - // length = uint32(src[s-3]) | (uint32(src[s-2])<<8) | (uint32(src[s-1])<<16) + 65540 - MOVBU -1(R_SRC), R_TMP0 - MOVHU -3(R_SRC), R_LEN - ORR R_TMP0<<16, R_LEN, R_LEN - ADD $65540, R_LEN, R_LEN - B doCopyRepeat - -repeatLen2: - // s +=2 - ADD $2, R_SRC, R_SRC - - // if uint(s) > uint(len(src)) { etc } - TEST_SRC() - - // length = uint32(src[s-2]) | (uint32(src[s-1])<<8) + 260 - MOVHU -2(R_SRC), R_LEN - ADD $260, R_LEN, R_LEN - B doCopyRepeat - -repeatLen1: - // s +=1 - ADD $1, R_SRC, R_SRC - - // if uint(s) > uint(len(src)) { etc } - TEST_SRC() - - // length = src[s-1] + 8 - MOVBU -1(R_SRC), R_LEN - ADD $8, R_LEN, R_LEN - B doCopyRepeat - -doCopy: - // This is the end of the outer "switch", when we have a copy tag. - // - // We assume that: - // - R_LEN == length && R_LEN > 0 - // - R_OFF == offset - - // if d < offset { etc } - MOVD R_DST, R_TMP1 - SUB R_DBASE, R_TMP1, R_TMP1 - CMP R_OFF, R_TMP1 - BLT errCorrupt - - // Repeat values can skip the test above, since any offset > 0 will be in dst. -doCopyRepeat: - - // if offset <= 0 { etc } - CMP $0, R_OFF - BLE errCorrupt - - // if length > len(dst)-d { etc } - MOVD R_DEND, R_TMP1 - SUB R_DST, R_TMP1, R_TMP1 - CMP R_TMP1, R_LEN - BGT errCorrupt - - // forwardCopy(dst[d:d+length], dst[d-offset:]); d += length - // - // Set: - // - R_TMP2 = len(dst)-d - // - R_TMP3 = &dst[d-offset] - MOVD R_DEND, R_TMP2 - SUB R_DST, R_TMP2, R_TMP2 - MOVD R_DST, R_TMP3 - SUB R_OFF, R_TMP3, R_TMP3 - - // !!! Try a faster technique for short (16 or fewer bytes) forward copies. - // - // First, try using two 8-byte load/stores, similar to the doLit technique - // above. Even if dst[d:d+length] and dst[d-offset:] can overlap, this is - // still OK if offset >= 8. Note that this has to be two 8-byte load/stores - // and not one 16-byte load/store, and the first store has to be before the - // second load, due to the overlap if offset is in the range [8, 16). - // - // if length > 16 || offset < 8 || len(dst)-d < 16 { - // goto slowForwardCopy - // } - // copy 16 bytes - // d += length - CMP $16, R_LEN - BGT slowForwardCopy - CMP $8, R_OFF - BLT slowForwardCopy - CMP $16, R_TMP2 - BLT slowForwardCopy - MOVD 0(R_TMP3), R_TMP0 - MOVD R_TMP0, 0(R_DST) - MOVD 8(R_TMP3), R_TMP1 - MOVD R_TMP1, 8(R_DST) - ADD R_LEN, R_DST, R_DST - B loop - -slowForwardCopy: - // !!! If the forward copy is longer than 16 bytes, or if offset < 8, we - // can still try 8-byte load stores, provided we can overrun up to 10 extra - // bytes. As above, the overrun will be fixed up by subsequent iterations - // of the outermost loop. - // - // The C++ snappy code calls this technique IncrementalCopyFastPath. Its - // commentary says: - // - // ---- - // - // The main part of this loop is a simple copy of eight bytes at a time - // until we've copied (at least) the requested amount of bytes. However, - // if d and d-offset are less than eight bytes apart (indicating a - // repeating pattern of length < 8), we first need to expand the pattern in - // order to get the correct results. For instance, if the buffer looks like - // this, with the eight-byte and patterns marked as - // intervals: - // - // abxxxxxxxxxxxx - // [------] d-offset - // [------] d - // - // a single eight-byte copy from to will repeat the pattern - // once, after which we can move two bytes without moving : - // - // ababxxxxxxxxxx - // [------] d-offset - // [------] d - // - // and repeat the exercise until the two no longer overlap. - // - // This allows us to do very well in the special case of one single byte - // repeated many times, without taking a big hit for more general cases. - // - // The worst case of extra writing past the end of the match occurs when - // offset == 1 and length == 1; the last copy will read from byte positions - // [0..7] and write to [4..11], whereas it was only supposed to write to - // position 1. Thus, ten excess bytes. - // - // ---- - // - // That "10 byte overrun" worst case is confirmed by Go's - // TestSlowForwardCopyOverrun, which also tests the fixUpSlowForwardCopy - // and finishSlowForwardCopy algorithm. - // - // if length > len(dst)-d-10 { - // goto verySlowForwardCopy - // } - SUB $10, R_TMP2, R_TMP2 - CMP R_TMP2, R_LEN - BGT verySlowForwardCopy - - // We want to keep the offset, so we use R_TMP2 from here. - MOVD R_OFF, R_TMP2 - -makeOffsetAtLeast8: - // !!! As above, expand the pattern so that offset >= 8 and we can use - // 8-byte load/stores. - // - // for offset < 8 { - // copy 8 bytes from dst[d-offset:] to dst[d:] - // length -= offset - // d += offset - // offset += offset - // // The two previous lines together means that d-offset, and therefore - // // R_TMP3, is unchanged. - // } - CMP $8, R_TMP2 - BGE fixUpSlowForwardCopy - MOVD (R_TMP3), R_TMP1 - MOVD R_TMP1, (R_DST) - SUB R_TMP2, R_LEN, R_LEN - ADD R_TMP2, R_DST, R_DST - ADD R_TMP2, R_TMP2, R_TMP2 - B makeOffsetAtLeast8 - -fixUpSlowForwardCopy: - // !!! Add length (which might be negative now) to d (implied by R_DST being - // &dst[d]) so that d ends up at the right place when we jump back to the - // top of the loop. Before we do that, though, we save R_DST to R_TMP0 so that, if - // length is positive, copying the remaining length bytes will write to the - // right place. - MOVD R_DST, R_TMP0 - ADD R_LEN, R_DST, R_DST - -finishSlowForwardCopy: - // !!! Repeat 8-byte load/stores until length <= 0. Ending with a negative - // length means that we overrun, but as above, that will be fixed up by - // subsequent iterations of the outermost loop. - MOVD $0, R1 - CMP R1, R_LEN - BLE loop - MOVD (R_TMP3), R_TMP1 - MOVD R_TMP1, (R_TMP0) - ADD $8, R_TMP3, R_TMP3 - ADD $8, R_TMP0, R_TMP0 - SUB $8, R_LEN, R_LEN - B finishSlowForwardCopy - -verySlowForwardCopy: - // verySlowForwardCopy is a simple implementation of forward copy. In C - // parlance, this is a do/while loop instead of a while loop, since we know - // that length > 0. In Go syntax: - // - // for { - // dst[d] = dst[d - offset] - // d++ - // length-- - // if length == 0 { - // break - // } - // } - MOVB (R_TMP3), R_TMP1 - MOVB R_TMP1, (R_DST) - ADD $1, R_TMP3, R_TMP3 - ADD $1, R_DST, R_DST - SUB $1, R_LEN, R_LEN - CBNZ R_LEN, verySlowForwardCopy - B loop - - // The code above handles copy tags. - // ---------------------------------------- - -end: - // This is the end of the "for s < len(src)". - // - // if d != len(dst) { etc } - CMP R_DEND, R_DST - BNE errCorrupt - - // return 0 - MOVD $0, ret+48(FP) - RET - -errCorrupt: - // return decodeErrCodeCorrupt - MOVD $1, R_TMP0 - MOVD R_TMP0, ret+48(FP) - RET diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_asm.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_asm.go deleted file mode 100644 index cb3576ed..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_asm.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2016 The Snappy-Go Authors. All rights reserved. -// Copyright (c) 2019 Klaus Post. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build (amd64 || arm64) && !appengine && gc && !noasm -// +build amd64 arm64 -// +build !appengine -// +build gc -// +build !noasm - -package s2 - -// decode has the same semantics as in decode_other.go. -// -//go:noescape -func s2Decode(dst, src []byte) int diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_other.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_other.go deleted file mode 100644 index c99d40b6..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/decode_other.go +++ /dev/null @@ -1,288 +0,0 @@ -// Copyright 2016 The Snappy-Go Authors. All rights reserved. -// Copyright (c) 2019 Klaus Post. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build (!amd64 && !arm64) || appengine || !gc || noasm -// +build !amd64,!arm64 appengine !gc noasm - -package s2 - -import ( - "fmt" - "strconv" - - "github.com/klauspost/compress/internal/le" -) - -// decode writes the decoding of src to dst. It assumes that the varint-encoded -// length of the decompressed bytes has already been read, and that len(dst) -// equals that length. -// -// It returns 0 on success or a decodeErrCodeXxx error code on failure. -func s2Decode(dst, src []byte) int { - const debug = false - if debug { - fmt.Println("Starting decode, dst len:", len(dst)) - } - var d, s, length int - offset := 0 - - // As long as we can read at least 5 bytes... - for s < len(src)-5 { - // Removing bounds checks is SLOWER, when if doing - // in := src[s:s+5] - // Checked on Go 1.18 - switch src[s] & 0x03 { - case tagLiteral: - x := uint32(src[s] >> 2) - switch { - case x < 60: - s++ - case x == 60: - x = uint32(src[s+1]) - s += 2 - case x == 61: - x = uint32(le.Load16(src, s+1)) - s += 3 - case x == 62: - // Load as 32 bit and shift down. - x = le.Load32(src, s) - x >>= 8 - s += 4 - case x == 63: - x = le.Load32(src, s+1) - s += 5 - } - length = int(x) + 1 - if length > len(dst)-d || length > len(src)-s || (strconv.IntSize == 32 && length <= 0) { - if debug { - fmt.Println("corrupt: lit size", length) - } - return decodeErrCodeCorrupt - } - if debug { - fmt.Println("literals, length:", length, "d-after:", d+length) - } - - copy(dst[d:], src[s:s+length]) - d += length - s += length - continue - - case tagCopy1: - s += 2 - toffset := int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1])) - length = int(src[s-2]) >> 2 & 0x7 - if toffset == 0 { - if debug { - fmt.Print("(repeat) ") - } - // keep last offset - switch length { - case 5: - length = int(src[s]) + 4 - s += 1 - case 6: - length = int(le.Load16(src, s)) + 1<<8 - s += 2 - case 7: - in := src[s : s+3] - length = int((uint32(in[2])<<16)|(uint32(in[1])<<8)|uint32(in[0])) + (1 << 16) - s += 3 - default: // 0-> 4 - } - } else { - offset = toffset - } - length += 4 - case tagCopy2: - offset = int(le.Load16(src, s+1)) - length = 1 + int(src[s])>>2 - s += 3 - - case tagCopy4: - offset = int(le.Load32(src, s+1)) - length = 1 + int(src[s])>>2 - s += 5 - } - - if offset <= 0 || d < offset || length > len(dst)-d { - if debug { - fmt.Println("corrupt: match, length", length, "offset:", offset, "dst avail:", len(dst)-d, "dst pos:", d) - } - - return decodeErrCodeCorrupt - } - - if debug { - fmt.Println("copy, length:", length, "offset:", offset, "d-after:", d+length) - } - - // Copy from an earlier sub-slice of dst to a later sub-slice. - // If no overlap, use the built-in copy: - if offset > length { - copy(dst[d:d+length], dst[d-offset:]) - d += length - continue - } - - // Unlike the built-in copy function, this byte-by-byte copy always runs - // forwards, even if the slices overlap. Conceptually, this is: - // - // d += forwardCopy(dst[d:d+length], dst[d-offset:]) - // - // We align the slices into a and b and show the compiler they are the same size. - // This allows the loop to run without bounds checks. - a := dst[d : d+length] - b := dst[d-offset:] - b = b[:len(a)] - for i := range a { - a[i] = b[i] - } - d += length - } - - // Remaining with extra checks... - for s < len(src) { - switch src[s] & 0x03 { - case tagLiteral: - x := uint32(src[s] >> 2) - switch { - case x < 60: - s++ - case x == 60: - s += 2 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - return decodeErrCodeCorrupt - } - x = uint32(src[s-1]) - case x == 61: - s += 3 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - return decodeErrCodeCorrupt - } - x = uint32(src[s-2]) | uint32(src[s-1])<<8 - case x == 62: - s += 4 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - return decodeErrCodeCorrupt - } - x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16 - case x == 63: - s += 5 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - return decodeErrCodeCorrupt - } - x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24 - } - length = int(x) + 1 - if length > len(dst)-d || length > len(src)-s || (strconv.IntSize == 32 && length <= 0) { - if debug { - fmt.Println("corrupt: lit size", length) - } - return decodeErrCodeCorrupt - } - if debug { - fmt.Println("literals, length:", length, "d-after:", d+length) - } - - copy(dst[d:], src[s:s+length]) - d += length - s += length - continue - - case tagCopy1: - s += 2 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - return decodeErrCodeCorrupt - } - length = int(src[s-2]) >> 2 & 0x7 - toffset := int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1])) - if toffset == 0 { - if debug { - fmt.Print("(repeat) ") - } - // keep last offset - switch length { - case 5: - s += 1 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - return decodeErrCodeCorrupt - } - length = int(uint32(src[s-1])) + 4 - case 6: - s += 2 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - return decodeErrCodeCorrupt - } - length = int(uint32(src[s-2])|(uint32(src[s-1])<<8)) + (1 << 8) - case 7: - s += 3 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - return decodeErrCodeCorrupt - } - length = int(uint32(src[s-3])|(uint32(src[s-2])<<8)|(uint32(src[s-1])<<16)) + (1 << 16) - default: // 0-> 4 - } - } else { - offset = toffset - } - length += 4 - case tagCopy2: - s += 3 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - return decodeErrCodeCorrupt - } - length = 1 + int(src[s-3])>>2 - offset = int(uint32(src[s-2]) | uint32(src[s-1])<<8) - - case tagCopy4: - s += 5 - if uint(s) > uint(len(src)) { // The uint conversions catch overflow from the previous line. - return decodeErrCodeCorrupt - } - length = 1 + int(src[s-5])>>2 - offset = int(uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24) - } - - if offset <= 0 || d < offset || length > len(dst)-d { - if debug { - fmt.Println("corrupt: match, length", length, "offset:", offset, "dst avail:", len(dst)-d, "dst pos:", d) - } - return decodeErrCodeCorrupt - } - - if debug { - fmt.Println("copy, length:", length, "offset:", offset, "d-after:", d+length) - } - - // Copy from an earlier sub-slice of dst to a later sub-slice. - // If no overlap, use the built-in copy: - if offset > length { - copy(dst[d:d+length], dst[d-offset:]) - d += length - continue - } - - // Unlike the built-in copy function, this byte-by-byte copy always runs - // forwards, even if the slices overlap. Conceptually, this is: - // - // d += forwardCopy(dst[d:d+length], dst[d-offset:]) - // - // We align the slices into a and b and show the compiler they are the same size. - // This allows the loop to run without bounds checks. - a := dst[d : d+length] - b := dst[d-offset:] - b = b[:len(a)] - for i := range a { - a[i] = b[i] - } - d += length - } - - if d != len(dst) { - return decodeErrCodeCorrupt - } - return 0 -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/dict.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/dict.go deleted file mode 100644 index f125ad09..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/dict.go +++ /dev/null @@ -1,350 +0,0 @@ -// Copyright (c) 2022+ Klaus Post. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package s2 - -import ( - "bytes" - "encoding/binary" - "sync" -) - -const ( - // MinDictSize is the minimum dictionary size when repeat has been read. - MinDictSize = 16 - - // MaxDictSize is the maximum dictionary size when repeat has been read. - MaxDictSize = 65536 - - // MaxDictSrcOffset is the maximum offset where a dictionary entry can start. - MaxDictSrcOffset = 65535 -) - -// Dict contains a dictionary that can be used for encoding and decoding s2 -type Dict struct { - dict []byte - repeat int // Repeat as index of dict - - fast, better, best sync.Once - fastTable *[1 << 14]uint16 - - betterTableShort *[1 << 14]uint16 - betterTableLong *[1 << 17]uint16 - - bestTableShort *[1 << 16]uint32 - bestTableLong *[1 << 19]uint32 -} - -// NewDict will read a dictionary. -// It will return nil if the dictionary is invalid. -func NewDict(dict []byte) *Dict { - if len(dict) == 0 { - return nil - } - var d Dict - // Repeat is the first value of the dict - r, n := binary.Uvarint(dict) - if n <= 0 { - return nil - } - dict = dict[n:] - d.dict = dict - if cap(d.dict) < len(d.dict)+16 { - d.dict = append(make([]byte, 0, len(d.dict)+16), d.dict...) - } - if len(dict) < MinDictSize || len(dict) > MaxDictSize { - return nil - } - d.repeat = int(r) - if d.repeat > len(dict) { - return nil - } - return &d -} - -// Bytes will return a serialized version of the dictionary. -// The output can be sent to NewDict. -func (d *Dict) Bytes() []byte { - dst := make([]byte, binary.MaxVarintLen16+len(d.dict)) - return append(dst[:binary.PutUvarint(dst, uint64(d.repeat))], d.dict...) -} - -// MakeDict will create a dictionary. -// 'data' must be at least MinDictSize. -// If data is longer than MaxDictSize only the last MaxDictSize bytes will be used. -// If searchStart is set the start repeat value will be set to the last -// match of this content. -// If no matches are found, it will attempt to find shorter matches. -// This content should match the typical start of a block. -// If at least 4 bytes cannot be matched, repeat is set to start of block. -func MakeDict(data []byte, searchStart []byte) *Dict { - if len(data) == 0 { - return nil - } - if len(data) > MaxDictSize { - data = data[len(data)-MaxDictSize:] - } - var d Dict - dict := data - d.dict = dict - if cap(d.dict) < len(d.dict)+16 { - d.dict = append(make([]byte, 0, len(d.dict)+16), d.dict...) - } - if len(dict) < MinDictSize { - return nil - } - - // Find the longest match possible, last entry if multiple. - for s := len(searchStart); s > 4; s-- { - if idx := bytes.LastIndex(data, searchStart[:s]); idx >= 0 && idx <= len(data)-8 { - d.repeat = idx - break - } - } - - return &d -} - -// MakeDictManual will create a dictionary. -// 'data' must be at least MinDictSize and less than or equal to MaxDictSize. -// A manual first repeat index into data must be provided. -// It must be less than len(data)-8. -func MakeDictManual(data []byte, firstIdx uint16) *Dict { - if len(data) < MinDictSize || int(firstIdx) >= len(data)-8 || len(data) > MaxDictSize { - return nil - } - var d Dict - dict := data - d.dict = dict - if cap(d.dict) < len(d.dict)+16 { - d.dict = append(make([]byte, 0, len(d.dict)+16), d.dict...) - } - - d.repeat = int(firstIdx) - return &d -} - -// Encode returns the encoded form of src. The returned slice may be a sub- -// slice of dst if dst was large enough to hold the entire encoded block. -// Otherwise, a newly allocated slice will be returned. -// -// The dst and src must not overlap. It is valid to pass a nil dst. -// -// The blocks will require the same amount of memory to decode as encoding, -// and does not make for concurrent decoding. -// Also note that blocks do not contain CRC information, so corruption may be undetected. -// -// If you need to encode larger amounts of data, consider using -// the streaming interface which gives all of these features. -func (d *Dict) Encode(dst, src []byte) []byte { - if n := MaxEncodedLen(len(src)); n < 0 { - panic(ErrTooLarge) - } else if cap(dst) < n { - dst = make([]byte, n) - } else { - dst = dst[:n] - } - - // The block starts with the varint-encoded length of the decompressed bytes. - dstP := binary.PutUvarint(dst, uint64(len(src))) - - if len(src) == 0 { - return dst[:dstP] - } - if len(src) < minNonLiteralBlockSize { - dstP += emitLiteral(dst[dstP:], src) - return dst[:dstP] - } - n := encodeBlockDictGo(dst[dstP:], src, d) - if n > 0 { - dstP += n - return dst[:dstP] - } - // Not compressible - dstP += emitLiteral(dst[dstP:], src) - return dst[:dstP] -} - -// EncodeBetter returns the encoded form of src. The returned slice may be a sub- -// slice of dst if dst was large enough to hold the entire encoded block. -// Otherwise, a newly allocated slice will be returned. -// -// EncodeBetter compresses better than Encode but typically with a -// 10-40% speed decrease on both compression and decompression. -// -// The dst and src must not overlap. It is valid to pass a nil dst. -// -// The blocks will require the same amount of memory to decode as encoding, -// and does not make for concurrent decoding. -// Also note that blocks do not contain CRC information, so corruption may be undetected. -// -// If you need to encode larger amounts of data, consider using -// the streaming interface which gives all of these features. -func (d *Dict) EncodeBetter(dst, src []byte) []byte { - if n := MaxEncodedLen(len(src)); n < 0 { - panic(ErrTooLarge) - } else if len(dst) < n { - dst = make([]byte, n) - } - - // The block starts with the varint-encoded length of the decompressed bytes. - dstP := binary.PutUvarint(dst, uint64(len(src))) - - if len(src) == 0 { - return dst[:dstP] - } - if len(src) < minNonLiteralBlockSize { - dstP += emitLiteral(dst[dstP:], src) - return dst[:dstP] - } - n := encodeBlockBetterDict(dst[dstP:], src, d) - if n > 0 { - dstP += n - return dst[:dstP] - } - // Not compressible - dstP += emitLiteral(dst[dstP:], src) - return dst[:dstP] -} - -// EncodeBest returns the encoded form of src. The returned slice may be a sub- -// slice of dst if dst was large enough to hold the entire encoded block. -// Otherwise, a newly allocated slice will be returned. -// -// EncodeBest compresses as good as reasonably possible but with a -// big speed decrease. -// -// The dst and src must not overlap. It is valid to pass a nil dst. -// -// The blocks will require the same amount of memory to decode as encoding, -// and does not make for concurrent decoding. -// Also note that blocks do not contain CRC information, so corruption may be undetected. -// -// If you need to encode larger amounts of data, consider using -// the streaming interface which gives all of these features. -func (d *Dict) EncodeBest(dst, src []byte) []byte { - if n := MaxEncodedLen(len(src)); n < 0 { - panic(ErrTooLarge) - } else if len(dst) < n { - dst = make([]byte, n) - } - - // The block starts with the varint-encoded length of the decompressed bytes. - dstP := binary.PutUvarint(dst, uint64(len(src))) - - if len(src) == 0 { - return dst[:dstP] - } - if len(src) < minNonLiteralBlockSize { - dstP += emitLiteral(dst[dstP:], src) - return dst[:dstP] - } - n := encodeBlockBest(dst[dstP:], src, d) - if n > 0 { - dstP += n - return dst[:dstP] - } - // Not compressible - dstP += emitLiteral(dst[dstP:], src) - return dst[:dstP] -} - -// Decode returns the decoded form of src. The returned slice may be a sub- -// slice of dst if dst was large enough to hold the entire decoded block. -// Otherwise, a newly allocated slice will be returned. -// -// The dst and src must not overlap. It is valid to pass a nil dst. -func (d *Dict) Decode(dst, src []byte) ([]byte, error) { - dLen, s, err := decodedLen(src) - if err != nil { - return nil, err - } - if dLen <= cap(dst) { - dst = dst[:dLen] - } else { - dst = make([]byte, dLen) - } - if s2DecodeDict(dst, src[s:], d) != 0 { - return nil, ErrCorrupt - } - return dst, nil -} - -func (d *Dict) initFast() { - d.fast.Do(func() { - const ( - tableBits = 14 - maxTableSize = 1 << tableBits - ) - - var table [maxTableSize]uint16 - // We stop so any entry of length 8 can always be read. - for i := 0; i < len(d.dict)-8-2; i += 3 { - x0 := load64(d.dict, i) - h0 := hash6(x0, tableBits) - h1 := hash6(x0>>8, tableBits) - h2 := hash6(x0>>16, tableBits) - table[h0] = uint16(i) - table[h1] = uint16(i + 1) - table[h2] = uint16(i + 2) - } - d.fastTable = &table - }) -} - -func (d *Dict) initBetter() { - d.better.Do(func() { - const ( - // Long hash matches. - lTableBits = 17 - maxLTableSize = 1 << lTableBits - - // Short hash matches. - sTableBits = 14 - maxSTableSize = 1 << sTableBits - ) - - var lTable [maxLTableSize]uint16 - var sTable [maxSTableSize]uint16 - - // We stop so any entry of length 8 can always be read. - for i := 0; i < len(d.dict)-8; i++ { - cv := load64(d.dict, i) - lTable[hash7(cv, lTableBits)] = uint16(i) - sTable[hash4(cv, sTableBits)] = uint16(i) - } - d.betterTableShort = &sTable - d.betterTableLong = &lTable - }) -} - -func (d *Dict) initBest() { - d.best.Do(func() { - const ( - // Long hash matches. - lTableBits = 19 - maxLTableSize = 1 << lTableBits - - // Short hash matches. - sTableBits = 16 - maxSTableSize = 1 << sTableBits - ) - - var lTable [maxLTableSize]uint32 - var sTable [maxSTableSize]uint32 - - // We stop so any entry of length 8 can always be read. - for i := 0; i < len(d.dict)-8; i++ { - cv := load64(d.dict, i) - hashL := hash8(cv, lTableBits) - hashS := hash4(cv, sTableBits) - candidateL := lTable[hashL] - candidateS := sTable[hashS] - lTable[hashL] = uint32(i) | candidateL<<16 - sTable[hashS] = uint32(i) | candidateS<<16 - } - d.bestTableShort = &sTable - d.bestTableLong = &lTable - }) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode.go deleted file mode 100644 index 20b80227..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode.go +++ /dev/null @@ -1,414 +0,0 @@ -// Copyright 2011 The Snappy-Go Authors. All rights reserved. -// Copyright (c) 2019 Klaus Post. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package s2 - -import ( - "encoding/binary" - "math" - "math/bits" - "sync" - - "github.com/klauspost/compress/internal/race" -) - -// Encode returns the encoded form of src. The returned slice may be a sub- -// slice of dst if dst was large enough to hold the entire encoded block. -// Otherwise, a newly allocated slice will be returned. -// -// The dst and src must not overlap. It is valid to pass a nil dst. -// -// The blocks will require the same amount of memory to decode as encoding, -// and does not make for concurrent decoding. -// Also note that blocks do not contain CRC information, so corruption may be undetected. -// -// If you need to encode larger amounts of data, consider using -// the streaming interface which gives all of these features. -func Encode(dst, src []byte) []byte { - if n := MaxEncodedLen(len(src)); n < 0 { - panic(ErrTooLarge) - } else if cap(dst) < n { - dst = make([]byte, n) - } else { - dst = dst[:n] - } - - // The block starts with the varint-encoded length of the decompressed bytes. - d := binary.PutUvarint(dst, uint64(len(src))) - - if len(src) == 0 { - return dst[:d] - } - if len(src) < minNonLiteralBlockSize { - d += emitLiteral(dst[d:], src) - return dst[:d] - } - n := encodeBlock(dst[d:], src) - if n > 0 { - d += n - return dst[:d] - } - // Not compressible - d += emitLiteral(dst[d:], src) - return dst[:d] -} - -var estblockPool [2]sync.Pool - -// EstimateBlockSize will perform a very fast compression -// without outputting the result and return the compressed output size. -// The function returns -1 if no improvement could be achieved. -// Using actual compression will most often produce better compression than the estimate. -func EstimateBlockSize(src []byte) (d int) { - if len(src) <= inputMargin || int64(len(src)) > 0xffffffff { - return -1 - } - if len(src) <= 1024 { - const sz, pool = 2048, 0 - tmp, ok := estblockPool[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer estblockPool[pool].Put(tmp) - - d = calcBlockSizeSmall(src, tmp) - } else { - const sz, pool = 32768, 1 - tmp, ok := estblockPool[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer estblockPool[pool].Put(tmp) - - d = calcBlockSize(src, tmp) - } - - if d == 0 { - return -1 - } - // Size of the varint encoded block size. - d += (bits.Len64(uint64(len(src))) + 7) / 7 - - if d >= len(src) { - return -1 - } - return d -} - -// EncodeBetter returns the encoded form of src. The returned slice may be a sub- -// slice of dst if dst was large enough to hold the entire encoded block. -// Otherwise, a newly allocated slice will be returned. -// -// EncodeBetter compresses better than Encode but typically with a -// 10-40% speed decrease on both compression and decompression. -// -// The dst and src must not overlap. It is valid to pass a nil dst. -// -// The blocks will require the same amount of memory to decode as encoding, -// and does not make for concurrent decoding. -// Also note that blocks do not contain CRC information, so corruption may be undetected. -// -// If you need to encode larger amounts of data, consider using -// the streaming interface which gives all of these features. -func EncodeBetter(dst, src []byte) []byte { - if n := MaxEncodedLen(len(src)); n < 0 { - panic(ErrTooLarge) - } else if len(dst) < n { - dst = make([]byte, n) - } - - // The block starts with the varint-encoded length of the decompressed bytes. - d := binary.PutUvarint(dst, uint64(len(src))) - - if len(src) == 0 { - return dst[:d] - } - if len(src) < minNonLiteralBlockSize { - d += emitLiteral(dst[d:], src) - return dst[:d] - } - n := encodeBlockBetter(dst[d:], src) - if n > 0 { - d += n - return dst[:d] - } - // Not compressible - d += emitLiteral(dst[d:], src) - return dst[:d] -} - -// EncodeBest returns the encoded form of src. The returned slice may be a sub- -// slice of dst if dst was large enough to hold the entire encoded block. -// Otherwise, a newly allocated slice will be returned. -// -// EncodeBest compresses as good as reasonably possible but with a -// big speed decrease. -// -// The dst and src must not overlap. It is valid to pass a nil dst. -// -// The blocks will require the same amount of memory to decode as encoding, -// and does not make for concurrent decoding. -// Also note that blocks do not contain CRC information, so corruption may be undetected. -// -// If you need to encode larger amounts of data, consider using -// the streaming interface which gives all of these features. -func EncodeBest(dst, src []byte) []byte { - if n := MaxEncodedLen(len(src)); n < 0 { - panic(ErrTooLarge) - } else if len(dst) < n { - dst = make([]byte, n) - } - - // The block starts with the varint-encoded length of the decompressed bytes. - d := binary.PutUvarint(dst, uint64(len(src))) - - if len(src) == 0 { - return dst[:d] - } - if len(src) < minNonLiteralBlockSize { - d += emitLiteral(dst[d:], src) - return dst[:d] - } - n := encodeBlockBest(dst[d:], src, nil) - if n > 0 { - d += n - return dst[:d] - } - // Not compressible - d += emitLiteral(dst[d:], src) - return dst[:d] -} - -// EncodeSnappy returns the encoded form of src. The returned slice may be a sub- -// slice of dst if dst was large enough to hold the entire encoded block. -// Otherwise, a newly allocated slice will be returned. -// -// The output is Snappy compatible and will likely decompress faster. -// -// The dst and src must not overlap. It is valid to pass a nil dst. -// -// The blocks will require the same amount of memory to decode as encoding, -// and does not make for concurrent decoding. -// Also note that blocks do not contain CRC information, so corruption may be undetected. -// -// If you need to encode larger amounts of data, consider using -// the streaming interface which gives all of these features. -func EncodeSnappy(dst, src []byte) []byte { - if n := MaxEncodedLen(len(src)); n < 0 { - panic(ErrTooLarge) - } else if cap(dst) < n { - dst = make([]byte, n) - } else { - dst = dst[:n] - } - - // The block starts with the varint-encoded length of the decompressed bytes. - d := binary.PutUvarint(dst, uint64(len(src))) - - if len(src) == 0 { - return dst[:d] - } - if len(src) < minNonLiteralBlockSize { - d += emitLiteral(dst[d:], src) - return dst[:d] - } - - n := encodeBlockSnappy(dst[d:], src) - if n > 0 { - d += n - return dst[:d] - } - // Not compressible - d += emitLiteral(dst[d:], src) - return dst[:d] -} - -// EncodeSnappyBetter returns the encoded form of src. The returned slice may be a sub- -// slice of dst if dst was large enough to hold the entire encoded block. -// Otherwise, a newly allocated slice will be returned. -// -// The output is Snappy compatible and will likely decompress faster. -// -// The dst and src must not overlap. It is valid to pass a nil dst. -// -// The blocks will require the same amount of memory to decode as encoding, -// and does not make for concurrent decoding. -// Also note that blocks do not contain CRC information, so corruption may be undetected. -// -// If you need to encode larger amounts of data, consider using -// the streaming interface which gives all of these features. -func EncodeSnappyBetter(dst, src []byte) []byte { - if n := MaxEncodedLen(len(src)); n < 0 { - panic(ErrTooLarge) - } else if cap(dst) < n { - dst = make([]byte, n) - } else { - dst = dst[:n] - } - - // The block starts with the varint-encoded length of the decompressed bytes. - d := binary.PutUvarint(dst, uint64(len(src))) - - if len(src) == 0 { - return dst[:d] - } - if len(src) < minNonLiteralBlockSize { - d += emitLiteral(dst[d:], src) - return dst[:d] - } - - n := encodeBlockBetterSnappy(dst[d:], src) - if n > 0 { - d += n - return dst[:d] - } - // Not compressible - d += emitLiteral(dst[d:], src) - return dst[:d] -} - -// EncodeSnappyBest returns the encoded form of src. The returned slice may be a sub- -// slice of dst if dst was large enough to hold the entire encoded block. -// Otherwise, a newly allocated slice will be returned. -// -// The output is Snappy compatible and will likely decompress faster. -// -// The dst and src must not overlap. It is valid to pass a nil dst. -// -// The blocks will require the same amount of memory to decode as encoding, -// and does not make for concurrent decoding. -// Also note that blocks do not contain CRC information, so corruption may be undetected. -// -// If you need to encode larger amounts of data, consider using -// the streaming interface which gives all of these features. -func EncodeSnappyBest(dst, src []byte) []byte { - if n := MaxEncodedLen(len(src)); n < 0 { - panic(ErrTooLarge) - } else if cap(dst) < n { - dst = make([]byte, n) - } else { - dst = dst[:n] - } - - // The block starts with the varint-encoded length of the decompressed bytes. - d := binary.PutUvarint(dst, uint64(len(src))) - - if len(src) == 0 { - return dst[:d] - } - if len(src) < minNonLiteralBlockSize { - d += emitLiteral(dst[d:], src) - return dst[:d] - } - - n := encodeBlockBestSnappy(dst[d:], src) - if n > 0 { - d += n - return dst[:d] - } - // Not compressible - d += emitLiteral(dst[d:], src) - return dst[:d] -} - -// ConcatBlocks will concatenate the supplied blocks and append them to the supplied destination. -// If the destination is nil or too small, a new will be allocated. -// The blocks are not validated, so garbage in = garbage out. -// dst may not overlap block data. -// Any data in dst is preserved as is, so it will not be considered a block. -func ConcatBlocks(dst []byte, blocks ...[]byte) ([]byte, error) { - totalSize := uint64(0) - compSize := 0 - for _, b := range blocks { - l, hdr, err := decodedLen(b) - if err != nil { - return nil, err - } - totalSize += uint64(l) - compSize += len(b) - hdr - } - if totalSize == 0 { - dst = append(dst, 0) - return dst, nil - } - if totalSize > math.MaxUint32 { - return nil, ErrTooLarge - } - var tmp [binary.MaxVarintLen32]byte - hdrSize := binary.PutUvarint(tmp[:], totalSize) - wantSize := hdrSize + compSize - - if cap(dst)-len(dst) < wantSize { - dst = append(make([]byte, 0, wantSize+len(dst)), dst...) - } - dst = append(dst, tmp[:hdrSize]...) - for _, b := range blocks { - _, hdr, err := decodedLen(b) - if err != nil { - return nil, err - } - dst = append(dst, b[hdr:]...) - } - return dst, nil -} - -// inputMargin is the minimum number of extra input bytes to keep, inside -// encodeBlock's inner loop. On some architectures, this margin lets us -// implement a fast path for emitLiteral, where the copy of short (<= 16 byte) -// literals can be implemented as a single load to and store from a 16-byte -// register. That literal's actual length can be as short as 1 byte, so this -// can copy up to 15 bytes too much, but that's OK as subsequent iterations of -// the encoding loop will fix up the copy overrun, and this inputMargin ensures -// that we don't overrun the dst and src buffers. -const inputMargin = 8 - -// minNonLiteralBlockSize is the minimum size of the input to encodeBlock that -// will be accepted by the encoder. -const minNonLiteralBlockSize = 32 - -const intReduction = 2 - (1 << (^uint(0) >> 63)) // 1 (32 bits) or 0 (64 bits) - -// MaxBlockSize is the maximum value where MaxEncodedLen will return a valid block size. -// Blocks this big are highly discouraged, though. -// Half the size on 32 bit systems. -const MaxBlockSize = (1<<(32-intReduction) - 1) - binary.MaxVarintLen32 - 5 - -// MaxEncodedLen returns the maximum length of a snappy block, given its -// uncompressed length. -// -// It will return a negative value if srcLen is too large to encode. -// 32 bit platforms will have lower thresholds for rejecting big content. -func MaxEncodedLen(srcLen int) int { - n := uint64(srcLen) - if intReduction == 1 { - // 32 bits - if n > math.MaxInt32 { - // Also includes negative. - return -1 - } - } else if n > 0xffffffff { - // 64 bits - // Also includes negative. - return -1 - } - // Size of the varint encoded block size. - n = n + uint64((bits.Len64(n)+7)/7) - - // Add maximum size of encoding block as literals. - n += uint64(literalExtraSize(int64(srcLen))) - if intReduction == 1 { - // 32 bits - if n > math.MaxInt32 { - return -1 - } - } else if n > 0xffffffff { - // 64 bits - // Also includes negative. - return -1 - } - return int(n) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_all.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_all.go deleted file mode 100644 index a473b645..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_all.go +++ /dev/null @@ -1,1480 +0,0 @@ -// Copyright 2016 The Snappy-Go Authors. All rights reserved. -// Copyright (c) 2019 Klaus Post. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package s2 - -import ( - "bytes" - "encoding/binary" - "fmt" - "math/bits" - - "github.com/klauspost/compress/internal/le" -) - -func load32(b []byte, i int) uint32 { - return le.Load32(b, i) -} - -func load64(b []byte, i int) uint64 { - return le.Load64(b, i) -} - -// hash6 returns the hash of the lowest 6 bytes of u to fit in a hash table with h bits. -// Preferably h should be a constant and should always be <64. -func hash6(u uint64, h uint8) uint32 { - const prime6bytes = 227718039650203 - return uint32(((u << (64 - 48)) * prime6bytes) >> ((64 - h) & 63)) -} - -func encodeGo(dst, src []byte) []byte { - if n := MaxEncodedLen(len(src)); n < 0 { - panic(ErrTooLarge) - } else if len(dst) < n { - dst = make([]byte, n) - } - - // The block starts with the varint-encoded length of the decompressed bytes. - d := binary.PutUvarint(dst, uint64(len(src))) - - if len(src) == 0 { - return dst[:d] - } - if len(src) < minNonLiteralBlockSize { - d += emitLiteral(dst[d:], src) - return dst[:d] - } - var n int - if len(src) < 64<<10 { - n = encodeBlockGo64K(dst[d:], src) - } else { - n = encodeBlockGo(dst[d:], src) - } - if n > 0 { - d += n - return dst[:d] - } - // Not compressible - d += emitLiteral(dst[d:], src) - return dst[:d] -} - -// encodeBlockGo encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) && -// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize -func encodeBlockGo(dst, src []byte) (d int) { - // Initialize the hash table. - const ( - tableBits = 14 - maxTableSize = 1 << tableBits - - debug = false - ) - var table [maxTableSize]uint32 - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := len(src) - inputMargin - - // Bail if we can't compress to at least this. - dstLimit := len(src) - len(src)>>5 - 5 - - // nextEmit is where in src the next emitLiteral should start from. - nextEmit := 0 - - // The encoded form must start with a literal, as there are no previous - // bytes to copy, so we start looking for hash matches at s == 1. - s := 1 - cv := load64(src, s) - - // We search for a repeat at -1, but don't output repeats when nextEmit == 0 - repeat := 1 - - for { - candidate := 0 - for { - // Next src position to check - nextS := s + (s-nextEmit)>>6 + 4 - if nextS > sLimit { - goto emitRemainder - } - hash0 := hash6(cv, tableBits) - hash1 := hash6(cv>>8, tableBits) - candidate = int(table[hash0]) - candidate2 := int(table[hash1]) - table[hash0] = uint32(s) - table[hash1] = uint32(s + 1) - hash2 := hash6(cv>>16, tableBits) - - // Check repeat at offset checkRep. - const checkRep = 1 - if uint32(cv>>(checkRep*8)) == load32(src, s-repeat+checkRep) { - base := s + checkRep - // Extend back - for i := base - repeat; base > nextEmit && i > 0 && src[i-1] == src[base-1]; { - i-- - base-- - } - - // Bail if we exceed the maximum size. - if d+(base-nextEmit) > dstLimit { - return 0 - } - - d += emitLiteral(dst[d:], src[nextEmit:base]) - - // Extend forward - candidate := s - repeat + 4 + checkRep - s += 4 + checkRep - for s <= sLimit { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - if debug { - // Validate match. - if s <= candidate { - panic("s <= candidate") - } - a := src[base:s] - b := src[base-repeat : base-repeat+(s-base)] - if !bytes.Equal(a, b) { - panic("mismatch") - } - } - if nextEmit > 0 { - // same as `add := emitCopy(dst[d:], repeat, s-base)` but skips storing offset. - d += emitRepeat(dst[d:], repeat, s-base) - } else { - // First match, cannot be repeat. - d += emitCopy(dst[d:], repeat, s-base) - } - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - cv = load64(src, s) - continue - } - - if uint32(cv) == load32(src, candidate) { - break - } - candidate = int(table[hash2]) - if uint32(cv>>8) == load32(src, candidate2) { - table[hash2] = uint32(s + 2) - candidate = candidate2 - s++ - break - } - table[hash2] = uint32(s + 2) - if uint32(cv>>16) == load32(src, candidate) { - s += 2 - break - } - - cv = load64(src, nextS) - s = nextS - } - - // Extend backwards. - // The top bytes will be rechecked to get the full match. - for candidate > 0 && s > nextEmit && src[candidate-1] == src[s-1] { - candidate-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - - d += emitLiteral(dst[d:], src[nextEmit:s]) - - // Call emitCopy, and then see if another emitCopy could be our next - // move. Repeat until we find no match for the input immediately after - // what was consumed by the last emitCopy call. - // - // If we exit this loop normally then we need to call emitLiteral next, - // though we don't yet know how big the literal will be. We handle that - // by proceeding to the next iteration of the main loop. We also can - // exit this loop via goto if we get close to exhausting the input. - for { - // Invariant: we have a 4-byte match at s, and no need to emit any - // literal bytes prior to s. - base := s - repeat = base - candidate - - // Extend the 4-byte match as long as possible. - s += 4 - candidate += 4 - for s <= len(src)-8 { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - - d += emitCopy(dst[d:], repeat, s-base) - if debug { - // Validate match. - if s <= candidate { - panic("s <= candidate") - } - a := src[base:s] - b := src[base-repeat : base-repeat+(s-base)] - if !bytes.Equal(a, b) { - panic("mismatch") - } - } - - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - // Check for an immediate match, otherwise start search at s+1 - x := load64(src, s-2) - m2Hash := hash6(x, tableBits) - currHash := hash6(x>>16, tableBits) - candidate = int(table[currHash]) - table[m2Hash] = uint32(s - 2) - table[currHash] = uint32(s) - if debug && s == candidate { - panic("s == candidate") - } - if uint32(x>>16) != load32(src, candidate) { - cv = load64(src, s+1) - s++ - break - } - } - } - -emitRemainder: - if nextEmit < len(src) { - // Bail if we exceed the maximum size. - if d+len(src)-nextEmit > dstLimit { - return 0 - } - d += emitLiteral(dst[d:], src[nextEmit:]) - } - return d -} - -// encodeBlockGo64K is a specialized version for compressing blocks <= 64KB -func encodeBlockGo64K(dst, src []byte) (d int) { - // Initialize the hash table. - const ( - tableBits = 14 - maxTableSize = 1 << tableBits - - debug = false - ) - - var table [maxTableSize]uint16 - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := len(src) - inputMargin - - // Bail if we can't compress to at least this. - dstLimit := len(src) - len(src)>>5 - 5 - - // nextEmit is where in src the next emitLiteral should start from. - nextEmit := 0 - - // The encoded form must start with a literal, as there are no previous - // bytes to copy, so we start looking for hash matches at s == 1. - s := 1 - cv := load64(src, s) - - // We search for a repeat at -1, but don't output repeats when nextEmit == 0 - repeat := 1 - - for { - candidate := 0 - for { - // Next src position to check - nextS := s + (s-nextEmit)>>5 + 4 - if nextS > sLimit { - goto emitRemainder - } - hash0 := hash6(cv, tableBits) - hash1 := hash6(cv>>8, tableBits) - candidate = int(table[hash0]) - candidate2 := int(table[hash1]) - table[hash0] = uint16(s) - table[hash1] = uint16(s + 1) - hash2 := hash6(cv>>16, tableBits) - - // Check repeat at offset checkRep. - const checkRep = 1 - if uint32(cv>>(checkRep*8)) == load32(src, s-repeat+checkRep) { - base := s + checkRep - // Extend back - for i := base - repeat; base > nextEmit && i > 0 && src[i-1] == src[base-1]; { - i-- - base-- - } - - // Bail if we exceed the maximum size. - if d+(base-nextEmit) > dstLimit { - return 0 - } - - d += emitLiteral(dst[d:], src[nextEmit:base]) - - // Extend forward - candidate := s - repeat + 4 + checkRep - s += 4 + checkRep - for s <= sLimit { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - if debug { - // Validate match. - if s <= candidate { - panic("s <= candidate") - } - a := src[base:s] - b := src[base-repeat : base-repeat+(s-base)] - if !bytes.Equal(a, b) { - panic("mismatch") - } - } - if nextEmit > 0 { - // same as `add := emitCopy(dst[d:], repeat, s-base)` but skips storing offset. - d += emitRepeat(dst[d:], repeat, s-base) - } else { - // First match, cannot be repeat. - d += emitCopy(dst[d:], repeat, s-base) - } - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - cv = load64(src, s) - continue - } - - if uint32(cv) == load32(src, candidate) { - break - } - candidate = int(table[hash2]) - if uint32(cv>>8) == load32(src, candidate2) { - table[hash2] = uint16(s + 2) - candidate = candidate2 - s++ - break - } - table[hash2] = uint16(s + 2) - if uint32(cv>>16) == load32(src, candidate) { - s += 2 - break - } - - cv = load64(src, nextS) - s = nextS - } - - // Extend backwards. - // The top bytes will be rechecked to get the full match. - for candidate > 0 && s > nextEmit && src[candidate-1] == src[s-1] { - candidate-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - - d += emitLiteral(dst[d:], src[nextEmit:s]) - - // Call emitCopy, and then see if another emitCopy could be our next - // move. Repeat until we find no match for the input immediately after - // what was consumed by the last emitCopy call. - // - // If we exit this loop normally then we need to call emitLiteral next, - // though we don't yet know how big the literal will be. We handle that - // by proceeding to the next iteration of the main loop. We also can - // exit this loop via goto if we get close to exhausting the input. - for { - // Invariant: we have a 4-byte match at s, and no need to emit any - // literal bytes prior to s. - base := s - repeat = base - candidate - - // Extend the 4-byte match as long as possible. - s += 4 - candidate += 4 - for s <= len(src)-8 { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - - d += emitCopy(dst[d:], repeat, s-base) - if debug { - // Validate match. - if s <= candidate { - panic("s <= candidate") - } - a := src[base:s] - b := src[base-repeat : base-repeat+(s-base)] - if !bytes.Equal(a, b) { - panic("mismatch") - } - } - - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - // Check for an immediate match, otherwise start search at s+1 - x := load64(src, s-2) - m2Hash := hash6(x, tableBits) - currHash := hash6(x>>16, tableBits) - candidate = int(table[currHash]) - table[m2Hash] = uint16(s - 2) - table[currHash] = uint16(s) - if debug && s == candidate { - panic("s == candidate") - } - if uint32(x>>16) != load32(src, candidate) { - cv = load64(src, s+1) - s++ - break - } - } - } - -emitRemainder: - if nextEmit < len(src) { - // Bail if we exceed the maximum size. - if d+len(src)-nextEmit > dstLimit { - return 0 - } - d += emitLiteral(dst[d:], src[nextEmit:]) - } - return d -} - -func encodeBlockSnappyGo(dst, src []byte) (d int) { - // Initialize the hash table. - const ( - tableBits = 14 - maxTableSize = 1 << tableBits - ) - var table [maxTableSize]uint32 - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := len(src) - inputMargin - - // Bail if we can't compress to at least this. - dstLimit := len(src) - len(src)>>5 - 5 - - // nextEmit is where in src the next emitLiteral should start from. - nextEmit := 0 - - // The encoded form must start with a literal, as there are no previous - // bytes to copy, so we start looking for hash matches at s == 1. - s := 1 - cv := load64(src, s) - - // We search for a repeat at -1, but don't output repeats when nextEmit == 0 - repeat := 1 - - for { - candidate := 0 - for { - // Next src position to check - nextS := s + (s-nextEmit)>>6 + 4 - if nextS > sLimit { - goto emitRemainder - } - hash0 := hash6(cv, tableBits) - hash1 := hash6(cv>>8, tableBits) - candidate = int(table[hash0]) - candidate2 := int(table[hash1]) - table[hash0] = uint32(s) - table[hash1] = uint32(s + 1) - hash2 := hash6(cv>>16, tableBits) - - // Check repeat at offset checkRep. - const checkRep = 1 - if uint32(cv>>(checkRep*8)) == load32(src, s-repeat+checkRep) { - base := s + checkRep - // Extend back - for i := base - repeat; base > nextEmit && i > 0 && src[i-1] == src[base-1]; { - i-- - base-- - } - // Bail if we exceed the maximum size. - if d+(base-nextEmit) > dstLimit { - return 0 - } - - d += emitLiteral(dst[d:], src[nextEmit:base]) - - // Extend forward - candidate := s - repeat + 4 + checkRep - s += 4 + checkRep - for s <= sLimit { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - - d += emitCopyNoRepeat(dst[d:], repeat, s-base) - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - cv = load64(src, s) - continue - } - - if uint32(cv) == load32(src, candidate) { - break - } - candidate = int(table[hash2]) - if uint32(cv>>8) == load32(src, candidate2) { - table[hash2] = uint32(s + 2) - candidate = candidate2 - s++ - break - } - table[hash2] = uint32(s + 2) - if uint32(cv>>16) == load32(src, candidate) { - s += 2 - break - } - - cv = load64(src, nextS) - s = nextS - } - - // Extend backwards - for candidate > 0 && s > nextEmit && src[candidate-1] == src[s-1] { - candidate-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - - d += emitLiteral(dst[d:], src[nextEmit:s]) - - // Call emitCopy, and then see if another emitCopy could be our next - // move. Repeat until we find no match for the input immediately after - // what was consumed by the last emitCopy call. - // - // If we exit this loop normally then we need to call emitLiteral next, - // though we don't yet know how big the literal will be. We handle that - // by proceeding to the next iteration of the main loop. We also can - // exit this loop via goto if we get close to exhausting the input. - for { - // Invariant: we have a 4-byte match at s, and no need to emit any - // literal bytes prior to s. - base := s - repeat = base - candidate - - // Extend the 4-byte match as long as possible. - s += 4 - candidate += 4 - for s <= len(src)-8 { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - - d += emitCopyNoRepeat(dst[d:], repeat, s-base) - if false { - // Validate match. - a := src[base:s] - b := src[base-repeat : base-repeat+(s-base)] - if !bytes.Equal(a, b) { - panic("mismatch") - } - } - - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - // Check for an immediate match, otherwise start search at s+1 - x := load64(src, s-2) - m2Hash := hash6(x, tableBits) - currHash := hash6(x>>16, tableBits) - candidate = int(table[currHash]) - table[m2Hash] = uint32(s - 2) - table[currHash] = uint32(s) - if uint32(x>>16) != load32(src, candidate) { - cv = load64(src, s+1) - s++ - break - } - } - } - -emitRemainder: - if nextEmit < len(src) { - // Bail if we exceed the maximum size. - if d+len(src)-nextEmit > dstLimit { - return 0 - } - d += emitLiteral(dst[d:], src[nextEmit:]) - } - return d -} - -// encodeBlockSnappyGo64K is a special version of encodeBlockSnappyGo for sizes <64KB -func encodeBlockSnappyGo64K(dst, src []byte) (d int) { - // Initialize the hash table. - const ( - tableBits = 14 - maxTableSize = 1 << tableBits - ) - - var table [maxTableSize]uint16 - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := len(src) - inputMargin - - // Bail if we can't compress to at least this. - dstLimit := len(src) - len(src)>>5 - 5 - - // nextEmit is where in src the next emitLiteral should start from. - nextEmit := 0 - - // The encoded form must start with a literal, as there are no previous - // bytes to copy, so we start looking for hash matches at s == 1. - s := 1 - cv := load64(src, s) - - // We search for a repeat at -1, but don't output repeats when nextEmit == 0 - repeat := 1 - - for { - candidate := 0 - for { - // Next src position to check - nextS := s + (s-nextEmit)>>5 + 4 - if nextS > sLimit { - goto emitRemainder - } - hash0 := hash6(cv, tableBits) - hash1 := hash6(cv>>8, tableBits) - candidate = int(table[hash0]) - candidate2 := int(table[hash1]) - table[hash0] = uint16(s) - table[hash1] = uint16(s + 1) - hash2 := hash6(cv>>16, tableBits) - - // Check repeat at offset checkRep. - const checkRep = 1 - if uint32(cv>>(checkRep*8)) == load32(src, s-repeat+checkRep) { - base := s + checkRep - // Extend back - for i := base - repeat; base > nextEmit && i > 0 && src[i-1] == src[base-1]; { - i-- - base-- - } - // Bail if we exceed the maximum size. - if d+(base-nextEmit) > dstLimit { - return 0 - } - - d += emitLiteral(dst[d:], src[nextEmit:base]) - - // Extend forward - candidate := s - repeat + 4 + checkRep - s += 4 + checkRep - for s <= sLimit { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - - d += emitCopyNoRepeat(dst[d:], repeat, s-base) - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - cv = load64(src, s) - continue - } - - if uint32(cv) == load32(src, candidate) { - break - } - candidate = int(table[hash2]) - if uint32(cv>>8) == load32(src, candidate2) { - table[hash2] = uint16(s + 2) - candidate = candidate2 - s++ - break - } - table[hash2] = uint16(s + 2) - if uint32(cv>>16) == load32(src, candidate) { - s += 2 - break - } - - cv = load64(src, nextS) - s = nextS - } - - // Extend backwards - for candidate > 0 && s > nextEmit && src[candidate-1] == src[s-1] { - candidate-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - - d += emitLiteral(dst[d:], src[nextEmit:s]) - - // Call emitCopy, and then see if another emitCopy could be our next - // move. Repeat until we find no match for the input immediately after - // what was consumed by the last emitCopy call. - // - // If we exit this loop normally then we need to call emitLiteral next, - // though we don't yet know how big the literal will be. We handle that - // by proceeding to the next iteration of the main loop. We also can - // exit this loop via goto if we get close to exhausting the input. - for { - // Invariant: we have a 4-byte match at s, and no need to emit any - // literal bytes prior to s. - base := s - repeat = base - candidate - - // Extend the 4-byte match as long as possible. - s += 4 - candidate += 4 - for s <= len(src)-8 { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - - d += emitCopyNoRepeat(dst[d:], repeat, s-base) - if false { - // Validate match. - a := src[base:s] - b := src[base-repeat : base-repeat+(s-base)] - if !bytes.Equal(a, b) { - panic("mismatch") - } - } - - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - // Check for an immediate match, otherwise start search at s+1 - x := load64(src, s-2) - m2Hash := hash6(x, tableBits) - currHash := hash6(x>>16, tableBits) - candidate = int(table[currHash]) - table[m2Hash] = uint16(s - 2) - table[currHash] = uint16(s) - if uint32(x>>16) != load32(src, candidate) { - cv = load64(src, s+1) - s++ - break - } - } - } - -emitRemainder: - if nextEmit < len(src) { - // Bail if we exceed the maximum size. - if d+len(src)-nextEmit > dstLimit { - return 0 - } - d += emitLiteral(dst[d:], src[nextEmit:]) - } - return d -} - -// encodeBlockGo encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) && -// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize -func encodeBlockDictGo(dst, src []byte, dict *Dict) (d int) { - // Initialize the hash table. - const ( - tableBits = 14 - maxTableSize = 1 << tableBits - maxAhead = 8 // maximum bytes ahead without checking sLimit - - debug = false - ) - dict.initFast() - - var table [maxTableSize]uint32 - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := len(src) - inputMargin - if sLimit > MaxDictSrcOffset-maxAhead { - sLimit = MaxDictSrcOffset - maxAhead - } - - // Bail if we can't compress to at least this. - dstLimit := len(src) - len(src)>>5 - 5 - - // nextEmit is where in src the next emitLiteral should start from. - nextEmit := 0 - - // The encoded form can start with a dict entry (copy or repeat). - s := 0 - - // Convert dict repeat to offset - repeat := len(dict.dict) - dict.repeat - cv := load64(src, 0) - - // While in dict -searchDict: - for { - // Next src position to check - nextS := s + (s-nextEmit)>>6 + 4 - hash0 := hash6(cv, tableBits) - hash1 := hash6(cv>>8, tableBits) - if nextS > sLimit { - if debug { - fmt.Println("slimit reached", s, nextS) - } - break searchDict - } - candidateDict := int(dict.fastTable[hash0]) - candidateDict2 := int(dict.fastTable[hash1]) - candidate2 := int(table[hash1]) - candidate := int(table[hash0]) - table[hash0] = uint32(s) - table[hash1] = uint32(s + 1) - hash2 := hash6(cv>>16, tableBits) - - // Check repeat at offset checkRep. - const checkRep = 1 - - if repeat > s { - candidate := len(dict.dict) - repeat + s - if repeat-s >= 4 && uint32(cv) == load32(dict.dict, candidate) { - // Extend back - base := s - for i := candidate; base > nextEmit && i > 0 && dict.dict[i-1] == src[base-1]; { - i-- - base-- - } - // Bail if we exceed the maximum size. - if d+(base-nextEmit) > dstLimit { - return 0 - } - - d += emitLiteral(dst[d:], src[nextEmit:base]) - if debug && nextEmit != base { - fmt.Println("emitted ", base-nextEmit, "literals") - } - s += 4 - candidate += 4 - for candidate < len(dict.dict)-8 && s <= len(src)-8 { - if diff := load64(src, s) ^ load64(dict.dict, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - d += emitRepeat(dst[d:], repeat, s-base) - if debug { - fmt.Println("emitted dict repeat length", s-base, "offset:", repeat, "s:", s) - } - nextEmit = s - if s >= sLimit { - break searchDict - } - cv = load64(src, s) - continue - } - } else if uint32(cv>>(checkRep*8)) == load32(src, s-repeat+checkRep) { - base := s + checkRep - // Extend back - for i := base - repeat; base > nextEmit && i > 0 && src[i-1] == src[base-1]; { - i-- - base-- - } - d += emitLiteral(dst[d:], src[nextEmit:base]) - if debug && nextEmit != base { - fmt.Println("emitted ", base-nextEmit, "literals") - } - - // Extend forward - candidate := s - repeat + 4 + checkRep - s += 4 + checkRep - for s <= sLimit { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - if debug { - // Validate match. - if s <= candidate { - panic("s <= candidate") - } - a := src[base:s] - b := src[base-repeat : base-repeat+(s-base)] - if !bytes.Equal(a, b) { - panic("mismatch") - } - } - - if nextEmit > 0 { - // same as `add := emitCopy(dst[d:], repeat, s-base)` but skips storing offset. - d += emitRepeat(dst[d:], repeat, s-base) - } else { - // First match, cannot be repeat. - d += emitCopy(dst[d:], repeat, s-base) - } - - nextEmit = s - if s >= sLimit { - break searchDict - } - if debug { - fmt.Println("emitted reg repeat", s-base, "s:", s) - } - cv = load64(src, s) - continue searchDict - } - if s == 0 { - cv = load64(src, nextS) - s = nextS - continue searchDict - } - // Start with table. These matches will always be closer. - if uint32(cv) == load32(src, candidate) { - goto emitMatch - } - candidate = int(table[hash2]) - if uint32(cv>>8) == load32(src, candidate2) { - table[hash2] = uint32(s + 2) - candidate = candidate2 - s++ - goto emitMatch - } - - // Check dict. Dicts have longer offsets, so we want longer matches. - if cv == load64(dict.dict, candidateDict) { - table[hash2] = uint32(s + 2) - goto emitDict - } - - candidateDict = int(dict.fastTable[hash2]) - // Check if upper 7 bytes match - if candidateDict2 >= 1 { - if cv^load64(dict.dict, candidateDict2-1) < (1 << 8) { - table[hash2] = uint32(s + 2) - candidateDict = candidateDict2 - s++ - goto emitDict - } - } - - table[hash2] = uint32(s + 2) - if uint32(cv>>16) == load32(src, candidate) { - s += 2 - goto emitMatch - } - if candidateDict >= 2 { - // Check if upper 6 bytes match - if cv^load64(dict.dict, candidateDict-2) < (1 << 16) { - s += 2 - goto emitDict - } - } - - cv = load64(src, nextS) - s = nextS - continue searchDict - - emitDict: - { - if debug { - if load32(dict.dict, candidateDict) != load32(src, s) { - panic("dict emit mismatch") - } - } - // Extend backwards. - // The top bytes will be rechecked to get the full match. - for candidateDict > 0 && s > nextEmit && dict.dict[candidateDict-1] == src[s-1] { - candidateDict-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - - d += emitLiteral(dst[d:], src[nextEmit:s]) - if debug && nextEmit != s { - fmt.Println("emitted ", s-nextEmit, "literals") - } - { - // Invariant: we have a 4-byte match at s, and no need to emit any - // literal bytes prior to s. - base := s - repeat = s + (len(dict.dict)) - candidateDict - - // Extend the 4-byte match as long as possible. - s += 4 - candidateDict += 4 - for s <= len(src)-8 && len(dict.dict)-candidateDict >= 8 { - if diff := load64(src, s) ^ load64(dict.dict, candidateDict); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidateDict += 8 - } - - // Matches longer than 64 are split. - if s <= sLimit || s-base < 8 { - d += emitCopy(dst[d:], repeat, s-base) - } else { - // Split to ensure we don't start a copy within next block - d += emitCopy(dst[d:], repeat, 4) - d += emitRepeat(dst[d:], repeat, s-base-4) - } - if false { - // Validate match. - if s <= candidate { - panic("s <= candidate") - } - a := src[base:s] - b := dict.dict[base-repeat : base-repeat+(s-base)] - if !bytes.Equal(a, b) { - panic("mismatch") - } - } - if debug { - fmt.Println("emitted dict copy, length", s-base, "offset:", repeat, "s:", s) - } - nextEmit = s - if s >= sLimit { - break searchDict - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - - // Index and continue loop to try new candidate. - x := load64(src, s-2) - m2Hash := hash6(x, tableBits) - currHash := hash6(x>>8, tableBits) - table[m2Hash] = uint32(s - 2) - table[currHash] = uint32(s - 1) - cv = load64(src, s) - } - continue - } - emitMatch: - - // Extend backwards. - // The top bytes will be rechecked to get the full match. - for candidate > 0 && s > nextEmit && src[candidate-1] == src[s-1] { - candidate-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - - d += emitLiteral(dst[d:], src[nextEmit:s]) - if debug && nextEmit != s { - fmt.Println("emitted ", s-nextEmit, "literals") - } - // Call emitCopy, and then see if another emitCopy could be our next - // move. Repeat until we find no match for the input immediately after - // what was consumed by the last emitCopy call. - // - // If we exit this loop normally then we need to call emitLiteral next, - // though we don't yet know how big the literal will be. We handle that - // by proceeding to the next iteration of the main loop. We also can - // exit this loop via goto if we get close to exhausting the input. - for { - // Invariant: we have a 4-byte match at s, and no need to emit any - // literal bytes prior to s. - base := s - repeat = base - candidate - - // Extend the 4-byte match as long as possible. - s += 4 - candidate += 4 - for s <= len(src)-8 { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - - d += emitCopy(dst[d:], repeat, s-base) - if debug { - // Validate match. - if s <= candidate { - panic("s <= candidate") - } - a := src[base:s] - b := src[base-repeat : base-repeat+(s-base)] - if !bytes.Equal(a, b) { - panic("mismatch") - } - } - if debug { - fmt.Println("emitted src copy, length", s-base, "offset:", repeat, "s:", s) - } - nextEmit = s - if s >= sLimit { - break searchDict - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - // Check for an immediate match, otherwise start search at s+1 - x := load64(src, s-2) - m2Hash := hash6(x, tableBits) - currHash := hash6(x>>16, tableBits) - candidate = int(table[currHash]) - table[m2Hash] = uint32(s - 2) - table[currHash] = uint32(s) - if debug && s == candidate { - panic("s == candidate") - } - if uint32(x>>16) != load32(src, candidate) { - cv = load64(src, s+1) - s++ - break - } - } - } - - // Search without dict: - if repeat > s { - repeat = 0 - } - - // No more dict - sLimit = len(src) - inputMargin - if s >= sLimit { - goto emitRemainder - } - if debug { - fmt.Println("non-dict matching at", s, "repeat:", repeat) - } - cv = load64(src, s) - if debug { - fmt.Println("now", s, "->", sLimit, "out:", d, "left:", len(src)-s, "nextemit:", nextEmit, "dstLimit:", dstLimit, "s:", s) - } - for { - candidate := 0 - for { - // Next src position to check - nextS := s + (s-nextEmit)>>6 + 4 - if nextS > sLimit { - goto emitRemainder - } - hash0 := hash6(cv, tableBits) - hash1 := hash6(cv>>8, tableBits) - candidate = int(table[hash0]) - candidate2 := int(table[hash1]) - table[hash0] = uint32(s) - table[hash1] = uint32(s + 1) - hash2 := hash6(cv>>16, tableBits) - - // Check repeat at offset checkRep. - const checkRep = 1 - if repeat > 0 && uint32(cv>>(checkRep*8)) == load32(src, s-repeat+checkRep) { - base := s + checkRep - // Extend back - for i := base - repeat; base > nextEmit && i > 0 && src[i-1] == src[base-1]; { - i-- - base-- - } - // Bail if we exceed the maximum size. - if d+(base-nextEmit) > dstLimit { - return 0 - } - - d += emitLiteral(dst[d:], src[nextEmit:base]) - if debug && nextEmit != base { - fmt.Println("emitted ", base-nextEmit, "literals") - } - // Extend forward - candidate := s - repeat + 4 + checkRep - s += 4 + checkRep - for s <= sLimit { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - if debug { - // Validate match. - if s <= candidate { - panic("s <= candidate") - } - a := src[base:s] - b := src[base-repeat : base-repeat+(s-base)] - if !bytes.Equal(a, b) { - panic("mismatch") - } - } - if nextEmit > 0 { - // same as `add := emitCopy(dst[d:], repeat, s-base)` but skips storing offset. - d += emitRepeat(dst[d:], repeat, s-base) - } else { - // First match, cannot be repeat. - d += emitCopy(dst[d:], repeat, s-base) - } - if debug { - fmt.Println("emitted src repeat length", s-base, "offset:", repeat, "s:", s) - } - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - cv = load64(src, s) - continue - } - - if uint32(cv) == load32(src, candidate) { - break - } - candidate = int(table[hash2]) - if uint32(cv>>8) == load32(src, candidate2) { - table[hash2] = uint32(s + 2) - candidate = candidate2 - s++ - break - } - table[hash2] = uint32(s + 2) - if uint32(cv>>16) == load32(src, candidate) { - s += 2 - break - } - - cv = load64(src, nextS) - s = nextS - } - - // Extend backwards. - // The top bytes will be rechecked to get the full match. - for candidate > 0 && s > nextEmit && src[candidate-1] == src[s-1] { - candidate-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - - d += emitLiteral(dst[d:], src[nextEmit:s]) - if debug && nextEmit != s { - fmt.Println("emitted ", s-nextEmit, "literals") - } - // Call emitCopy, and then see if another emitCopy could be our next - // move. Repeat until we find no match for the input immediately after - // what was consumed by the last emitCopy call. - // - // If we exit this loop normally then we need to call emitLiteral next, - // though we don't yet know how big the literal will be. We handle that - // by proceeding to the next iteration of the main loop. We also can - // exit this loop via goto if we get close to exhausting the input. - for { - // Invariant: we have a 4-byte match at s, and no need to emit any - // literal bytes prior to s. - base := s - repeat = base - candidate - - // Extend the 4-byte match as long as possible. - s += 4 - candidate += 4 - for s <= len(src)-8 { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - - d += emitCopy(dst[d:], repeat, s-base) - if debug { - // Validate match. - if s <= candidate { - panic("s <= candidate") - } - a := src[base:s] - b := src[base-repeat : base-repeat+(s-base)] - if !bytes.Equal(a, b) { - panic("mismatch") - } - } - if debug { - fmt.Println("emitted src copy, length", s-base, "offset:", repeat, "s:", s) - } - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - // Check for an immediate match, otherwise start search at s+1 - x := load64(src, s-2) - m2Hash := hash6(x, tableBits) - currHash := hash6(x>>16, tableBits) - candidate = int(table[currHash]) - table[m2Hash] = uint32(s - 2) - table[currHash] = uint32(s) - if debug && s == candidate { - panic("s == candidate") - } - if uint32(x>>16) != load32(src, candidate) { - cv = load64(src, s+1) - s++ - break - } - } - } - -emitRemainder: - if nextEmit < len(src) { - // Bail if we exceed the maximum size. - if d+len(src)-nextEmit > dstLimit { - return 0 - } - d += emitLiteral(dst[d:], src[nextEmit:]) - if debug && nextEmit != s { - fmt.Println("emitted ", len(src)-nextEmit, "literals") - } - } - return d -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_amd64.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_amd64.go deleted file mode 100644 index 7aadd255..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_amd64.go +++ /dev/null @@ -1,317 +0,0 @@ -//go:build !appengine && !noasm && gc -// +build !appengine,!noasm,gc - -package s2 - -import ( - "sync" - - "github.com/klauspost/compress/internal/race" -) - -const hasAmd64Asm = true - -var encPools [4]sync.Pool - -// encodeBlock encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) && -// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize -func encodeBlock(dst, src []byte) (d int) { - race.ReadSlice(src) - race.WriteSlice(dst) - - const ( - // Use 12 bit table when less than... - limit12B = 16 << 10 - // Use 10 bit table when less than... - limit10B = 4 << 10 - // Use 8 bit table when less than... - limit8B = 512 - ) - - if len(src) >= 4<<20 { - const sz, pool = 65536, 0 - tmp, ok := encPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encPools[pool].Put(tmp) - return encodeBlockAsm(dst, src, tmp) - } - if len(src) >= limit12B { - const sz, pool = 65536, 0 - tmp, ok := encPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encPools[pool].Put(tmp) - return encodeBlockAsm4MB(dst, src, tmp) - } - if len(src) >= limit10B { - const sz, pool = 16384, 1 - tmp, ok := encPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encPools[pool].Put(tmp) - return encodeBlockAsm12B(dst, src, tmp) - } - if len(src) >= limit8B { - const sz, pool = 4096, 2 - tmp, ok := encPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encPools[pool].Put(tmp) - return encodeBlockAsm10B(dst, src, tmp) - } - if len(src) < minNonLiteralBlockSize { - return 0 - } - const sz, pool = 1024, 3 - tmp, ok := encPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encPools[pool].Put(tmp) - return encodeBlockAsm8B(dst, src, tmp) -} - -var encBetterPools [5]sync.Pool - -// encodeBlockBetter encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) && -// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize -func encodeBlockBetter(dst, src []byte) (d int) { - race.ReadSlice(src) - race.WriteSlice(dst) - - const ( - // Use 12 bit table when less than... - limit12B = 16 << 10 - // Use 10 bit table when less than... - limit10B = 4 << 10 - // Use 8 bit table when less than... - limit8B = 512 - ) - - if len(src) > 4<<20 { - const sz, pool = 589824, 0 - tmp, ok := encBetterPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encBetterPools[pool].Put(tmp) - return encodeBetterBlockAsm(dst, src, tmp) - } - if len(src) >= limit12B { - const sz, pool = 589824, 0 - tmp, ok := encBetterPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encBetterPools[pool].Put(tmp) - - return encodeBetterBlockAsm4MB(dst, src, tmp) - } - if len(src) >= limit10B { - const sz, pool = 81920, 0 - tmp, ok := encBetterPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encBetterPools[pool].Put(tmp) - - return encodeBetterBlockAsm12B(dst, src, tmp) - } - if len(src) >= limit8B { - const sz, pool = 20480, 1 - tmp, ok := encBetterPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encBetterPools[pool].Put(tmp) - return encodeBetterBlockAsm10B(dst, src, tmp) - } - if len(src) < minNonLiteralBlockSize { - return 0 - } - - const sz, pool = 5120, 2 - tmp, ok := encBetterPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encBetterPools[pool].Put(tmp) - return encodeBetterBlockAsm8B(dst, src, tmp) -} - -// encodeBlockSnappy encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) && -// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize -func encodeBlockSnappy(dst, src []byte) (d int) { - race.ReadSlice(src) - race.WriteSlice(dst) - - const ( - // Use 12 bit table when less than... - limit12B = 16 << 10 - // Use 10 bit table when less than... - limit10B = 4 << 10 - // Use 8 bit table when less than... - limit8B = 512 - ) - if len(src) > 65536 { - const sz, pool = 65536, 0 - tmp, ok := encPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encPools[pool].Put(tmp) - return encodeSnappyBlockAsm(dst, src, tmp) - } - if len(src) >= limit12B { - const sz, pool = 65536, 0 - tmp, ok := encPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encPools[pool].Put(tmp) - return encodeSnappyBlockAsm64K(dst, src, tmp) - } - if len(src) >= limit10B { - const sz, pool = 16384, 1 - tmp, ok := encPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encPools[pool].Put(tmp) - return encodeSnappyBlockAsm12B(dst, src, tmp) - } - if len(src) >= limit8B { - const sz, pool = 4096, 2 - tmp, ok := encPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encPools[pool].Put(tmp) - return encodeSnappyBlockAsm10B(dst, src, tmp) - } - if len(src) < minNonLiteralBlockSize { - return 0 - } - const sz, pool = 1024, 3 - tmp, ok := encPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encPools[pool].Put(tmp) - return encodeSnappyBlockAsm8B(dst, src, tmp) -} - -// encodeBlockSnappy encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) && -// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize -func encodeBlockBetterSnappy(dst, src []byte) (d int) { - race.ReadSlice(src) - race.WriteSlice(dst) - - const ( - // Use 12 bit table when less than... - limit12B = 16 << 10 - // Use 10 bit table when less than... - limit10B = 4 << 10 - // Use 8 bit table when less than... - limit8B = 512 - ) - if len(src) > 65536 { - const sz, pool = 589824, 0 - tmp, ok := encBetterPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encBetterPools[pool].Put(tmp) - return encodeSnappyBetterBlockAsm(dst, src, tmp) - } - - if len(src) >= limit12B { - const sz, pool = 294912, 4 - tmp, ok := encBetterPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encBetterPools[pool].Put(tmp) - - return encodeSnappyBetterBlockAsm64K(dst, src, tmp) - } - if len(src) >= limit10B { - const sz, pool = 81920, 0 - tmp, ok := encBetterPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encBetterPools[pool].Put(tmp) - - return encodeSnappyBetterBlockAsm12B(dst, src, tmp) - } - if len(src) >= limit8B { - const sz, pool = 20480, 1 - tmp, ok := encBetterPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encBetterPools[pool].Put(tmp) - return encodeSnappyBetterBlockAsm10B(dst, src, tmp) - } - if len(src) < minNonLiteralBlockSize { - return 0 - } - - const sz, pool = 5120, 2 - tmp, ok := encBetterPools[pool].Get().(*[sz]byte) - if !ok { - tmp = &[sz]byte{} - } - race.WriteSlice(tmp[:]) - defer encBetterPools[pool].Put(tmp) - return encodeSnappyBetterBlockAsm8B(dst, src, tmp) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_best.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_best.go deleted file mode 100644 index 47bac742..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_best.go +++ /dev/null @@ -1,796 +0,0 @@ -// Copyright 2016 The Snappy-Go Authors. All rights reserved. -// Copyright (c) 2019 Klaus Post. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package s2 - -import ( - "fmt" - "math" - "math/bits" -) - -// encodeBlockBest encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) && -// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize -func encodeBlockBest(dst, src []byte, dict *Dict) (d int) { - // Initialize the hash tables. - const ( - // Long hash matches. - lTableBits = 19 - maxLTableSize = 1 << lTableBits - - // Short hash matches. - sTableBits = 16 - maxSTableSize = 1 << sTableBits - - inputMargin = 8 + 2 - - debug = false - ) - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := len(src) - inputMargin - if len(src) < minNonLiteralBlockSize { - return 0 - } - sLimitDict := len(src) - inputMargin - if sLimitDict > MaxDictSrcOffset-inputMargin { - sLimitDict = MaxDictSrcOffset - inputMargin - } - - var lTable [maxLTableSize]uint64 - var sTable [maxSTableSize]uint64 - - // Bail if we can't compress to at least this. - dstLimit := len(src) - 5 - - // nextEmit is where in src the next emitLiteral should start from. - nextEmit := 0 - - // The encoded form must start with a literal, as there are no previous - // bytes to copy, so we start looking for hash matches at s == 1. - s := 1 - repeat := 1 - if dict != nil { - dict.initBest() - s = 0 - repeat = len(dict.dict) - dict.repeat - } - cv := load64(src, s) - - // We search for a repeat at -1, but don't output repeats when nextEmit == 0 - const lowbitMask = 0xffffffff - getCur := func(x uint64) int { - return int(x & lowbitMask) - } - getPrev := func(x uint64) int { - return int(x >> 32) - } - const maxSkip = 64 - - for { - type match struct { - offset int - s int - length int - score int - rep, dict bool - } - var best match - for { - // Next src position to check - nextS := (s-nextEmit)>>8 + 1 - if nextS > maxSkip { - nextS = s + maxSkip - } else { - nextS += s - } - if nextS > sLimit { - goto emitRemainder - } - if dict != nil && s >= MaxDictSrcOffset { - dict = nil - if repeat > s { - repeat = math.MinInt32 - } - } - hashL := hash8(cv, lTableBits) - hashS := hash4(cv, sTableBits) - candidateL := lTable[hashL] - candidateS := sTable[hashS] - - score := func(m match) int { - // Matches that are longer forward are penalized since we must emit it as a literal. - score := m.length - m.s - if nextEmit == m.s { - // If we do not have to emit literals, we save 1 byte - score++ - } - offset := m.s - m.offset - if m.rep { - return score - emitRepeatSize(offset, m.length) - } - return score - emitCopySize(offset, m.length) - } - - matchAt := func(offset, s int, first uint32, rep bool) match { - if best.length != 0 && best.s-best.offset == s-offset { - // Don't retest if we have the same offset. - return match{offset: offset, s: s} - } - if load32(src, offset) != first { - return match{offset: offset, s: s} - } - m := match{offset: offset, s: s, length: 4 + offset, rep: rep} - s += 4 - for s < len(src) { - if len(src)-s < 8 { - if src[s] == src[m.length] { - m.length++ - s++ - continue - } - break - } - if diff := load64(src, s) ^ load64(src, m.length); diff != 0 { - m.length += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - m.length += 8 - } - m.length -= offset - m.score = score(m) - if m.score <= -m.s { - // Eliminate if no savings, we might find a better one. - m.length = 0 - } - return m - } - matchDict := func(candidate, s int, first uint32, rep bool) match { - if s >= MaxDictSrcOffset { - return match{offset: candidate, s: s} - } - // Calculate offset as if in continuous array with s - offset := -len(dict.dict) + candidate - if best.length != 0 && best.s-best.offset == s-offset && !rep { - // Don't retest if we have the same offset. - return match{offset: offset, s: s} - } - - if load32(dict.dict, candidate) != first { - return match{offset: offset, s: s} - } - m := match{offset: offset, s: s, length: 4 + candidate, rep: rep, dict: true} - s += 4 - if !rep { - for s < sLimitDict && m.length < len(dict.dict) { - if len(src)-s < 8 || len(dict.dict)-m.length < 8 { - if src[s] == dict.dict[m.length] { - m.length++ - s++ - continue - } - break - } - if diff := load64(src, s) ^ load64(dict.dict, m.length); diff != 0 { - m.length += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - m.length += 8 - } - } else { - for s < len(src) && m.length < len(dict.dict) { - if len(src)-s < 8 || len(dict.dict)-m.length < 8 { - if src[s] == dict.dict[m.length] { - m.length++ - s++ - continue - } - break - } - if diff := load64(src, s) ^ load64(dict.dict, m.length); diff != 0 { - m.length += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - m.length += 8 - } - } - m.length -= candidate - m.score = score(m) - if m.score <= -m.s { - // Eliminate if no savings, we might find a better one. - m.length = 0 - } - return m - } - - bestOf := func(a, b match) match { - if b.length == 0 { - return a - } - if a.length == 0 { - return b - } - as := a.score + b.s - bs := b.score + a.s - if as >= bs { - return a - } - return b - } - - if s > 0 { - best = bestOf(matchAt(getCur(candidateL), s, uint32(cv), false), matchAt(getPrev(candidateL), s, uint32(cv), false)) - best = bestOf(best, matchAt(getCur(candidateS), s, uint32(cv), false)) - best = bestOf(best, matchAt(getPrev(candidateS), s, uint32(cv), false)) - } - if dict != nil { - candidateL := dict.bestTableLong[hashL] - candidateS := dict.bestTableShort[hashS] - best = bestOf(best, matchDict(int(candidateL&0xffff), s, uint32(cv), false)) - best = bestOf(best, matchDict(int(candidateL>>16), s, uint32(cv), false)) - best = bestOf(best, matchDict(int(candidateS&0xffff), s, uint32(cv), false)) - best = bestOf(best, matchDict(int(candidateS>>16), s, uint32(cv), false)) - } - { - if (dict == nil || repeat <= s) && repeat > 0 { - best = bestOf(best, matchAt(s-repeat+1, s+1, uint32(cv>>8), true)) - } else if s-repeat < -4 && dict != nil { - candidate := len(dict.dict) - (repeat - s) - best = bestOf(best, matchDict(candidate, s, uint32(cv), true)) - candidate++ - best = bestOf(best, matchDict(candidate, s+1, uint32(cv>>8), true)) - } - - if best.length > 0 { - hashS := hash4(cv>>8, sTableBits) - // s+1 - nextShort := sTable[hashS] - s := s + 1 - cv := load64(src, s) - hashL := hash8(cv, lTableBits) - nextLong := lTable[hashL] - best = bestOf(best, matchAt(getCur(nextShort), s, uint32(cv), false)) - best = bestOf(best, matchAt(getPrev(nextShort), s, uint32(cv), false)) - best = bestOf(best, matchAt(getCur(nextLong), s, uint32(cv), false)) - best = bestOf(best, matchAt(getPrev(nextLong), s, uint32(cv), false)) - - // Dict at + 1 - if dict != nil { - candidateL := dict.bestTableLong[hashL] - candidateS := dict.bestTableShort[hashS] - - best = bestOf(best, matchDict(int(candidateL&0xffff), s, uint32(cv), false)) - best = bestOf(best, matchDict(int(candidateS&0xffff), s, uint32(cv), false)) - } - - // s+2 - if true { - hashS := hash4(cv>>8, sTableBits) - - nextShort = sTable[hashS] - s++ - cv = load64(src, s) - hashL := hash8(cv, lTableBits) - nextLong = lTable[hashL] - - if (dict == nil || repeat <= s) && repeat > 0 { - // Repeat at + 2 - best = bestOf(best, matchAt(s-repeat, s, uint32(cv), true)) - } else if repeat-s > 4 && dict != nil { - candidate := len(dict.dict) - (repeat - s) - best = bestOf(best, matchDict(candidate, s, uint32(cv), true)) - } - best = bestOf(best, matchAt(getCur(nextShort), s, uint32(cv), false)) - best = bestOf(best, matchAt(getPrev(nextShort), s, uint32(cv), false)) - best = bestOf(best, matchAt(getCur(nextLong), s, uint32(cv), false)) - best = bestOf(best, matchAt(getPrev(nextLong), s, uint32(cv), false)) - - // Dict at +2 - // Very small gain - if dict != nil { - candidateL := dict.bestTableLong[hashL] - candidateS := dict.bestTableShort[hashS] - - best = bestOf(best, matchDict(int(candidateL&0xffff), s, uint32(cv), false)) - best = bestOf(best, matchDict(int(candidateS&0xffff), s, uint32(cv), false)) - } - } - // Search for a match at best match end, see if that is better. - // Allow some bytes at the beginning to mismatch. - // Sweet spot is around 1-2 bytes, but depends on input. - // The skipped bytes are tested in Extend backwards, - // and still picked up as part of the match if they do. - const skipBeginning = 2 - const skipEnd = 1 - if sAt := best.s + best.length - skipEnd; sAt < sLimit { - - sBack := best.s + skipBeginning - skipEnd - backL := best.length - skipBeginning - // Load initial values - cv = load64(src, sBack) - - // Grab candidates... - next := lTable[hash8(load64(src, sAt), lTableBits)] - - if checkAt := getCur(next) - backL; checkAt > 0 { - best = bestOf(best, matchAt(checkAt, sBack, uint32(cv), false)) - } - if checkAt := getPrev(next) - backL; checkAt > 0 { - best = bestOf(best, matchAt(checkAt, sBack, uint32(cv), false)) - } - // Disabled: Extremely small gain - if false { - next = sTable[hash4(load64(src, sAt), sTableBits)] - if checkAt := getCur(next) - backL; checkAt > 0 { - best = bestOf(best, matchAt(checkAt, sBack, uint32(cv), false)) - } - if checkAt := getPrev(next) - backL; checkAt > 0 { - best = bestOf(best, matchAt(checkAt, sBack, uint32(cv), false)) - } - } - } - } - } - - // Update table - lTable[hashL] = uint64(s) | candidateL<<32 - sTable[hashS] = uint64(s) | candidateS<<32 - - if best.length > 0 { - break - } - - cv = load64(src, nextS) - s = nextS - } - - // Extend backwards, not needed for repeats... - s = best.s - if !best.rep && !best.dict { - for best.offset > 0 && s > nextEmit && src[best.offset-1] == src[s-1] { - best.offset-- - best.length++ - s-- - } - } - if false && best.offset >= s { - panic(fmt.Errorf("t %d >= s %d", best.offset, s)) - } - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - base := s - offset := s - best.offset - s += best.length - - if offset > 65535 && s-base <= 5 && !best.rep { - // Bail if the match is equal or worse to the encoding. - s = best.s + 1 - if s >= sLimit { - goto emitRemainder - } - cv = load64(src, s) - continue - } - if debug && nextEmit != base { - fmt.Println("EMIT", base-nextEmit, "literals. base-after:", base) - } - d += emitLiteral(dst[d:], src[nextEmit:base]) - if best.rep { - if nextEmit > 0 || best.dict { - if debug { - fmt.Println("REPEAT, length", best.length, "offset:", offset, "s-after:", s, "dict:", best.dict, "best:", best) - } - // same as `add := emitCopy(dst[d:], repeat, s-base)` but skips storing offset. - d += emitRepeat(dst[d:], offset, best.length) - } else { - // First match without dict cannot be a repeat. - if debug { - fmt.Println("COPY, length", best.length, "offset:", offset, "s-after:", s, "dict:", best.dict, "best:", best) - } - d += emitCopy(dst[d:], offset, best.length) - } - } else { - if debug { - fmt.Println("COPY, length", best.length, "offset:", offset, "s-after:", s, "dict:", best.dict, "best:", best) - } - d += emitCopy(dst[d:], offset, best.length) - } - repeat = offset - - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - // Fill tables... - for i := best.s + 1; i < s; i++ { - cv0 := load64(src, i) - long0 := hash8(cv0, lTableBits) - short0 := hash4(cv0, sTableBits) - lTable[long0] = uint64(i) | lTable[long0]<<32 - sTable[short0] = uint64(i) | sTable[short0]<<32 - } - cv = load64(src, s) - } - -emitRemainder: - if nextEmit < len(src) { - // Bail if we exceed the maximum size. - if d+len(src)-nextEmit > dstLimit { - return 0 - } - if debug && nextEmit != s { - fmt.Println("emitted ", len(src)-nextEmit, "literals") - } - d += emitLiteral(dst[d:], src[nextEmit:]) - } - return d -} - -// encodeBlockBestSnappy encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) && -// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize -func encodeBlockBestSnappy(dst, src []byte) (d int) { - // Initialize the hash tables. - const ( - // Long hash matches. - lTableBits = 19 - maxLTableSize = 1 << lTableBits - - // Short hash matches. - sTableBits = 16 - maxSTableSize = 1 << sTableBits - - inputMargin = 8 + 2 - ) - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := len(src) - inputMargin - if len(src) < minNonLiteralBlockSize { - return 0 - } - - var lTable [maxLTableSize]uint64 - var sTable [maxSTableSize]uint64 - - // Bail if we can't compress to at least this. - dstLimit := len(src) - 5 - - // nextEmit is where in src the next emitLiteral should start from. - nextEmit := 0 - - // The encoded form must start with a literal, as there are no previous - // bytes to copy, so we start looking for hash matches at s == 1. - s := 1 - cv := load64(src, s) - - // We search for a repeat at -1, but don't output repeats when nextEmit == 0 - repeat := 1 - const lowbitMask = 0xffffffff - getCur := func(x uint64) int { - return int(x & lowbitMask) - } - getPrev := func(x uint64) int { - return int(x >> 32) - } - const maxSkip = 64 - - for { - type match struct { - offset int - s int - length int - score int - } - var best match - for { - // Next src position to check - nextS := (s-nextEmit)>>8 + 1 - if nextS > maxSkip { - nextS = s + maxSkip - } else { - nextS += s - } - if nextS > sLimit { - goto emitRemainder - } - hashL := hash8(cv, lTableBits) - hashS := hash4(cv, sTableBits) - candidateL := lTable[hashL] - candidateS := sTable[hashS] - - score := func(m match) int { - // Matches that are longer forward are penalized since we must emit it as a literal. - score := m.length - m.s - if nextEmit == m.s { - // If we do not have to emit literals, we save 1 byte - score++ - } - offset := m.s - m.offset - - return score - emitCopyNoRepeatSize(offset, m.length) - } - - matchAt := func(offset, s int, first uint32) match { - if best.length != 0 && best.s-best.offset == s-offset { - // Don't retest if we have the same offset. - return match{offset: offset, s: s} - } - if load32(src, offset) != first { - return match{offset: offset, s: s} - } - m := match{offset: offset, s: s, length: 4 + offset} - s += 4 - for s <= sLimit { - if diff := load64(src, s) ^ load64(src, m.length); diff != 0 { - m.length += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - m.length += 8 - } - m.length -= offset - m.score = score(m) - if m.score <= -m.s { - // Eliminate if no savings, we might find a better one. - m.length = 0 - } - return m - } - - bestOf := func(a, b match) match { - if b.length == 0 { - return a - } - if a.length == 0 { - return b - } - as := a.score + b.s - bs := b.score + a.s - if as >= bs { - return a - } - return b - } - - best = bestOf(matchAt(getCur(candidateL), s, uint32(cv)), matchAt(getPrev(candidateL), s, uint32(cv))) - best = bestOf(best, matchAt(getCur(candidateS), s, uint32(cv))) - best = bestOf(best, matchAt(getPrev(candidateS), s, uint32(cv))) - - { - best = bestOf(best, matchAt(s-repeat+1, s+1, uint32(cv>>8))) - if best.length > 0 { - // s+1 - nextShort := sTable[hash4(cv>>8, sTableBits)] - s := s + 1 - cv := load64(src, s) - nextLong := lTable[hash8(cv, lTableBits)] - best = bestOf(best, matchAt(getCur(nextShort), s, uint32(cv))) - best = bestOf(best, matchAt(getPrev(nextShort), s, uint32(cv))) - best = bestOf(best, matchAt(getCur(nextLong), s, uint32(cv))) - best = bestOf(best, matchAt(getPrev(nextLong), s, uint32(cv))) - // Repeat at + 2 - best = bestOf(best, matchAt(s-repeat+1, s+1, uint32(cv>>8))) - - // s+2 - if true { - nextShort = sTable[hash4(cv>>8, sTableBits)] - s++ - cv = load64(src, s) - nextLong = lTable[hash8(cv, lTableBits)] - best = bestOf(best, matchAt(getCur(nextShort), s, uint32(cv))) - best = bestOf(best, matchAt(getPrev(nextShort), s, uint32(cv))) - best = bestOf(best, matchAt(getCur(nextLong), s, uint32(cv))) - best = bestOf(best, matchAt(getPrev(nextLong), s, uint32(cv))) - } - // Search for a match at best match end, see if that is better. - if sAt := best.s + best.length; sAt < sLimit { - sBack := best.s - backL := best.length - // Load initial values - cv = load64(src, sBack) - // Search for mismatch - next := lTable[hash8(load64(src, sAt), lTableBits)] - //next := sTable[hash4(load64(src, sAt), sTableBits)] - - if checkAt := getCur(next) - backL; checkAt > 0 { - best = bestOf(best, matchAt(checkAt, sBack, uint32(cv))) - } - if checkAt := getPrev(next) - backL; checkAt > 0 { - best = bestOf(best, matchAt(checkAt, sBack, uint32(cv))) - } - } - } - } - - // Update table - lTable[hashL] = uint64(s) | candidateL<<32 - sTable[hashS] = uint64(s) | candidateS<<32 - - if best.length > 0 { - break - } - - cv = load64(src, nextS) - s = nextS - } - - // Extend backwards, not needed for repeats... - s = best.s - if true { - for best.offset > 0 && s > nextEmit && src[best.offset-1] == src[s-1] { - best.offset-- - best.length++ - s-- - } - } - if false && best.offset >= s { - panic(fmt.Errorf("t %d >= s %d", best.offset, s)) - } - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - base := s - offset := s - best.offset - - s += best.length - - if offset > 65535 && s-base <= 5 { - // Bail if the match is equal or worse to the encoding. - s = best.s + 1 - if s >= sLimit { - goto emitRemainder - } - cv = load64(src, s) - continue - } - d += emitLiteral(dst[d:], src[nextEmit:base]) - d += emitCopyNoRepeat(dst[d:], offset, best.length) - repeat = offset - - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - // Fill tables... - for i := best.s + 1; i < s; i++ { - cv0 := load64(src, i) - long0 := hash8(cv0, lTableBits) - short0 := hash4(cv0, sTableBits) - lTable[long0] = uint64(i) | lTable[long0]<<32 - sTable[short0] = uint64(i) | sTable[short0]<<32 - } - cv = load64(src, s) - } - -emitRemainder: - if nextEmit < len(src) { - // Bail if we exceed the maximum size. - if d+len(src)-nextEmit > dstLimit { - return 0 - } - d += emitLiteral(dst[d:], src[nextEmit:]) - } - return d -} - -// emitCopySize returns the size to encode the offset+length -// -// It assumes that: -// -// 1 <= offset && offset <= math.MaxUint32 -// 4 <= length && length <= 1 << 24 -func emitCopySize(offset, length int) int { - if offset >= 65536 { - i := 0 - if length > 64 { - length -= 64 - if length >= 4 { - // Emit remaining as repeats - return 5 + emitRepeatSize(offset, length) - } - i = 5 - } - if length == 0 { - return i - } - return i + 5 - } - - // Offset no more than 2 bytes. - if length > 64 { - if offset < 2048 { - // Emit 8 bytes, then rest as repeats... - return 2 + emitRepeatSize(offset, length-8) - } - // Emit remaining as repeats, at least 4 bytes remain. - return 3 + emitRepeatSize(offset, length-60) - } - if length >= 12 || offset >= 2048 { - return 3 - } - // Emit the remaining copy, encoded as 2 bytes. - return 2 -} - -// emitCopyNoRepeatSize returns the size to encode the offset+length -// -// It assumes that: -// -// 1 <= offset && offset <= math.MaxUint32 -// 4 <= length && length <= 1 << 24 -func emitCopyNoRepeatSize(offset, length int) int { - if offset >= 65536 { - return 5 + 5*(length/64) - } - - // Offset no more than 2 bytes. - if length > 64 { - // Emit remaining as repeats, at least 4 bytes remain. - return 3 + 3*(length/60) - } - if length >= 12 || offset >= 2048 { - return 3 - } - // Emit the remaining copy, encoded as 2 bytes. - return 2 -} - -// emitRepeatSize returns the number of bytes required to encode a repeat. -// Length must be at least 4 and < 1<<24 -func emitRepeatSize(offset, length int) int { - // Repeat offset, make length cheaper - if length <= 4+4 || (length < 8+4 && offset < 2048) { - return 2 - } - if length < (1<<8)+4+4 { - return 3 - } - if length < (1<<16)+(1<<8)+4 { - return 4 - } - const maxRepeat = (1 << 24) - 1 - length -= (1 << 16) - 4 - left := 0 - if length > maxRepeat { - left = length - maxRepeat + 4 - } - if left > 0 { - return 5 + emitRepeatSize(offset, left) - } - return 5 -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_better.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_better.go deleted file mode 100644 index 90ebf89c..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_better.go +++ /dev/null @@ -1,1510 +0,0 @@ -// Copyright 2016 The Snappy-Go Authors. All rights reserved. -// Copyright (c) 2019 Klaus Post. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package s2 - -import ( - "bytes" - "fmt" - "math/bits" -) - -// hash4 returns the hash of the lowest 4 bytes of u to fit in a hash table with h bits. -// Preferably h should be a constant and should always be <32. -func hash4(u uint64, h uint8) uint32 { - const prime4bytes = 2654435761 - return (uint32(u) * prime4bytes) >> ((32 - h) & 31) -} - -// hash5 returns the hash of the lowest 5 bytes of u to fit in a hash table with h bits. -// Preferably h should be a constant and should always be <64. -func hash5(u uint64, h uint8) uint32 { - const prime5bytes = 889523592379 - return uint32(((u << (64 - 40)) * prime5bytes) >> ((64 - h) & 63)) -} - -// hash7 returns the hash of the lowest 7 bytes of u to fit in a hash table with h bits. -// Preferably h should be a constant and should always be <64. -func hash7(u uint64, h uint8) uint32 { - const prime7bytes = 58295818150454627 - return uint32(((u << (64 - 56)) * prime7bytes) >> ((64 - h) & 63)) -} - -// hash8 returns the hash of u to fit in a hash table with h bits. -// Preferably h should be a constant and should always be <64. -func hash8(u uint64, h uint8) uint32 { - const prime8bytes = 0xcf1bbcdcb7a56463 - return uint32((u * prime8bytes) >> ((64 - h) & 63)) -} - -// encodeBlockBetter encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) && -// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize -func encodeBlockBetterGo(dst, src []byte) (d int) { - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := len(src) - inputMargin - if len(src) < minNonLiteralBlockSize { - return 0 - } - - // Initialize the hash tables. - const ( - // Long hash matches. - lTableBits = 17 - maxLTableSize = 1 << lTableBits - - // Short hash matches. - sTableBits = 14 - maxSTableSize = 1 << sTableBits - ) - - var lTable [maxLTableSize]uint32 - var sTable [maxSTableSize]uint32 - - // Bail if we can't compress to at least this. - dstLimit := len(src) - len(src)>>5 - 6 - - // nextEmit is where in src the next emitLiteral should start from. - nextEmit := 0 - - // The encoded form must start with a literal, as there are no previous - // bytes to copy, so we start looking for hash matches at s == 1. - s := 1 - cv := load64(src, s) - - // We initialize repeat to 0, so we never match on first attempt - repeat := 0 - - for { - candidateL := 0 - nextS := 0 - for { - // Next src position to check - nextS = s + (s-nextEmit)>>7 + 1 - if nextS > sLimit { - goto emitRemainder - } - hashL := hash7(cv, lTableBits) - hashS := hash4(cv, sTableBits) - candidateL = int(lTable[hashL]) - candidateS := int(sTable[hashS]) - lTable[hashL] = uint32(s) - sTable[hashS] = uint32(s) - - valLong := load64(src, candidateL) - valShort := load64(src, candidateS) - - // If long matches at least 8 bytes, use that. - if cv == valLong { - break - } - if cv == valShort { - candidateL = candidateS - break - } - - // Check repeat at offset checkRep. - const checkRep = 1 - // Minimum length of a repeat. Tested with various values. - // While 4-5 offers improvements in some, 6 reduces - // regressions significantly. - const wantRepeatBytes = 6 - const repeatMask = ((1 << (wantRepeatBytes * 8)) - 1) << (8 * checkRep) - if false && repeat > 0 && cv&repeatMask == load64(src, s-repeat)&repeatMask { - base := s + checkRep - // Extend back - for i := base - repeat; base > nextEmit && i > 0 && src[i-1] == src[base-1]; { - i-- - base-- - } - d += emitLiteral(dst[d:], src[nextEmit:base]) - - // Extend forward - candidate := s - repeat + wantRepeatBytes + checkRep - s += wantRepeatBytes + checkRep - for s < len(src) { - if len(src)-s < 8 { - if src[s] == src[candidate] { - s++ - candidate++ - continue - } - break - } - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - // same as `add := emitCopy(dst[d:], repeat, s-base)` but skips storing offset. - d += emitRepeat(dst[d:], repeat, s-base) - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - // Index in-between - index0 := base + 1 - index1 := s - 2 - - for index0 < index1 { - cv0 := load64(src, index0) - cv1 := load64(src, index1) - lTable[hash7(cv0, lTableBits)] = uint32(index0) - sTable[hash4(cv0>>8, sTableBits)] = uint32(index0 + 1) - - lTable[hash7(cv1, lTableBits)] = uint32(index1) - sTable[hash4(cv1>>8, sTableBits)] = uint32(index1 + 1) - index0 += 2 - index1 -= 2 - } - - cv = load64(src, s) - continue - } - - // Long likely matches 7, so take that. - if uint32(cv) == uint32(valLong) { - break - } - - // Check our short candidate - if uint32(cv) == uint32(valShort) { - // Try a long candidate at s+1 - hashL = hash7(cv>>8, lTableBits) - candidateL = int(lTable[hashL]) - lTable[hashL] = uint32(s + 1) - if uint32(cv>>8) == load32(src, candidateL) { - s++ - break - } - // Use our short candidate. - candidateL = candidateS - break - } - - cv = load64(src, nextS) - s = nextS - } - - // Extend backwards - for candidateL > 0 && s > nextEmit && src[candidateL-1] == src[s-1] { - candidateL-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - base := s - offset := base - candidateL - - // Extend the 4-byte match as long as possible. - s += 4 - candidateL += 4 - for s < len(src) { - if len(src)-s < 8 { - if src[s] == src[candidateL] { - s++ - candidateL++ - continue - } - break - } - if diff := load64(src, s) ^ load64(src, candidateL); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidateL += 8 - } - - if offset > 65535 && s-base <= 5 && repeat != offset { - // Bail if the match is equal or worse to the encoding. - s = nextS + 1 - if s >= sLimit { - goto emitRemainder - } - cv = load64(src, s) - continue - } - - d += emitLiteral(dst[d:], src[nextEmit:base]) - if repeat == offset { - d += emitRepeat(dst[d:], offset, s-base) - } else { - d += emitCopy(dst[d:], offset, s-base) - repeat = offset - } - - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - - // Index short & long - index0 := base + 1 - index1 := s - 2 - - cv0 := load64(src, index0) - cv1 := load64(src, index1) - lTable[hash7(cv0, lTableBits)] = uint32(index0) - sTable[hash4(cv0>>8, sTableBits)] = uint32(index0 + 1) - - // lTable could be postponed, but very minor difference. - lTable[hash7(cv1, lTableBits)] = uint32(index1) - sTable[hash4(cv1>>8, sTableBits)] = uint32(index1 + 1) - index0 += 1 - index1 -= 1 - cv = load64(src, s) - - // Index large values sparsely in between. - // We do two starting from different offsets for speed. - index2 := (index0 + index1 + 1) >> 1 - for index2 < index1 { - lTable[hash7(load64(src, index0), lTableBits)] = uint32(index0) - lTable[hash7(load64(src, index2), lTableBits)] = uint32(index2) - index0 += 2 - index2 += 2 - } - } - -emitRemainder: - if nextEmit < len(src) { - // Bail if we exceed the maximum size. - if d+len(src)-nextEmit > dstLimit { - return 0 - } - d += emitLiteral(dst[d:], src[nextEmit:]) - } - return d -} - -// encodeBlockBetterSnappyGo encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) && -// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize -func encodeBlockBetterSnappyGo(dst, src []byte) (d int) { - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := len(src) - inputMargin - if len(src) < minNonLiteralBlockSize { - return 0 - } - - // Initialize the hash tables. - const ( - // Long hash matches. - lTableBits = 16 - maxLTableSize = 1 << lTableBits - - // Short hash matches. - sTableBits = 14 - maxSTableSize = 1 << sTableBits - ) - - var lTable [maxLTableSize]uint32 - var sTable [maxSTableSize]uint32 - - // Bail if we can't compress to at least this. - dstLimit := len(src) - len(src)>>5 - 6 - - // nextEmit is where in src the next emitLiteral should start from. - nextEmit := 0 - - // The encoded form must start with a literal, as there are no previous - // bytes to copy, so we start looking for hash matches at s == 1. - s := 1 - cv := load64(src, s) - - // We initialize repeat to 0, so we never match on first attempt - repeat := 0 - const maxSkip = 100 - - for { - candidateL := 0 - nextS := 0 - for { - // Next src position to check - nextS = min(s+(s-nextEmit)>>7+1, s+maxSkip) - - if nextS > sLimit { - goto emitRemainder - } - hashL := hash7(cv, lTableBits) - hashS := hash4(cv, sTableBits) - candidateL = int(lTable[hashL]) - candidateS := int(sTable[hashS]) - lTable[hashL] = uint32(s) - sTable[hashS] = uint32(s) - - if uint32(cv) == load32(src, candidateL) { - break - } - - // Check our short candidate - if uint32(cv) == load32(src, candidateS) { - // Try a long candidate at s+1 - hashL = hash7(cv>>8, lTableBits) - candidateL = int(lTable[hashL]) - lTable[hashL] = uint32(s + 1) - if uint32(cv>>8) == load32(src, candidateL) { - s++ - break - } - // Use our short candidate. - candidateL = candidateS - break - } - - cv = load64(src, nextS) - s = nextS - } - - // Extend backwards - for candidateL > 0 && s > nextEmit && src[candidateL-1] == src[s-1] { - candidateL-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - base := s - offset := base - candidateL - - // Extend the 4-byte match as long as possible. - s += 4 - candidateL += 4 - for s < len(src) { - if len(src)-s < 8 { - if src[s] == src[candidateL] { - s++ - candidateL++ - continue - } - break - } - if diff := load64(src, s) ^ load64(src, candidateL); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidateL += 8 - } - - if offset > 65535 && s-base <= 5 && repeat != offset { - // Bail if the match is equal or worse to the encoding. - s = nextS + 1 - if s >= sLimit { - goto emitRemainder - } - cv = load64(src, s) - continue - } - - d += emitLiteral(dst[d:], src[nextEmit:base]) - d += emitCopyNoRepeat(dst[d:], offset, s-base) - repeat = offset - - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - - // Index short & long - index0 := base + 1 - index1 := s - 2 - - cv0 := load64(src, index0) - cv1 := load64(src, index1) - lTable[hash7(cv0, lTableBits)] = uint32(index0) - sTable[hash4(cv0>>8, sTableBits)] = uint32(index0 + 1) - - lTable[hash7(cv1, lTableBits)] = uint32(index1) - sTable[hash4(cv1>>8, sTableBits)] = uint32(index1 + 1) - index0 += 1 - index1 -= 1 - cv = load64(src, s) - - // Index large values sparsely in between. - // We do two starting from different offsets for speed. - index2 := (index0 + index1 + 1) >> 1 - for index2 < index1 { - lTable[hash7(load64(src, index0), lTableBits)] = uint32(index0) - lTable[hash7(load64(src, index2), lTableBits)] = uint32(index2) - index0 += 2 - index2 += 2 - } - } - -emitRemainder: - if nextEmit < len(src) { - // Bail if we exceed the maximum size. - if d+len(src)-nextEmit > dstLimit { - return 0 - } - d += emitLiteral(dst[d:], src[nextEmit:]) - } - return d -} - -func encodeBlockBetterGo64K(dst, src []byte) (d int) { - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := len(src) - inputMargin - if len(src) < minNonLiteralBlockSize { - return 0 - } - // Initialize the hash tables. - // Use smaller tables for smaller blocks - const ( - // Long hash matches. - lTableBits = 16 - maxLTableSize = 1 << lTableBits - - // Short hash matches. - sTableBits = 13 - maxSTableSize = 1 << sTableBits - ) - - var lTable [maxLTableSize]uint16 - var sTable [maxSTableSize]uint16 - - // Bail if we can't compress to at least this. - dstLimit := len(src) - len(src)>>5 - 6 - - // nextEmit is where in src the next emitLiteral should start from. - nextEmit := 0 - - // The encoded form must start with a literal, as there are no previous - // bytes to copy, so we start looking for hash matches at s == 1. - s := 1 - cv := load64(src, s) - - // We initialize repeat to 0, so we never match on first attempt - repeat := 0 - - for { - candidateL := 0 - nextS := 0 - for { - // Next src position to check - nextS = s + (s-nextEmit)>>6 + 1 - if nextS > sLimit { - goto emitRemainder - } - hashL := hash7(cv, lTableBits) - hashS := hash4(cv, sTableBits) - candidateL = int(lTable[hashL]) - candidateS := int(sTable[hashS]) - lTable[hashL] = uint16(s) - sTable[hashS] = uint16(s) - - valLong := load64(src, candidateL) - valShort := load64(src, candidateS) - - // If long matches at least 8 bytes, use that. - if cv == valLong { - break - } - if cv == valShort { - candidateL = candidateS - break - } - - // Check repeat at offset checkRep. - const checkRep = 1 - // Minimum length of a repeat. Tested with various values. - // While 4-5 offers improvements in some, 6 reduces - // regressions significantly. - const wantRepeatBytes = 6 - const repeatMask = ((1 << (wantRepeatBytes * 8)) - 1) << (8 * checkRep) - if false && repeat > 0 && cv&repeatMask == load64(src, s-repeat)&repeatMask { - base := s + checkRep - // Extend back - for i := base - repeat; base > nextEmit && i > 0 && src[i-1] == src[base-1]; { - i-- - base-- - } - d += emitLiteral(dst[d:], src[nextEmit:base]) - - // Extend forward - candidate := s - repeat + wantRepeatBytes + checkRep - s += wantRepeatBytes + checkRep - for s < len(src) { - if len(src)-s < 8 { - if src[s] == src[candidate] { - s++ - candidate++ - continue - } - break - } - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - // same as `add := emitCopy(dst[d:], repeat, s-base)` but skips storing offset. - d += emitRepeat(dst[d:], repeat, s-base) - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - // Index in-between - index0 := base + 1 - index1 := s - 2 - - for index0 < index1 { - cv0 := load64(src, index0) - cv1 := load64(src, index1) - lTable[hash7(cv0, lTableBits)] = uint16(index0) - sTable[hash4(cv0>>8, sTableBits)] = uint16(index0 + 1) - - lTable[hash7(cv1, lTableBits)] = uint16(index1) - sTable[hash4(cv1>>8, sTableBits)] = uint16(index1 + 1) - index0 += 2 - index1 -= 2 - } - - cv = load64(src, s) - continue - } - - // Long likely matches 7, so take that. - if uint32(cv) == uint32(valLong) { - break - } - - // Check our short candidate - if uint32(cv) == uint32(valShort) { - // Try a long candidate at s+1 - hashL = hash7(cv>>8, lTableBits) - candidateL = int(lTable[hashL]) - lTable[hashL] = uint16(s + 1) - if uint32(cv>>8) == load32(src, candidateL) { - s++ - break - } - // Use our short candidate. - candidateL = candidateS - break - } - - cv = load64(src, nextS) - s = nextS - } - - // Extend backwards - for candidateL > 0 && s > nextEmit && src[candidateL-1] == src[s-1] { - candidateL-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - base := s - offset := base - candidateL - - // Extend the 4-byte match as long as possible. - s += 4 - candidateL += 4 - for s < len(src) { - if len(src)-s < 8 { - if src[s] == src[candidateL] { - s++ - candidateL++ - continue - } - break - } - if diff := load64(src, s) ^ load64(src, candidateL); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidateL += 8 - } - - d += emitLiteral(dst[d:], src[nextEmit:base]) - if repeat == offset { - d += emitRepeat(dst[d:], offset, s-base) - } else { - d += emitCopy(dst[d:], offset, s-base) - repeat = offset - } - - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - - // Index short & long - index0 := base + 1 - index1 := s - 2 - - cv0 := load64(src, index0) - cv1 := load64(src, index1) - lTable[hash7(cv0, lTableBits)] = uint16(index0) - sTable[hash4(cv0>>8, sTableBits)] = uint16(index0 + 1) - - // lTable could be postponed, but very minor difference. - lTable[hash7(cv1, lTableBits)] = uint16(index1) - sTable[hash4(cv1>>8, sTableBits)] = uint16(index1 + 1) - index0 += 1 - index1 -= 1 - cv = load64(src, s) - - // Index large values sparsely in between. - // We do two starting from different offsets for speed. - index2 := (index0 + index1 + 1) >> 1 - for index2 < index1 { - lTable[hash7(load64(src, index0), lTableBits)] = uint16(index0) - lTable[hash7(load64(src, index2), lTableBits)] = uint16(index2) - index0 += 2 - index2 += 2 - } - } - -emitRemainder: - if nextEmit < len(src) { - // Bail if we exceed the maximum size. - if d+len(src)-nextEmit > dstLimit { - return 0 - } - d += emitLiteral(dst[d:], src[nextEmit:]) - } - return d -} - -// encodeBlockBetterSnappyGo encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) && -// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize -func encodeBlockBetterSnappyGo64K(dst, src []byte) (d int) { - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := len(src) - inputMargin - if len(src) < minNonLiteralBlockSize { - return 0 - } - - // Initialize the hash tables. - // Use smaller tables for smaller blocks - const ( - // Long hash matches. - lTableBits = 15 - maxLTableSize = 1 << lTableBits - - // Short hash matches. - sTableBits = 13 - maxSTableSize = 1 << sTableBits - ) - - var lTable [maxLTableSize]uint16 - var sTable [maxSTableSize]uint16 - - // Bail if we can't compress to at least this. - dstLimit := len(src) - len(src)>>5 - 6 - - // nextEmit is where in src the next emitLiteral should start from. - nextEmit := 0 - - // The encoded form must start with a literal, as there are no previous - // bytes to copy, so we start looking for hash matches at s == 1. - s := 1 - cv := load64(src, s) - - const maxSkip = 100 - - for { - candidateL := 0 - nextS := 0 - for { - // Next src position to check - nextS = min(s+(s-nextEmit)>>6+1, s+maxSkip) - - if nextS > sLimit { - goto emitRemainder - } - hashL := hash7(cv, lTableBits) - hashS := hash4(cv, sTableBits) - candidateL = int(lTable[hashL]) - candidateS := int(sTable[hashS]) - lTable[hashL] = uint16(s) - sTable[hashS] = uint16(s) - - if uint32(cv) == load32(src, candidateL) { - break - } - - // Check our short candidate - if uint32(cv) == load32(src, candidateS) { - // Try a long candidate at s+1 - hashL = hash7(cv>>8, lTableBits) - candidateL = int(lTable[hashL]) - lTable[hashL] = uint16(s + 1) - if uint32(cv>>8) == load32(src, candidateL) { - s++ - break - } - // Use our short candidate. - candidateL = candidateS - break - } - - cv = load64(src, nextS) - s = nextS - } - - // Extend backwards - for candidateL > 0 && s > nextEmit && src[candidateL-1] == src[s-1] { - candidateL-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - base := s - offset := base - candidateL - - // Extend the 4-byte match as long as possible. - s += 4 - candidateL += 4 - for s < len(src) { - if len(src)-s < 8 { - if src[s] == src[candidateL] { - s++ - candidateL++ - continue - } - break - } - if diff := load64(src, s) ^ load64(src, candidateL); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidateL += 8 - } - - d += emitLiteral(dst[d:], src[nextEmit:base]) - d += emitCopyNoRepeat(dst[d:], offset, s-base) - - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - - // Index short & long - index0 := base + 1 - index1 := s - 2 - - cv0 := load64(src, index0) - cv1 := load64(src, index1) - lTable[hash7(cv0, lTableBits)] = uint16(index0) - sTable[hash4(cv0>>8, sTableBits)] = uint16(index0 + 1) - - lTable[hash7(cv1, lTableBits)] = uint16(index1) - sTable[hash4(cv1>>8, sTableBits)] = uint16(index1 + 1) - index0 += 1 - index1 -= 1 - cv = load64(src, s) - - // Index large values sparsely in between. - // We do two starting from different offsets for speed. - index2 := (index0 + index1 + 1) >> 1 - for index2 < index1 { - lTable[hash7(load64(src, index0), lTableBits)] = uint16(index0) - lTable[hash7(load64(src, index2), lTableBits)] = uint16(index2) - index0 += 2 - index2 += 2 - } - } - -emitRemainder: - if nextEmit < len(src) { - // Bail if we exceed the maximum size. - if d+len(src)-nextEmit > dstLimit { - return 0 - } - d += emitLiteral(dst[d:], src[nextEmit:]) - } - return d -} - -// encodeBlockBetterDict encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) && -// minNonLiteralBlockSize <= len(src) && len(src) <= maxBlockSize -func encodeBlockBetterDict(dst, src []byte, dict *Dict) (d int) { - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - // Initialize the hash tables. - const ( - // Long hash matches. - lTableBits = 17 - maxLTableSize = 1 << lTableBits - - // Short hash matches. - sTableBits = 14 - maxSTableSize = 1 << sTableBits - - maxAhead = 8 // maximum bytes ahead without checking sLimit - - debug = false - ) - - sLimit := len(src) - inputMargin - if sLimit > MaxDictSrcOffset-maxAhead { - sLimit = MaxDictSrcOffset - maxAhead - } - if len(src) < minNonLiteralBlockSize { - return 0 - } - - dict.initBetter() - - var lTable [maxLTableSize]uint32 - var sTable [maxSTableSize]uint32 - - // Bail if we can't compress to at least this. - dstLimit := len(src) - len(src)>>5 - 6 - - // nextEmit is where in src the next emitLiteral should start from. - nextEmit := 0 - - // The encoded form must start with a literal, as there are no previous - // bytes to copy, so we start looking for hash matches at s == 1. - s := 0 - cv := load64(src, s) - - // We initialize repeat to 0, so we never match on first attempt - repeat := len(dict.dict) - dict.repeat - - // While in dict -searchDict: - for { - candidateL := 0 - nextS := 0 - for { - // Next src position to check - nextS = s + (s-nextEmit)>>7 + 1 - if nextS > sLimit { - break searchDict - } - hashL := hash7(cv, lTableBits) - hashS := hash4(cv, sTableBits) - candidateL = int(lTable[hashL]) - candidateS := int(sTable[hashS]) - dictL := int(dict.betterTableLong[hashL]) - dictS := int(dict.betterTableShort[hashS]) - lTable[hashL] = uint32(s) - sTable[hashS] = uint32(s) - - valLong := load64(src, candidateL) - valShort := load64(src, candidateS) - - // If long matches at least 8 bytes, use that. - if s != 0 { - if cv == valLong { - goto emitMatch - } - if cv == valShort { - candidateL = candidateS - goto emitMatch - } - } - - // Check dict repeat. - if repeat >= s+4 { - candidate := len(dict.dict) - repeat + s - if candidate > 0 && uint32(cv) == load32(dict.dict, candidate) { - // Extend back - base := s - for i := candidate; base > nextEmit && i > 0 && dict.dict[i-1] == src[base-1]; { - i-- - base-- - } - d += emitLiteral(dst[d:], src[nextEmit:base]) - if debug && nextEmit != base { - fmt.Println("emitted ", base-nextEmit, "literals") - } - s += 4 - candidate += 4 - for candidate < len(dict.dict)-8 && s <= len(src)-8 { - if diff := load64(src, s) ^ load64(dict.dict, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - d += emitRepeat(dst[d:], repeat, s-base) - if debug { - fmt.Println("emitted dict repeat length", s-base, "offset:", repeat, "s:", s) - } - nextEmit = s - if s >= sLimit { - break searchDict - } - // Index in-between - index0 := base + 1 - index1 := s - 2 - - cv = load64(src, s) - for index0 < index1 { - cv0 := load64(src, index0) - cv1 := load64(src, index1) - lTable[hash7(cv0, lTableBits)] = uint32(index0) - sTable[hash4(cv0>>8, sTableBits)] = uint32(index0 + 1) - - lTable[hash7(cv1, lTableBits)] = uint32(index1) - sTable[hash4(cv1>>8, sTableBits)] = uint32(index1 + 1) - index0 += 2 - index1 -= 2 - } - continue - } - } - // Don't try to find match at s==0 - if s == 0 { - cv = load64(src, nextS) - s = nextS - continue - } - - // Long likely matches 7, so take that. - if uint32(cv) == uint32(valLong) { - goto emitMatch - } - - // Long dict... - if uint32(cv) == load32(dict.dict, dictL) { - candidateL = dictL - goto emitDict - } - - // Check our short candidate - if uint32(cv) == uint32(valShort) { - // Try a long candidate at s+1 - hashL = hash7(cv>>8, lTableBits) - candidateL = int(lTable[hashL]) - lTable[hashL] = uint32(s + 1) - if uint32(cv>>8) == load32(src, candidateL) { - s++ - goto emitMatch - } - // Use our short candidate. - candidateL = candidateS - goto emitMatch - } - if uint32(cv) == load32(dict.dict, dictS) { - // Try a long candidate at s+1 - hashL = hash7(cv>>8, lTableBits) - candidateL = int(lTable[hashL]) - lTable[hashL] = uint32(s + 1) - if uint32(cv>>8) == load32(src, candidateL) { - s++ - goto emitMatch - } - candidateL = dictS - goto emitDict - } - cv = load64(src, nextS) - s = nextS - } - emitDict: - { - if debug { - if load32(dict.dict, candidateL) != load32(src, s) { - panic("dict emit mismatch") - } - } - // Extend backwards. - // The top bytes will be rechecked to get the full match. - for candidateL > 0 && s > nextEmit && dict.dict[candidateL-1] == src[s-1] { - candidateL-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - - d += emitLiteral(dst[d:], src[nextEmit:s]) - if debug && nextEmit != s { - fmt.Println("emitted ", s-nextEmit, "literals") - } - { - // Invariant: we have a 4-byte match at s, and no need to emit any - // literal bytes prior to s. - base := s - offset := s + (len(dict.dict)) - candidateL - - // Extend the 4-byte match as long as possible. - s += 4 - candidateL += 4 - for s <= len(src)-8 && len(dict.dict)-candidateL >= 8 { - if diff := load64(src, s) ^ load64(dict.dict, candidateL); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidateL += 8 - } - - if repeat == offset { - if debug { - fmt.Println("emitted dict repeat, length", s-base, "offset:", offset, "s:", s, "dict offset:", candidateL) - } - d += emitRepeat(dst[d:], offset, s-base) - } else { - if debug { - fmt.Println("emitted dict copy, length", s-base, "offset:", offset, "s:", s, "dict offset:", candidateL) - } - // Matches longer than 64 are split. - if s <= sLimit || s-base < 8 { - d += emitCopy(dst[d:], offset, s-base) - } else { - // Split to ensure we don't start a copy within next block. - d += emitCopy(dst[d:], offset, 4) - d += emitRepeat(dst[d:], offset, s-base-4) - } - repeat = offset - } - if false { - // Validate match. - if s <= candidateL { - panic("s <= candidate") - } - a := src[base:s] - b := dict.dict[base-repeat : base-repeat+(s-base)] - if !bytes.Equal(a, b) { - panic("mismatch") - } - } - - nextEmit = s - if s >= sLimit { - break searchDict - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - - // Index short & long - index0 := base + 1 - index1 := s - 2 - - cv0 := load64(src, index0) - cv1 := load64(src, index1) - lTable[hash7(cv0, lTableBits)] = uint32(index0) - sTable[hash4(cv0>>8, sTableBits)] = uint32(index0 + 1) - - lTable[hash7(cv1, lTableBits)] = uint32(index1) - sTable[hash4(cv1>>8, sTableBits)] = uint32(index1 + 1) - index0 += 1 - index1 -= 1 - cv = load64(src, s) - - // index every second long in between. - for index0 < index1 { - lTable[hash7(load64(src, index0), lTableBits)] = uint32(index0) - lTable[hash7(load64(src, index1), lTableBits)] = uint32(index1) - index0 += 2 - index1 -= 2 - } - } - continue - } - emitMatch: - - // Extend backwards - for candidateL > 0 && s > nextEmit && src[candidateL-1] == src[s-1] { - candidateL-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - base := s - offset := base - candidateL - - // Extend the 4-byte match as long as possible. - s += 4 - candidateL += 4 - for s < len(src) { - if len(src)-s < 8 { - if src[s] == src[candidateL] { - s++ - candidateL++ - continue - } - break - } - if diff := load64(src, s) ^ load64(src, candidateL); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidateL += 8 - } - - if offset > 65535 && s-base <= 5 && repeat != offset { - // Bail if the match is equal or worse to the encoding. - s = nextS + 1 - if s >= sLimit { - goto emitRemainder - } - cv = load64(src, s) - continue - } - - d += emitLiteral(dst[d:], src[nextEmit:base]) - if debug && nextEmit != s { - fmt.Println("emitted ", s-nextEmit, "literals") - } - if repeat == offset { - if debug { - fmt.Println("emitted match repeat, length", s-base, "offset:", offset, "s:", s) - } - d += emitRepeat(dst[d:], offset, s-base) - } else { - if debug { - fmt.Println("emitted match copy, length", s-base, "offset:", offset, "s:", s) - } - d += emitCopy(dst[d:], offset, s-base) - repeat = offset - } - - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - - // Index short & long - index0 := base + 1 - index1 := s - 2 - - cv0 := load64(src, index0) - cv1 := load64(src, index1) - lTable[hash7(cv0, lTableBits)] = uint32(index0) - sTable[hash4(cv0>>8, sTableBits)] = uint32(index0 + 1) - - lTable[hash7(cv1, lTableBits)] = uint32(index1) - sTable[hash4(cv1>>8, sTableBits)] = uint32(index1 + 1) - index0 += 1 - index1 -= 1 - cv = load64(src, s) - - // Index large values sparsely in between. - // We do two starting from different offsets for speed. - index2 := (index0 + index1 + 1) >> 1 - for index2 < index1 { - lTable[hash7(load64(src, index0), lTableBits)] = uint32(index0) - lTable[hash7(load64(src, index2), lTableBits)] = uint32(index2) - index0 += 2 - index2 += 2 - } - } - - // Search without dict: - if repeat > s { - repeat = 0 - } - - // No more dict - sLimit = len(src) - inputMargin - if s >= sLimit { - goto emitRemainder - } - cv = load64(src, s) - if debug { - fmt.Println("now", s, "->", sLimit, "out:", d, "left:", len(src)-s, "nextemit:", nextEmit, "dstLimit:", dstLimit, "s:", s) - } - for { - candidateL := 0 - nextS := 0 - for { - // Next src position to check - nextS = s + (s-nextEmit)>>7 + 1 - if nextS > sLimit { - goto emitRemainder - } - hashL := hash7(cv, lTableBits) - hashS := hash4(cv, sTableBits) - candidateL = int(lTable[hashL]) - candidateS := int(sTable[hashS]) - lTable[hashL] = uint32(s) - sTable[hashS] = uint32(s) - - valLong := load64(src, candidateL) - valShort := load64(src, candidateS) - - // If long matches at least 8 bytes, use that. - if cv == valLong { - break - } - if cv == valShort { - candidateL = candidateS - break - } - - // Check repeat at offset checkRep. - const checkRep = 1 - // Minimum length of a repeat. Tested with various values. - // While 4-5 offers improvements in some, 6 reduces - // regressions significantly. - const wantRepeatBytes = 6 - const repeatMask = ((1 << (wantRepeatBytes * 8)) - 1) << (8 * checkRep) - if false && repeat > 0 && cv&repeatMask == load64(src, s-repeat)&repeatMask { - base := s + checkRep - // Extend back - for i := base - repeat; base > nextEmit && i > 0 && src[i-1] == src[base-1]; { - i-- - base-- - } - d += emitLiteral(dst[d:], src[nextEmit:base]) - - // Extend forward - candidate := s - repeat + wantRepeatBytes + checkRep - s += wantRepeatBytes + checkRep - for s < len(src) { - if len(src)-s < 8 { - if src[s] == src[candidate] { - s++ - candidate++ - continue - } - break - } - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - // same as `add := emitCopy(dst[d:], repeat, s-base)` but skips storing offset. - d += emitRepeat(dst[d:], repeat, s-base) - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - // Index in-between - index0 := base + 1 - index1 := s - 2 - - for index0 < index1 { - cv0 := load64(src, index0) - cv1 := load64(src, index1) - lTable[hash7(cv0, lTableBits)] = uint32(index0) - sTable[hash4(cv0>>8, sTableBits)] = uint32(index0 + 1) - - lTable[hash7(cv1, lTableBits)] = uint32(index1) - sTable[hash4(cv1>>8, sTableBits)] = uint32(index1 + 1) - index0 += 2 - index1 -= 2 - } - - cv = load64(src, s) - continue - } - - // Long likely matches 7, so take that. - if uint32(cv) == uint32(valLong) { - break - } - - // Check our short candidate - if uint32(cv) == uint32(valShort) { - // Try a long candidate at s+1 - hashL = hash7(cv>>8, lTableBits) - candidateL = int(lTable[hashL]) - lTable[hashL] = uint32(s + 1) - if uint32(cv>>8) == load32(src, candidateL) { - s++ - break - } - // Use our short candidate. - candidateL = candidateS - break - } - - cv = load64(src, nextS) - s = nextS - } - - // Extend backwards - for candidateL > 0 && s > nextEmit && src[candidateL-1] == src[s-1] { - candidateL-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - base := s - offset := base - candidateL - - // Extend the 4-byte match as long as possible. - s += 4 - candidateL += 4 - for s < len(src) { - if len(src)-s < 8 { - if src[s] == src[candidateL] { - s++ - candidateL++ - continue - } - break - } - if diff := load64(src, s) ^ load64(src, candidateL); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidateL += 8 - } - - if offset > 65535 && s-base <= 5 && repeat != offset { - // Bail if the match is equal or worse to the encoding. - s = nextS + 1 - if s >= sLimit { - goto emitRemainder - } - cv = load64(src, s) - continue - } - - d += emitLiteral(dst[d:], src[nextEmit:base]) - if repeat == offset { - d += emitRepeat(dst[d:], offset, s-base) - } else { - d += emitCopy(dst[d:], offset, s-base) - repeat = offset - } - - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - - // Index short & long - index0 := base + 1 - index1 := s - 2 - - cv0 := load64(src, index0) - cv1 := load64(src, index1) - lTable[hash7(cv0, lTableBits)] = uint32(index0) - sTable[hash4(cv0>>8, sTableBits)] = uint32(index0 + 1) - - lTable[hash7(cv1, lTableBits)] = uint32(index1) - sTable[hash4(cv1>>8, sTableBits)] = uint32(index1 + 1) - index0 += 1 - index1 -= 1 - cv = load64(src, s) - - // Index large values sparsely in between. - // We do two starting from different offsets for speed. - index2 := (index0 + index1 + 1) >> 1 - for index2 < index1 { - lTable[hash7(load64(src, index0), lTableBits)] = uint32(index0) - lTable[hash7(load64(src, index2), lTableBits)] = uint32(index2) - index0 += 2 - index2 += 2 - } - } - -emitRemainder: - if nextEmit < len(src) { - // Bail if we exceed the maximum size. - if d+len(src)-nextEmit > dstLimit { - return 0 - } - d += emitLiteral(dst[d:], src[nextEmit:]) - } - return d -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_go.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_go.go deleted file mode 100644 index e25b7844..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encode_go.go +++ /dev/null @@ -1,741 +0,0 @@ -//go:build !amd64 || appengine || !gc || noasm -// +build !amd64 appengine !gc noasm - -package s2 - -import ( - "bytes" - "math/bits" -) - -const hasAmd64Asm = false - -// encodeBlock encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) -func encodeBlock(dst, src []byte) (d int) { - if len(src) < minNonLiteralBlockSize { - return 0 - } - if len(src) <= 64<<10 { - return encodeBlockGo64K(dst, src) - } - return encodeBlockGo(dst, src) -} - -// encodeBlockBetter encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) -func encodeBlockBetter(dst, src []byte) (d int) { - if len(src) <= 64<<10 { - return encodeBlockBetterGo64K(dst, src) - } - return encodeBlockBetterGo(dst, src) -} - -// encodeBlockBetter encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) -func encodeBlockBetterSnappy(dst, src []byte) (d int) { - if len(src) <= 64<<10 { - return encodeBlockBetterSnappyGo64K(dst, src) - } - return encodeBlockBetterSnappyGo(dst, src) -} - -// encodeBlock encodes a non-empty src to a guaranteed-large-enough dst. It -// assumes that the varint-encoded length of the decompressed bytes has already -// been written. -// -// It also assumes that: -// -// len(dst) >= MaxEncodedLen(len(src)) -func encodeBlockSnappy(dst, src []byte) (d int) { - if len(src) < minNonLiteralBlockSize { - return 0 - } - if len(src) <= 64<<10 { - return encodeBlockSnappyGo64K(dst, src) - } - return encodeBlockSnappyGo(dst, src) -} - -// emitLiteral writes a literal chunk and returns the number of bytes written. -// -// It assumes that: -// -// dst is long enough to hold the encoded bytes -// 0 <= len(lit) && len(lit) <= math.MaxUint32 -func emitLiteral(dst, lit []byte) int { - if len(lit) == 0 { - return 0 - } - const num = 63<<2 | tagLiteral - i, n := 0, uint(len(lit)-1) - switch { - case n < 60: - dst[0] = uint8(n)<<2 | tagLiteral - i = 1 - case n < 1<<8: - dst[1] = uint8(n) - dst[0] = 60<<2 | tagLiteral - i = 2 - case n < 1<<16: - dst[2] = uint8(n >> 8) - dst[1] = uint8(n) - dst[0] = 61<<2 | tagLiteral - i = 3 - case n < 1<<24: - dst[3] = uint8(n >> 16) - dst[2] = uint8(n >> 8) - dst[1] = uint8(n) - dst[0] = 62<<2 | tagLiteral - i = 4 - default: - dst[4] = uint8(n >> 24) - dst[3] = uint8(n >> 16) - dst[2] = uint8(n >> 8) - dst[1] = uint8(n) - dst[0] = 63<<2 | tagLiteral - i = 5 - } - return i + copy(dst[i:], lit) -} - -// emitRepeat writes a repeat chunk and returns the number of bytes written. -// Length must be at least 4 and < 1<<24 -func emitRepeat(dst []byte, offset, length int) int { - // Repeat offset, make length cheaper - length -= 4 - if length <= 4 { - dst[0] = uint8(length)<<2 | tagCopy1 - dst[1] = 0 - return 2 - } - if length < 8 && offset < 2048 { - // Encode WITH offset - dst[1] = uint8(offset) - dst[0] = uint8(offset>>8)<<5 | uint8(length)<<2 | tagCopy1 - return 2 - } - if length < (1<<8)+4 { - length -= 4 - dst[2] = uint8(length) - dst[1] = 0 - dst[0] = 5<<2 | tagCopy1 - return 3 - } - if length < (1<<16)+(1<<8) { - length -= 1 << 8 - dst[3] = uint8(length >> 8) - dst[2] = uint8(length >> 0) - dst[1] = 0 - dst[0] = 6<<2 | tagCopy1 - return 4 - } - const maxRepeat = (1 << 24) - 1 - length -= 1 << 16 - left := 0 - if length > maxRepeat { - left = length - maxRepeat + 4 - length = maxRepeat - 4 - } - dst[4] = uint8(length >> 16) - dst[3] = uint8(length >> 8) - dst[2] = uint8(length >> 0) - dst[1] = 0 - dst[0] = 7<<2 | tagCopy1 - if left > 0 { - return 5 + emitRepeat(dst[5:], offset, left) - } - return 5 -} - -// emitCopy writes a copy chunk and returns the number of bytes written. -// -// It assumes that: -// -// dst is long enough to hold the encoded bytes -// 1 <= offset && offset <= math.MaxUint32 -// 4 <= length && length <= 1 << 24 -func emitCopy(dst []byte, offset, length int) int { - if offset >= 65536 { - i := 0 - if length > 64 { - // Emit a length 64 copy, encoded as 5 bytes. - dst[4] = uint8(offset >> 24) - dst[3] = uint8(offset >> 16) - dst[2] = uint8(offset >> 8) - dst[1] = uint8(offset) - dst[0] = 63<<2 | tagCopy4 - length -= 64 - if length >= 4 { - // Emit remaining as repeats - return 5 + emitRepeat(dst[5:], offset, length) - } - i = 5 - } - if length == 0 { - return i - } - // Emit a copy, offset encoded as 4 bytes. - dst[i+0] = uint8(length-1)<<2 | tagCopy4 - dst[i+1] = uint8(offset) - dst[i+2] = uint8(offset >> 8) - dst[i+3] = uint8(offset >> 16) - dst[i+4] = uint8(offset >> 24) - return i + 5 - } - - // Offset no more than 2 bytes. - if length > 64 { - off := 3 - if offset < 2048 { - // emit 8 bytes as tagCopy1, rest as repeats. - dst[1] = uint8(offset) - dst[0] = uint8(offset>>8)<<5 | uint8(8-4)<<2 | tagCopy1 - length -= 8 - off = 2 - } else { - // Emit a length 60 copy, encoded as 3 bytes. - // Emit remaining as repeat value (minimum 4 bytes). - dst[2] = uint8(offset >> 8) - dst[1] = uint8(offset) - dst[0] = 59<<2 | tagCopy2 - length -= 60 - } - // Emit remaining as repeats, at least 4 bytes remain. - return off + emitRepeat(dst[off:], offset, length) - } - if length >= 12 || offset >= 2048 { - // Emit the remaining copy, encoded as 3 bytes. - dst[2] = uint8(offset >> 8) - dst[1] = uint8(offset) - dst[0] = uint8(length-1)<<2 | tagCopy2 - return 3 - } - // Emit the remaining copy, encoded as 2 bytes. - dst[1] = uint8(offset) - dst[0] = uint8(offset>>8)<<5 | uint8(length-4)<<2 | tagCopy1 - return 2 -} - -// emitCopyNoRepeat writes a copy chunk and returns the number of bytes written. -// -// It assumes that: -// -// dst is long enough to hold the encoded bytes -// 1 <= offset && offset <= math.MaxUint32 -// 4 <= length && length <= 1 << 24 -func emitCopyNoRepeat(dst []byte, offset, length int) int { - if offset >= 65536 { - i := 0 - if length > 64 { - // Emit a length 64 copy, encoded as 5 bytes. - dst[4] = uint8(offset >> 24) - dst[3] = uint8(offset >> 16) - dst[2] = uint8(offset >> 8) - dst[1] = uint8(offset) - dst[0] = 63<<2 | tagCopy4 - length -= 64 - if length >= 4 { - // Emit remaining as repeats - return 5 + emitCopyNoRepeat(dst[5:], offset, length) - } - i = 5 - } - if length == 0 { - return i - } - // Emit a copy, offset encoded as 4 bytes. - dst[i+0] = uint8(length-1)<<2 | tagCopy4 - dst[i+1] = uint8(offset) - dst[i+2] = uint8(offset >> 8) - dst[i+3] = uint8(offset >> 16) - dst[i+4] = uint8(offset >> 24) - return i + 5 - } - - // Offset no more than 2 bytes. - if length > 64 { - // Emit a length 60 copy, encoded as 3 bytes. - // Emit remaining as repeat value (minimum 4 bytes). - dst[2] = uint8(offset >> 8) - dst[1] = uint8(offset) - dst[0] = 59<<2 | tagCopy2 - length -= 60 - // Emit remaining as repeats, at least 4 bytes remain. - return 3 + emitCopyNoRepeat(dst[3:], offset, length) - } - if length >= 12 || offset >= 2048 { - // Emit the remaining copy, encoded as 3 bytes. - dst[2] = uint8(offset >> 8) - dst[1] = uint8(offset) - dst[0] = uint8(length-1)<<2 | tagCopy2 - return 3 - } - // Emit the remaining copy, encoded as 2 bytes. - dst[1] = uint8(offset) - dst[0] = uint8(offset>>8)<<5 | uint8(length-4)<<2 | tagCopy1 - return 2 -} - -// matchLen returns how many bytes match in a and b -// -// It assumes that: -// -// len(a) <= len(b) -func matchLen(a []byte, b []byte) int { - b = b[:len(a)] - var checked int - if len(a) > 4 { - // Try 4 bytes first - if diff := load32(a, 0) ^ load32(b, 0); diff != 0 { - return bits.TrailingZeros32(diff) >> 3 - } - // Switch to 8 byte matching. - checked = 4 - a = a[4:] - b = b[4:] - for len(a) >= 8 { - b = b[:len(a)] - if diff := load64(a, 0) ^ load64(b, 0); diff != 0 { - return checked + (bits.TrailingZeros64(diff) >> 3) - } - checked += 8 - a = a[8:] - b = b[8:] - } - } - b = b[:len(a)] - for i := range a { - if a[i] != b[i] { - return int(i) + checked - } - } - return len(a) + checked -} - -// input must be > inputMargin -func calcBlockSize(src []byte, _ *[32768]byte) (d int) { - // Initialize the hash table. - const ( - tableBits = 13 - maxTableSize = 1 << tableBits - ) - - var table [maxTableSize]uint32 - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := len(src) - inputMargin - - // Bail if we can't compress to at least this. - dstLimit := len(src) - len(src)>>5 - 5 - - // nextEmit is where in src the next emitLiteral should start from. - nextEmit := 0 - - // The encoded form must start with a literal, as there are no previous - // bytes to copy, so we start looking for hash matches at s == 1. - s := 1 - cv := load64(src, s) - - // We search for a repeat at -1, but don't output repeats when nextEmit == 0 - repeat := 1 - - for { - candidate := 0 - for { - // Next src position to check - nextS := s + (s-nextEmit)>>6 + 4 - if nextS > sLimit { - goto emitRemainder - } - hash0 := hash6(cv, tableBits) - hash1 := hash6(cv>>8, tableBits) - candidate = int(table[hash0]) - candidate2 := int(table[hash1]) - table[hash0] = uint32(s) - table[hash1] = uint32(s + 1) - hash2 := hash6(cv>>16, tableBits) - - // Check repeat at offset checkRep. - const checkRep = 1 - if uint32(cv>>(checkRep*8)) == load32(src, s-repeat+checkRep) { - base := s + checkRep - // Extend back - for i := base - repeat; base > nextEmit && i > 0 && src[i-1] == src[base-1]; { - i-- - base-- - } - d += emitLiteralSize(src[nextEmit:base]) - - // Extend forward - candidate := s - repeat + 4 + checkRep - s += 4 + checkRep - for s <= sLimit { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - - d += emitCopyNoRepeatSize(repeat, s-base) - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - cv = load64(src, s) - continue - } - - if uint32(cv) == load32(src, candidate) { - break - } - candidate = int(table[hash2]) - if uint32(cv>>8) == load32(src, candidate2) { - table[hash2] = uint32(s + 2) - candidate = candidate2 - s++ - break - } - table[hash2] = uint32(s + 2) - if uint32(cv>>16) == load32(src, candidate) { - s += 2 - break - } - - cv = load64(src, nextS) - s = nextS - } - - // Extend backwards - for candidate > 0 && s > nextEmit && src[candidate-1] == src[s-1] { - candidate-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - - d += emitLiteralSize(src[nextEmit:s]) - - // Call emitCopy, and then see if another emitCopy could be our next - // move. Repeat until we find no match for the input immediately after - // what was consumed by the last emitCopy call. - // - // If we exit this loop normally then we need to call emitLiteral next, - // though we don't yet know how big the literal will be. We handle that - // by proceeding to the next iteration of the main loop. We also can - // exit this loop via goto if we get close to exhausting the input. - for { - // Invariant: we have a 4-byte match at s, and no need to emit any - // literal bytes prior to s. - base := s - repeat = base - candidate - - // Extend the 4-byte match as long as possible. - s += 4 - candidate += 4 - for s <= len(src)-8 { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - - d += emitCopyNoRepeatSize(repeat, s-base) - if false { - // Validate match. - a := src[base:s] - b := src[base-repeat : base-repeat+(s-base)] - if !bytes.Equal(a, b) { - panic("mismatch") - } - } - - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - // Check for an immediate match, otherwise start search at s+1 - x := load64(src, s-2) - m2Hash := hash6(x, tableBits) - currHash := hash6(x>>16, tableBits) - candidate = int(table[currHash]) - table[m2Hash] = uint32(s - 2) - table[currHash] = uint32(s) - if uint32(x>>16) != load32(src, candidate) { - cv = load64(src, s+1) - s++ - break - } - } - } - -emitRemainder: - if nextEmit < len(src) { - // Bail if we exceed the maximum size. - if d+len(src)-nextEmit > dstLimit { - return 0 - } - d += emitLiteralSize(src[nextEmit:]) - } - return d -} - -// length must be > inputMargin. -func calcBlockSizeSmall(src []byte, _ *[2048]byte) (d int) { - // Initialize the hash table. - const ( - tableBits = 9 - maxTableSize = 1 << tableBits - ) - - var table [maxTableSize]uint32 - - // sLimit is when to stop looking for offset/length copies. The inputMargin - // lets us use a fast path for emitLiteral in the main loop, while we are - // looking for copies. - sLimit := len(src) - inputMargin - - // Bail if we can't compress to at least this. - dstLimit := len(src) - len(src)>>5 - 5 - - // nextEmit is where in src the next emitLiteral should start from. - nextEmit := 0 - - // The encoded form must start with a literal, as there are no previous - // bytes to copy, so we start looking for hash matches at s == 1. - s := 1 - cv := load64(src, s) - - // We search for a repeat at -1, but don't output repeats when nextEmit == 0 - repeat := 1 - - for { - candidate := 0 - for { - // Next src position to check - nextS := s + (s-nextEmit)>>6 + 4 - if nextS > sLimit { - goto emitRemainder - } - hash0 := hash6(cv, tableBits) - hash1 := hash6(cv>>8, tableBits) - candidate = int(table[hash0]) - candidate2 := int(table[hash1]) - table[hash0] = uint32(s) - table[hash1] = uint32(s + 1) - hash2 := hash6(cv>>16, tableBits) - - // Check repeat at offset checkRep. - const checkRep = 1 - if uint32(cv>>(checkRep*8)) == load32(src, s-repeat+checkRep) { - base := s + checkRep - // Extend back - for i := base - repeat; base > nextEmit && i > 0 && src[i-1] == src[base-1]; { - i-- - base-- - } - d += emitLiteralSize(src[nextEmit:base]) - - // Extend forward - candidate := s - repeat + 4 + checkRep - s += 4 + checkRep - for s <= sLimit { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - - d += emitCopyNoRepeatSize(repeat, s-base) - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - cv = load64(src, s) - continue - } - - if uint32(cv) == load32(src, candidate) { - break - } - candidate = int(table[hash2]) - if uint32(cv>>8) == load32(src, candidate2) { - table[hash2] = uint32(s + 2) - candidate = candidate2 - s++ - break - } - table[hash2] = uint32(s + 2) - if uint32(cv>>16) == load32(src, candidate) { - s += 2 - break - } - - cv = load64(src, nextS) - s = nextS - } - - // Extend backwards - for candidate > 0 && s > nextEmit && src[candidate-1] == src[s-1] { - candidate-- - s-- - } - - // Bail if we exceed the maximum size. - if d+(s-nextEmit) > dstLimit { - return 0 - } - - // A 4-byte match has been found. We'll later see if more than 4 bytes - // match. But, prior to the match, src[nextEmit:s] are unmatched. Emit - // them as literal bytes. - - d += emitLiteralSize(src[nextEmit:s]) - - // Call emitCopy, and then see if another emitCopy could be our next - // move. Repeat until we find no match for the input immediately after - // what was consumed by the last emitCopy call. - // - // If we exit this loop normally then we need to call emitLiteral next, - // though we don't yet know how big the literal will be. We handle that - // by proceeding to the next iteration of the main loop. We also can - // exit this loop via goto if we get close to exhausting the input. - for { - // Invariant: we have a 4-byte match at s, and no need to emit any - // literal bytes prior to s. - base := s - repeat = base - candidate - - // Extend the 4-byte match as long as possible. - s += 4 - candidate += 4 - for s <= len(src)-8 { - if diff := load64(src, s) ^ load64(src, candidate); diff != 0 { - s += bits.TrailingZeros64(diff) >> 3 - break - } - s += 8 - candidate += 8 - } - - d += emitCopyNoRepeatSize(repeat, s-base) - if false { - // Validate match. - a := src[base:s] - b := src[base-repeat : base-repeat+(s-base)] - if !bytes.Equal(a, b) { - panic("mismatch") - } - } - - nextEmit = s - if s >= sLimit { - goto emitRemainder - } - - if d > dstLimit { - // Do we have space for more, if not bail. - return 0 - } - // Check for an immediate match, otherwise start search at s+1 - x := load64(src, s-2) - m2Hash := hash6(x, tableBits) - currHash := hash6(x>>16, tableBits) - candidate = int(table[currHash]) - table[m2Hash] = uint32(s - 2) - table[currHash] = uint32(s) - if uint32(x>>16) != load32(src, candidate) { - cv = load64(src, s+1) - s++ - break - } - } - } - -emitRemainder: - if nextEmit < len(src) { - // Bail if we exceed the maximum size. - if d+len(src)-nextEmit > dstLimit { - return 0 - } - d += emitLiteralSize(src[nextEmit:]) - } - return d -} - -// emitLiteral writes a literal chunk and returns the number of bytes written. -// -// It assumes that: -// -// dst is long enough to hold the encoded bytes -// 0 <= len(lit) && len(lit) <= math.MaxUint32 -func emitLiteralSize(lit []byte) int { - if len(lit) == 0 { - return 0 - } - switch { - case len(lit) <= 60: - return len(lit) + 1 - case len(lit) <= 1<<8: - return len(lit) + 2 - case len(lit) <= 1<<16: - return len(lit) + 3 - case len(lit) <= 1<<24: - return len(lit) + 4 - default: - return len(lit) + 5 - } -} - -func cvtLZ4BlockAsm(dst []byte, src []byte) (uncompressed int, dstUsed int) { - panic("cvtLZ4BlockAsm should be unreachable") -} - -func cvtLZ4BlockSnappyAsm(dst []byte, src []byte) (uncompressed int, dstUsed int) { - panic("cvtLZ4BlockSnappyAsm should be unreachable") -} - -func cvtLZ4sBlockAsm(dst []byte, src []byte) (uncompressed int, dstUsed int) { - panic("cvtLZ4sBlockAsm should be unreachable") -} - -func cvtLZ4sBlockSnappyAsm(dst []byte, src []byte) (uncompressed int, dstUsed int) { - panic("cvtLZ4sBlockSnappyAsm should be unreachable") -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encodeblock_amd64.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encodeblock_amd64.go deleted file mode 100644 index f43aa815..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encodeblock_amd64.go +++ /dev/null @@ -1,228 +0,0 @@ -// Code generated by command: go run gen.go -out ../encodeblock_amd64.s -stubs ../encodeblock_amd64.go -pkg=s2. DO NOT EDIT. - -//go:build !appengine && !noasm && gc && !noasm - -package s2 - -func _dummy_() - -// encodeBlockAsm encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 4294967295 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeBlockAsm(dst []byte, src []byte, tmp *[65536]byte) int - -// encodeBlockAsm4MB encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 4194304 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeBlockAsm4MB(dst []byte, src []byte, tmp *[65536]byte) int - -// encodeBlockAsm12B encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 16383 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeBlockAsm12B(dst []byte, src []byte, tmp *[16384]byte) int - -// encodeBlockAsm10B encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 4095 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeBlockAsm10B(dst []byte, src []byte, tmp *[4096]byte) int - -// encodeBlockAsm8B encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 511 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeBlockAsm8B(dst []byte, src []byte, tmp *[1024]byte) int - -// encodeBetterBlockAsm encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 4294967295 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeBetterBlockAsm(dst []byte, src []byte, tmp *[589824]byte) int - -// encodeBetterBlockAsm4MB encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 4194304 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeBetterBlockAsm4MB(dst []byte, src []byte, tmp *[589824]byte) int - -// encodeBetterBlockAsm12B encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 16383 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeBetterBlockAsm12B(dst []byte, src []byte, tmp *[81920]byte) int - -// encodeBetterBlockAsm10B encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 4095 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeBetterBlockAsm10B(dst []byte, src []byte, tmp *[20480]byte) int - -// encodeBetterBlockAsm8B encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 511 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeBetterBlockAsm8B(dst []byte, src []byte, tmp *[5120]byte) int - -// encodeSnappyBlockAsm encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 4294967295 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeSnappyBlockAsm(dst []byte, src []byte, tmp *[65536]byte) int - -// encodeSnappyBlockAsm64K encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 65535 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeSnappyBlockAsm64K(dst []byte, src []byte, tmp *[65536]byte) int - -// encodeSnappyBlockAsm12B encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 16383 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeSnappyBlockAsm12B(dst []byte, src []byte, tmp *[16384]byte) int - -// encodeSnappyBlockAsm10B encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 4095 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeSnappyBlockAsm10B(dst []byte, src []byte, tmp *[4096]byte) int - -// encodeSnappyBlockAsm8B encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 511 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeSnappyBlockAsm8B(dst []byte, src []byte, tmp *[1024]byte) int - -// encodeSnappyBetterBlockAsm encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 4294967295 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeSnappyBetterBlockAsm(dst []byte, src []byte, tmp *[589824]byte) int - -// encodeSnappyBetterBlockAsm64K encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 65535 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeSnappyBetterBlockAsm64K(dst []byte, src []byte, tmp *[294912]byte) int - -// encodeSnappyBetterBlockAsm12B encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 16383 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeSnappyBetterBlockAsm12B(dst []byte, src []byte, tmp *[81920]byte) int - -// encodeSnappyBetterBlockAsm10B encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 4095 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeSnappyBetterBlockAsm10B(dst []byte, src []byte, tmp *[20480]byte) int - -// encodeSnappyBetterBlockAsm8B encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 511 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func encodeSnappyBetterBlockAsm8B(dst []byte, src []byte, tmp *[5120]byte) int - -// calcBlockSize encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 4294967295 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func calcBlockSize(src []byte, tmp *[32768]byte) int - -// calcBlockSizeSmall encodes a non-empty src to a guaranteed-large-enough dst. -// Maximum input 1024 bytes. -// It assumes that the varint-encoded length of the decompressed bytes has already been written. -// -//go:noescape -func calcBlockSizeSmall(src []byte, tmp *[2048]byte) int - -// emitLiteral writes a literal chunk and returns the number of bytes written. -// -// It assumes that: -// -// dst is long enough to hold the encoded bytes with margin of 0 bytes -// 0 <= len(lit) && len(lit) <= math.MaxUint32 -// -//go:noescape -func emitLiteral(dst []byte, lit []byte) int - -// emitRepeat writes a repeat chunk and returns the number of bytes written. -// Length must be at least 4 and < 1<<32 -// -//go:noescape -func emitRepeat(dst []byte, offset int, length int) int - -// emitCopy writes a copy chunk and returns the number of bytes written. -// -// It assumes that: -// -// dst is long enough to hold the encoded bytes -// 1 <= offset && offset <= math.MaxUint32 -// 4 <= length && length <= 1 << 24 -// -//go:noescape -func emitCopy(dst []byte, offset int, length int) int - -// emitCopyNoRepeat writes a copy chunk and returns the number of bytes written. -// -// It assumes that: -// -// dst is long enough to hold the encoded bytes -// 1 <= offset && offset <= math.MaxUint32 -// 4 <= length && length <= 1 << 24 -// -//go:noescape -func emitCopyNoRepeat(dst []byte, offset int, length int) int - -// matchLen returns how many bytes match in a and b -// -// It assumes that: -// -// len(a) <= len(b) -// -//go:noescape -func matchLen(a []byte, b []byte) int - -// cvtLZ4Block converts an LZ4 block to S2 -// -//go:noescape -func cvtLZ4BlockAsm(dst []byte, src []byte) (uncompressed int, dstUsed int) - -// cvtLZ4sBlock converts an LZ4s block to S2 -// -//go:noescape -func cvtLZ4sBlockAsm(dst []byte, src []byte) (uncompressed int, dstUsed int) - -// cvtLZ4Block converts an LZ4 block to Snappy -// -//go:noescape -func cvtLZ4BlockSnappyAsm(dst []byte, src []byte) (uncompressed int, dstUsed int) - -// cvtLZ4sBlock converts an LZ4s block to Snappy -// -//go:noescape -func cvtLZ4sBlockSnappyAsm(dst []byte, src []byte) (uncompressed int, dstUsed int) diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encodeblock_amd64.s b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encodeblock_amd64.s deleted file mode 100644 index df9be687..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/encodeblock_amd64.s +++ /dev/null @@ -1,21303 +0,0 @@ -// Code generated by command: go run gen.go -out ../encodeblock_amd64.s -stubs ../encodeblock_amd64.go -pkg=s2. DO NOT EDIT. - -//go:build !appengine && !noasm && gc && !noasm - -#include "textflag.h" - -// func _dummy_() -TEXT ·_dummy_(SB), $0 -#ifdef GOAMD64_v4 -#ifndef GOAMD64_v3 -#define GOAMD64_v3 -#endif -#endif - RET - -// func encodeBlockAsm(dst []byte, src []byte, tmp *[65536]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeBlockAsm(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00000200, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeBlockAsm: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeBlockAsm - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL DX, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeBlockAsm: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x06, SI - LEAL 4(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeBlockAsm - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ DI, R10 - MOVQ DI, R11 - SHRQ $0x08, R11 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x32, R10 - SHLQ $0x10, R11 - IMULQ R9, R11 - SHRQ $0x32, R11 - MOVL (AX)(R10*4), SI - MOVL (AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - LEAL 1(DX), R10 - MOVL R10, (AX)(R11*4) - MOVQ DI, R10 - SHRQ $0x10, R10 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x32, R10 - MOVL DX, R9 - SUBL 16(SP), R9 - MOVL 1(BX)(R9*1), R11 - MOVQ DI, R9 - SHRQ $0x08, R9 - CMPL R9, R11 - JNE no_repeat_found_encodeBlockAsm - LEAL 1(DX), DI - MOVL 12(SP), R8 - MOVL DI, SI - SUBL 16(SP), SI - JZ repeat_extend_back_end_encodeBlockAsm - -repeat_extend_back_loop_encodeBlockAsm: - CMPL DI, R8 - JBE repeat_extend_back_end_encodeBlockAsm - MOVB -1(BX)(SI*1), R9 - MOVB -1(BX)(DI*1), R10 - CMPB R9, R10 - JNE repeat_extend_back_end_encodeBlockAsm - LEAL -1(DI), DI - DECL SI - JNZ repeat_extend_back_loop_encodeBlockAsm - -repeat_extend_back_end_encodeBlockAsm: - MOVL DI, SI - SUBL 12(SP), SI - LEAQ 5(CX)(SI*1), SI - CMPQ SI, (SP) - JB repeat_dst_size_check_encodeBlockAsm - MOVQ $0x00000000, ret+56(FP) - RET - -repeat_dst_size_check_encodeBlockAsm: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_repeat_emit_encodeBlockAsm - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_repeat_emit_encodeBlockAsm - CMPL SI, $0x00000100 - JB two_bytes_repeat_emit_encodeBlockAsm - CMPL SI, $0x00010000 - JB three_bytes_repeat_emit_encodeBlockAsm - CMPL SI, $0x01000000 - JB four_bytes_repeat_emit_encodeBlockAsm - MOVB $0xfc, (CX) - MOVL SI, 1(CX) - ADDQ $0x05, CX - JMP memmove_long_repeat_emit_encodeBlockAsm - -four_bytes_repeat_emit_encodeBlockAsm: - MOVL SI, R11 - SHRL $0x10, R11 - MOVB $0xf8, (CX) - MOVW SI, 1(CX) - MOVB R11, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_repeat_emit_encodeBlockAsm - -three_bytes_repeat_emit_encodeBlockAsm: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_repeat_emit_encodeBlockAsm - -two_bytes_repeat_emit_encodeBlockAsm: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_repeat_emit_encodeBlockAsm - JMP memmove_long_repeat_emit_encodeBlockAsm - -one_byte_repeat_emit_encodeBlockAsm: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_repeat_emit_encodeBlockAsm: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_repeat_emit_encodeBlockAsm_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_repeat_emit_encodeBlockAsm_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_repeat_emit_encodeBlockAsm_memmove_move_17through32 - JMP emit_lit_memmove_repeat_emit_encodeBlockAsm_memmove_move_33through64 - -emit_lit_memmove_repeat_emit_encodeBlockAsm_memmove_move_8: - MOVQ (R10), R11 - MOVQ R11, (CX) - JMP memmove_end_copy_repeat_emit_encodeBlockAsm - -emit_lit_memmove_repeat_emit_encodeBlockAsm_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_repeat_emit_encodeBlockAsm - -emit_lit_memmove_repeat_emit_encodeBlockAsm_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_repeat_emit_encodeBlockAsm - -emit_lit_memmove_repeat_emit_encodeBlockAsm_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_repeat_emit_encodeBlockAsm: - MOVQ SI, CX - JMP emit_literal_done_repeat_emit_encodeBlockAsm - -memmove_long_repeat_emit_encodeBlockAsm: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R12 - SHRQ $0x05, R12 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R13 - SUBQ R11, R13 - DECQ R12 - JA emit_lit_memmove_long_repeat_emit_encodeBlockAsmlarge_forward_sse_loop_32 - LEAQ -32(R10)(R13*1), R11 - LEAQ -32(CX)(R13*1), R14 - -emit_lit_memmove_long_repeat_emit_encodeBlockAsmlarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R14) - MOVOA X5, 16(R14) - ADDQ $0x20, R14 - ADDQ $0x20, R11 - ADDQ $0x20, R13 - DECQ R12 - JNA emit_lit_memmove_long_repeat_emit_encodeBlockAsmlarge_big_loop_back - -emit_lit_memmove_long_repeat_emit_encodeBlockAsmlarge_forward_sse_loop_32: - MOVOU -32(R10)(R13*1), X4 - MOVOU -16(R10)(R13*1), X5 - MOVOA X4, -32(CX)(R13*1) - MOVOA X5, -16(CX)(R13*1) - ADDQ $0x20, R13 - CMPQ R9, R13 - JAE emit_lit_memmove_long_repeat_emit_encodeBlockAsmlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_repeat_emit_encodeBlockAsm: - ADDL $0x05, DX - MOVL DX, SI - SUBL 16(SP), SI - MOVQ src_len+32(FP), R9 - SUBL DX, R9 - LEAQ (BX)(DX*1), R10 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R12, R12 - -matchlen_loopback_16_repeat_extend_encodeBlockAsm: - CMPL R9, $0x10 - JB matchlen_match8_repeat_extend_encodeBlockAsm - MOVQ (R10)(R12*1), R11 - MOVQ 8(R10)(R12*1), R13 - XORQ (SI)(R12*1), R11 - JNZ matchlen_bsf_8_repeat_extend_encodeBlockAsm - XORQ 8(SI)(R12*1), R13 - JNZ matchlen_bsf_16repeat_extend_encodeBlockAsm - LEAL -16(R9), R9 - LEAL 16(R12), R12 - JMP matchlen_loopback_16_repeat_extend_encodeBlockAsm - -matchlen_bsf_16repeat_extend_encodeBlockAsm: -#ifdef GOAMD64_v3 - TZCNTQ R13, R13 - -#else - BSFQ R13, R13 - -#endif - SARQ $0x03, R13 - LEAL 8(R12)(R13*1), R12 - JMP repeat_extend_forward_end_encodeBlockAsm - -matchlen_match8_repeat_extend_encodeBlockAsm: - CMPL R9, $0x08 - JB matchlen_match4_repeat_extend_encodeBlockAsm - MOVQ (R10)(R12*1), R11 - XORQ (SI)(R12*1), R11 - JNZ matchlen_bsf_8_repeat_extend_encodeBlockAsm - LEAL -8(R9), R9 - LEAL 8(R12), R12 - JMP matchlen_match4_repeat_extend_encodeBlockAsm - -matchlen_bsf_8_repeat_extend_encodeBlockAsm: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL (R12)(R11*1), R12 - JMP repeat_extend_forward_end_encodeBlockAsm - -matchlen_match4_repeat_extend_encodeBlockAsm: - CMPL R9, $0x04 - JB matchlen_match2_repeat_extend_encodeBlockAsm - MOVL (R10)(R12*1), R11 - CMPL (SI)(R12*1), R11 - JNE matchlen_match2_repeat_extend_encodeBlockAsm - LEAL -4(R9), R9 - LEAL 4(R12), R12 - -matchlen_match2_repeat_extend_encodeBlockAsm: - CMPL R9, $0x01 - JE matchlen_match1_repeat_extend_encodeBlockAsm - JB repeat_extend_forward_end_encodeBlockAsm - MOVW (R10)(R12*1), R11 - CMPW (SI)(R12*1), R11 - JNE matchlen_match1_repeat_extend_encodeBlockAsm - LEAL 2(R12), R12 - SUBL $0x02, R9 - JZ repeat_extend_forward_end_encodeBlockAsm - -matchlen_match1_repeat_extend_encodeBlockAsm: - MOVB (R10)(R12*1), R11 - CMPB (SI)(R12*1), R11 - JNE repeat_extend_forward_end_encodeBlockAsm - LEAL 1(R12), R12 - -repeat_extend_forward_end_encodeBlockAsm: - ADDL R12, DX - MOVL DX, SI - SUBL DI, SI - MOVL 16(SP), DI - TESTL R8, R8 - JZ repeat_as_copy_encodeBlockAsm - - // emitRepeat -emit_repeat_again_match_repeat_encodeBlockAsm: - MOVL SI, R8 - LEAL -4(SI), SI - CMPL R8, $0x08 - JBE repeat_two_match_repeat_encodeBlockAsm - CMPL R8, $0x0c - JAE cant_repeat_two_offset_match_repeat_encodeBlockAsm - CMPL DI, $0x00000800 - JB repeat_two_offset_match_repeat_encodeBlockAsm - -cant_repeat_two_offset_match_repeat_encodeBlockAsm: - CMPL SI, $0x00000104 - JB repeat_three_match_repeat_encodeBlockAsm - CMPL SI, $0x00010100 - JB repeat_four_match_repeat_encodeBlockAsm - CMPL SI, $0x0100ffff - JB repeat_five_match_repeat_encodeBlockAsm - LEAL -16842747(SI), SI - MOVL $0xfffb001d, (CX) - MOVB $0xff, 4(CX) - ADDQ $0x05, CX - JMP emit_repeat_again_match_repeat_encodeBlockAsm - -repeat_five_match_repeat_encodeBlockAsm: - LEAL -65536(SI), SI - MOVL SI, DI - MOVW $0x001d, (CX) - MOVW SI, 2(CX) - SARL $0x10, DI - MOVB DI, 4(CX) - ADDQ $0x05, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_four_match_repeat_encodeBlockAsm: - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_three_match_repeat_encodeBlockAsm: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_two_match_repeat_encodeBlockAsm: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_two_offset_match_repeat_encodeBlockAsm: - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_as_copy_encodeBlockAsm: - // emitCopy - CMPL DI, $0x00010000 - JB two_byte_offset_repeat_as_copy_encodeBlockAsm - CMPL SI, $0x40 - JBE four_bytes_remain_repeat_as_copy_encodeBlockAsm - MOVB $0xff, (CX) - MOVL DI, 1(CX) - LEAL -64(SI), SI - ADDQ $0x05, CX - CMPL SI, $0x04 - JB four_bytes_remain_repeat_as_copy_encodeBlockAsm - - // emitRepeat -emit_repeat_again_repeat_as_copy_encodeBlockAsm_emit_copy: - MOVL SI, R8 - LEAL -4(SI), SI - CMPL R8, $0x08 - JBE repeat_two_repeat_as_copy_encodeBlockAsm_emit_copy - CMPL R8, $0x0c - JAE cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm_emit_copy - CMPL DI, $0x00000800 - JB repeat_two_offset_repeat_as_copy_encodeBlockAsm_emit_copy - -cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm_emit_copy: - CMPL SI, $0x00000104 - JB repeat_three_repeat_as_copy_encodeBlockAsm_emit_copy - CMPL SI, $0x00010100 - JB repeat_four_repeat_as_copy_encodeBlockAsm_emit_copy - CMPL SI, $0x0100ffff - JB repeat_five_repeat_as_copy_encodeBlockAsm_emit_copy - LEAL -16842747(SI), SI - MOVL $0xfffb001d, (CX) - MOVB $0xff, 4(CX) - ADDQ $0x05, CX - JMP emit_repeat_again_repeat_as_copy_encodeBlockAsm_emit_copy - -repeat_five_repeat_as_copy_encodeBlockAsm_emit_copy: - LEAL -65536(SI), SI - MOVL SI, DI - MOVW $0x001d, (CX) - MOVW SI, 2(CX) - SARL $0x10, DI - MOVB DI, 4(CX) - ADDQ $0x05, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_four_repeat_as_copy_encodeBlockAsm_emit_copy: - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_three_repeat_as_copy_encodeBlockAsm_emit_copy: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_two_repeat_as_copy_encodeBlockAsm_emit_copy: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_two_offset_repeat_as_copy_encodeBlockAsm_emit_copy: - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm - -four_bytes_remain_repeat_as_copy_encodeBlockAsm: - TESTL SI, SI - JZ repeat_end_emit_encodeBlockAsm - XORL R8, R8 - LEAL -1(R8)(SI*4), SI - MOVB SI, (CX) - MOVL DI, 1(CX) - ADDQ $0x05, CX - JMP repeat_end_emit_encodeBlockAsm - -two_byte_offset_repeat_as_copy_encodeBlockAsm: - CMPL SI, $0x40 - JBE two_byte_offset_short_repeat_as_copy_encodeBlockAsm - CMPL DI, $0x00000800 - JAE long_offset_short_repeat_as_copy_encodeBlockAsm - MOVL $0x00000001, R8 - LEAL 16(R8), R8 - MOVB DI, 1(CX) - MOVL DI, R9 - SHRL $0x08, R9 - SHLL $0x05, R9 - ORL R9, R8 - MOVB R8, (CX) - ADDQ $0x02, CX - SUBL $0x08, SI - - // emitRepeat - LEAL -4(SI), SI - JMP cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm_emit_copy_short_2b - -emit_repeat_again_repeat_as_copy_encodeBlockAsm_emit_copy_short_2b: - MOVL SI, R8 - LEAL -4(SI), SI - CMPL R8, $0x08 - JBE repeat_two_repeat_as_copy_encodeBlockAsm_emit_copy_short_2b - CMPL R8, $0x0c - JAE cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm_emit_copy_short_2b - CMPL DI, $0x00000800 - JB repeat_two_offset_repeat_as_copy_encodeBlockAsm_emit_copy_short_2b - -cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm_emit_copy_short_2b: - CMPL SI, $0x00000104 - JB repeat_three_repeat_as_copy_encodeBlockAsm_emit_copy_short_2b - CMPL SI, $0x00010100 - JB repeat_four_repeat_as_copy_encodeBlockAsm_emit_copy_short_2b - CMPL SI, $0x0100ffff - JB repeat_five_repeat_as_copy_encodeBlockAsm_emit_copy_short_2b - LEAL -16842747(SI), SI - MOVL $0xfffb001d, (CX) - MOVB $0xff, 4(CX) - ADDQ $0x05, CX - JMP emit_repeat_again_repeat_as_copy_encodeBlockAsm_emit_copy_short_2b - -repeat_five_repeat_as_copy_encodeBlockAsm_emit_copy_short_2b: - LEAL -65536(SI), SI - MOVL SI, DI - MOVW $0x001d, (CX) - MOVW SI, 2(CX) - SARL $0x10, DI - MOVB DI, 4(CX) - ADDQ $0x05, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_four_repeat_as_copy_encodeBlockAsm_emit_copy_short_2b: - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_three_repeat_as_copy_encodeBlockAsm_emit_copy_short_2b: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_two_repeat_as_copy_encodeBlockAsm_emit_copy_short_2b: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_two_offset_repeat_as_copy_encodeBlockAsm_emit_copy_short_2b: - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm - -long_offset_short_repeat_as_copy_encodeBlockAsm: - MOVB $0xee, (CX) - MOVW DI, 1(CX) - LEAL -60(SI), SI - ADDQ $0x03, CX - - // emitRepeat -emit_repeat_again_repeat_as_copy_encodeBlockAsm_emit_copy_short: - MOVL SI, R8 - LEAL -4(SI), SI - CMPL R8, $0x08 - JBE repeat_two_repeat_as_copy_encodeBlockAsm_emit_copy_short - CMPL R8, $0x0c - JAE cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm_emit_copy_short - CMPL DI, $0x00000800 - JB repeat_two_offset_repeat_as_copy_encodeBlockAsm_emit_copy_short - -cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm_emit_copy_short: - CMPL SI, $0x00000104 - JB repeat_three_repeat_as_copy_encodeBlockAsm_emit_copy_short - CMPL SI, $0x00010100 - JB repeat_four_repeat_as_copy_encodeBlockAsm_emit_copy_short - CMPL SI, $0x0100ffff - JB repeat_five_repeat_as_copy_encodeBlockAsm_emit_copy_short - LEAL -16842747(SI), SI - MOVL $0xfffb001d, (CX) - MOVB $0xff, 4(CX) - ADDQ $0x05, CX - JMP emit_repeat_again_repeat_as_copy_encodeBlockAsm_emit_copy_short - -repeat_five_repeat_as_copy_encodeBlockAsm_emit_copy_short: - LEAL -65536(SI), SI - MOVL SI, DI - MOVW $0x001d, (CX) - MOVW SI, 2(CX) - SARL $0x10, DI - MOVB DI, 4(CX) - ADDQ $0x05, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_four_repeat_as_copy_encodeBlockAsm_emit_copy_short: - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_three_repeat_as_copy_encodeBlockAsm_emit_copy_short: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_two_repeat_as_copy_encodeBlockAsm_emit_copy_short: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm - -repeat_two_offset_repeat_as_copy_encodeBlockAsm_emit_copy_short: - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm - -two_byte_offset_short_repeat_as_copy_encodeBlockAsm: - MOVL SI, R8 - SHLL $0x02, R8 - CMPL SI, $0x0c - JAE emit_copy_three_repeat_as_copy_encodeBlockAsm - CMPL DI, $0x00000800 - JAE emit_copy_three_repeat_as_copy_encodeBlockAsm - LEAL -15(R8), R8 - MOVB DI, 1(CX) - SHRL $0x08, DI - SHLL $0x05, DI - ORL DI, R8 - MOVB R8, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm - -emit_copy_three_repeat_as_copy_encodeBlockAsm: - LEAL -2(R8), R8 - MOVB R8, (CX) - MOVW DI, 1(CX) - ADDQ $0x03, CX - -repeat_end_emit_encodeBlockAsm: - MOVL DX, 12(SP) - JMP search_loop_encodeBlockAsm - -no_repeat_found_encodeBlockAsm: - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeBlockAsm - SHRQ $0x08, DI - MOVL (AX)(R10*4), SI - LEAL 2(DX), R9 - CMPL (BX)(R8*1), DI - JEQ candidate2_match_encodeBlockAsm - MOVL R9, (AX)(R10*4) - SHRQ $0x08, DI - CMPL (BX)(SI*1), DI - JEQ candidate3_match_encodeBlockAsm - MOVL 20(SP), DX - JMP search_loop_encodeBlockAsm - -candidate3_match_encodeBlockAsm: - ADDL $0x02, DX - JMP candidate_match_encodeBlockAsm - -candidate2_match_encodeBlockAsm: - MOVL R9, (AX)(R10*4) - INCL DX - MOVL R8, SI - -candidate_match_encodeBlockAsm: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeBlockAsm - -match_extend_back_loop_encodeBlockAsm: - CMPL DX, DI - JBE match_extend_back_end_encodeBlockAsm - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeBlockAsm - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeBlockAsm - JMP match_extend_back_loop_encodeBlockAsm - -match_extend_back_end_encodeBlockAsm: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 5(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeBlockAsm - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeBlockAsm: - MOVL DX, DI - MOVL 12(SP), R8 - CMPL R8, DI - JEQ emit_literal_done_match_emit_encodeBlockAsm - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(R8*1), DI - SUBL R8, R9 - LEAL -1(R9), R8 - CMPL R8, $0x3c - JB one_byte_match_emit_encodeBlockAsm - CMPL R8, $0x00000100 - JB two_bytes_match_emit_encodeBlockAsm - CMPL R8, $0x00010000 - JB three_bytes_match_emit_encodeBlockAsm - CMPL R8, $0x01000000 - JB four_bytes_match_emit_encodeBlockAsm - MOVB $0xfc, (CX) - MOVL R8, 1(CX) - ADDQ $0x05, CX - JMP memmove_long_match_emit_encodeBlockAsm - -four_bytes_match_emit_encodeBlockAsm: - MOVL R8, R10 - SHRL $0x10, R10 - MOVB $0xf8, (CX) - MOVW R8, 1(CX) - MOVB R10, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_match_emit_encodeBlockAsm - -three_bytes_match_emit_encodeBlockAsm: - MOVB $0xf4, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeBlockAsm - -two_bytes_match_emit_encodeBlockAsm: - MOVB $0xf0, (CX) - MOVB R8, 1(CX) - ADDQ $0x02, CX - CMPL R8, $0x40 - JB memmove_match_emit_encodeBlockAsm - JMP memmove_long_match_emit_encodeBlockAsm - -one_byte_match_emit_encodeBlockAsm: - SHLB $0x02, R8 - MOVB R8, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeBlockAsm: - LEAQ (CX)(R9*1), R8 - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_match_emit_encodeBlockAsm_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeBlockAsm_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeBlockAsm_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeBlockAsm_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeBlockAsm_memmove_move_8: - MOVQ (DI), R10 - MOVQ R10, (CX) - JMP memmove_end_copy_match_emit_encodeBlockAsm - -emit_lit_memmove_match_emit_encodeBlockAsm_memmove_move_8through16: - MOVQ (DI), R10 - MOVQ -8(DI)(R9*1), DI - MOVQ R10, (CX) - MOVQ DI, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBlockAsm - -emit_lit_memmove_match_emit_encodeBlockAsm_memmove_move_17through32: - MOVOU (DI), X0 - MOVOU -16(DI)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBlockAsm - -emit_lit_memmove_match_emit_encodeBlockAsm_memmove_move_33through64: - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeBlockAsm: - MOVQ R8, CX - JMP emit_literal_done_match_emit_encodeBlockAsm - -memmove_long_match_emit_encodeBlockAsm: - LEAQ (CX)(R9*1), R8 - - // genMemMoveLong - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVQ R9, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R12 - SUBQ R10, R12 - DECQ R11 - JA emit_lit_memmove_long_match_emit_encodeBlockAsmlarge_forward_sse_loop_32 - LEAQ -32(DI)(R12*1), R10 - LEAQ -32(CX)(R12*1), R13 - -emit_lit_memmove_long_match_emit_encodeBlockAsmlarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R13) - MOVOA X5, 16(R13) - ADDQ $0x20, R13 - ADDQ $0x20, R10 - ADDQ $0x20, R12 - DECQ R11 - JNA emit_lit_memmove_long_match_emit_encodeBlockAsmlarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeBlockAsmlarge_forward_sse_loop_32: - MOVOU -32(DI)(R12*1), X4 - MOVOU -16(DI)(R12*1), X5 - MOVOA X4, -32(CX)(R12*1) - MOVOA X5, -16(CX)(R12*1) - ADDQ $0x20, R12 - CMPQ R9, R12 - JAE emit_lit_memmove_long_match_emit_encodeBlockAsmlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ R8, CX - -emit_literal_done_match_emit_encodeBlockAsm: -match_nolit_loop_encodeBlockAsm: - MOVL DX, DI - SUBL SI, DI - MOVL DI, 16(SP) - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), DI - SUBL DX, DI - LEAQ (BX)(DX*1), R8 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R10, R10 - -matchlen_loopback_16_match_nolit_encodeBlockAsm: - CMPL DI, $0x10 - JB matchlen_match8_match_nolit_encodeBlockAsm - MOVQ (R8)(R10*1), R9 - MOVQ 8(R8)(R10*1), R11 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeBlockAsm - XORQ 8(SI)(R10*1), R11 - JNZ matchlen_bsf_16match_nolit_encodeBlockAsm - LEAL -16(DI), DI - LEAL 16(R10), R10 - JMP matchlen_loopback_16_match_nolit_encodeBlockAsm - -matchlen_bsf_16match_nolit_encodeBlockAsm: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL 8(R10)(R11*1), R10 - JMP match_nolit_end_encodeBlockAsm - -matchlen_match8_match_nolit_encodeBlockAsm: - CMPL DI, $0x08 - JB matchlen_match4_match_nolit_encodeBlockAsm - MOVQ (R8)(R10*1), R9 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeBlockAsm - LEAL -8(DI), DI - LEAL 8(R10), R10 - JMP matchlen_match4_match_nolit_encodeBlockAsm - -matchlen_bsf_8_match_nolit_encodeBlockAsm: -#ifdef GOAMD64_v3 - TZCNTQ R9, R9 - -#else - BSFQ R9, R9 - -#endif - SARQ $0x03, R9 - LEAL (R10)(R9*1), R10 - JMP match_nolit_end_encodeBlockAsm - -matchlen_match4_match_nolit_encodeBlockAsm: - CMPL DI, $0x04 - JB matchlen_match2_match_nolit_encodeBlockAsm - MOVL (R8)(R10*1), R9 - CMPL (SI)(R10*1), R9 - JNE matchlen_match2_match_nolit_encodeBlockAsm - LEAL -4(DI), DI - LEAL 4(R10), R10 - -matchlen_match2_match_nolit_encodeBlockAsm: - CMPL DI, $0x01 - JE matchlen_match1_match_nolit_encodeBlockAsm - JB match_nolit_end_encodeBlockAsm - MOVW (R8)(R10*1), R9 - CMPW (SI)(R10*1), R9 - JNE matchlen_match1_match_nolit_encodeBlockAsm - LEAL 2(R10), R10 - SUBL $0x02, DI - JZ match_nolit_end_encodeBlockAsm - -matchlen_match1_match_nolit_encodeBlockAsm: - MOVB (R8)(R10*1), R9 - CMPB (SI)(R10*1), R9 - JNE match_nolit_end_encodeBlockAsm - LEAL 1(R10), R10 - -match_nolit_end_encodeBlockAsm: - ADDL R10, DX - MOVL 16(SP), SI - ADDL $0x04, R10 - MOVL DX, 12(SP) - - // emitCopy - CMPL SI, $0x00010000 - JB two_byte_offset_match_nolit_encodeBlockAsm - CMPL R10, $0x40 - JBE four_bytes_remain_match_nolit_encodeBlockAsm - MOVB $0xff, (CX) - MOVL SI, 1(CX) - LEAL -64(R10), R10 - ADDQ $0x05, CX - CMPL R10, $0x04 - JB four_bytes_remain_match_nolit_encodeBlockAsm - - // emitRepeat -emit_repeat_again_match_nolit_encodeBlockAsm_emit_copy: - MOVL R10, DI - LEAL -4(R10), R10 - CMPL DI, $0x08 - JBE repeat_two_match_nolit_encodeBlockAsm_emit_copy - CMPL DI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBlockAsm_emit_copy - CMPL SI, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBlockAsm_emit_copy - -cant_repeat_two_offset_match_nolit_encodeBlockAsm_emit_copy: - CMPL R10, $0x00000104 - JB repeat_three_match_nolit_encodeBlockAsm_emit_copy - CMPL R10, $0x00010100 - JB repeat_four_match_nolit_encodeBlockAsm_emit_copy - CMPL R10, $0x0100ffff - JB repeat_five_match_nolit_encodeBlockAsm_emit_copy - LEAL -16842747(R10), R10 - MOVL $0xfffb001d, (CX) - MOVB $0xff, 4(CX) - ADDQ $0x05, CX - JMP emit_repeat_again_match_nolit_encodeBlockAsm_emit_copy - -repeat_five_match_nolit_encodeBlockAsm_emit_copy: - LEAL -65536(R10), R10 - MOVL R10, SI - MOVW $0x001d, (CX) - MOVW R10, 2(CX) - SARL $0x10, SI - MOVB SI, 4(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -repeat_four_match_nolit_encodeBlockAsm_emit_copy: - LEAL -256(R10), R10 - MOVW $0x0019, (CX) - MOVW R10, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -repeat_three_match_nolit_encodeBlockAsm_emit_copy: - LEAL -4(R10), R10 - MOVW $0x0015, (CX) - MOVB R10, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -repeat_two_match_nolit_encodeBlockAsm_emit_copy: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -repeat_two_offset_match_nolit_encodeBlockAsm_emit_copy: - XORQ DI, DI - LEAL 1(DI)(R10*4), R10 - MOVB SI, 1(CX) - SARL $0x08, SI - SHLL $0x05, SI - ORL SI, R10 - MOVB R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -four_bytes_remain_match_nolit_encodeBlockAsm: - TESTL R10, R10 - JZ match_nolit_emitcopy_end_encodeBlockAsm - XORL DI, DI - LEAL -1(DI)(R10*4), R10 - MOVB R10, (CX) - MOVL SI, 1(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -two_byte_offset_match_nolit_encodeBlockAsm: - CMPL R10, $0x40 - JBE two_byte_offset_short_match_nolit_encodeBlockAsm - CMPL SI, $0x00000800 - JAE long_offset_short_match_nolit_encodeBlockAsm - MOVL $0x00000001, DI - LEAL 16(DI), DI - MOVB SI, 1(CX) - MOVL SI, R8 - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, DI - MOVB DI, (CX) - ADDQ $0x02, CX - SUBL $0x08, R10 - - // emitRepeat - LEAL -4(R10), R10 - JMP cant_repeat_two_offset_match_nolit_encodeBlockAsm_emit_copy_short_2b - -emit_repeat_again_match_nolit_encodeBlockAsm_emit_copy_short_2b: - MOVL R10, DI - LEAL -4(R10), R10 - CMPL DI, $0x08 - JBE repeat_two_match_nolit_encodeBlockAsm_emit_copy_short_2b - CMPL DI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBlockAsm_emit_copy_short_2b - CMPL SI, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBlockAsm_emit_copy_short_2b - -cant_repeat_two_offset_match_nolit_encodeBlockAsm_emit_copy_short_2b: - CMPL R10, $0x00000104 - JB repeat_three_match_nolit_encodeBlockAsm_emit_copy_short_2b - CMPL R10, $0x00010100 - JB repeat_four_match_nolit_encodeBlockAsm_emit_copy_short_2b - CMPL R10, $0x0100ffff - JB repeat_five_match_nolit_encodeBlockAsm_emit_copy_short_2b - LEAL -16842747(R10), R10 - MOVL $0xfffb001d, (CX) - MOVB $0xff, 4(CX) - ADDQ $0x05, CX - JMP emit_repeat_again_match_nolit_encodeBlockAsm_emit_copy_short_2b - -repeat_five_match_nolit_encodeBlockAsm_emit_copy_short_2b: - LEAL -65536(R10), R10 - MOVL R10, SI - MOVW $0x001d, (CX) - MOVW R10, 2(CX) - SARL $0x10, SI - MOVB SI, 4(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -repeat_four_match_nolit_encodeBlockAsm_emit_copy_short_2b: - LEAL -256(R10), R10 - MOVW $0x0019, (CX) - MOVW R10, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -repeat_three_match_nolit_encodeBlockAsm_emit_copy_short_2b: - LEAL -4(R10), R10 - MOVW $0x0015, (CX) - MOVB R10, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -repeat_two_match_nolit_encodeBlockAsm_emit_copy_short_2b: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -repeat_two_offset_match_nolit_encodeBlockAsm_emit_copy_short_2b: - XORQ DI, DI - LEAL 1(DI)(R10*4), R10 - MOVB SI, 1(CX) - SARL $0x08, SI - SHLL $0x05, SI - ORL SI, R10 - MOVB R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -long_offset_short_match_nolit_encodeBlockAsm: - MOVB $0xee, (CX) - MOVW SI, 1(CX) - LEAL -60(R10), R10 - ADDQ $0x03, CX - - // emitRepeat -emit_repeat_again_match_nolit_encodeBlockAsm_emit_copy_short: - MOVL R10, DI - LEAL -4(R10), R10 - CMPL DI, $0x08 - JBE repeat_two_match_nolit_encodeBlockAsm_emit_copy_short - CMPL DI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBlockAsm_emit_copy_short - CMPL SI, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBlockAsm_emit_copy_short - -cant_repeat_two_offset_match_nolit_encodeBlockAsm_emit_copy_short: - CMPL R10, $0x00000104 - JB repeat_three_match_nolit_encodeBlockAsm_emit_copy_short - CMPL R10, $0x00010100 - JB repeat_four_match_nolit_encodeBlockAsm_emit_copy_short - CMPL R10, $0x0100ffff - JB repeat_five_match_nolit_encodeBlockAsm_emit_copy_short - LEAL -16842747(R10), R10 - MOVL $0xfffb001d, (CX) - MOVB $0xff, 4(CX) - ADDQ $0x05, CX - JMP emit_repeat_again_match_nolit_encodeBlockAsm_emit_copy_short - -repeat_five_match_nolit_encodeBlockAsm_emit_copy_short: - LEAL -65536(R10), R10 - MOVL R10, SI - MOVW $0x001d, (CX) - MOVW R10, 2(CX) - SARL $0x10, SI - MOVB SI, 4(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -repeat_four_match_nolit_encodeBlockAsm_emit_copy_short: - LEAL -256(R10), R10 - MOVW $0x0019, (CX) - MOVW R10, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -repeat_three_match_nolit_encodeBlockAsm_emit_copy_short: - LEAL -4(R10), R10 - MOVW $0x0015, (CX) - MOVB R10, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -repeat_two_match_nolit_encodeBlockAsm_emit_copy_short: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -repeat_two_offset_match_nolit_encodeBlockAsm_emit_copy_short: - XORQ DI, DI - LEAL 1(DI)(R10*4), R10 - MOVB SI, 1(CX) - SARL $0x08, SI - SHLL $0x05, SI - ORL SI, R10 - MOVB R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -two_byte_offset_short_match_nolit_encodeBlockAsm: - MOVL R10, DI - SHLL $0x02, DI - CMPL R10, $0x0c - JAE emit_copy_three_match_nolit_encodeBlockAsm - CMPL SI, $0x00000800 - JAE emit_copy_three_match_nolit_encodeBlockAsm - LEAL -15(DI), DI - MOVB SI, 1(CX) - SHRL $0x08, SI - SHLL $0x05, SI - ORL SI, DI - MOVB DI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm - -emit_copy_three_match_nolit_encodeBlockAsm: - LEAL -2(DI), DI - MOVB DI, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - -match_nolit_emitcopy_end_encodeBlockAsm: - CMPL DX, 8(SP) - JAE emit_remainder_encodeBlockAsm - MOVQ -2(BX)(DX*1), DI - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeBlockAsm - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeBlockAsm: - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ DI, R8 - SHRQ $0x10, DI - MOVQ DI, SI - SHLQ $0x10, R8 - IMULQ R9, R8 - SHRQ $0x32, R8 - SHLQ $0x10, SI - IMULQ R9, SI - SHRQ $0x32, SI - LEAL -2(DX), R9 - LEAQ (AX)(SI*4), R10 - MOVL (R10), SI - MOVL R9, (AX)(R8*4) - MOVL DX, (R10) - CMPL (BX)(SI*1), DI - JEQ match_nolit_loop_encodeBlockAsm - INCL DX - JMP search_loop_encodeBlockAsm - -emit_remainder_encodeBlockAsm: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 5(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeBlockAsm - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeBlockAsm: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeBlockAsm - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeBlockAsm - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeBlockAsm - CMPL DX, $0x00010000 - JB three_bytes_emit_remainder_encodeBlockAsm - CMPL DX, $0x01000000 - JB four_bytes_emit_remainder_encodeBlockAsm - MOVB $0xfc, (CX) - MOVL DX, 1(CX) - ADDQ $0x05, CX - JMP memmove_long_emit_remainder_encodeBlockAsm - -four_bytes_emit_remainder_encodeBlockAsm: - MOVL DX, BX - SHRL $0x10, BX - MOVB $0xf8, (CX) - MOVW DX, 1(CX) - MOVB BL, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_emit_remainder_encodeBlockAsm - -three_bytes_emit_remainder_encodeBlockAsm: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeBlockAsm - -two_bytes_emit_remainder_encodeBlockAsm: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeBlockAsm - JMP memmove_long_emit_remainder_encodeBlockAsm - -one_byte_emit_remainder_encodeBlockAsm: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeBlockAsm: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeBlockAsm_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeBlockAsm_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeBlockAsm_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeBlockAsm_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeBlockAsm_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeBlockAsm_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeBlockAsm_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm - -emit_lit_memmove_emit_remainder_encodeBlockAsm_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm - -emit_lit_memmove_emit_remainder_encodeBlockAsm_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm - -emit_lit_memmove_emit_remainder_encodeBlockAsm_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm - -emit_lit_memmove_emit_remainder_encodeBlockAsm_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm - -emit_lit_memmove_emit_remainder_encodeBlockAsm_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeBlockAsm: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeBlockAsm - -memmove_long_emit_remainder_encodeBlockAsm: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeBlockAsmlarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeBlockAsmlarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeBlockAsmlarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeBlockAsmlarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeBlockAsmlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeBlockAsm: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeBlockAsm4MB(dst []byte, src []byte, tmp *[65536]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeBlockAsm4MB(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00000200, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeBlockAsm4MB: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeBlockAsm4MB - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL DX, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeBlockAsm4MB: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x06, SI - LEAL 4(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeBlockAsm4MB - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ DI, R10 - MOVQ DI, R11 - SHRQ $0x08, R11 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x32, R10 - SHLQ $0x10, R11 - IMULQ R9, R11 - SHRQ $0x32, R11 - MOVL (AX)(R10*4), SI - MOVL (AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - LEAL 1(DX), R10 - MOVL R10, (AX)(R11*4) - MOVQ DI, R10 - SHRQ $0x10, R10 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x32, R10 - MOVL DX, R9 - SUBL 16(SP), R9 - MOVL 1(BX)(R9*1), R11 - MOVQ DI, R9 - SHRQ $0x08, R9 - CMPL R9, R11 - JNE no_repeat_found_encodeBlockAsm4MB - LEAL 1(DX), DI - MOVL 12(SP), R8 - MOVL DI, SI - SUBL 16(SP), SI - JZ repeat_extend_back_end_encodeBlockAsm4MB - -repeat_extend_back_loop_encodeBlockAsm4MB: - CMPL DI, R8 - JBE repeat_extend_back_end_encodeBlockAsm4MB - MOVB -1(BX)(SI*1), R9 - MOVB -1(BX)(DI*1), R10 - CMPB R9, R10 - JNE repeat_extend_back_end_encodeBlockAsm4MB - LEAL -1(DI), DI - DECL SI - JNZ repeat_extend_back_loop_encodeBlockAsm4MB - -repeat_extend_back_end_encodeBlockAsm4MB: - MOVL DI, SI - SUBL 12(SP), SI - LEAQ 4(CX)(SI*1), SI - CMPQ SI, (SP) - JB repeat_dst_size_check_encodeBlockAsm4MB - MOVQ $0x00000000, ret+56(FP) - RET - -repeat_dst_size_check_encodeBlockAsm4MB: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_repeat_emit_encodeBlockAsm4MB - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_repeat_emit_encodeBlockAsm4MB - CMPL SI, $0x00000100 - JB two_bytes_repeat_emit_encodeBlockAsm4MB - CMPL SI, $0x00010000 - JB three_bytes_repeat_emit_encodeBlockAsm4MB - MOVL SI, R11 - SHRL $0x10, R11 - MOVB $0xf8, (CX) - MOVW SI, 1(CX) - MOVB R11, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_repeat_emit_encodeBlockAsm4MB - -three_bytes_repeat_emit_encodeBlockAsm4MB: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_repeat_emit_encodeBlockAsm4MB - -two_bytes_repeat_emit_encodeBlockAsm4MB: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_repeat_emit_encodeBlockAsm4MB - JMP memmove_long_repeat_emit_encodeBlockAsm4MB - -one_byte_repeat_emit_encodeBlockAsm4MB: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_repeat_emit_encodeBlockAsm4MB: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_repeat_emit_encodeBlockAsm4MB_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_repeat_emit_encodeBlockAsm4MB_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_repeat_emit_encodeBlockAsm4MB_memmove_move_17through32 - JMP emit_lit_memmove_repeat_emit_encodeBlockAsm4MB_memmove_move_33through64 - -emit_lit_memmove_repeat_emit_encodeBlockAsm4MB_memmove_move_8: - MOVQ (R10), R11 - MOVQ R11, (CX) - JMP memmove_end_copy_repeat_emit_encodeBlockAsm4MB - -emit_lit_memmove_repeat_emit_encodeBlockAsm4MB_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_repeat_emit_encodeBlockAsm4MB - -emit_lit_memmove_repeat_emit_encodeBlockAsm4MB_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_repeat_emit_encodeBlockAsm4MB - -emit_lit_memmove_repeat_emit_encodeBlockAsm4MB_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_repeat_emit_encodeBlockAsm4MB: - MOVQ SI, CX - JMP emit_literal_done_repeat_emit_encodeBlockAsm4MB - -memmove_long_repeat_emit_encodeBlockAsm4MB: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R12 - SHRQ $0x05, R12 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R13 - SUBQ R11, R13 - DECQ R12 - JA emit_lit_memmove_long_repeat_emit_encodeBlockAsm4MBlarge_forward_sse_loop_32 - LEAQ -32(R10)(R13*1), R11 - LEAQ -32(CX)(R13*1), R14 - -emit_lit_memmove_long_repeat_emit_encodeBlockAsm4MBlarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R14) - MOVOA X5, 16(R14) - ADDQ $0x20, R14 - ADDQ $0x20, R11 - ADDQ $0x20, R13 - DECQ R12 - JNA emit_lit_memmove_long_repeat_emit_encodeBlockAsm4MBlarge_big_loop_back - -emit_lit_memmove_long_repeat_emit_encodeBlockAsm4MBlarge_forward_sse_loop_32: - MOVOU -32(R10)(R13*1), X4 - MOVOU -16(R10)(R13*1), X5 - MOVOA X4, -32(CX)(R13*1) - MOVOA X5, -16(CX)(R13*1) - ADDQ $0x20, R13 - CMPQ R9, R13 - JAE emit_lit_memmove_long_repeat_emit_encodeBlockAsm4MBlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_repeat_emit_encodeBlockAsm4MB: - ADDL $0x05, DX - MOVL DX, SI - SUBL 16(SP), SI - MOVQ src_len+32(FP), R9 - SUBL DX, R9 - LEAQ (BX)(DX*1), R10 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R12, R12 - -matchlen_loopback_16_repeat_extend_encodeBlockAsm4MB: - CMPL R9, $0x10 - JB matchlen_match8_repeat_extend_encodeBlockAsm4MB - MOVQ (R10)(R12*1), R11 - MOVQ 8(R10)(R12*1), R13 - XORQ (SI)(R12*1), R11 - JNZ matchlen_bsf_8_repeat_extend_encodeBlockAsm4MB - XORQ 8(SI)(R12*1), R13 - JNZ matchlen_bsf_16repeat_extend_encodeBlockAsm4MB - LEAL -16(R9), R9 - LEAL 16(R12), R12 - JMP matchlen_loopback_16_repeat_extend_encodeBlockAsm4MB - -matchlen_bsf_16repeat_extend_encodeBlockAsm4MB: -#ifdef GOAMD64_v3 - TZCNTQ R13, R13 - -#else - BSFQ R13, R13 - -#endif - SARQ $0x03, R13 - LEAL 8(R12)(R13*1), R12 - JMP repeat_extend_forward_end_encodeBlockAsm4MB - -matchlen_match8_repeat_extend_encodeBlockAsm4MB: - CMPL R9, $0x08 - JB matchlen_match4_repeat_extend_encodeBlockAsm4MB - MOVQ (R10)(R12*1), R11 - XORQ (SI)(R12*1), R11 - JNZ matchlen_bsf_8_repeat_extend_encodeBlockAsm4MB - LEAL -8(R9), R9 - LEAL 8(R12), R12 - JMP matchlen_match4_repeat_extend_encodeBlockAsm4MB - -matchlen_bsf_8_repeat_extend_encodeBlockAsm4MB: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL (R12)(R11*1), R12 - JMP repeat_extend_forward_end_encodeBlockAsm4MB - -matchlen_match4_repeat_extend_encodeBlockAsm4MB: - CMPL R9, $0x04 - JB matchlen_match2_repeat_extend_encodeBlockAsm4MB - MOVL (R10)(R12*1), R11 - CMPL (SI)(R12*1), R11 - JNE matchlen_match2_repeat_extend_encodeBlockAsm4MB - LEAL -4(R9), R9 - LEAL 4(R12), R12 - -matchlen_match2_repeat_extend_encodeBlockAsm4MB: - CMPL R9, $0x01 - JE matchlen_match1_repeat_extend_encodeBlockAsm4MB - JB repeat_extend_forward_end_encodeBlockAsm4MB - MOVW (R10)(R12*1), R11 - CMPW (SI)(R12*1), R11 - JNE matchlen_match1_repeat_extend_encodeBlockAsm4MB - LEAL 2(R12), R12 - SUBL $0x02, R9 - JZ repeat_extend_forward_end_encodeBlockAsm4MB - -matchlen_match1_repeat_extend_encodeBlockAsm4MB: - MOVB (R10)(R12*1), R11 - CMPB (SI)(R12*1), R11 - JNE repeat_extend_forward_end_encodeBlockAsm4MB - LEAL 1(R12), R12 - -repeat_extend_forward_end_encodeBlockAsm4MB: - ADDL R12, DX - MOVL DX, SI - SUBL DI, SI - MOVL 16(SP), DI - TESTL R8, R8 - JZ repeat_as_copy_encodeBlockAsm4MB - - // emitRepeat - MOVL SI, R8 - LEAL -4(SI), SI - CMPL R8, $0x08 - JBE repeat_two_match_repeat_encodeBlockAsm4MB - CMPL R8, $0x0c - JAE cant_repeat_two_offset_match_repeat_encodeBlockAsm4MB - CMPL DI, $0x00000800 - JB repeat_two_offset_match_repeat_encodeBlockAsm4MB - -cant_repeat_two_offset_match_repeat_encodeBlockAsm4MB: - CMPL SI, $0x00000104 - JB repeat_three_match_repeat_encodeBlockAsm4MB - CMPL SI, $0x00010100 - JB repeat_four_match_repeat_encodeBlockAsm4MB - LEAL -65536(SI), SI - MOVL SI, DI - MOVW $0x001d, (CX) - MOVW SI, 2(CX) - SARL $0x10, DI - MOVB DI, 4(CX) - ADDQ $0x05, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_four_match_repeat_encodeBlockAsm4MB: - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_three_match_repeat_encodeBlockAsm4MB: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_two_match_repeat_encodeBlockAsm4MB: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_two_offset_match_repeat_encodeBlockAsm4MB: - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_as_copy_encodeBlockAsm4MB: - // emitCopy - CMPL DI, $0x00010000 - JB two_byte_offset_repeat_as_copy_encodeBlockAsm4MB - CMPL SI, $0x40 - JBE four_bytes_remain_repeat_as_copy_encodeBlockAsm4MB - MOVB $0xff, (CX) - MOVL DI, 1(CX) - LEAL -64(SI), SI - ADDQ $0x05, CX - CMPL SI, $0x04 - JB four_bytes_remain_repeat_as_copy_encodeBlockAsm4MB - - // emitRepeat - MOVL SI, R8 - LEAL -4(SI), SI - CMPL R8, $0x08 - JBE repeat_two_repeat_as_copy_encodeBlockAsm4MB_emit_copy - CMPL R8, $0x0c - JAE cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm4MB_emit_copy - CMPL DI, $0x00000800 - JB repeat_two_offset_repeat_as_copy_encodeBlockAsm4MB_emit_copy - -cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm4MB_emit_copy: - CMPL SI, $0x00000104 - JB repeat_three_repeat_as_copy_encodeBlockAsm4MB_emit_copy - CMPL SI, $0x00010100 - JB repeat_four_repeat_as_copy_encodeBlockAsm4MB_emit_copy - LEAL -65536(SI), SI - MOVL SI, DI - MOVW $0x001d, (CX) - MOVW SI, 2(CX) - SARL $0x10, DI - MOVB DI, 4(CX) - ADDQ $0x05, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_four_repeat_as_copy_encodeBlockAsm4MB_emit_copy: - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_three_repeat_as_copy_encodeBlockAsm4MB_emit_copy: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_two_repeat_as_copy_encodeBlockAsm4MB_emit_copy: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_two_offset_repeat_as_copy_encodeBlockAsm4MB_emit_copy: - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -four_bytes_remain_repeat_as_copy_encodeBlockAsm4MB: - TESTL SI, SI - JZ repeat_end_emit_encodeBlockAsm4MB - XORL R8, R8 - LEAL -1(R8)(SI*4), SI - MOVB SI, (CX) - MOVL DI, 1(CX) - ADDQ $0x05, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -two_byte_offset_repeat_as_copy_encodeBlockAsm4MB: - CMPL SI, $0x40 - JBE two_byte_offset_short_repeat_as_copy_encodeBlockAsm4MB - CMPL DI, $0x00000800 - JAE long_offset_short_repeat_as_copy_encodeBlockAsm4MB - MOVL $0x00000001, R8 - LEAL 16(R8), R8 - MOVB DI, 1(CX) - SHRL $0x08, DI - SHLL $0x05, DI - ORL DI, R8 - MOVB R8, (CX) - ADDQ $0x02, CX - SUBL $0x08, SI - - // emitRepeat - LEAL -4(SI), SI - JMP cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short_2b - MOVL SI, R8 - LEAL -4(SI), SI - CMPL R8, $0x08 - JBE repeat_two_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short_2b - CMPL R8, $0x0c - JAE cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short_2b - CMPL DI, $0x00000800 - JB repeat_two_offset_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short_2b - -cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short_2b: - CMPL SI, $0x00000104 - JB repeat_three_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short_2b - CMPL SI, $0x00010100 - JB repeat_four_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short_2b - LEAL -65536(SI), SI - MOVL SI, DI - MOVW $0x001d, (CX) - MOVW SI, 2(CX) - SARL $0x10, DI - MOVB DI, 4(CX) - ADDQ $0x05, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_four_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short_2b: - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_three_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short_2b: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_two_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short_2b: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_two_offset_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short_2b: - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -long_offset_short_repeat_as_copy_encodeBlockAsm4MB: - MOVB $0xee, (CX) - MOVW DI, 1(CX) - LEAL -60(SI), SI - ADDQ $0x03, CX - - // emitRepeat - MOVL SI, R8 - LEAL -4(SI), SI - CMPL R8, $0x08 - JBE repeat_two_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short - CMPL R8, $0x0c - JAE cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short - CMPL DI, $0x00000800 - JB repeat_two_offset_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short - -cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short: - CMPL SI, $0x00000104 - JB repeat_three_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short - CMPL SI, $0x00010100 - JB repeat_four_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short - LEAL -65536(SI), SI - MOVL SI, DI - MOVW $0x001d, (CX) - MOVW SI, 2(CX) - SARL $0x10, DI - MOVB DI, 4(CX) - ADDQ $0x05, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_four_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short: - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_three_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_two_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -repeat_two_offset_repeat_as_copy_encodeBlockAsm4MB_emit_copy_short: - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -two_byte_offset_short_repeat_as_copy_encodeBlockAsm4MB: - MOVL SI, R8 - SHLL $0x02, R8 - CMPL SI, $0x0c - JAE emit_copy_three_repeat_as_copy_encodeBlockAsm4MB - CMPL DI, $0x00000800 - JAE emit_copy_three_repeat_as_copy_encodeBlockAsm4MB - LEAL -15(R8), R8 - MOVB DI, 1(CX) - SHRL $0x08, DI - SHLL $0x05, DI - ORL DI, R8 - MOVB R8, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm4MB - -emit_copy_three_repeat_as_copy_encodeBlockAsm4MB: - LEAL -2(R8), R8 - MOVB R8, (CX) - MOVW DI, 1(CX) - ADDQ $0x03, CX - -repeat_end_emit_encodeBlockAsm4MB: - MOVL DX, 12(SP) - JMP search_loop_encodeBlockAsm4MB - -no_repeat_found_encodeBlockAsm4MB: - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeBlockAsm4MB - SHRQ $0x08, DI - MOVL (AX)(R10*4), SI - LEAL 2(DX), R9 - CMPL (BX)(R8*1), DI - JEQ candidate2_match_encodeBlockAsm4MB - MOVL R9, (AX)(R10*4) - SHRQ $0x08, DI - CMPL (BX)(SI*1), DI - JEQ candidate3_match_encodeBlockAsm4MB - MOVL 20(SP), DX - JMP search_loop_encodeBlockAsm4MB - -candidate3_match_encodeBlockAsm4MB: - ADDL $0x02, DX - JMP candidate_match_encodeBlockAsm4MB - -candidate2_match_encodeBlockAsm4MB: - MOVL R9, (AX)(R10*4) - INCL DX - MOVL R8, SI - -candidate_match_encodeBlockAsm4MB: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeBlockAsm4MB - -match_extend_back_loop_encodeBlockAsm4MB: - CMPL DX, DI - JBE match_extend_back_end_encodeBlockAsm4MB - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeBlockAsm4MB - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeBlockAsm4MB - JMP match_extend_back_loop_encodeBlockAsm4MB - -match_extend_back_end_encodeBlockAsm4MB: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 4(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeBlockAsm4MB - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeBlockAsm4MB: - MOVL DX, DI - MOVL 12(SP), R8 - CMPL R8, DI - JEQ emit_literal_done_match_emit_encodeBlockAsm4MB - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(R8*1), DI - SUBL R8, R9 - LEAL -1(R9), R8 - CMPL R8, $0x3c - JB one_byte_match_emit_encodeBlockAsm4MB - CMPL R8, $0x00000100 - JB two_bytes_match_emit_encodeBlockAsm4MB - CMPL R8, $0x00010000 - JB three_bytes_match_emit_encodeBlockAsm4MB - MOVL R8, R10 - SHRL $0x10, R10 - MOVB $0xf8, (CX) - MOVW R8, 1(CX) - MOVB R10, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_match_emit_encodeBlockAsm4MB - -three_bytes_match_emit_encodeBlockAsm4MB: - MOVB $0xf4, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeBlockAsm4MB - -two_bytes_match_emit_encodeBlockAsm4MB: - MOVB $0xf0, (CX) - MOVB R8, 1(CX) - ADDQ $0x02, CX - CMPL R8, $0x40 - JB memmove_match_emit_encodeBlockAsm4MB - JMP memmove_long_match_emit_encodeBlockAsm4MB - -one_byte_match_emit_encodeBlockAsm4MB: - SHLB $0x02, R8 - MOVB R8, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeBlockAsm4MB: - LEAQ (CX)(R9*1), R8 - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_match_emit_encodeBlockAsm4MB_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeBlockAsm4MB_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeBlockAsm4MB_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeBlockAsm4MB_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeBlockAsm4MB_memmove_move_8: - MOVQ (DI), R10 - MOVQ R10, (CX) - JMP memmove_end_copy_match_emit_encodeBlockAsm4MB - -emit_lit_memmove_match_emit_encodeBlockAsm4MB_memmove_move_8through16: - MOVQ (DI), R10 - MOVQ -8(DI)(R9*1), DI - MOVQ R10, (CX) - MOVQ DI, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBlockAsm4MB - -emit_lit_memmove_match_emit_encodeBlockAsm4MB_memmove_move_17through32: - MOVOU (DI), X0 - MOVOU -16(DI)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBlockAsm4MB - -emit_lit_memmove_match_emit_encodeBlockAsm4MB_memmove_move_33through64: - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeBlockAsm4MB: - MOVQ R8, CX - JMP emit_literal_done_match_emit_encodeBlockAsm4MB - -memmove_long_match_emit_encodeBlockAsm4MB: - LEAQ (CX)(R9*1), R8 - - // genMemMoveLong - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVQ R9, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R12 - SUBQ R10, R12 - DECQ R11 - JA emit_lit_memmove_long_match_emit_encodeBlockAsm4MBlarge_forward_sse_loop_32 - LEAQ -32(DI)(R12*1), R10 - LEAQ -32(CX)(R12*1), R13 - -emit_lit_memmove_long_match_emit_encodeBlockAsm4MBlarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R13) - MOVOA X5, 16(R13) - ADDQ $0x20, R13 - ADDQ $0x20, R10 - ADDQ $0x20, R12 - DECQ R11 - JNA emit_lit_memmove_long_match_emit_encodeBlockAsm4MBlarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeBlockAsm4MBlarge_forward_sse_loop_32: - MOVOU -32(DI)(R12*1), X4 - MOVOU -16(DI)(R12*1), X5 - MOVOA X4, -32(CX)(R12*1) - MOVOA X5, -16(CX)(R12*1) - ADDQ $0x20, R12 - CMPQ R9, R12 - JAE emit_lit_memmove_long_match_emit_encodeBlockAsm4MBlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ R8, CX - -emit_literal_done_match_emit_encodeBlockAsm4MB: -match_nolit_loop_encodeBlockAsm4MB: - MOVL DX, DI - SUBL SI, DI - MOVL DI, 16(SP) - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), DI - SUBL DX, DI - LEAQ (BX)(DX*1), R8 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R10, R10 - -matchlen_loopback_16_match_nolit_encodeBlockAsm4MB: - CMPL DI, $0x10 - JB matchlen_match8_match_nolit_encodeBlockAsm4MB - MOVQ (R8)(R10*1), R9 - MOVQ 8(R8)(R10*1), R11 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeBlockAsm4MB - XORQ 8(SI)(R10*1), R11 - JNZ matchlen_bsf_16match_nolit_encodeBlockAsm4MB - LEAL -16(DI), DI - LEAL 16(R10), R10 - JMP matchlen_loopback_16_match_nolit_encodeBlockAsm4MB - -matchlen_bsf_16match_nolit_encodeBlockAsm4MB: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL 8(R10)(R11*1), R10 - JMP match_nolit_end_encodeBlockAsm4MB - -matchlen_match8_match_nolit_encodeBlockAsm4MB: - CMPL DI, $0x08 - JB matchlen_match4_match_nolit_encodeBlockAsm4MB - MOVQ (R8)(R10*1), R9 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeBlockAsm4MB - LEAL -8(DI), DI - LEAL 8(R10), R10 - JMP matchlen_match4_match_nolit_encodeBlockAsm4MB - -matchlen_bsf_8_match_nolit_encodeBlockAsm4MB: -#ifdef GOAMD64_v3 - TZCNTQ R9, R9 - -#else - BSFQ R9, R9 - -#endif - SARQ $0x03, R9 - LEAL (R10)(R9*1), R10 - JMP match_nolit_end_encodeBlockAsm4MB - -matchlen_match4_match_nolit_encodeBlockAsm4MB: - CMPL DI, $0x04 - JB matchlen_match2_match_nolit_encodeBlockAsm4MB - MOVL (R8)(R10*1), R9 - CMPL (SI)(R10*1), R9 - JNE matchlen_match2_match_nolit_encodeBlockAsm4MB - LEAL -4(DI), DI - LEAL 4(R10), R10 - -matchlen_match2_match_nolit_encodeBlockAsm4MB: - CMPL DI, $0x01 - JE matchlen_match1_match_nolit_encodeBlockAsm4MB - JB match_nolit_end_encodeBlockAsm4MB - MOVW (R8)(R10*1), R9 - CMPW (SI)(R10*1), R9 - JNE matchlen_match1_match_nolit_encodeBlockAsm4MB - LEAL 2(R10), R10 - SUBL $0x02, DI - JZ match_nolit_end_encodeBlockAsm4MB - -matchlen_match1_match_nolit_encodeBlockAsm4MB: - MOVB (R8)(R10*1), R9 - CMPB (SI)(R10*1), R9 - JNE match_nolit_end_encodeBlockAsm4MB - LEAL 1(R10), R10 - -match_nolit_end_encodeBlockAsm4MB: - ADDL R10, DX - MOVL 16(SP), SI - ADDL $0x04, R10 - MOVL DX, 12(SP) - - // emitCopy - CMPL SI, $0x00010000 - JB two_byte_offset_match_nolit_encodeBlockAsm4MB - CMPL R10, $0x40 - JBE four_bytes_remain_match_nolit_encodeBlockAsm4MB - MOVB $0xff, (CX) - MOVL SI, 1(CX) - LEAL -64(R10), R10 - ADDQ $0x05, CX - CMPL R10, $0x04 - JB four_bytes_remain_match_nolit_encodeBlockAsm4MB - - // emitRepeat - MOVL R10, DI - LEAL -4(R10), R10 - CMPL DI, $0x08 - JBE repeat_two_match_nolit_encodeBlockAsm4MB_emit_copy - CMPL DI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBlockAsm4MB_emit_copy - CMPL SI, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBlockAsm4MB_emit_copy - -cant_repeat_two_offset_match_nolit_encodeBlockAsm4MB_emit_copy: - CMPL R10, $0x00000104 - JB repeat_three_match_nolit_encodeBlockAsm4MB_emit_copy - CMPL R10, $0x00010100 - JB repeat_four_match_nolit_encodeBlockAsm4MB_emit_copy - LEAL -65536(R10), R10 - MOVL R10, SI - MOVW $0x001d, (CX) - MOVW R10, 2(CX) - SARL $0x10, SI - MOVB SI, 4(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -repeat_four_match_nolit_encodeBlockAsm4MB_emit_copy: - LEAL -256(R10), R10 - MOVW $0x0019, (CX) - MOVW R10, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -repeat_three_match_nolit_encodeBlockAsm4MB_emit_copy: - LEAL -4(R10), R10 - MOVW $0x0015, (CX) - MOVB R10, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -repeat_two_match_nolit_encodeBlockAsm4MB_emit_copy: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -repeat_two_offset_match_nolit_encodeBlockAsm4MB_emit_copy: - XORQ DI, DI - LEAL 1(DI)(R10*4), R10 - MOVB SI, 1(CX) - SARL $0x08, SI - SHLL $0x05, SI - ORL SI, R10 - MOVB R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -four_bytes_remain_match_nolit_encodeBlockAsm4MB: - TESTL R10, R10 - JZ match_nolit_emitcopy_end_encodeBlockAsm4MB - XORL DI, DI - LEAL -1(DI)(R10*4), R10 - MOVB R10, (CX) - MOVL SI, 1(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -two_byte_offset_match_nolit_encodeBlockAsm4MB: - CMPL R10, $0x40 - JBE two_byte_offset_short_match_nolit_encodeBlockAsm4MB - CMPL SI, $0x00000800 - JAE long_offset_short_match_nolit_encodeBlockAsm4MB - MOVL $0x00000001, DI - LEAL 16(DI), DI - MOVB SI, 1(CX) - SHRL $0x08, SI - SHLL $0x05, SI - ORL SI, DI - MOVB DI, (CX) - ADDQ $0x02, CX - SUBL $0x08, R10 - - // emitRepeat - LEAL -4(R10), R10 - JMP cant_repeat_two_offset_match_nolit_encodeBlockAsm4MB_emit_copy_short_2b - MOVL R10, DI - LEAL -4(R10), R10 - CMPL DI, $0x08 - JBE repeat_two_match_nolit_encodeBlockAsm4MB_emit_copy_short_2b - CMPL DI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBlockAsm4MB_emit_copy_short_2b - CMPL SI, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBlockAsm4MB_emit_copy_short_2b - -cant_repeat_two_offset_match_nolit_encodeBlockAsm4MB_emit_copy_short_2b: - CMPL R10, $0x00000104 - JB repeat_three_match_nolit_encodeBlockAsm4MB_emit_copy_short_2b - CMPL R10, $0x00010100 - JB repeat_four_match_nolit_encodeBlockAsm4MB_emit_copy_short_2b - LEAL -65536(R10), R10 - MOVL R10, SI - MOVW $0x001d, (CX) - MOVW R10, 2(CX) - SARL $0x10, SI - MOVB SI, 4(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -repeat_four_match_nolit_encodeBlockAsm4MB_emit_copy_short_2b: - LEAL -256(R10), R10 - MOVW $0x0019, (CX) - MOVW R10, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -repeat_three_match_nolit_encodeBlockAsm4MB_emit_copy_short_2b: - LEAL -4(R10), R10 - MOVW $0x0015, (CX) - MOVB R10, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -repeat_two_match_nolit_encodeBlockAsm4MB_emit_copy_short_2b: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -repeat_two_offset_match_nolit_encodeBlockAsm4MB_emit_copy_short_2b: - XORQ DI, DI - LEAL 1(DI)(R10*4), R10 - MOVB SI, 1(CX) - SARL $0x08, SI - SHLL $0x05, SI - ORL SI, R10 - MOVB R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -long_offset_short_match_nolit_encodeBlockAsm4MB: - MOVB $0xee, (CX) - MOVW SI, 1(CX) - LEAL -60(R10), R10 - ADDQ $0x03, CX - - // emitRepeat - MOVL R10, DI - LEAL -4(R10), R10 - CMPL DI, $0x08 - JBE repeat_two_match_nolit_encodeBlockAsm4MB_emit_copy_short - CMPL DI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBlockAsm4MB_emit_copy_short - CMPL SI, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBlockAsm4MB_emit_copy_short - -cant_repeat_two_offset_match_nolit_encodeBlockAsm4MB_emit_copy_short: - CMPL R10, $0x00000104 - JB repeat_three_match_nolit_encodeBlockAsm4MB_emit_copy_short - CMPL R10, $0x00010100 - JB repeat_four_match_nolit_encodeBlockAsm4MB_emit_copy_short - LEAL -65536(R10), R10 - MOVL R10, SI - MOVW $0x001d, (CX) - MOVW R10, 2(CX) - SARL $0x10, SI - MOVB SI, 4(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -repeat_four_match_nolit_encodeBlockAsm4MB_emit_copy_short: - LEAL -256(R10), R10 - MOVW $0x0019, (CX) - MOVW R10, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -repeat_three_match_nolit_encodeBlockAsm4MB_emit_copy_short: - LEAL -4(R10), R10 - MOVW $0x0015, (CX) - MOVB R10, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -repeat_two_match_nolit_encodeBlockAsm4MB_emit_copy_short: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -repeat_two_offset_match_nolit_encodeBlockAsm4MB_emit_copy_short: - XORQ DI, DI - LEAL 1(DI)(R10*4), R10 - MOVB SI, 1(CX) - SARL $0x08, SI - SHLL $0x05, SI - ORL SI, R10 - MOVB R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -two_byte_offset_short_match_nolit_encodeBlockAsm4MB: - MOVL R10, DI - SHLL $0x02, DI - CMPL R10, $0x0c - JAE emit_copy_three_match_nolit_encodeBlockAsm4MB - CMPL SI, $0x00000800 - JAE emit_copy_three_match_nolit_encodeBlockAsm4MB - LEAL -15(DI), DI - MOVB SI, 1(CX) - SHRL $0x08, SI - SHLL $0x05, SI - ORL SI, DI - MOVB DI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm4MB - -emit_copy_three_match_nolit_encodeBlockAsm4MB: - LEAL -2(DI), DI - MOVB DI, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - -match_nolit_emitcopy_end_encodeBlockAsm4MB: - CMPL DX, 8(SP) - JAE emit_remainder_encodeBlockAsm4MB - MOVQ -2(BX)(DX*1), DI - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeBlockAsm4MB - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeBlockAsm4MB: - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ DI, R8 - SHRQ $0x10, DI - MOVQ DI, SI - SHLQ $0x10, R8 - IMULQ R9, R8 - SHRQ $0x32, R8 - SHLQ $0x10, SI - IMULQ R9, SI - SHRQ $0x32, SI - LEAL -2(DX), R9 - LEAQ (AX)(SI*4), R10 - MOVL (R10), SI - MOVL R9, (AX)(R8*4) - MOVL DX, (R10) - CMPL (BX)(SI*1), DI - JEQ match_nolit_loop_encodeBlockAsm4MB - INCL DX - JMP search_loop_encodeBlockAsm4MB - -emit_remainder_encodeBlockAsm4MB: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 4(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeBlockAsm4MB - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeBlockAsm4MB: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeBlockAsm4MB - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeBlockAsm4MB - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeBlockAsm4MB - CMPL DX, $0x00010000 - JB three_bytes_emit_remainder_encodeBlockAsm4MB - MOVL DX, BX - SHRL $0x10, BX - MOVB $0xf8, (CX) - MOVW DX, 1(CX) - MOVB BL, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_emit_remainder_encodeBlockAsm4MB - -three_bytes_emit_remainder_encodeBlockAsm4MB: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeBlockAsm4MB - -two_bytes_emit_remainder_encodeBlockAsm4MB: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeBlockAsm4MB - JMP memmove_long_emit_remainder_encodeBlockAsm4MB - -one_byte_emit_remainder_encodeBlockAsm4MB: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeBlockAsm4MB: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeBlockAsm4MB_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeBlockAsm4MB_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeBlockAsm4MB_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeBlockAsm4MB_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeBlockAsm4MB_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeBlockAsm4MB_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeBlockAsm4MB_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm4MB - -emit_lit_memmove_emit_remainder_encodeBlockAsm4MB_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm4MB - -emit_lit_memmove_emit_remainder_encodeBlockAsm4MB_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm4MB - -emit_lit_memmove_emit_remainder_encodeBlockAsm4MB_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm4MB - -emit_lit_memmove_emit_remainder_encodeBlockAsm4MB_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm4MB - -emit_lit_memmove_emit_remainder_encodeBlockAsm4MB_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeBlockAsm4MB: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeBlockAsm4MB - -memmove_long_emit_remainder_encodeBlockAsm4MB: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeBlockAsm4MBlarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeBlockAsm4MBlarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeBlockAsm4MBlarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeBlockAsm4MBlarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeBlockAsm4MBlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeBlockAsm4MB: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeBlockAsm12B(dst []byte, src []byte, tmp *[16384]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeBlockAsm12B(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00000080, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeBlockAsm12B: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeBlockAsm12B - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL DX, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeBlockAsm12B: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x05, SI - LEAL 4(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeBlockAsm12B - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x000000cf1bbcdcbb, R9 - MOVQ DI, R10 - MOVQ DI, R11 - SHRQ $0x08, R11 - SHLQ $0x18, R10 - IMULQ R9, R10 - SHRQ $0x34, R10 - SHLQ $0x18, R11 - IMULQ R9, R11 - SHRQ $0x34, R11 - MOVL (AX)(R10*4), SI - MOVL (AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - LEAL 1(DX), R10 - MOVL R10, (AX)(R11*4) - MOVQ DI, R10 - SHRQ $0x10, R10 - SHLQ $0x18, R10 - IMULQ R9, R10 - SHRQ $0x34, R10 - MOVL DX, R9 - SUBL 16(SP), R9 - MOVL 1(BX)(R9*1), R11 - MOVQ DI, R9 - SHRQ $0x08, R9 - CMPL R9, R11 - JNE no_repeat_found_encodeBlockAsm12B - LEAL 1(DX), DI - MOVL 12(SP), R8 - MOVL DI, SI - SUBL 16(SP), SI - JZ repeat_extend_back_end_encodeBlockAsm12B - -repeat_extend_back_loop_encodeBlockAsm12B: - CMPL DI, R8 - JBE repeat_extend_back_end_encodeBlockAsm12B - MOVB -1(BX)(SI*1), R9 - MOVB -1(BX)(DI*1), R10 - CMPB R9, R10 - JNE repeat_extend_back_end_encodeBlockAsm12B - LEAL -1(DI), DI - DECL SI - JNZ repeat_extend_back_loop_encodeBlockAsm12B - -repeat_extend_back_end_encodeBlockAsm12B: - MOVL DI, SI - SUBL 12(SP), SI - LEAQ 3(CX)(SI*1), SI - CMPQ SI, (SP) - JB repeat_dst_size_check_encodeBlockAsm12B - MOVQ $0x00000000, ret+56(FP) - RET - -repeat_dst_size_check_encodeBlockAsm12B: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_repeat_emit_encodeBlockAsm12B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_repeat_emit_encodeBlockAsm12B - CMPL SI, $0x00000100 - JB two_bytes_repeat_emit_encodeBlockAsm12B - JB three_bytes_repeat_emit_encodeBlockAsm12B - -three_bytes_repeat_emit_encodeBlockAsm12B: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_repeat_emit_encodeBlockAsm12B - -two_bytes_repeat_emit_encodeBlockAsm12B: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_repeat_emit_encodeBlockAsm12B - JMP memmove_long_repeat_emit_encodeBlockAsm12B - -one_byte_repeat_emit_encodeBlockAsm12B: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_repeat_emit_encodeBlockAsm12B: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_repeat_emit_encodeBlockAsm12B_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_repeat_emit_encodeBlockAsm12B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_repeat_emit_encodeBlockAsm12B_memmove_move_17through32 - JMP emit_lit_memmove_repeat_emit_encodeBlockAsm12B_memmove_move_33through64 - -emit_lit_memmove_repeat_emit_encodeBlockAsm12B_memmove_move_8: - MOVQ (R10), R11 - MOVQ R11, (CX) - JMP memmove_end_copy_repeat_emit_encodeBlockAsm12B - -emit_lit_memmove_repeat_emit_encodeBlockAsm12B_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_repeat_emit_encodeBlockAsm12B - -emit_lit_memmove_repeat_emit_encodeBlockAsm12B_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_repeat_emit_encodeBlockAsm12B - -emit_lit_memmove_repeat_emit_encodeBlockAsm12B_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_repeat_emit_encodeBlockAsm12B: - MOVQ SI, CX - JMP emit_literal_done_repeat_emit_encodeBlockAsm12B - -memmove_long_repeat_emit_encodeBlockAsm12B: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R12 - SHRQ $0x05, R12 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R13 - SUBQ R11, R13 - DECQ R12 - JA emit_lit_memmove_long_repeat_emit_encodeBlockAsm12Blarge_forward_sse_loop_32 - LEAQ -32(R10)(R13*1), R11 - LEAQ -32(CX)(R13*1), R14 - -emit_lit_memmove_long_repeat_emit_encodeBlockAsm12Blarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R14) - MOVOA X5, 16(R14) - ADDQ $0x20, R14 - ADDQ $0x20, R11 - ADDQ $0x20, R13 - DECQ R12 - JNA emit_lit_memmove_long_repeat_emit_encodeBlockAsm12Blarge_big_loop_back - -emit_lit_memmove_long_repeat_emit_encodeBlockAsm12Blarge_forward_sse_loop_32: - MOVOU -32(R10)(R13*1), X4 - MOVOU -16(R10)(R13*1), X5 - MOVOA X4, -32(CX)(R13*1) - MOVOA X5, -16(CX)(R13*1) - ADDQ $0x20, R13 - CMPQ R9, R13 - JAE emit_lit_memmove_long_repeat_emit_encodeBlockAsm12Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_repeat_emit_encodeBlockAsm12B: - ADDL $0x05, DX - MOVL DX, SI - SUBL 16(SP), SI - MOVQ src_len+32(FP), R9 - SUBL DX, R9 - LEAQ (BX)(DX*1), R10 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R12, R12 - -matchlen_loopback_16_repeat_extend_encodeBlockAsm12B: - CMPL R9, $0x10 - JB matchlen_match8_repeat_extend_encodeBlockAsm12B - MOVQ (R10)(R12*1), R11 - MOVQ 8(R10)(R12*1), R13 - XORQ (SI)(R12*1), R11 - JNZ matchlen_bsf_8_repeat_extend_encodeBlockAsm12B - XORQ 8(SI)(R12*1), R13 - JNZ matchlen_bsf_16repeat_extend_encodeBlockAsm12B - LEAL -16(R9), R9 - LEAL 16(R12), R12 - JMP matchlen_loopback_16_repeat_extend_encodeBlockAsm12B - -matchlen_bsf_16repeat_extend_encodeBlockAsm12B: -#ifdef GOAMD64_v3 - TZCNTQ R13, R13 - -#else - BSFQ R13, R13 - -#endif - SARQ $0x03, R13 - LEAL 8(R12)(R13*1), R12 - JMP repeat_extend_forward_end_encodeBlockAsm12B - -matchlen_match8_repeat_extend_encodeBlockAsm12B: - CMPL R9, $0x08 - JB matchlen_match4_repeat_extend_encodeBlockAsm12B - MOVQ (R10)(R12*1), R11 - XORQ (SI)(R12*1), R11 - JNZ matchlen_bsf_8_repeat_extend_encodeBlockAsm12B - LEAL -8(R9), R9 - LEAL 8(R12), R12 - JMP matchlen_match4_repeat_extend_encodeBlockAsm12B - -matchlen_bsf_8_repeat_extend_encodeBlockAsm12B: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL (R12)(R11*1), R12 - JMP repeat_extend_forward_end_encodeBlockAsm12B - -matchlen_match4_repeat_extend_encodeBlockAsm12B: - CMPL R9, $0x04 - JB matchlen_match2_repeat_extend_encodeBlockAsm12B - MOVL (R10)(R12*1), R11 - CMPL (SI)(R12*1), R11 - JNE matchlen_match2_repeat_extend_encodeBlockAsm12B - LEAL -4(R9), R9 - LEAL 4(R12), R12 - -matchlen_match2_repeat_extend_encodeBlockAsm12B: - CMPL R9, $0x01 - JE matchlen_match1_repeat_extend_encodeBlockAsm12B - JB repeat_extend_forward_end_encodeBlockAsm12B - MOVW (R10)(R12*1), R11 - CMPW (SI)(R12*1), R11 - JNE matchlen_match1_repeat_extend_encodeBlockAsm12B - LEAL 2(R12), R12 - SUBL $0x02, R9 - JZ repeat_extend_forward_end_encodeBlockAsm12B - -matchlen_match1_repeat_extend_encodeBlockAsm12B: - MOVB (R10)(R12*1), R11 - CMPB (SI)(R12*1), R11 - JNE repeat_extend_forward_end_encodeBlockAsm12B - LEAL 1(R12), R12 - -repeat_extend_forward_end_encodeBlockAsm12B: - ADDL R12, DX - MOVL DX, SI - SUBL DI, SI - MOVL 16(SP), DI - TESTL R8, R8 - JZ repeat_as_copy_encodeBlockAsm12B - - // emitRepeat - MOVL SI, R8 - LEAL -4(SI), SI - CMPL R8, $0x08 - JBE repeat_two_match_repeat_encodeBlockAsm12B - CMPL R8, $0x0c - JAE cant_repeat_two_offset_match_repeat_encodeBlockAsm12B - CMPL DI, $0x00000800 - JB repeat_two_offset_match_repeat_encodeBlockAsm12B - -cant_repeat_two_offset_match_repeat_encodeBlockAsm12B: - CMPL SI, $0x00000104 - JB repeat_three_match_repeat_encodeBlockAsm12B - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm12B - -repeat_three_match_repeat_encodeBlockAsm12B: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm12B - -repeat_two_match_repeat_encodeBlockAsm12B: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm12B - -repeat_two_offset_match_repeat_encodeBlockAsm12B: - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm12B - -repeat_as_copy_encodeBlockAsm12B: - // emitCopy - CMPL SI, $0x40 - JBE two_byte_offset_short_repeat_as_copy_encodeBlockAsm12B - CMPL DI, $0x00000800 - JAE long_offset_short_repeat_as_copy_encodeBlockAsm12B - MOVL $0x00000001, R8 - LEAL 16(R8), R8 - MOVB DI, 1(CX) - SHRL $0x08, DI - SHLL $0x05, DI - ORL DI, R8 - MOVB R8, (CX) - ADDQ $0x02, CX - SUBL $0x08, SI - - // emitRepeat - LEAL -4(SI), SI - JMP cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm12B_emit_copy_short_2b - MOVL SI, R8 - LEAL -4(SI), SI - CMPL R8, $0x08 - JBE repeat_two_repeat_as_copy_encodeBlockAsm12B_emit_copy_short_2b - CMPL R8, $0x0c - JAE cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm12B_emit_copy_short_2b - CMPL DI, $0x00000800 - JB repeat_two_offset_repeat_as_copy_encodeBlockAsm12B_emit_copy_short_2b - -cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm12B_emit_copy_short_2b: - CMPL SI, $0x00000104 - JB repeat_three_repeat_as_copy_encodeBlockAsm12B_emit_copy_short_2b - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm12B - -repeat_three_repeat_as_copy_encodeBlockAsm12B_emit_copy_short_2b: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm12B - -repeat_two_repeat_as_copy_encodeBlockAsm12B_emit_copy_short_2b: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm12B - -repeat_two_offset_repeat_as_copy_encodeBlockAsm12B_emit_copy_short_2b: - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm12B - -long_offset_short_repeat_as_copy_encodeBlockAsm12B: - MOVB $0xee, (CX) - MOVW DI, 1(CX) - LEAL -60(SI), SI - ADDQ $0x03, CX - - // emitRepeat - MOVL SI, R8 - LEAL -4(SI), SI - CMPL R8, $0x08 - JBE repeat_two_repeat_as_copy_encodeBlockAsm12B_emit_copy_short - CMPL R8, $0x0c - JAE cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm12B_emit_copy_short - CMPL DI, $0x00000800 - JB repeat_two_offset_repeat_as_copy_encodeBlockAsm12B_emit_copy_short - -cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm12B_emit_copy_short: - CMPL SI, $0x00000104 - JB repeat_three_repeat_as_copy_encodeBlockAsm12B_emit_copy_short - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm12B - -repeat_three_repeat_as_copy_encodeBlockAsm12B_emit_copy_short: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm12B - -repeat_two_repeat_as_copy_encodeBlockAsm12B_emit_copy_short: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm12B - -repeat_two_offset_repeat_as_copy_encodeBlockAsm12B_emit_copy_short: - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm12B - -two_byte_offset_short_repeat_as_copy_encodeBlockAsm12B: - MOVL SI, R8 - SHLL $0x02, R8 - CMPL SI, $0x0c - JAE emit_copy_three_repeat_as_copy_encodeBlockAsm12B - CMPL DI, $0x00000800 - JAE emit_copy_three_repeat_as_copy_encodeBlockAsm12B - LEAL -15(R8), R8 - MOVB DI, 1(CX) - SHRL $0x08, DI - SHLL $0x05, DI - ORL DI, R8 - MOVB R8, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm12B - -emit_copy_three_repeat_as_copy_encodeBlockAsm12B: - LEAL -2(R8), R8 - MOVB R8, (CX) - MOVW DI, 1(CX) - ADDQ $0x03, CX - -repeat_end_emit_encodeBlockAsm12B: - MOVL DX, 12(SP) - JMP search_loop_encodeBlockAsm12B - -no_repeat_found_encodeBlockAsm12B: - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeBlockAsm12B - SHRQ $0x08, DI - MOVL (AX)(R10*4), SI - LEAL 2(DX), R9 - CMPL (BX)(R8*1), DI - JEQ candidate2_match_encodeBlockAsm12B - MOVL R9, (AX)(R10*4) - SHRQ $0x08, DI - CMPL (BX)(SI*1), DI - JEQ candidate3_match_encodeBlockAsm12B - MOVL 20(SP), DX - JMP search_loop_encodeBlockAsm12B - -candidate3_match_encodeBlockAsm12B: - ADDL $0x02, DX - JMP candidate_match_encodeBlockAsm12B - -candidate2_match_encodeBlockAsm12B: - MOVL R9, (AX)(R10*4) - INCL DX - MOVL R8, SI - -candidate_match_encodeBlockAsm12B: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeBlockAsm12B - -match_extend_back_loop_encodeBlockAsm12B: - CMPL DX, DI - JBE match_extend_back_end_encodeBlockAsm12B - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeBlockAsm12B - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeBlockAsm12B - JMP match_extend_back_loop_encodeBlockAsm12B - -match_extend_back_end_encodeBlockAsm12B: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 3(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeBlockAsm12B - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeBlockAsm12B: - MOVL DX, DI - MOVL 12(SP), R8 - CMPL R8, DI - JEQ emit_literal_done_match_emit_encodeBlockAsm12B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(R8*1), DI - SUBL R8, R9 - LEAL -1(R9), R8 - CMPL R8, $0x3c - JB one_byte_match_emit_encodeBlockAsm12B - CMPL R8, $0x00000100 - JB two_bytes_match_emit_encodeBlockAsm12B - JB three_bytes_match_emit_encodeBlockAsm12B - -three_bytes_match_emit_encodeBlockAsm12B: - MOVB $0xf4, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeBlockAsm12B - -two_bytes_match_emit_encodeBlockAsm12B: - MOVB $0xf0, (CX) - MOVB R8, 1(CX) - ADDQ $0x02, CX - CMPL R8, $0x40 - JB memmove_match_emit_encodeBlockAsm12B - JMP memmove_long_match_emit_encodeBlockAsm12B - -one_byte_match_emit_encodeBlockAsm12B: - SHLB $0x02, R8 - MOVB R8, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeBlockAsm12B: - LEAQ (CX)(R9*1), R8 - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_match_emit_encodeBlockAsm12B_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeBlockAsm12B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeBlockAsm12B_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeBlockAsm12B_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeBlockAsm12B_memmove_move_8: - MOVQ (DI), R10 - MOVQ R10, (CX) - JMP memmove_end_copy_match_emit_encodeBlockAsm12B - -emit_lit_memmove_match_emit_encodeBlockAsm12B_memmove_move_8through16: - MOVQ (DI), R10 - MOVQ -8(DI)(R9*1), DI - MOVQ R10, (CX) - MOVQ DI, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBlockAsm12B - -emit_lit_memmove_match_emit_encodeBlockAsm12B_memmove_move_17through32: - MOVOU (DI), X0 - MOVOU -16(DI)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBlockAsm12B - -emit_lit_memmove_match_emit_encodeBlockAsm12B_memmove_move_33through64: - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeBlockAsm12B: - MOVQ R8, CX - JMP emit_literal_done_match_emit_encodeBlockAsm12B - -memmove_long_match_emit_encodeBlockAsm12B: - LEAQ (CX)(R9*1), R8 - - // genMemMoveLong - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVQ R9, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R12 - SUBQ R10, R12 - DECQ R11 - JA emit_lit_memmove_long_match_emit_encodeBlockAsm12Blarge_forward_sse_loop_32 - LEAQ -32(DI)(R12*1), R10 - LEAQ -32(CX)(R12*1), R13 - -emit_lit_memmove_long_match_emit_encodeBlockAsm12Blarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R13) - MOVOA X5, 16(R13) - ADDQ $0x20, R13 - ADDQ $0x20, R10 - ADDQ $0x20, R12 - DECQ R11 - JNA emit_lit_memmove_long_match_emit_encodeBlockAsm12Blarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeBlockAsm12Blarge_forward_sse_loop_32: - MOVOU -32(DI)(R12*1), X4 - MOVOU -16(DI)(R12*1), X5 - MOVOA X4, -32(CX)(R12*1) - MOVOA X5, -16(CX)(R12*1) - ADDQ $0x20, R12 - CMPQ R9, R12 - JAE emit_lit_memmove_long_match_emit_encodeBlockAsm12Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ R8, CX - -emit_literal_done_match_emit_encodeBlockAsm12B: -match_nolit_loop_encodeBlockAsm12B: - MOVL DX, DI - SUBL SI, DI - MOVL DI, 16(SP) - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), DI - SUBL DX, DI - LEAQ (BX)(DX*1), R8 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R10, R10 - -matchlen_loopback_16_match_nolit_encodeBlockAsm12B: - CMPL DI, $0x10 - JB matchlen_match8_match_nolit_encodeBlockAsm12B - MOVQ (R8)(R10*1), R9 - MOVQ 8(R8)(R10*1), R11 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeBlockAsm12B - XORQ 8(SI)(R10*1), R11 - JNZ matchlen_bsf_16match_nolit_encodeBlockAsm12B - LEAL -16(DI), DI - LEAL 16(R10), R10 - JMP matchlen_loopback_16_match_nolit_encodeBlockAsm12B - -matchlen_bsf_16match_nolit_encodeBlockAsm12B: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL 8(R10)(R11*1), R10 - JMP match_nolit_end_encodeBlockAsm12B - -matchlen_match8_match_nolit_encodeBlockAsm12B: - CMPL DI, $0x08 - JB matchlen_match4_match_nolit_encodeBlockAsm12B - MOVQ (R8)(R10*1), R9 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeBlockAsm12B - LEAL -8(DI), DI - LEAL 8(R10), R10 - JMP matchlen_match4_match_nolit_encodeBlockAsm12B - -matchlen_bsf_8_match_nolit_encodeBlockAsm12B: -#ifdef GOAMD64_v3 - TZCNTQ R9, R9 - -#else - BSFQ R9, R9 - -#endif - SARQ $0x03, R9 - LEAL (R10)(R9*1), R10 - JMP match_nolit_end_encodeBlockAsm12B - -matchlen_match4_match_nolit_encodeBlockAsm12B: - CMPL DI, $0x04 - JB matchlen_match2_match_nolit_encodeBlockAsm12B - MOVL (R8)(R10*1), R9 - CMPL (SI)(R10*1), R9 - JNE matchlen_match2_match_nolit_encodeBlockAsm12B - LEAL -4(DI), DI - LEAL 4(R10), R10 - -matchlen_match2_match_nolit_encodeBlockAsm12B: - CMPL DI, $0x01 - JE matchlen_match1_match_nolit_encodeBlockAsm12B - JB match_nolit_end_encodeBlockAsm12B - MOVW (R8)(R10*1), R9 - CMPW (SI)(R10*1), R9 - JNE matchlen_match1_match_nolit_encodeBlockAsm12B - LEAL 2(R10), R10 - SUBL $0x02, DI - JZ match_nolit_end_encodeBlockAsm12B - -matchlen_match1_match_nolit_encodeBlockAsm12B: - MOVB (R8)(R10*1), R9 - CMPB (SI)(R10*1), R9 - JNE match_nolit_end_encodeBlockAsm12B - LEAL 1(R10), R10 - -match_nolit_end_encodeBlockAsm12B: - ADDL R10, DX - MOVL 16(SP), SI - ADDL $0x04, R10 - MOVL DX, 12(SP) - - // emitCopy - CMPL R10, $0x40 - JBE two_byte_offset_short_match_nolit_encodeBlockAsm12B - CMPL SI, $0x00000800 - JAE long_offset_short_match_nolit_encodeBlockAsm12B - MOVL $0x00000001, DI - LEAL 16(DI), DI - MOVB SI, 1(CX) - SHRL $0x08, SI - SHLL $0x05, SI - ORL SI, DI - MOVB DI, (CX) - ADDQ $0x02, CX - SUBL $0x08, R10 - - // emitRepeat - LEAL -4(R10), R10 - JMP cant_repeat_two_offset_match_nolit_encodeBlockAsm12B_emit_copy_short_2b - MOVL R10, DI - LEAL -4(R10), R10 - CMPL DI, $0x08 - JBE repeat_two_match_nolit_encodeBlockAsm12B_emit_copy_short_2b - CMPL DI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBlockAsm12B_emit_copy_short_2b - CMPL SI, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBlockAsm12B_emit_copy_short_2b - -cant_repeat_two_offset_match_nolit_encodeBlockAsm12B_emit_copy_short_2b: - CMPL R10, $0x00000104 - JB repeat_three_match_nolit_encodeBlockAsm12B_emit_copy_short_2b - LEAL -256(R10), R10 - MOVW $0x0019, (CX) - MOVW R10, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm12B - -repeat_three_match_nolit_encodeBlockAsm12B_emit_copy_short_2b: - LEAL -4(R10), R10 - MOVW $0x0015, (CX) - MOVB R10, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm12B - -repeat_two_match_nolit_encodeBlockAsm12B_emit_copy_short_2b: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm12B - -repeat_two_offset_match_nolit_encodeBlockAsm12B_emit_copy_short_2b: - XORQ DI, DI - LEAL 1(DI)(R10*4), R10 - MOVB SI, 1(CX) - SARL $0x08, SI - SHLL $0x05, SI - ORL SI, R10 - MOVB R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm12B - -long_offset_short_match_nolit_encodeBlockAsm12B: - MOVB $0xee, (CX) - MOVW SI, 1(CX) - LEAL -60(R10), R10 - ADDQ $0x03, CX - - // emitRepeat - MOVL R10, DI - LEAL -4(R10), R10 - CMPL DI, $0x08 - JBE repeat_two_match_nolit_encodeBlockAsm12B_emit_copy_short - CMPL DI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBlockAsm12B_emit_copy_short - CMPL SI, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBlockAsm12B_emit_copy_short - -cant_repeat_two_offset_match_nolit_encodeBlockAsm12B_emit_copy_short: - CMPL R10, $0x00000104 - JB repeat_three_match_nolit_encodeBlockAsm12B_emit_copy_short - LEAL -256(R10), R10 - MOVW $0x0019, (CX) - MOVW R10, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm12B - -repeat_three_match_nolit_encodeBlockAsm12B_emit_copy_short: - LEAL -4(R10), R10 - MOVW $0x0015, (CX) - MOVB R10, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm12B - -repeat_two_match_nolit_encodeBlockAsm12B_emit_copy_short: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm12B - -repeat_two_offset_match_nolit_encodeBlockAsm12B_emit_copy_short: - XORQ DI, DI - LEAL 1(DI)(R10*4), R10 - MOVB SI, 1(CX) - SARL $0x08, SI - SHLL $0x05, SI - ORL SI, R10 - MOVB R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm12B - -two_byte_offset_short_match_nolit_encodeBlockAsm12B: - MOVL R10, DI - SHLL $0x02, DI - CMPL R10, $0x0c - JAE emit_copy_three_match_nolit_encodeBlockAsm12B - CMPL SI, $0x00000800 - JAE emit_copy_three_match_nolit_encodeBlockAsm12B - LEAL -15(DI), DI - MOVB SI, 1(CX) - SHRL $0x08, SI - SHLL $0x05, SI - ORL SI, DI - MOVB DI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm12B - -emit_copy_three_match_nolit_encodeBlockAsm12B: - LEAL -2(DI), DI - MOVB DI, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - -match_nolit_emitcopy_end_encodeBlockAsm12B: - CMPL DX, 8(SP) - JAE emit_remainder_encodeBlockAsm12B - MOVQ -2(BX)(DX*1), DI - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeBlockAsm12B - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeBlockAsm12B: - MOVQ $0x000000cf1bbcdcbb, R9 - MOVQ DI, R8 - SHRQ $0x10, DI - MOVQ DI, SI - SHLQ $0x18, R8 - IMULQ R9, R8 - SHRQ $0x34, R8 - SHLQ $0x18, SI - IMULQ R9, SI - SHRQ $0x34, SI - LEAL -2(DX), R9 - LEAQ (AX)(SI*4), R10 - MOVL (R10), SI - MOVL R9, (AX)(R8*4) - MOVL DX, (R10) - CMPL (BX)(SI*1), DI - JEQ match_nolit_loop_encodeBlockAsm12B - INCL DX - JMP search_loop_encodeBlockAsm12B - -emit_remainder_encodeBlockAsm12B: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 3(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeBlockAsm12B - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeBlockAsm12B: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeBlockAsm12B - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeBlockAsm12B - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeBlockAsm12B - JB three_bytes_emit_remainder_encodeBlockAsm12B - -three_bytes_emit_remainder_encodeBlockAsm12B: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeBlockAsm12B - -two_bytes_emit_remainder_encodeBlockAsm12B: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeBlockAsm12B - JMP memmove_long_emit_remainder_encodeBlockAsm12B - -one_byte_emit_remainder_encodeBlockAsm12B: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeBlockAsm12B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeBlockAsm12B_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeBlockAsm12B_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeBlockAsm12B_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeBlockAsm12B_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeBlockAsm12B_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeBlockAsm12B_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeBlockAsm12B_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeBlockAsm12B_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeBlockAsm12B_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeBlockAsm12B_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeBlockAsm12B_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeBlockAsm12B_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeBlockAsm12B: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeBlockAsm12B - -memmove_long_emit_remainder_encodeBlockAsm12B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeBlockAsm12Blarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeBlockAsm12Blarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeBlockAsm12Blarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeBlockAsm12Blarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeBlockAsm12Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeBlockAsm12B: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeBlockAsm10B(dst []byte, src []byte, tmp *[4096]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeBlockAsm10B(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00000020, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeBlockAsm10B: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeBlockAsm10B - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL DX, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeBlockAsm10B: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x05, SI - LEAL 4(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeBlockAsm10B - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x9e3779b1, R9 - MOVQ DI, R10 - MOVQ DI, R11 - SHRQ $0x08, R11 - SHLQ $0x20, R10 - IMULQ R9, R10 - SHRQ $0x36, R10 - SHLQ $0x20, R11 - IMULQ R9, R11 - SHRQ $0x36, R11 - MOVL (AX)(R10*4), SI - MOVL (AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - LEAL 1(DX), R10 - MOVL R10, (AX)(R11*4) - MOVQ DI, R10 - SHRQ $0x10, R10 - SHLQ $0x20, R10 - IMULQ R9, R10 - SHRQ $0x36, R10 - MOVL DX, R9 - SUBL 16(SP), R9 - MOVL 1(BX)(R9*1), R11 - MOVQ DI, R9 - SHRQ $0x08, R9 - CMPL R9, R11 - JNE no_repeat_found_encodeBlockAsm10B - LEAL 1(DX), DI - MOVL 12(SP), R8 - MOVL DI, SI - SUBL 16(SP), SI - JZ repeat_extend_back_end_encodeBlockAsm10B - -repeat_extend_back_loop_encodeBlockAsm10B: - CMPL DI, R8 - JBE repeat_extend_back_end_encodeBlockAsm10B - MOVB -1(BX)(SI*1), R9 - MOVB -1(BX)(DI*1), R10 - CMPB R9, R10 - JNE repeat_extend_back_end_encodeBlockAsm10B - LEAL -1(DI), DI - DECL SI - JNZ repeat_extend_back_loop_encodeBlockAsm10B - -repeat_extend_back_end_encodeBlockAsm10B: - MOVL DI, SI - SUBL 12(SP), SI - LEAQ 3(CX)(SI*1), SI - CMPQ SI, (SP) - JB repeat_dst_size_check_encodeBlockAsm10B - MOVQ $0x00000000, ret+56(FP) - RET - -repeat_dst_size_check_encodeBlockAsm10B: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_repeat_emit_encodeBlockAsm10B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_repeat_emit_encodeBlockAsm10B - CMPL SI, $0x00000100 - JB two_bytes_repeat_emit_encodeBlockAsm10B - JB three_bytes_repeat_emit_encodeBlockAsm10B - -three_bytes_repeat_emit_encodeBlockAsm10B: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_repeat_emit_encodeBlockAsm10B - -two_bytes_repeat_emit_encodeBlockAsm10B: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_repeat_emit_encodeBlockAsm10B - JMP memmove_long_repeat_emit_encodeBlockAsm10B - -one_byte_repeat_emit_encodeBlockAsm10B: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_repeat_emit_encodeBlockAsm10B: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_repeat_emit_encodeBlockAsm10B_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_repeat_emit_encodeBlockAsm10B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_repeat_emit_encodeBlockAsm10B_memmove_move_17through32 - JMP emit_lit_memmove_repeat_emit_encodeBlockAsm10B_memmove_move_33through64 - -emit_lit_memmove_repeat_emit_encodeBlockAsm10B_memmove_move_8: - MOVQ (R10), R11 - MOVQ R11, (CX) - JMP memmove_end_copy_repeat_emit_encodeBlockAsm10B - -emit_lit_memmove_repeat_emit_encodeBlockAsm10B_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_repeat_emit_encodeBlockAsm10B - -emit_lit_memmove_repeat_emit_encodeBlockAsm10B_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_repeat_emit_encodeBlockAsm10B - -emit_lit_memmove_repeat_emit_encodeBlockAsm10B_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_repeat_emit_encodeBlockAsm10B: - MOVQ SI, CX - JMP emit_literal_done_repeat_emit_encodeBlockAsm10B - -memmove_long_repeat_emit_encodeBlockAsm10B: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R12 - SHRQ $0x05, R12 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R13 - SUBQ R11, R13 - DECQ R12 - JA emit_lit_memmove_long_repeat_emit_encodeBlockAsm10Blarge_forward_sse_loop_32 - LEAQ -32(R10)(R13*1), R11 - LEAQ -32(CX)(R13*1), R14 - -emit_lit_memmove_long_repeat_emit_encodeBlockAsm10Blarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R14) - MOVOA X5, 16(R14) - ADDQ $0x20, R14 - ADDQ $0x20, R11 - ADDQ $0x20, R13 - DECQ R12 - JNA emit_lit_memmove_long_repeat_emit_encodeBlockAsm10Blarge_big_loop_back - -emit_lit_memmove_long_repeat_emit_encodeBlockAsm10Blarge_forward_sse_loop_32: - MOVOU -32(R10)(R13*1), X4 - MOVOU -16(R10)(R13*1), X5 - MOVOA X4, -32(CX)(R13*1) - MOVOA X5, -16(CX)(R13*1) - ADDQ $0x20, R13 - CMPQ R9, R13 - JAE emit_lit_memmove_long_repeat_emit_encodeBlockAsm10Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_repeat_emit_encodeBlockAsm10B: - ADDL $0x05, DX - MOVL DX, SI - SUBL 16(SP), SI - MOVQ src_len+32(FP), R9 - SUBL DX, R9 - LEAQ (BX)(DX*1), R10 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R12, R12 - -matchlen_loopback_16_repeat_extend_encodeBlockAsm10B: - CMPL R9, $0x10 - JB matchlen_match8_repeat_extend_encodeBlockAsm10B - MOVQ (R10)(R12*1), R11 - MOVQ 8(R10)(R12*1), R13 - XORQ (SI)(R12*1), R11 - JNZ matchlen_bsf_8_repeat_extend_encodeBlockAsm10B - XORQ 8(SI)(R12*1), R13 - JNZ matchlen_bsf_16repeat_extend_encodeBlockAsm10B - LEAL -16(R9), R9 - LEAL 16(R12), R12 - JMP matchlen_loopback_16_repeat_extend_encodeBlockAsm10B - -matchlen_bsf_16repeat_extend_encodeBlockAsm10B: -#ifdef GOAMD64_v3 - TZCNTQ R13, R13 - -#else - BSFQ R13, R13 - -#endif - SARQ $0x03, R13 - LEAL 8(R12)(R13*1), R12 - JMP repeat_extend_forward_end_encodeBlockAsm10B - -matchlen_match8_repeat_extend_encodeBlockAsm10B: - CMPL R9, $0x08 - JB matchlen_match4_repeat_extend_encodeBlockAsm10B - MOVQ (R10)(R12*1), R11 - XORQ (SI)(R12*1), R11 - JNZ matchlen_bsf_8_repeat_extend_encodeBlockAsm10B - LEAL -8(R9), R9 - LEAL 8(R12), R12 - JMP matchlen_match4_repeat_extend_encodeBlockAsm10B - -matchlen_bsf_8_repeat_extend_encodeBlockAsm10B: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL (R12)(R11*1), R12 - JMP repeat_extend_forward_end_encodeBlockAsm10B - -matchlen_match4_repeat_extend_encodeBlockAsm10B: - CMPL R9, $0x04 - JB matchlen_match2_repeat_extend_encodeBlockAsm10B - MOVL (R10)(R12*1), R11 - CMPL (SI)(R12*1), R11 - JNE matchlen_match2_repeat_extend_encodeBlockAsm10B - LEAL -4(R9), R9 - LEAL 4(R12), R12 - -matchlen_match2_repeat_extend_encodeBlockAsm10B: - CMPL R9, $0x01 - JE matchlen_match1_repeat_extend_encodeBlockAsm10B - JB repeat_extend_forward_end_encodeBlockAsm10B - MOVW (R10)(R12*1), R11 - CMPW (SI)(R12*1), R11 - JNE matchlen_match1_repeat_extend_encodeBlockAsm10B - LEAL 2(R12), R12 - SUBL $0x02, R9 - JZ repeat_extend_forward_end_encodeBlockAsm10B - -matchlen_match1_repeat_extend_encodeBlockAsm10B: - MOVB (R10)(R12*1), R11 - CMPB (SI)(R12*1), R11 - JNE repeat_extend_forward_end_encodeBlockAsm10B - LEAL 1(R12), R12 - -repeat_extend_forward_end_encodeBlockAsm10B: - ADDL R12, DX - MOVL DX, SI - SUBL DI, SI - MOVL 16(SP), DI - TESTL R8, R8 - JZ repeat_as_copy_encodeBlockAsm10B - - // emitRepeat - MOVL SI, R8 - LEAL -4(SI), SI - CMPL R8, $0x08 - JBE repeat_two_match_repeat_encodeBlockAsm10B - CMPL R8, $0x0c - JAE cant_repeat_two_offset_match_repeat_encodeBlockAsm10B - CMPL DI, $0x00000800 - JB repeat_two_offset_match_repeat_encodeBlockAsm10B - -cant_repeat_two_offset_match_repeat_encodeBlockAsm10B: - CMPL SI, $0x00000104 - JB repeat_three_match_repeat_encodeBlockAsm10B - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm10B - -repeat_three_match_repeat_encodeBlockAsm10B: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm10B - -repeat_two_match_repeat_encodeBlockAsm10B: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm10B - -repeat_two_offset_match_repeat_encodeBlockAsm10B: - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm10B - -repeat_as_copy_encodeBlockAsm10B: - // emitCopy - CMPL SI, $0x40 - JBE two_byte_offset_short_repeat_as_copy_encodeBlockAsm10B - CMPL DI, $0x00000800 - JAE long_offset_short_repeat_as_copy_encodeBlockAsm10B - MOVL $0x00000001, R8 - LEAL 16(R8), R8 - MOVB DI, 1(CX) - SHRL $0x08, DI - SHLL $0x05, DI - ORL DI, R8 - MOVB R8, (CX) - ADDQ $0x02, CX - SUBL $0x08, SI - - // emitRepeat - LEAL -4(SI), SI - JMP cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm10B_emit_copy_short_2b - MOVL SI, R8 - LEAL -4(SI), SI - CMPL R8, $0x08 - JBE repeat_two_repeat_as_copy_encodeBlockAsm10B_emit_copy_short_2b - CMPL R8, $0x0c - JAE cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm10B_emit_copy_short_2b - CMPL DI, $0x00000800 - JB repeat_two_offset_repeat_as_copy_encodeBlockAsm10B_emit_copy_short_2b - -cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm10B_emit_copy_short_2b: - CMPL SI, $0x00000104 - JB repeat_three_repeat_as_copy_encodeBlockAsm10B_emit_copy_short_2b - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm10B - -repeat_three_repeat_as_copy_encodeBlockAsm10B_emit_copy_short_2b: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm10B - -repeat_two_repeat_as_copy_encodeBlockAsm10B_emit_copy_short_2b: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm10B - -repeat_two_offset_repeat_as_copy_encodeBlockAsm10B_emit_copy_short_2b: - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm10B - -long_offset_short_repeat_as_copy_encodeBlockAsm10B: - MOVB $0xee, (CX) - MOVW DI, 1(CX) - LEAL -60(SI), SI - ADDQ $0x03, CX - - // emitRepeat - MOVL SI, R8 - LEAL -4(SI), SI - CMPL R8, $0x08 - JBE repeat_two_repeat_as_copy_encodeBlockAsm10B_emit_copy_short - CMPL R8, $0x0c - JAE cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm10B_emit_copy_short - CMPL DI, $0x00000800 - JB repeat_two_offset_repeat_as_copy_encodeBlockAsm10B_emit_copy_short - -cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm10B_emit_copy_short: - CMPL SI, $0x00000104 - JB repeat_three_repeat_as_copy_encodeBlockAsm10B_emit_copy_short - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm10B - -repeat_three_repeat_as_copy_encodeBlockAsm10B_emit_copy_short: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm10B - -repeat_two_repeat_as_copy_encodeBlockAsm10B_emit_copy_short: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm10B - -repeat_two_offset_repeat_as_copy_encodeBlockAsm10B_emit_copy_short: - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm10B - -two_byte_offset_short_repeat_as_copy_encodeBlockAsm10B: - MOVL SI, R8 - SHLL $0x02, R8 - CMPL SI, $0x0c - JAE emit_copy_three_repeat_as_copy_encodeBlockAsm10B - CMPL DI, $0x00000800 - JAE emit_copy_three_repeat_as_copy_encodeBlockAsm10B - LEAL -15(R8), R8 - MOVB DI, 1(CX) - SHRL $0x08, DI - SHLL $0x05, DI - ORL DI, R8 - MOVB R8, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm10B - -emit_copy_three_repeat_as_copy_encodeBlockAsm10B: - LEAL -2(R8), R8 - MOVB R8, (CX) - MOVW DI, 1(CX) - ADDQ $0x03, CX - -repeat_end_emit_encodeBlockAsm10B: - MOVL DX, 12(SP) - JMP search_loop_encodeBlockAsm10B - -no_repeat_found_encodeBlockAsm10B: - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeBlockAsm10B - SHRQ $0x08, DI - MOVL (AX)(R10*4), SI - LEAL 2(DX), R9 - CMPL (BX)(R8*1), DI - JEQ candidate2_match_encodeBlockAsm10B - MOVL R9, (AX)(R10*4) - SHRQ $0x08, DI - CMPL (BX)(SI*1), DI - JEQ candidate3_match_encodeBlockAsm10B - MOVL 20(SP), DX - JMP search_loop_encodeBlockAsm10B - -candidate3_match_encodeBlockAsm10B: - ADDL $0x02, DX - JMP candidate_match_encodeBlockAsm10B - -candidate2_match_encodeBlockAsm10B: - MOVL R9, (AX)(R10*4) - INCL DX - MOVL R8, SI - -candidate_match_encodeBlockAsm10B: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeBlockAsm10B - -match_extend_back_loop_encodeBlockAsm10B: - CMPL DX, DI - JBE match_extend_back_end_encodeBlockAsm10B - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeBlockAsm10B - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeBlockAsm10B - JMP match_extend_back_loop_encodeBlockAsm10B - -match_extend_back_end_encodeBlockAsm10B: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 3(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeBlockAsm10B - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeBlockAsm10B: - MOVL DX, DI - MOVL 12(SP), R8 - CMPL R8, DI - JEQ emit_literal_done_match_emit_encodeBlockAsm10B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(R8*1), DI - SUBL R8, R9 - LEAL -1(R9), R8 - CMPL R8, $0x3c - JB one_byte_match_emit_encodeBlockAsm10B - CMPL R8, $0x00000100 - JB two_bytes_match_emit_encodeBlockAsm10B - JB three_bytes_match_emit_encodeBlockAsm10B - -three_bytes_match_emit_encodeBlockAsm10B: - MOVB $0xf4, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeBlockAsm10B - -two_bytes_match_emit_encodeBlockAsm10B: - MOVB $0xf0, (CX) - MOVB R8, 1(CX) - ADDQ $0x02, CX - CMPL R8, $0x40 - JB memmove_match_emit_encodeBlockAsm10B - JMP memmove_long_match_emit_encodeBlockAsm10B - -one_byte_match_emit_encodeBlockAsm10B: - SHLB $0x02, R8 - MOVB R8, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeBlockAsm10B: - LEAQ (CX)(R9*1), R8 - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_match_emit_encodeBlockAsm10B_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeBlockAsm10B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeBlockAsm10B_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeBlockAsm10B_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeBlockAsm10B_memmove_move_8: - MOVQ (DI), R10 - MOVQ R10, (CX) - JMP memmove_end_copy_match_emit_encodeBlockAsm10B - -emit_lit_memmove_match_emit_encodeBlockAsm10B_memmove_move_8through16: - MOVQ (DI), R10 - MOVQ -8(DI)(R9*1), DI - MOVQ R10, (CX) - MOVQ DI, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBlockAsm10B - -emit_lit_memmove_match_emit_encodeBlockAsm10B_memmove_move_17through32: - MOVOU (DI), X0 - MOVOU -16(DI)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBlockAsm10B - -emit_lit_memmove_match_emit_encodeBlockAsm10B_memmove_move_33through64: - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeBlockAsm10B: - MOVQ R8, CX - JMP emit_literal_done_match_emit_encodeBlockAsm10B - -memmove_long_match_emit_encodeBlockAsm10B: - LEAQ (CX)(R9*1), R8 - - // genMemMoveLong - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVQ R9, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R12 - SUBQ R10, R12 - DECQ R11 - JA emit_lit_memmove_long_match_emit_encodeBlockAsm10Blarge_forward_sse_loop_32 - LEAQ -32(DI)(R12*1), R10 - LEAQ -32(CX)(R12*1), R13 - -emit_lit_memmove_long_match_emit_encodeBlockAsm10Blarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R13) - MOVOA X5, 16(R13) - ADDQ $0x20, R13 - ADDQ $0x20, R10 - ADDQ $0x20, R12 - DECQ R11 - JNA emit_lit_memmove_long_match_emit_encodeBlockAsm10Blarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeBlockAsm10Blarge_forward_sse_loop_32: - MOVOU -32(DI)(R12*1), X4 - MOVOU -16(DI)(R12*1), X5 - MOVOA X4, -32(CX)(R12*1) - MOVOA X5, -16(CX)(R12*1) - ADDQ $0x20, R12 - CMPQ R9, R12 - JAE emit_lit_memmove_long_match_emit_encodeBlockAsm10Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ R8, CX - -emit_literal_done_match_emit_encodeBlockAsm10B: -match_nolit_loop_encodeBlockAsm10B: - MOVL DX, DI - SUBL SI, DI - MOVL DI, 16(SP) - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), DI - SUBL DX, DI - LEAQ (BX)(DX*1), R8 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R10, R10 - -matchlen_loopback_16_match_nolit_encodeBlockAsm10B: - CMPL DI, $0x10 - JB matchlen_match8_match_nolit_encodeBlockAsm10B - MOVQ (R8)(R10*1), R9 - MOVQ 8(R8)(R10*1), R11 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeBlockAsm10B - XORQ 8(SI)(R10*1), R11 - JNZ matchlen_bsf_16match_nolit_encodeBlockAsm10B - LEAL -16(DI), DI - LEAL 16(R10), R10 - JMP matchlen_loopback_16_match_nolit_encodeBlockAsm10B - -matchlen_bsf_16match_nolit_encodeBlockAsm10B: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL 8(R10)(R11*1), R10 - JMP match_nolit_end_encodeBlockAsm10B - -matchlen_match8_match_nolit_encodeBlockAsm10B: - CMPL DI, $0x08 - JB matchlen_match4_match_nolit_encodeBlockAsm10B - MOVQ (R8)(R10*1), R9 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeBlockAsm10B - LEAL -8(DI), DI - LEAL 8(R10), R10 - JMP matchlen_match4_match_nolit_encodeBlockAsm10B - -matchlen_bsf_8_match_nolit_encodeBlockAsm10B: -#ifdef GOAMD64_v3 - TZCNTQ R9, R9 - -#else - BSFQ R9, R9 - -#endif - SARQ $0x03, R9 - LEAL (R10)(R9*1), R10 - JMP match_nolit_end_encodeBlockAsm10B - -matchlen_match4_match_nolit_encodeBlockAsm10B: - CMPL DI, $0x04 - JB matchlen_match2_match_nolit_encodeBlockAsm10B - MOVL (R8)(R10*1), R9 - CMPL (SI)(R10*1), R9 - JNE matchlen_match2_match_nolit_encodeBlockAsm10B - LEAL -4(DI), DI - LEAL 4(R10), R10 - -matchlen_match2_match_nolit_encodeBlockAsm10B: - CMPL DI, $0x01 - JE matchlen_match1_match_nolit_encodeBlockAsm10B - JB match_nolit_end_encodeBlockAsm10B - MOVW (R8)(R10*1), R9 - CMPW (SI)(R10*1), R9 - JNE matchlen_match1_match_nolit_encodeBlockAsm10B - LEAL 2(R10), R10 - SUBL $0x02, DI - JZ match_nolit_end_encodeBlockAsm10B - -matchlen_match1_match_nolit_encodeBlockAsm10B: - MOVB (R8)(R10*1), R9 - CMPB (SI)(R10*1), R9 - JNE match_nolit_end_encodeBlockAsm10B - LEAL 1(R10), R10 - -match_nolit_end_encodeBlockAsm10B: - ADDL R10, DX - MOVL 16(SP), SI - ADDL $0x04, R10 - MOVL DX, 12(SP) - - // emitCopy - CMPL R10, $0x40 - JBE two_byte_offset_short_match_nolit_encodeBlockAsm10B - CMPL SI, $0x00000800 - JAE long_offset_short_match_nolit_encodeBlockAsm10B - MOVL $0x00000001, DI - LEAL 16(DI), DI - MOVB SI, 1(CX) - SHRL $0x08, SI - SHLL $0x05, SI - ORL SI, DI - MOVB DI, (CX) - ADDQ $0x02, CX - SUBL $0x08, R10 - - // emitRepeat - LEAL -4(R10), R10 - JMP cant_repeat_two_offset_match_nolit_encodeBlockAsm10B_emit_copy_short_2b - MOVL R10, DI - LEAL -4(R10), R10 - CMPL DI, $0x08 - JBE repeat_two_match_nolit_encodeBlockAsm10B_emit_copy_short_2b - CMPL DI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBlockAsm10B_emit_copy_short_2b - CMPL SI, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBlockAsm10B_emit_copy_short_2b - -cant_repeat_two_offset_match_nolit_encodeBlockAsm10B_emit_copy_short_2b: - CMPL R10, $0x00000104 - JB repeat_three_match_nolit_encodeBlockAsm10B_emit_copy_short_2b - LEAL -256(R10), R10 - MOVW $0x0019, (CX) - MOVW R10, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm10B - -repeat_three_match_nolit_encodeBlockAsm10B_emit_copy_short_2b: - LEAL -4(R10), R10 - MOVW $0x0015, (CX) - MOVB R10, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm10B - -repeat_two_match_nolit_encodeBlockAsm10B_emit_copy_short_2b: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm10B - -repeat_two_offset_match_nolit_encodeBlockAsm10B_emit_copy_short_2b: - XORQ DI, DI - LEAL 1(DI)(R10*4), R10 - MOVB SI, 1(CX) - SARL $0x08, SI - SHLL $0x05, SI - ORL SI, R10 - MOVB R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm10B - -long_offset_short_match_nolit_encodeBlockAsm10B: - MOVB $0xee, (CX) - MOVW SI, 1(CX) - LEAL -60(R10), R10 - ADDQ $0x03, CX - - // emitRepeat - MOVL R10, DI - LEAL -4(R10), R10 - CMPL DI, $0x08 - JBE repeat_two_match_nolit_encodeBlockAsm10B_emit_copy_short - CMPL DI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBlockAsm10B_emit_copy_short - CMPL SI, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBlockAsm10B_emit_copy_short - -cant_repeat_two_offset_match_nolit_encodeBlockAsm10B_emit_copy_short: - CMPL R10, $0x00000104 - JB repeat_three_match_nolit_encodeBlockAsm10B_emit_copy_short - LEAL -256(R10), R10 - MOVW $0x0019, (CX) - MOVW R10, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm10B - -repeat_three_match_nolit_encodeBlockAsm10B_emit_copy_short: - LEAL -4(R10), R10 - MOVW $0x0015, (CX) - MOVB R10, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm10B - -repeat_two_match_nolit_encodeBlockAsm10B_emit_copy_short: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm10B - -repeat_two_offset_match_nolit_encodeBlockAsm10B_emit_copy_short: - XORQ DI, DI - LEAL 1(DI)(R10*4), R10 - MOVB SI, 1(CX) - SARL $0x08, SI - SHLL $0x05, SI - ORL SI, R10 - MOVB R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm10B - -two_byte_offset_short_match_nolit_encodeBlockAsm10B: - MOVL R10, DI - SHLL $0x02, DI - CMPL R10, $0x0c - JAE emit_copy_three_match_nolit_encodeBlockAsm10B - CMPL SI, $0x00000800 - JAE emit_copy_three_match_nolit_encodeBlockAsm10B - LEAL -15(DI), DI - MOVB SI, 1(CX) - SHRL $0x08, SI - SHLL $0x05, SI - ORL SI, DI - MOVB DI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm10B - -emit_copy_three_match_nolit_encodeBlockAsm10B: - LEAL -2(DI), DI - MOVB DI, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - -match_nolit_emitcopy_end_encodeBlockAsm10B: - CMPL DX, 8(SP) - JAE emit_remainder_encodeBlockAsm10B - MOVQ -2(BX)(DX*1), DI - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeBlockAsm10B - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeBlockAsm10B: - MOVQ $0x9e3779b1, R9 - MOVQ DI, R8 - SHRQ $0x10, DI - MOVQ DI, SI - SHLQ $0x20, R8 - IMULQ R9, R8 - SHRQ $0x36, R8 - SHLQ $0x20, SI - IMULQ R9, SI - SHRQ $0x36, SI - LEAL -2(DX), R9 - LEAQ (AX)(SI*4), R10 - MOVL (R10), SI - MOVL R9, (AX)(R8*4) - MOVL DX, (R10) - CMPL (BX)(SI*1), DI - JEQ match_nolit_loop_encodeBlockAsm10B - INCL DX - JMP search_loop_encodeBlockAsm10B - -emit_remainder_encodeBlockAsm10B: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 3(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeBlockAsm10B - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeBlockAsm10B: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeBlockAsm10B - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeBlockAsm10B - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeBlockAsm10B - JB three_bytes_emit_remainder_encodeBlockAsm10B - -three_bytes_emit_remainder_encodeBlockAsm10B: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeBlockAsm10B - -two_bytes_emit_remainder_encodeBlockAsm10B: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeBlockAsm10B - JMP memmove_long_emit_remainder_encodeBlockAsm10B - -one_byte_emit_remainder_encodeBlockAsm10B: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeBlockAsm10B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeBlockAsm10B_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeBlockAsm10B_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeBlockAsm10B_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeBlockAsm10B_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeBlockAsm10B_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeBlockAsm10B_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeBlockAsm10B_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeBlockAsm10B_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeBlockAsm10B_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeBlockAsm10B_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeBlockAsm10B_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeBlockAsm10B_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeBlockAsm10B: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeBlockAsm10B - -memmove_long_emit_remainder_encodeBlockAsm10B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeBlockAsm10Blarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeBlockAsm10Blarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeBlockAsm10Blarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeBlockAsm10Blarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeBlockAsm10Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeBlockAsm10B: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeBlockAsm8B(dst []byte, src []byte, tmp *[1024]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeBlockAsm8B(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00000008, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeBlockAsm8B: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeBlockAsm8B - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL DX, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeBlockAsm8B: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x04, SI - LEAL 4(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeBlockAsm8B - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x9e3779b1, R9 - MOVQ DI, R10 - MOVQ DI, R11 - SHRQ $0x08, R11 - SHLQ $0x20, R10 - IMULQ R9, R10 - SHRQ $0x38, R10 - SHLQ $0x20, R11 - IMULQ R9, R11 - SHRQ $0x38, R11 - MOVL (AX)(R10*4), SI - MOVL (AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - LEAL 1(DX), R10 - MOVL R10, (AX)(R11*4) - MOVQ DI, R10 - SHRQ $0x10, R10 - SHLQ $0x20, R10 - IMULQ R9, R10 - SHRQ $0x38, R10 - MOVL DX, R9 - SUBL 16(SP), R9 - MOVL 1(BX)(R9*1), R11 - MOVQ DI, R9 - SHRQ $0x08, R9 - CMPL R9, R11 - JNE no_repeat_found_encodeBlockAsm8B - LEAL 1(DX), DI - MOVL 12(SP), R8 - MOVL DI, SI - SUBL 16(SP), SI - JZ repeat_extend_back_end_encodeBlockAsm8B - -repeat_extend_back_loop_encodeBlockAsm8B: - CMPL DI, R8 - JBE repeat_extend_back_end_encodeBlockAsm8B - MOVB -1(BX)(SI*1), R9 - MOVB -1(BX)(DI*1), R10 - CMPB R9, R10 - JNE repeat_extend_back_end_encodeBlockAsm8B - LEAL -1(DI), DI - DECL SI - JNZ repeat_extend_back_loop_encodeBlockAsm8B - -repeat_extend_back_end_encodeBlockAsm8B: - MOVL DI, SI - SUBL 12(SP), SI - LEAQ 3(CX)(SI*1), SI - CMPQ SI, (SP) - JB repeat_dst_size_check_encodeBlockAsm8B - MOVQ $0x00000000, ret+56(FP) - RET - -repeat_dst_size_check_encodeBlockAsm8B: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_repeat_emit_encodeBlockAsm8B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_repeat_emit_encodeBlockAsm8B - CMPL SI, $0x00000100 - JB two_bytes_repeat_emit_encodeBlockAsm8B - JB three_bytes_repeat_emit_encodeBlockAsm8B - -three_bytes_repeat_emit_encodeBlockAsm8B: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_repeat_emit_encodeBlockAsm8B - -two_bytes_repeat_emit_encodeBlockAsm8B: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_repeat_emit_encodeBlockAsm8B - JMP memmove_long_repeat_emit_encodeBlockAsm8B - -one_byte_repeat_emit_encodeBlockAsm8B: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_repeat_emit_encodeBlockAsm8B: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_repeat_emit_encodeBlockAsm8B_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_repeat_emit_encodeBlockAsm8B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_repeat_emit_encodeBlockAsm8B_memmove_move_17through32 - JMP emit_lit_memmove_repeat_emit_encodeBlockAsm8B_memmove_move_33through64 - -emit_lit_memmove_repeat_emit_encodeBlockAsm8B_memmove_move_8: - MOVQ (R10), R11 - MOVQ R11, (CX) - JMP memmove_end_copy_repeat_emit_encodeBlockAsm8B - -emit_lit_memmove_repeat_emit_encodeBlockAsm8B_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_repeat_emit_encodeBlockAsm8B - -emit_lit_memmove_repeat_emit_encodeBlockAsm8B_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_repeat_emit_encodeBlockAsm8B - -emit_lit_memmove_repeat_emit_encodeBlockAsm8B_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_repeat_emit_encodeBlockAsm8B: - MOVQ SI, CX - JMP emit_literal_done_repeat_emit_encodeBlockAsm8B - -memmove_long_repeat_emit_encodeBlockAsm8B: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R12 - SHRQ $0x05, R12 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R13 - SUBQ R11, R13 - DECQ R12 - JA emit_lit_memmove_long_repeat_emit_encodeBlockAsm8Blarge_forward_sse_loop_32 - LEAQ -32(R10)(R13*1), R11 - LEAQ -32(CX)(R13*1), R14 - -emit_lit_memmove_long_repeat_emit_encodeBlockAsm8Blarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R14) - MOVOA X5, 16(R14) - ADDQ $0x20, R14 - ADDQ $0x20, R11 - ADDQ $0x20, R13 - DECQ R12 - JNA emit_lit_memmove_long_repeat_emit_encodeBlockAsm8Blarge_big_loop_back - -emit_lit_memmove_long_repeat_emit_encodeBlockAsm8Blarge_forward_sse_loop_32: - MOVOU -32(R10)(R13*1), X4 - MOVOU -16(R10)(R13*1), X5 - MOVOA X4, -32(CX)(R13*1) - MOVOA X5, -16(CX)(R13*1) - ADDQ $0x20, R13 - CMPQ R9, R13 - JAE emit_lit_memmove_long_repeat_emit_encodeBlockAsm8Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_repeat_emit_encodeBlockAsm8B: - ADDL $0x05, DX - MOVL DX, SI - SUBL 16(SP), SI - MOVQ src_len+32(FP), R9 - SUBL DX, R9 - LEAQ (BX)(DX*1), R10 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R12, R12 - -matchlen_loopback_16_repeat_extend_encodeBlockAsm8B: - CMPL R9, $0x10 - JB matchlen_match8_repeat_extend_encodeBlockAsm8B - MOVQ (R10)(R12*1), R11 - MOVQ 8(R10)(R12*1), R13 - XORQ (SI)(R12*1), R11 - JNZ matchlen_bsf_8_repeat_extend_encodeBlockAsm8B - XORQ 8(SI)(R12*1), R13 - JNZ matchlen_bsf_16repeat_extend_encodeBlockAsm8B - LEAL -16(R9), R9 - LEAL 16(R12), R12 - JMP matchlen_loopback_16_repeat_extend_encodeBlockAsm8B - -matchlen_bsf_16repeat_extend_encodeBlockAsm8B: -#ifdef GOAMD64_v3 - TZCNTQ R13, R13 - -#else - BSFQ R13, R13 - -#endif - SARQ $0x03, R13 - LEAL 8(R12)(R13*1), R12 - JMP repeat_extend_forward_end_encodeBlockAsm8B - -matchlen_match8_repeat_extend_encodeBlockAsm8B: - CMPL R9, $0x08 - JB matchlen_match4_repeat_extend_encodeBlockAsm8B - MOVQ (R10)(R12*1), R11 - XORQ (SI)(R12*1), R11 - JNZ matchlen_bsf_8_repeat_extend_encodeBlockAsm8B - LEAL -8(R9), R9 - LEAL 8(R12), R12 - JMP matchlen_match4_repeat_extend_encodeBlockAsm8B - -matchlen_bsf_8_repeat_extend_encodeBlockAsm8B: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL (R12)(R11*1), R12 - JMP repeat_extend_forward_end_encodeBlockAsm8B - -matchlen_match4_repeat_extend_encodeBlockAsm8B: - CMPL R9, $0x04 - JB matchlen_match2_repeat_extend_encodeBlockAsm8B - MOVL (R10)(R12*1), R11 - CMPL (SI)(R12*1), R11 - JNE matchlen_match2_repeat_extend_encodeBlockAsm8B - LEAL -4(R9), R9 - LEAL 4(R12), R12 - -matchlen_match2_repeat_extend_encodeBlockAsm8B: - CMPL R9, $0x01 - JE matchlen_match1_repeat_extend_encodeBlockAsm8B - JB repeat_extend_forward_end_encodeBlockAsm8B - MOVW (R10)(R12*1), R11 - CMPW (SI)(R12*1), R11 - JNE matchlen_match1_repeat_extend_encodeBlockAsm8B - LEAL 2(R12), R12 - SUBL $0x02, R9 - JZ repeat_extend_forward_end_encodeBlockAsm8B - -matchlen_match1_repeat_extend_encodeBlockAsm8B: - MOVB (R10)(R12*1), R11 - CMPB (SI)(R12*1), R11 - JNE repeat_extend_forward_end_encodeBlockAsm8B - LEAL 1(R12), R12 - -repeat_extend_forward_end_encodeBlockAsm8B: - ADDL R12, DX - MOVL DX, SI - SUBL DI, SI - MOVL 16(SP), DI - TESTL R8, R8 - JZ repeat_as_copy_encodeBlockAsm8B - - // emitRepeat - MOVL SI, DI - LEAL -4(SI), SI - CMPL DI, $0x08 - JBE repeat_two_match_repeat_encodeBlockAsm8B - CMPL DI, $0x0c - JAE cant_repeat_two_offset_match_repeat_encodeBlockAsm8B - -cant_repeat_two_offset_match_repeat_encodeBlockAsm8B: - CMPL SI, $0x00000104 - JB repeat_three_match_repeat_encodeBlockAsm8B - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm8B - -repeat_three_match_repeat_encodeBlockAsm8B: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm8B - -repeat_two_match_repeat_encodeBlockAsm8B: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm8B - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm8B - -repeat_as_copy_encodeBlockAsm8B: - // emitCopy - CMPL SI, $0x40 - JBE two_byte_offset_short_repeat_as_copy_encodeBlockAsm8B - CMPL DI, $0x00000800 - JAE long_offset_short_repeat_as_copy_encodeBlockAsm8B - MOVL $0x00000001, R8 - LEAL 16(R8), R8 - MOVB DI, 1(CX) - SHRL $0x08, DI - SHLL $0x05, DI - ORL DI, R8 - MOVB R8, (CX) - ADDQ $0x02, CX - SUBL $0x08, SI - - // emitRepeat - LEAL -4(SI), SI - JMP cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm8B_emit_copy_short_2b - MOVL SI, DI - LEAL -4(SI), SI - CMPL DI, $0x08 - JBE repeat_two_repeat_as_copy_encodeBlockAsm8B_emit_copy_short_2b - CMPL DI, $0x0c - JAE cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm8B_emit_copy_short_2b - -cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm8B_emit_copy_short_2b: - CMPL SI, $0x00000104 - JB repeat_three_repeat_as_copy_encodeBlockAsm8B_emit_copy_short_2b - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm8B - -repeat_three_repeat_as_copy_encodeBlockAsm8B_emit_copy_short_2b: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm8B - -repeat_two_repeat_as_copy_encodeBlockAsm8B_emit_copy_short_2b: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm8B - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm8B - -long_offset_short_repeat_as_copy_encodeBlockAsm8B: - MOVB $0xee, (CX) - MOVW DI, 1(CX) - LEAL -60(SI), SI - ADDQ $0x03, CX - - // emitRepeat - MOVL SI, DI - LEAL -4(SI), SI - CMPL DI, $0x08 - JBE repeat_two_repeat_as_copy_encodeBlockAsm8B_emit_copy_short - CMPL DI, $0x0c - JAE cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm8B_emit_copy_short - -cant_repeat_two_offset_repeat_as_copy_encodeBlockAsm8B_emit_copy_short: - CMPL SI, $0x00000104 - JB repeat_three_repeat_as_copy_encodeBlockAsm8B_emit_copy_short - LEAL -256(SI), SI - MOVW $0x0019, (CX) - MOVW SI, 2(CX) - ADDQ $0x04, CX - JMP repeat_end_emit_encodeBlockAsm8B - -repeat_three_repeat_as_copy_encodeBlockAsm8B_emit_copy_short: - LEAL -4(SI), SI - MOVW $0x0015, (CX) - MOVB SI, 2(CX) - ADDQ $0x03, CX - JMP repeat_end_emit_encodeBlockAsm8B - -repeat_two_repeat_as_copy_encodeBlockAsm8B_emit_copy_short: - SHLL $0x02, SI - ORL $0x01, SI - MOVW SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm8B - XORQ R8, R8 - LEAL 1(R8)(SI*4), SI - MOVB DI, 1(CX) - SARL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm8B - -two_byte_offset_short_repeat_as_copy_encodeBlockAsm8B: - MOVL SI, R8 - SHLL $0x02, R8 - CMPL SI, $0x0c - JAE emit_copy_three_repeat_as_copy_encodeBlockAsm8B - LEAL -15(R8), R8 - MOVB DI, 1(CX) - SHRL $0x08, DI - SHLL $0x05, DI - ORL DI, R8 - MOVB R8, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeBlockAsm8B - -emit_copy_three_repeat_as_copy_encodeBlockAsm8B: - LEAL -2(R8), R8 - MOVB R8, (CX) - MOVW DI, 1(CX) - ADDQ $0x03, CX - -repeat_end_emit_encodeBlockAsm8B: - MOVL DX, 12(SP) - JMP search_loop_encodeBlockAsm8B - -no_repeat_found_encodeBlockAsm8B: - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeBlockAsm8B - SHRQ $0x08, DI - MOVL (AX)(R10*4), SI - LEAL 2(DX), R9 - CMPL (BX)(R8*1), DI - JEQ candidate2_match_encodeBlockAsm8B - MOVL R9, (AX)(R10*4) - SHRQ $0x08, DI - CMPL (BX)(SI*1), DI - JEQ candidate3_match_encodeBlockAsm8B - MOVL 20(SP), DX - JMP search_loop_encodeBlockAsm8B - -candidate3_match_encodeBlockAsm8B: - ADDL $0x02, DX - JMP candidate_match_encodeBlockAsm8B - -candidate2_match_encodeBlockAsm8B: - MOVL R9, (AX)(R10*4) - INCL DX - MOVL R8, SI - -candidate_match_encodeBlockAsm8B: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeBlockAsm8B - -match_extend_back_loop_encodeBlockAsm8B: - CMPL DX, DI - JBE match_extend_back_end_encodeBlockAsm8B - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeBlockAsm8B - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeBlockAsm8B - JMP match_extend_back_loop_encodeBlockAsm8B - -match_extend_back_end_encodeBlockAsm8B: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 3(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeBlockAsm8B - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeBlockAsm8B: - MOVL DX, DI - MOVL 12(SP), R8 - CMPL R8, DI - JEQ emit_literal_done_match_emit_encodeBlockAsm8B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(R8*1), DI - SUBL R8, R9 - LEAL -1(R9), R8 - CMPL R8, $0x3c - JB one_byte_match_emit_encodeBlockAsm8B - CMPL R8, $0x00000100 - JB two_bytes_match_emit_encodeBlockAsm8B - JB three_bytes_match_emit_encodeBlockAsm8B - -three_bytes_match_emit_encodeBlockAsm8B: - MOVB $0xf4, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeBlockAsm8B - -two_bytes_match_emit_encodeBlockAsm8B: - MOVB $0xf0, (CX) - MOVB R8, 1(CX) - ADDQ $0x02, CX - CMPL R8, $0x40 - JB memmove_match_emit_encodeBlockAsm8B - JMP memmove_long_match_emit_encodeBlockAsm8B - -one_byte_match_emit_encodeBlockAsm8B: - SHLB $0x02, R8 - MOVB R8, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeBlockAsm8B: - LEAQ (CX)(R9*1), R8 - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_match_emit_encodeBlockAsm8B_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeBlockAsm8B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeBlockAsm8B_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeBlockAsm8B_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeBlockAsm8B_memmove_move_8: - MOVQ (DI), R10 - MOVQ R10, (CX) - JMP memmove_end_copy_match_emit_encodeBlockAsm8B - -emit_lit_memmove_match_emit_encodeBlockAsm8B_memmove_move_8through16: - MOVQ (DI), R10 - MOVQ -8(DI)(R9*1), DI - MOVQ R10, (CX) - MOVQ DI, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBlockAsm8B - -emit_lit_memmove_match_emit_encodeBlockAsm8B_memmove_move_17through32: - MOVOU (DI), X0 - MOVOU -16(DI)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBlockAsm8B - -emit_lit_memmove_match_emit_encodeBlockAsm8B_memmove_move_33through64: - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeBlockAsm8B: - MOVQ R8, CX - JMP emit_literal_done_match_emit_encodeBlockAsm8B - -memmove_long_match_emit_encodeBlockAsm8B: - LEAQ (CX)(R9*1), R8 - - // genMemMoveLong - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVQ R9, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R12 - SUBQ R10, R12 - DECQ R11 - JA emit_lit_memmove_long_match_emit_encodeBlockAsm8Blarge_forward_sse_loop_32 - LEAQ -32(DI)(R12*1), R10 - LEAQ -32(CX)(R12*1), R13 - -emit_lit_memmove_long_match_emit_encodeBlockAsm8Blarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R13) - MOVOA X5, 16(R13) - ADDQ $0x20, R13 - ADDQ $0x20, R10 - ADDQ $0x20, R12 - DECQ R11 - JNA emit_lit_memmove_long_match_emit_encodeBlockAsm8Blarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeBlockAsm8Blarge_forward_sse_loop_32: - MOVOU -32(DI)(R12*1), X4 - MOVOU -16(DI)(R12*1), X5 - MOVOA X4, -32(CX)(R12*1) - MOVOA X5, -16(CX)(R12*1) - ADDQ $0x20, R12 - CMPQ R9, R12 - JAE emit_lit_memmove_long_match_emit_encodeBlockAsm8Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ R8, CX - -emit_literal_done_match_emit_encodeBlockAsm8B: -match_nolit_loop_encodeBlockAsm8B: - MOVL DX, DI - SUBL SI, DI - MOVL DI, 16(SP) - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), DI - SUBL DX, DI - LEAQ (BX)(DX*1), R8 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R10, R10 - -matchlen_loopback_16_match_nolit_encodeBlockAsm8B: - CMPL DI, $0x10 - JB matchlen_match8_match_nolit_encodeBlockAsm8B - MOVQ (R8)(R10*1), R9 - MOVQ 8(R8)(R10*1), R11 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeBlockAsm8B - XORQ 8(SI)(R10*1), R11 - JNZ matchlen_bsf_16match_nolit_encodeBlockAsm8B - LEAL -16(DI), DI - LEAL 16(R10), R10 - JMP matchlen_loopback_16_match_nolit_encodeBlockAsm8B - -matchlen_bsf_16match_nolit_encodeBlockAsm8B: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL 8(R10)(R11*1), R10 - JMP match_nolit_end_encodeBlockAsm8B - -matchlen_match8_match_nolit_encodeBlockAsm8B: - CMPL DI, $0x08 - JB matchlen_match4_match_nolit_encodeBlockAsm8B - MOVQ (R8)(R10*1), R9 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeBlockAsm8B - LEAL -8(DI), DI - LEAL 8(R10), R10 - JMP matchlen_match4_match_nolit_encodeBlockAsm8B - -matchlen_bsf_8_match_nolit_encodeBlockAsm8B: -#ifdef GOAMD64_v3 - TZCNTQ R9, R9 - -#else - BSFQ R9, R9 - -#endif - SARQ $0x03, R9 - LEAL (R10)(R9*1), R10 - JMP match_nolit_end_encodeBlockAsm8B - -matchlen_match4_match_nolit_encodeBlockAsm8B: - CMPL DI, $0x04 - JB matchlen_match2_match_nolit_encodeBlockAsm8B - MOVL (R8)(R10*1), R9 - CMPL (SI)(R10*1), R9 - JNE matchlen_match2_match_nolit_encodeBlockAsm8B - LEAL -4(DI), DI - LEAL 4(R10), R10 - -matchlen_match2_match_nolit_encodeBlockAsm8B: - CMPL DI, $0x01 - JE matchlen_match1_match_nolit_encodeBlockAsm8B - JB match_nolit_end_encodeBlockAsm8B - MOVW (R8)(R10*1), R9 - CMPW (SI)(R10*1), R9 - JNE matchlen_match1_match_nolit_encodeBlockAsm8B - LEAL 2(R10), R10 - SUBL $0x02, DI - JZ match_nolit_end_encodeBlockAsm8B - -matchlen_match1_match_nolit_encodeBlockAsm8B: - MOVB (R8)(R10*1), R9 - CMPB (SI)(R10*1), R9 - JNE match_nolit_end_encodeBlockAsm8B - LEAL 1(R10), R10 - -match_nolit_end_encodeBlockAsm8B: - ADDL R10, DX - MOVL 16(SP), SI - ADDL $0x04, R10 - MOVL DX, 12(SP) - - // emitCopy - CMPL R10, $0x40 - JBE two_byte_offset_short_match_nolit_encodeBlockAsm8B - CMPL SI, $0x00000800 - JAE long_offset_short_match_nolit_encodeBlockAsm8B - MOVL $0x00000001, DI - LEAL 16(DI), DI - MOVB SI, 1(CX) - SHRL $0x08, SI - SHLL $0x05, SI - ORL SI, DI - MOVB DI, (CX) - ADDQ $0x02, CX - SUBL $0x08, R10 - - // emitRepeat - LEAL -4(R10), R10 - JMP cant_repeat_two_offset_match_nolit_encodeBlockAsm8B_emit_copy_short_2b - MOVL R10, SI - LEAL -4(R10), R10 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_encodeBlockAsm8B_emit_copy_short_2b - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBlockAsm8B_emit_copy_short_2b - -cant_repeat_two_offset_match_nolit_encodeBlockAsm8B_emit_copy_short_2b: - CMPL R10, $0x00000104 - JB repeat_three_match_nolit_encodeBlockAsm8B_emit_copy_short_2b - LEAL -256(R10), R10 - MOVW $0x0019, (CX) - MOVW R10, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm8B - -repeat_three_match_nolit_encodeBlockAsm8B_emit_copy_short_2b: - LEAL -4(R10), R10 - MOVW $0x0015, (CX) - MOVB R10, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm8B - -repeat_two_match_nolit_encodeBlockAsm8B_emit_copy_short_2b: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm8B - XORQ DI, DI - LEAL 1(DI)(R10*4), R10 - MOVB SI, 1(CX) - SARL $0x08, SI - SHLL $0x05, SI - ORL SI, R10 - MOVB R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm8B - -long_offset_short_match_nolit_encodeBlockAsm8B: - MOVB $0xee, (CX) - MOVW SI, 1(CX) - LEAL -60(R10), R10 - ADDQ $0x03, CX - - // emitRepeat - MOVL R10, SI - LEAL -4(R10), R10 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_encodeBlockAsm8B_emit_copy_short - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBlockAsm8B_emit_copy_short - -cant_repeat_two_offset_match_nolit_encodeBlockAsm8B_emit_copy_short: - CMPL R10, $0x00000104 - JB repeat_three_match_nolit_encodeBlockAsm8B_emit_copy_short - LEAL -256(R10), R10 - MOVW $0x0019, (CX) - MOVW R10, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm8B - -repeat_three_match_nolit_encodeBlockAsm8B_emit_copy_short: - LEAL -4(R10), R10 - MOVW $0x0015, (CX) - MOVB R10, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm8B - -repeat_two_match_nolit_encodeBlockAsm8B_emit_copy_short: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm8B - XORQ DI, DI - LEAL 1(DI)(R10*4), R10 - MOVB SI, 1(CX) - SARL $0x08, SI - SHLL $0x05, SI - ORL SI, R10 - MOVB R10, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm8B - -two_byte_offset_short_match_nolit_encodeBlockAsm8B: - MOVL R10, DI - SHLL $0x02, DI - CMPL R10, $0x0c - JAE emit_copy_three_match_nolit_encodeBlockAsm8B - LEAL -15(DI), DI - MOVB SI, 1(CX) - SHRL $0x08, SI - SHLL $0x05, SI - ORL SI, DI - MOVB DI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBlockAsm8B - -emit_copy_three_match_nolit_encodeBlockAsm8B: - LEAL -2(DI), DI - MOVB DI, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - -match_nolit_emitcopy_end_encodeBlockAsm8B: - CMPL DX, 8(SP) - JAE emit_remainder_encodeBlockAsm8B - MOVQ -2(BX)(DX*1), DI - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeBlockAsm8B - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeBlockAsm8B: - MOVQ $0x9e3779b1, R9 - MOVQ DI, R8 - SHRQ $0x10, DI - MOVQ DI, SI - SHLQ $0x20, R8 - IMULQ R9, R8 - SHRQ $0x38, R8 - SHLQ $0x20, SI - IMULQ R9, SI - SHRQ $0x38, SI - LEAL -2(DX), R9 - LEAQ (AX)(SI*4), R10 - MOVL (R10), SI - MOVL R9, (AX)(R8*4) - MOVL DX, (R10) - CMPL (BX)(SI*1), DI - JEQ match_nolit_loop_encodeBlockAsm8B - INCL DX - JMP search_loop_encodeBlockAsm8B - -emit_remainder_encodeBlockAsm8B: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 3(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeBlockAsm8B - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeBlockAsm8B: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeBlockAsm8B - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeBlockAsm8B - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeBlockAsm8B - JB three_bytes_emit_remainder_encodeBlockAsm8B - -three_bytes_emit_remainder_encodeBlockAsm8B: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeBlockAsm8B - -two_bytes_emit_remainder_encodeBlockAsm8B: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeBlockAsm8B - JMP memmove_long_emit_remainder_encodeBlockAsm8B - -one_byte_emit_remainder_encodeBlockAsm8B: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeBlockAsm8B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeBlockAsm8B_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeBlockAsm8B_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeBlockAsm8B_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeBlockAsm8B_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeBlockAsm8B_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeBlockAsm8B_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeBlockAsm8B_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeBlockAsm8B_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeBlockAsm8B_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeBlockAsm8B_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeBlockAsm8B_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeBlockAsm8B_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeBlockAsm8B: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeBlockAsm8B - -memmove_long_emit_remainder_encodeBlockAsm8B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeBlockAsm8Blarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeBlockAsm8Blarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeBlockAsm8Blarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeBlockAsm8Blarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeBlockAsm8Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeBlockAsm8B: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeBetterBlockAsm(dst []byte, src []byte, tmp *[589824]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeBetterBlockAsm(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00001200, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeBetterBlockAsm: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeBetterBlockAsm - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -6(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL $0x00000000, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeBetterBlockAsm: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x07, SI - CMPL SI, $0x63 - JBE check_maxskip_ok_encodeBetterBlockAsm - LEAL 100(DX), SI - JMP check_maxskip_cont_encodeBetterBlockAsm - -check_maxskip_ok_encodeBetterBlockAsm: - LEAL 1(DX)(SI*1), SI - -check_maxskip_cont_encodeBetterBlockAsm: - CMPL SI, 8(SP) - JAE emit_remainder_encodeBetterBlockAsm - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x00cf1bbcdcbfa563, R9 - MOVQ $0x9e3779b1, SI - MOVQ DI, R10 - MOVQ DI, R11 - SHLQ $0x08, R10 - IMULQ R9, R10 - SHRQ $0x2f, R10 - SHLQ $0x20, R11 - IMULQ SI, R11 - SHRQ $0x32, R11 - MOVL (AX)(R10*4), SI - MOVL 524288(AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - MOVL DX, 524288(AX)(R11*4) - MOVQ (BX)(SI*1), R10 - MOVQ (BX)(R8*1), R11 - CMPQ R10, DI - JEQ candidate_match_encodeBetterBlockAsm - CMPQ R11, DI - JNE no_short_found_encodeBetterBlockAsm - MOVL R8, SI - JMP candidate_match_encodeBetterBlockAsm - -no_short_found_encodeBetterBlockAsm: - CMPL R10, DI - JEQ candidate_match_encodeBetterBlockAsm - CMPL R11, DI - JEQ candidateS_match_encodeBetterBlockAsm - MOVL 20(SP), DX - JMP search_loop_encodeBetterBlockAsm - -candidateS_match_encodeBetterBlockAsm: - SHRQ $0x08, DI - MOVQ DI, R10 - SHLQ $0x08, R10 - IMULQ R9, R10 - SHRQ $0x2f, R10 - MOVL (AX)(R10*4), SI - INCL DX - MOVL DX, (AX)(R10*4) - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeBetterBlockAsm - DECL DX - MOVL R8, SI - -candidate_match_encodeBetterBlockAsm: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeBetterBlockAsm - -match_extend_back_loop_encodeBetterBlockAsm: - CMPL DX, DI - JBE match_extend_back_end_encodeBetterBlockAsm - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeBetterBlockAsm - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeBetterBlockAsm - JMP match_extend_back_loop_encodeBetterBlockAsm - -match_extend_back_end_encodeBetterBlockAsm: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 5(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeBetterBlockAsm - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeBetterBlockAsm: - MOVL DX, DI - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), R10 - - // matchLen - XORL R12, R12 - -matchlen_loopback_16_match_nolit_encodeBetterBlockAsm: - CMPL R8, $0x10 - JB matchlen_match8_match_nolit_encodeBetterBlockAsm - MOVQ (R9)(R12*1), R11 - MOVQ 8(R9)(R12*1), R13 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeBetterBlockAsm - XORQ 8(R10)(R12*1), R13 - JNZ matchlen_bsf_16match_nolit_encodeBetterBlockAsm - LEAL -16(R8), R8 - LEAL 16(R12), R12 - JMP matchlen_loopback_16_match_nolit_encodeBetterBlockAsm - -matchlen_bsf_16match_nolit_encodeBetterBlockAsm: -#ifdef GOAMD64_v3 - TZCNTQ R13, R13 - -#else - BSFQ R13, R13 - -#endif - SARQ $0x03, R13 - LEAL 8(R12)(R13*1), R12 - JMP match_nolit_end_encodeBetterBlockAsm - -matchlen_match8_match_nolit_encodeBetterBlockAsm: - CMPL R8, $0x08 - JB matchlen_match4_match_nolit_encodeBetterBlockAsm - MOVQ (R9)(R12*1), R11 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeBetterBlockAsm - LEAL -8(R8), R8 - LEAL 8(R12), R12 - JMP matchlen_match4_match_nolit_encodeBetterBlockAsm - -matchlen_bsf_8_match_nolit_encodeBetterBlockAsm: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL (R12)(R11*1), R12 - JMP match_nolit_end_encodeBetterBlockAsm - -matchlen_match4_match_nolit_encodeBetterBlockAsm: - CMPL R8, $0x04 - JB matchlen_match2_match_nolit_encodeBetterBlockAsm - MOVL (R9)(R12*1), R11 - CMPL (R10)(R12*1), R11 - JNE matchlen_match2_match_nolit_encodeBetterBlockAsm - LEAL -4(R8), R8 - LEAL 4(R12), R12 - -matchlen_match2_match_nolit_encodeBetterBlockAsm: - CMPL R8, $0x01 - JE matchlen_match1_match_nolit_encodeBetterBlockAsm - JB match_nolit_end_encodeBetterBlockAsm - MOVW (R9)(R12*1), R11 - CMPW (R10)(R12*1), R11 - JNE matchlen_match1_match_nolit_encodeBetterBlockAsm - LEAL 2(R12), R12 - SUBL $0x02, R8 - JZ match_nolit_end_encodeBetterBlockAsm - -matchlen_match1_match_nolit_encodeBetterBlockAsm: - MOVB (R9)(R12*1), R11 - CMPB (R10)(R12*1), R11 - JNE match_nolit_end_encodeBetterBlockAsm - LEAL 1(R12), R12 - -match_nolit_end_encodeBetterBlockAsm: - MOVL DX, R8 - SUBL SI, R8 - - // Check if repeat - CMPL 16(SP), R8 - JEQ match_is_repeat_encodeBetterBlockAsm - CMPL R12, $0x01 - JA match_length_ok_encodeBetterBlockAsm - CMPL R8, $0x0000ffff - JBE match_length_ok_encodeBetterBlockAsm - MOVL 20(SP), DX - INCL DX - JMP search_loop_encodeBetterBlockAsm - -match_length_ok_encodeBetterBlockAsm: - MOVL R8, 16(SP) - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_match_emit_encodeBetterBlockAsm - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_match_emit_encodeBetterBlockAsm - CMPL SI, $0x00000100 - JB two_bytes_match_emit_encodeBetterBlockAsm - CMPL SI, $0x00010000 - JB three_bytes_match_emit_encodeBetterBlockAsm - CMPL SI, $0x01000000 - JB four_bytes_match_emit_encodeBetterBlockAsm - MOVB $0xfc, (CX) - MOVL SI, 1(CX) - ADDQ $0x05, CX - JMP memmove_long_match_emit_encodeBetterBlockAsm - -four_bytes_match_emit_encodeBetterBlockAsm: - MOVL SI, R11 - SHRL $0x10, R11 - MOVB $0xf8, (CX) - MOVW SI, 1(CX) - MOVB R11, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_match_emit_encodeBetterBlockAsm - -three_bytes_match_emit_encodeBetterBlockAsm: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeBetterBlockAsm - -two_bytes_match_emit_encodeBetterBlockAsm: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_match_emit_encodeBetterBlockAsm - JMP memmove_long_match_emit_encodeBetterBlockAsm - -one_byte_match_emit_encodeBetterBlockAsm: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeBetterBlockAsm: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x04 - JBE emit_lit_memmove_match_emit_encodeBetterBlockAsm_memmove_move_4 - CMPQ R9, $0x08 - JB emit_lit_memmove_match_emit_encodeBetterBlockAsm_memmove_move_4through7 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeBetterBlockAsm_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeBetterBlockAsm_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeBetterBlockAsm_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeBetterBlockAsm_memmove_move_4: - MOVL (R10), R11 - MOVL R11, (CX) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm - -emit_lit_memmove_match_emit_encodeBetterBlockAsm_memmove_move_4through7: - MOVL (R10), R11 - MOVL -4(R10)(R9*1), R10 - MOVL R11, (CX) - MOVL R10, -4(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm - -emit_lit_memmove_match_emit_encodeBetterBlockAsm_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm - -emit_lit_memmove_match_emit_encodeBetterBlockAsm_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm - -emit_lit_memmove_match_emit_encodeBetterBlockAsm_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeBetterBlockAsm: - MOVQ SI, CX - JMP emit_literal_done_match_emit_encodeBetterBlockAsm - -memmove_long_match_emit_encodeBetterBlockAsm: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R14 - SUBQ R11, R14 - DECQ R13 - JA emit_lit_memmove_long_match_emit_encodeBetterBlockAsmlarge_forward_sse_loop_32 - LEAQ -32(R10)(R14*1), R11 - LEAQ -32(CX)(R14*1), R15 - -emit_lit_memmove_long_match_emit_encodeBetterBlockAsmlarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R11 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_match_emit_encodeBetterBlockAsmlarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeBetterBlockAsmlarge_forward_sse_loop_32: - MOVOU -32(R10)(R14*1), X4 - MOVOU -16(R10)(R14*1), X5 - MOVOA X4, -32(CX)(R14*1) - MOVOA X5, -16(CX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_match_emit_encodeBetterBlockAsmlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_match_emit_encodeBetterBlockAsm: - ADDL R12, DX - ADDL $0x04, R12 - MOVL DX, 12(SP) - - // emitCopy - CMPL R8, $0x00010000 - JB two_byte_offset_match_nolit_encodeBetterBlockAsm - CMPL R12, $0x40 - JBE four_bytes_remain_match_nolit_encodeBetterBlockAsm - MOVB $0xff, (CX) - MOVL R8, 1(CX) - LEAL -64(R12), R12 - ADDQ $0x05, CX - CMPL R12, $0x04 - JB four_bytes_remain_match_nolit_encodeBetterBlockAsm - - // emitRepeat -emit_repeat_again_match_nolit_encodeBetterBlockAsm_emit_copy: - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_encodeBetterBlockAsm_emit_copy - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm_emit_copy - CMPL R8, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBetterBlockAsm_emit_copy - -cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm_emit_copy: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_encodeBetterBlockAsm_emit_copy - CMPL R12, $0x00010100 - JB repeat_four_match_nolit_encodeBetterBlockAsm_emit_copy - CMPL R12, $0x0100ffff - JB repeat_five_match_nolit_encodeBetterBlockAsm_emit_copy - LEAL -16842747(R12), R12 - MOVL $0xfffb001d, (CX) - MOVB $0xff, 4(CX) - ADDQ $0x05, CX - JMP emit_repeat_again_match_nolit_encodeBetterBlockAsm_emit_copy - -repeat_five_match_nolit_encodeBetterBlockAsm_emit_copy: - LEAL -65536(R12), R12 - MOVL R12, R8 - MOVW $0x001d, (CX) - MOVW R12, 2(CX) - SARL $0x10, R8 - MOVB R8, 4(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_four_match_nolit_encodeBetterBlockAsm_emit_copy: - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_three_match_nolit_encodeBetterBlockAsm_emit_copy: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_two_match_nolit_encodeBetterBlockAsm_emit_copy: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_two_offset_match_nolit_encodeBetterBlockAsm_emit_copy: - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -four_bytes_remain_match_nolit_encodeBetterBlockAsm: - TESTL R12, R12 - JZ match_nolit_emitcopy_end_encodeBetterBlockAsm - XORL SI, SI - LEAL -1(SI)(R12*4), R12 - MOVB R12, (CX) - MOVL R8, 1(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -two_byte_offset_match_nolit_encodeBetterBlockAsm: - CMPL R12, $0x40 - JBE two_byte_offset_short_match_nolit_encodeBetterBlockAsm - CMPL R8, $0x00000800 - JAE long_offset_short_match_nolit_encodeBetterBlockAsm - MOVL $0x00000001, SI - LEAL 16(SI), SI - MOVB R8, 1(CX) - MOVL R8, R9 - SHRL $0x08, R9 - SHLL $0x05, R9 - ORL R9, SI - MOVB SI, (CX) - ADDQ $0x02, CX - SUBL $0x08, R12 - - // emitRepeat - LEAL -4(R12), R12 - JMP cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm_emit_copy_short_2b - -emit_repeat_again_match_nolit_encodeBetterBlockAsm_emit_copy_short_2b: - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_encodeBetterBlockAsm_emit_copy_short_2b - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm_emit_copy_short_2b - CMPL R8, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBetterBlockAsm_emit_copy_short_2b - -cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm_emit_copy_short_2b: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_encodeBetterBlockAsm_emit_copy_short_2b - CMPL R12, $0x00010100 - JB repeat_four_match_nolit_encodeBetterBlockAsm_emit_copy_short_2b - CMPL R12, $0x0100ffff - JB repeat_five_match_nolit_encodeBetterBlockAsm_emit_copy_short_2b - LEAL -16842747(R12), R12 - MOVL $0xfffb001d, (CX) - MOVB $0xff, 4(CX) - ADDQ $0x05, CX - JMP emit_repeat_again_match_nolit_encodeBetterBlockAsm_emit_copy_short_2b - -repeat_five_match_nolit_encodeBetterBlockAsm_emit_copy_short_2b: - LEAL -65536(R12), R12 - MOVL R12, R8 - MOVW $0x001d, (CX) - MOVW R12, 2(CX) - SARL $0x10, R8 - MOVB R8, 4(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_four_match_nolit_encodeBetterBlockAsm_emit_copy_short_2b: - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_three_match_nolit_encodeBetterBlockAsm_emit_copy_short_2b: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_two_match_nolit_encodeBetterBlockAsm_emit_copy_short_2b: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_two_offset_match_nolit_encodeBetterBlockAsm_emit_copy_short_2b: - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -long_offset_short_match_nolit_encodeBetterBlockAsm: - MOVB $0xee, (CX) - MOVW R8, 1(CX) - LEAL -60(R12), R12 - ADDQ $0x03, CX - - // emitRepeat -emit_repeat_again_match_nolit_encodeBetterBlockAsm_emit_copy_short: - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_encodeBetterBlockAsm_emit_copy_short - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm_emit_copy_short - CMPL R8, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBetterBlockAsm_emit_copy_short - -cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm_emit_copy_short: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_encodeBetterBlockAsm_emit_copy_short - CMPL R12, $0x00010100 - JB repeat_four_match_nolit_encodeBetterBlockAsm_emit_copy_short - CMPL R12, $0x0100ffff - JB repeat_five_match_nolit_encodeBetterBlockAsm_emit_copy_short - LEAL -16842747(R12), R12 - MOVL $0xfffb001d, (CX) - MOVB $0xff, 4(CX) - ADDQ $0x05, CX - JMP emit_repeat_again_match_nolit_encodeBetterBlockAsm_emit_copy_short - -repeat_five_match_nolit_encodeBetterBlockAsm_emit_copy_short: - LEAL -65536(R12), R12 - MOVL R12, R8 - MOVW $0x001d, (CX) - MOVW R12, 2(CX) - SARL $0x10, R8 - MOVB R8, 4(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_four_match_nolit_encodeBetterBlockAsm_emit_copy_short: - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_three_match_nolit_encodeBetterBlockAsm_emit_copy_short: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_two_match_nolit_encodeBetterBlockAsm_emit_copy_short: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_two_offset_match_nolit_encodeBetterBlockAsm_emit_copy_short: - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -two_byte_offset_short_match_nolit_encodeBetterBlockAsm: - MOVL R12, SI - SHLL $0x02, SI - CMPL R12, $0x0c - JAE emit_copy_three_match_nolit_encodeBetterBlockAsm - CMPL R8, $0x00000800 - JAE emit_copy_three_match_nolit_encodeBetterBlockAsm - LEAL -15(SI), SI - MOVB R8, 1(CX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -emit_copy_three_match_nolit_encodeBetterBlockAsm: - LEAL -2(SI), SI - MOVB SI, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -match_is_repeat_encodeBetterBlockAsm: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_match_emit_repeat_encodeBetterBlockAsm - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_match_emit_repeat_encodeBetterBlockAsm - CMPL SI, $0x00000100 - JB two_bytes_match_emit_repeat_encodeBetterBlockAsm - CMPL SI, $0x00010000 - JB three_bytes_match_emit_repeat_encodeBetterBlockAsm - CMPL SI, $0x01000000 - JB four_bytes_match_emit_repeat_encodeBetterBlockAsm - MOVB $0xfc, (CX) - MOVL SI, 1(CX) - ADDQ $0x05, CX - JMP memmove_long_match_emit_repeat_encodeBetterBlockAsm - -four_bytes_match_emit_repeat_encodeBetterBlockAsm: - MOVL SI, R11 - SHRL $0x10, R11 - MOVB $0xf8, (CX) - MOVW SI, 1(CX) - MOVB R11, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_match_emit_repeat_encodeBetterBlockAsm - -three_bytes_match_emit_repeat_encodeBetterBlockAsm: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_repeat_encodeBetterBlockAsm - -two_bytes_match_emit_repeat_encodeBetterBlockAsm: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_match_emit_repeat_encodeBetterBlockAsm - JMP memmove_long_match_emit_repeat_encodeBetterBlockAsm - -one_byte_match_emit_repeat_encodeBetterBlockAsm: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_match_emit_repeat_encodeBetterBlockAsm: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x04 - JBE emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm_memmove_move_4 - CMPQ R9, $0x08 - JB emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm_memmove_move_4through7 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm_memmove_move_33through64 - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm_memmove_move_4: - MOVL (R10), R11 - MOVL R11, (CX) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm_memmove_move_4through7: - MOVL (R10), R11 - MOVL -4(R10)(R9*1), R10 - MOVL R11, (CX) - MOVL R10, -4(CX)(R9*1) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm: - MOVQ SI, CX - JMP emit_literal_done_match_emit_repeat_encodeBetterBlockAsm - -memmove_long_match_emit_repeat_encodeBetterBlockAsm: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R14 - SUBQ R11, R14 - DECQ R13 - JA emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsmlarge_forward_sse_loop_32 - LEAQ -32(R10)(R14*1), R11 - LEAQ -32(CX)(R14*1), R15 - -emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsmlarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R11 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsmlarge_big_loop_back - -emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsmlarge_forward_sse_loop_32: - MOVOU -32(R10)(R14*1), X4 - MOVOU -16(R10)(R14*1), X5 - MOVOA X4, -32(CX)(R14*1) - MOVOA X5, -16(CX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsmlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_match_emit_repeat_encodeBetterBlockAsm: - ADDL R12, DX - ADDL $0x04, R12 - MOVL DX, 12(SP) - - // emitRepeat -emit_repeat_again_match_nolit_repeat_encodeBetterBlockAsm: - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_repeat_encodeBetterBlockAsm - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm - CMPL R8, $0x00000800 - JB repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm - -cant_repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_repeat_encodeBetterBlockAsm - CMPL R12, $0x00010100 - JB repeat_four_match_nolit_repeat_encodeBetterBlockAsm - CMPL R12, $0x0100ffff - JB repeat_five_match_nolit_repeat_encodeBetterBlockAsm - LEAL -16842747(R12), R12 - MOVL $0xfffb001d, (CX) - MOVB $0xff, 4(CX) - ADDQ $0x05, CX - JMP emit_repeat_again_match_nolit_repeat_encodeBetterBlockAsm - -repeat_five_match_nolit_repeat_encodeBetterBlockAsm: - LEAL -65536(R12), R12 - MOVL R12, R8 - MOVW $0x001d, (CX) - MOVW R12, 2(CX) - SARL $0x10, R8 - MOVB R8, 4(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_four_match_nolit_repeat_encodeBetterBlockAsm: - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_three_match_nolit_repeat_encodeBetterBlockAsm: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_two_match_nolit_repeat_encodeBetterBlockAsm: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm - -repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm: - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - -match_nolit_emitcopy_end_encodeBetterBlockAsm: - CMPL DX, 8(SP) - JAE emit_remainder_encodeBetterBlockAsm - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeBetterBlockAsm - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeBetterBlockAsm: - MOVQ $0x00cf1bbcdcbfa563, SI - MOVQ $0x9e3779b1, R8 - LEAQ 1(DI), DI - LEAQ -2(DX), R9 - MOVQ (BX)(DI*1), R10 - MOVQ 1(BX)(DI*1), R11 - MOVQ (BX)(R9*1), R12 - MOVQ 1(BX)(R9*1), R13 - SHLQ $0x08, R10 - IMULQ SI, R10 - SHRQ $0x2f, R10 - SHLQ $0x20, R11 - IMULQ R8, R11 - SHRQ $0x32, R11 - SHLQ $0x08, R12 - IMULQ SI, R12 - SHRQ $0x2f, R12 - SHLQ $0x20, R13 - IMULQ R8, R13 - SHRQ $0x32, R13 - LEAQ 1(DI), R8 - LEAQ 1(R9), R14 - MOVL DI, (AX)(R10*4) - MOVL R9, (AX)(R12*4) - MOVL R8, 524288(AX)(R11*4) - MOVL R14, 524288(AX)(R13*4) - LEAQ 1(R9)(DI*1), R8 - SHRQ $0x01, R8 - ADDQ $0x01, DI - SUBQ $0x01, R9 - -index_loop_encodeBetterBlockAsm: - CMPQ R8, R9 - JAE search_loop_encodeBetterBlockAsm - MOVQ (BX)(DI*1), R10 - MOVQ (BX)(R8*1), R11 - SHLQ $0x08, R10 - IMULQ SI, R10 - SHRQ $0x2f, R10 - SHLQ $0x08, R11 - IMULQ SI, R11 - SHRQ $0x2f, R11 - MOVL DI, (AX)(R10*4) - MOVL R8, (AX)(R11*4) - ADDQ $0x02, DI - ADDQ $0x02, R8 - JMP index_loop_encodeBetterBlockAsm - -emit_remainder_encodeBetterBlockAsm: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 5(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeBetterBlockAsm - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeBetterBlockAsm: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeBetterBlockAsm - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeBetterBlockAsm - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeBetterBlockAsm - CMPL DX, $0x00010000 - JB three_bytes_emit_remainder_encodeBetterBlockAsm - CMPL DX, $0x01000000 - JB four_bytes_emit_remainder_encodeBetterBlockAsm - MOVB $0xfc, (CX) - MOVL DX, 1(CX) - ADDQ $0x05, CX - JMP memmove_long_emit_remainder_encodeBetterBlockAsm - -four_bytes_emit_remainder_encodeBetterBlockAsm: - MOVL DX, BX - SHRL $0x10, BX - MOVB $0xf8, (CX) - MOVW DX, 1(CX) - MOVB BL, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_emit_remainder_encodeBetterBlockAsm - -three_bytes_emit_remainder_encodeBetterBlockAsm: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeBetterBlockAsm - -two_bytes_emit_remainder_encodeBetterBlockAsm: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeBetterBlockAsm - JMP memmove_long_emit_remainder_encodeBetterBlockAsm - -one_byte_emit_remainder_encodeBetterBlockAsm: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeBetterBlockAsm: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeBetterBlockAsm_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeBetterBlockAsm_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeBetterBlockAsm_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeBetterBlockAsm_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeBetterBlockAsm_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeBetterBlockAsm_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeBetterBlockAsm: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeBetterBlockAsm - -memmove_long_emit_remainder_encodeBetterBlockAsm: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsmlarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsmlarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsmlarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsmlarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsmlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeBetterBlockAsm: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeBetterBlockAsm4MB(dst []byte, src []byte, tmp *[589824]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeBetterBlockAsm4MB(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00001200, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeBetterBlockAsm4MB: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeBetterBlockAsm4MB - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -6(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL $0x00000000, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeBetterBlockAsm4MB: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x07, SI - CMPL SI, $0x63 - JBE check_maxskip_ok_encodeBetterBlockAsm4MB - LEAL 100(DX), SI - JMP check_maxskip_cont_encodeBetterBlockAsm4MB - -check_maxskip_ok_encodeBetterBlockAsm4MB: - LEAL 1(DX)(SI*1), SI - -check_maxskip_cont_encodeBetterBlockAsm4MB: - CMPL SI, 8(SP) - JAE emit_remainder_encodeBetterBlockAsm4MB - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x00cf1bbcdcbfa563, R9 - MOVQ $0x9e3779b1, SI - MOVQ DI, R10 - MOVQ DI, R11 - SHLQ $0x08, R10 - IMULQ R9, R10 - SHRQ $0x2f, R10 - SHLQ $0x20, R11 - IMULQ SI, R11 - SHRQ $0x32, R11 - MOVL (AX)(R10*4), SI - MOVL 524288(AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - MOVL DX, 524288(AX)(R11*4) - MOVQ (BX)(SI*1), R10 - MOVQ (BX)(R8*1), R11 - CMPQ R10, DI - JEQ candidate_match_encodeBetterBlockAsm4MB - CMPQ R11, DI - JNE no_short_found_encodeBetterBlockAsm4MB - MOVL R8, SI - JMP candidate_match_encodeBetterBlockAsm4MB - -no_short_found_encodeBetterBlockAsm4MB: - CMPL R10, DI - JEQ candidate_match_encodeBetterBlockAsm4MB - CMPL R11, DI - JEQ candidateS_match_encodeBetterBlockAsm4MB - MOVL 20(SP), DX - JMP search_loop_encodeBetterBlockAsm4MB - -candidateS_match_encodeBetterBlockAsm4MB: - SHRQ $0x08, DI - MOVQ DI, R10 - SHLQ $0x08, R10 - IMULQ R9, R10 - SHRQ $0x2f, R10 - MOVL (AX)(R10*4), SI - INCL DX - MOVL DX, (AX)(R10*4) - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeBetterBlockAsm4MB - DECL DX - MOVL R8, SI - -candidate_match_encodeBetterBlockAsm4MB: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeBetterBlockAsm4MB - -match_extend_back_loop_encodeBetterBlockAsm4MB: - CMPL DX, DI - JBE match_extend_back_end_encodeBetterBlockAsm4MB - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeBetterBlockAsm4MB - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeBetterBlockAsm4MB - JMP match_extend_back_loop_encodeBetterBlockAsm4MB - -match_extend_back_end_encodeBetterBlockAsm4MB: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 4(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeBetterBlockAsm4MB - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeBetterBlockAsm4MB: - MOVL DX, DI - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), R10 - - // matchLen - XORL R12, R12 - -matchlen_loopback_16_match_nolit_encodeBetterBlockAsm4MB: - CMPL R8, $0x10 - JB matchlen_match8_match_nolit_encodeBetterBlockAsm4MB - MOVQ (R9)(R12*1), R11 - MOVQ 8(R9)(R12*1), R13 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeBetterBlockAsm4MB - XORQ 8(R10)(R12*1), R13 - JNZ matchlen_bsf_16match_nolit_encodeBetterBlockAsm4MB - LEAL -16(R8), R8 - LEAL 16(R12), R12 - JMP matchlen_loopback_16_match_nolit_encodeBetterBlockAsm4MB - -matchlen_bsf_16match_nolit_encodeBetterBlockAsm4MB: -#ifdef GOAMD64_v3 - TZCNTQ R13, R13 - -#else - BSFQ R13, R13 - -#endif - SARQ $0x03, R13 - LEAL 8(R12)(R13*1), R12 - JMP match_nolit_end_encodeBetterBlockAsm4MB - -matchlen_match8_match_nolit_encodeBetterBlockAsm4MB: - CMPL R8, $0x08 - JB matchlen_match4_match_nolit_encodeBetterBlockAsm4MB - MOVQ (R9)(R12*1), R11 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeBetterBlockAsm4MB - LEAL -8(R8), R8 - LEAL 8(R12), R12 - JMP matchlen_match4_match_nolit_encodeBetterBlockAsm4MB - -matchlen_bsf_8_match_nolit_encodeBetterBlockAsm4MB: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL (R12)(R11*1), R12 - JMP match_nolit_end_encodeBetterBlockAsm4MB - -matchlen_match4_match_nolit_encodeBetterBlockAsm4MB: - CMPL R8, $0x04 - JB matchlen_match2_match_nolit_encodeBetterBlockAsm4MB - MOVL (R9)(R12*1), R11 - CMPL (R10)(R12*1), R11 - JNE matchlen_match2_match_nolit_encodeBetterBlockAsm4MB - LEAL -4(R8), R8 - LEAL 4(R12), R12 - -matchlen_match2_match_nolit_encodeBetterBlockAsm4MB: - CMPL R8, $0x01 - JE matchlen_match1_match_nolit_encodeBetterBlockAsm4MB - JB match_nolit_end_encodeBetterBlockAsm4MB - MOVW (R9)(R12*1), R11 - CMPW (R10)(R12*1), R11 - JNE matchlen_match1_match_nolit_encodeBetterBlockAsm4MB - LEAL 2(R12), R12 - SUBL $0x02, R8 - JZ match_nolit_end_encodeBetterBlockAsm4MB - -matchlen_match1_match_nolit_encodeBetterBlockAsm4MB: - MOVB (R9)(R12*1), R11 - CMPB (R10)(R12*1), R11 - JNE match_nolit_end_encodeBetterBlockAsm4MB - LEAL 1(R12), R12 - -match_nolit_end_encodeBetterBlockAsm4MB: - MOVL DX, R8 - SUBL SI, R8 - - // Check if repeat - CMPL 16(SP), R8 - JEQ match_is_repeat_encodeBetterBlockAsm4MB - CMPL R12, $0x01 - JA match_length_ok_encodeBetterBlockAsm4MB - CMPL R8, $0x0000ffff - JBE match_length_ok_encodeBetterBlockAsm4MB - MOVL 20(SP), DX - INCL DX - JMP search_loop_encodeBetterBlockAsm4MB - -match_length_ok_encodeBetterBlockAsm4MB: - MOVL R8, 16(SP) - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_match_emit_encodeBetterBlockAsm4MB - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_match_emit_encodeBetterBlockAsm4MB - CMPL SI, $0x00000100 - JB two_bytes_match_emit_encodeBetterBlockAsm4MB - CMPL SI, $0x00010000 - JB three_bytes_match_emit_encodeBetterBlockAsm4MB - MOVL SI, R11 - SHRL $0x10, R11 - MOVB $0xf8, (CX) - MOVW SI, 1(CX) - MOVB R11, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_match_emit_encodeBetterBlockAsm4MB - -three_bytes_match_emit_encodeBetterBlockAsm4MB: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeBetterBlockAsm4MB - -two_bytes_match_emit_encodeBetterBlockAsm4MB: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_match_emit_encodeBetterBlockAsm4MB - JMP memmove_long_match_emit_encodeBetterBlockAsm4MB - -one_byte_match_emit_encodeBetterBlockAsm4MB: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeBetterBlockAsm4MB: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x04 - JBE emit_lit_memmove_match_emit_encodeBetterBlockAsm4MB_memmove_move_4 - CMPQ R9, $0x08 - JB emit_lit_memmove_match_emit_encodeBetterBlockAsm4MB_memmove_move_4through7 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeBetterBlockAsm4MB_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeBetterBlockAsm4MB_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeBetterBlockAsm4MB_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeBetterBlockAsm4MB_memmove_move_4: - MOVL (R10), R11 - MOVL R11, (CX) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm4MB - -emit_lit_memmove_match_emit_encodeBetterBlockAsm4MB_memmove_move_4through7: - MOVL (R10), R11 - MOVL -4(R10)(R9*1), R10 - MOVL R11, (CX) - MOVL R10, -4(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm4MB - -emit_lit_memmove_match_emit_encodeBetterBlockAsm4MB_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm4MB - -emit_lit_memmove_match_emit_encodeBetterBlockAsm4MB_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm4MB - -emit_lit_memmove_match_emit_encodeBetterBlockAsm4MB_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeBetterBlockAsm4MB: - MOVQ SI, CX - JMP emit_literal_done_match_emit_encodeBetterBlockAsm4MB - -memmove_long_match_emit_encodeBetterBlockAsm4MB: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R14 - SUBQ R11, R14 - DECQ R13 - JA emit_lit_memmove_long_match_emit_encodeBetterBlockAsm4MBlarge_forward_sse_loop_32 - LEAQ -32(R10)(R14*1), R11 - LEAQ -32(CX)(R14*1), R15 - -emit_lit_memmove_long_match_emit_encodeBetterBlockAsm4MBlarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R11 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_match_emit_encodeBetterBlockAsm4MBlarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeBetterBlockAsm4MBlarge_forward_sse_loop_32: - MOVOU -32(R10)(R14*1), X4 - MOVOU -16(R10)(R14*1), X5 - MOVOA X4, -32(CX)(R14*1) - MOVOA X5, -16(CX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_match_emit_encodeBetterBlockAsm4MBlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_match_emit_encodeBetterBlockAsm4MB: - ADDL R12, DX - ADDL $0x04, R12 - MOVL DX, 12(SP) - - // emitCopy - CMPL R8, $0x00010000 - JB two_byte_offset_match_nolit_encodeBetterBlockAsm4MB - CMPL R12, $0x40 - JBE four_bytes_remain_match_nolit_encodeBetterBlockAsm4MB - MOVB $0xff, (CX) - MOVL R8, 1(CX) - LEAL -64(R12), R12 - ADDQ $0x05, CX - CMPL R12, $0x04 - JB four_bytes_remain_match_nolit_encodeBetterBlockAsm4MB - - // emitRepeat - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_encodeBetterBlockAsm4MB_emit_copy - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm4MB_emit_copy - CMPL R8, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBetterBlockAsm4MB_emit_copy - -cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm4MB_emit_copy: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_encodeBetterBlockAsm4MB_emit_copy - CMPL R12, $0x00010100 - JB repeat_four_match_nolit_encodeBetterBlockAsm4MB_emit_copy - LEAL -65536(R12), R12 - MOVL R12, R8 - MOVW $0x001d, (CX) - MOVW R12, 2(CX) - SARL $0x10, R8 - MOVB R8, 4(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_four_match_nolit_encodeBetterBlockAsm4MB_emit_copy: - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_three_match_nolit_encodeBetterBlockAsm4MB_emit_copy: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_two_match_nolit_encodeBetterBlockAsm4MB_emit_copy: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_two_offset_match_nolit_encodeBetterBlockAsm4MB_emit_copy: - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -four_bytes_remain_match_nolit_encodeBetterBlockAsm4MB: - TESTL R12, R12 - JZ match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - XORL SI, SI - LEAL -1(SI)(R12*4), R12 - MOVB R12, (CX) - MOVL R8, 1(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -two_byte_offset_match_nolit_encodeBetterBlockAsm4MB: - CMPL R12, $0x40 - JBE two_byte_offset_short_match_nolit_encodeBetterBlockAsm4MB - CMPL R8, $0x00000800 - JAE long_offset_short_match_nolit_encodeBetterBlockAsm4MB - MOVL $0x00000001, SI - LEAL 16(SI), SI - MOVB R8, 1(CX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, SI - MOVB SI, (CX) - ADDQ $0x02, CX - SUBL $0x08, R12 - - // emitRepeat - LEAL -4(R12), R12 - JMP cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short_2b - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short_2b - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short_2b - CMPL R8, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short_2b - -cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short_2b: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short_2b - CMPL R12, $0x00010100 - JB repeat_four_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short_2b - LEAL -65536(R12), R12 - MOVL R12, R8 - MOVW $0x001d, (CX) - MOVW R12, 2(CX) - SARL $0x10, R8 - MOVB R8, 4(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_four_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short_2b: - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_three_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short_2b: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_two_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short_2b: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_two_offset_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short_2b: - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -long_offset_short_match_nolit_encodeBetterBlockAsm4MB: - MOVB $0xee, (CX) - MOVW R8, 1(CX) - LEAL -60(R12), R12 - ADDQ $0x03, CX - - // emitRepeat - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short - CMPL R8, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short - -cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short - CMPL R12, $0x00010100 - JB repeat_four_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short - LEAL -65536(R12), R12 - MOVL R12, R8 - MOVW $0x001d, (CX) - MOVW R12, 2(CX) - SARL $0x10, R8 - MOVB R8, 4(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_four_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short: - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_three_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_two_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_two_offset_match_nolit_encodeBetterBlockAsm4MB_emit_copy_short: - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -two_byte_offset_short_match_nolit_encodeBetterBlockAsm4MB: - MOVL R12, SI - SHLL $0x02, SI - CMPL R12, $0x0c - JAE emit_copy_three_match_nolit_encodeBetterBlockAsm4MB - CMPL R8, $0x00000800 - JAE emit_copy_three_match_nolit_encodeBetterBlockAsm4MB - LEAL -15(SI), SI - MOVB R8, 1(CX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -emit_copy_three_match_nolit_encodeBetterBlockAsm4MB: - LEAL -2(SI), SI - MOVB SI, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -match_is_repeat_encodeBetterBlockAsm4MB: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_match_emit_repeat_encodeBetterBlockAsm4MB - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_match_emit_repeat_encodeBetterBlockAsm4MB - CMPL SI, $0x00000100 - JB two_bytes_match_emit_repeat_encodeBetterBlockAsm4MB - CMPL SI, $0x00010000 - JB three_bytes_match_emit_repeat_encodeBetterBlockAsm4MB - MOVL SI, R11 - SHRL $0x10, R11 - MOVB $0xf8, (CX) - MOVW SI, 1(CX) - MOVB R11, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_match_emit_repeat_encodeBetterBlockAsm4MB - -three_bytes_match_emit_repeat_encodeBetterBlockAsm4MB: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_repeat_encodeBetterBlockAsm4MB - -two_bytes_match_emit_repeat_encodeBetterBlockAsm4MB: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_match_emit_repeat_encodeBetterBlockAsm4MB - JMP memmove_long_match_emit_repeat_encodeBetterBlockAsm4MB - -one_byte_match_emit_repeat_encodeBetterBlockAsm4MB: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_match_emit_repeat_encodeBetterBlockAsm4MB: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x04 - JBE emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm4MB_memmove_move_4 - CMPQ R9, $0x08 - JB emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm4MB_memmove_move_4through7 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm4MB_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm4MB_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm4MB_memmove_move_33through64 - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm4MB_memmove_move_4: - MOVL (R10), R11 - MOVL R11, (CX) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm4MB - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm4MB_memmove_move_4through7: - MOVL (R10), R11 - MOVL -4(R10)(R9*1), R10 - MOVL R11, (CX) - MOVL R10, -4(CX)(R9*1) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm4MB - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm4MB_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm4MB - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm4MB_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm4MB - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm4MB_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm4MB: - MOVQ SI, CX - JMP emit_literal_done_match_emit_repeat_encodeBetterBlockAsm4MB - -memmove_long_match_emit_repeat_encodeBetterBlockAsm4MB: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R14 - SUBQ R11, R14 - DECQ R13 - JA emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm4MBlarge_forward_sse_loop_32 - LEAQ -32(R10)(R14*1), R11 - LEAQ -32(CX)(R14*1), R15 - -emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm4MBlarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R11 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm4MBlarge_big_loop_back - -emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm4MBlarge_forward_sse_loop_32: - MOVOU -32(R10)(R14*1), X4 - MOVOU -16(R10)(R14*1), X5 - MOVOA X4, -32(CX)(R14*1) - MOVOA X5, -16(CX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm4MBlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_match_emit_repeat_encodeBetterBlockAsm4MB: - ADDL R12, DX - ADDL $0x04, R12 - MOVL DX, 12(SP) - - // emitRepeat - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_repeat_encodeBetterBlockAsm4MB - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm4MB - CMPL R8, $0x00000800 - JB repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm4MB - -cant_repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm4MB: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_repeat_encodeBetterBlockAsm4MB - CMPL R12, $0x00010100 - JB repeat_four_match_nolit_repeat_encodeBetterBlockAsm4MB - LEAL -65536(R12), R12 - MOVL R12, R8 - MOVW $0x001d, (CX) - MOVW R12, 2(CX) - SARL $0x10, R8 - MOVB R8, 4(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_four_match_nolit_repeat_encodeBetterBlockAsm4MB: - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_three_match_nolit_repeat_encodeBetterBlockAsm4MB: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_two_match_nolit_repeat_encodeBetterBlockAsm4MB: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm4MB - -repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm4MB: - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - -match_nolit_emitcopy_end_encodeBetterBlockAsm4MB: - CMPL DX, 8(SP) - JAE emit_remainder_encodeBetterBlockAsm4MB - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeBetterBlockAsm4MB - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeBetterBlockAsm4MB: - MOVQ $0x00cf1bbcdcbfa563, SI - MOVQ $0x9e3779b1, R8 - LEAQ 1(DI), DI - LEAQ -2(DX), R9 - MOVQ (BX)(DI*1), R10 - MOVQ 1(BX)(DI*1), R11 - MOVQ (BX)(R9*1), R12 - MOVQ 1(BX)(R9*1), R13 - SHLQ $0x08, R10 - IMULQ SI, R10 - SHRQ $0x2f, R10 - SHLQ $0x20, R11 - IMULQ R8, R11 - SHRQ $0x32, R11 - SHLQ $0x08, R12 - IMULQ SI, R12 - SHRQ $0x2f, R12 - SHLQ $0x20, R13 - IMULQ R8, R13 - SHRQ $0x32, R13 - LEAQ 1(DI), R8 - LEAQ 1(R9), R14 - MOVL DI, (AX)(R10*4) - MOVL R9, (AX)(R12*4) - MOVL R8, 524288(AX)(R11*4) - MOVL R14, 524288(AX)(R13*4) - LEAQ 1(R9)(DI*1), R8 - SHRQ $0x01, R8 - ADDQ $0x01, DI - SUBQ $0x01, R9 - -index_loop_encodeBetterBlockAsm4MB: - CMPQ R8, R9 - JAE search_loop_encodeBetterBlockAsm4MB - MOVQ (BX)(DI*1), R10 - MOVQ (BX)(R8*1), R11 - SHLQ $0x08, R10 - IMULQ SI, R10 - SHRQ $0x2f, R10 - SHLQ $0x08, R11 - IMULQ SI, R11 - SHRQ $0x2f, R11 - MOVL DI, (AX)(R10*4) - MOVL R8, (AX)(R11*4) - ADDQ $0x02, DI - ADDQ $0x02, R8 - JMP index_loop_encodeBetterBlockAsm4MB - -emit_remainder_encodeBetterBlockAsm4MB: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 4(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeBetterBlockAsm4MB - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeBetterBlockAsm4MB: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeBetterBlockAsm4MB - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeBetterBlockAsm4MB - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeBetterBlockAsm4MB - CMPL DX, $0x00010000 - JB three_bytes_emit_remainder_encodeBetterBlockAsm4MB - MOVL DX, BX - SHRL $0x10, BX - MOVB $0xf8, (CX) - MOVW DX, 1(CX) - MOVB BL, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_emit_remainder_encodeBetterBlockAsm4MB - -three_bytes_emit_remainder_encodeBetterBlockAsm4MB: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeBetterBlockAsm4MB - -two_bytes_emit_remainder_encodeBetterBlockAsm4MB: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeBetterBlockAsm4MB - JMP memmove_long_emit_remainder_encodeBetterBlockAsm4MB - -one_byte_emit_remainder_encodeBetterBlockAsm4MB: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeBetterBlockAsm4MB: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeBetterBlockAsm4MB_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeBetterBlockAsm4MB_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeBetterBlockAsm4MB_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeBetterBlockAsm4MB_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeBetterBlockAsm4MB_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeBetterBlockAsm4MB_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm4MB_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm4MB - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm4MB_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm4MB - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm4MB_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm4MB - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm4MB_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm4MB - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm4MB_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm4MB - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm4MB_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeBetterBlockAsm4MB: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeBetterBlockAsm4MB - -memmove_long_emit_remainder_encodeBetterBlockAsm4MB: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm4MBlarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm4MBlarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm4MBlarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm4MBlarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm4MBlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeBetterBlockAsm4MB: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeBetterBlockAsm12B(dst []byte, src []byte, tmp *[81920]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeBetterBlockAsm12B(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00000280, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeBetterBlockAsm12B: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeBetterBlockAsm12B - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -6(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL $0x00000000, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeBetterBlockAsm12B: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x06, SI - LEAL 1(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeBetterBlockAsm12B - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ $0x9e3779b1, SI - MOVQ DI, R10 - MOVQ DI, R11 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x32, R10 - SHLQ $0x20, R11 - IMULQ SI, R11 - SHRQ $0x34, R11 - MOVL (AX)(R10*4), SI - MOVL 65536(AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - MOVL DX, 65536(AX)(R11*4) - MOVQ (BX)(SI*1), R10 - MOVQ (BX)(R8*1), R11 - CMPQ R10, DI - JEQ candidate_match_encodeBetterBlockAsm12B - CMPQ R11, DI - JNE no_short_found_encodeBetterBlockAsm12B - MOVL R8, SI - JMP candidate_match_encodeBetterBlockAsm12B - -no_short_found_encodeBetterBlockAsm12B: - CMPL R10, DI - JEQ candidate_match_encodeBetterBlockAsm12B - CMPL R11, DI - JEQ candidateS_match_encodeBetterBlockAsm12B - MOVL 20(SP), DX - JMP search_loop_encodeBetterBlockAsm12B - -candidateS_match_encodeBetterBlockAsm12B: - SHRQ $0x08, DI - MOVQ DI, R10 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x32, R10 - MOVL (AX)(R10*4), SI - INCL DX - MOVL DX, (AX)(R10*4) - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeBetterBlockAsm12B - DECL DX - MOVL R8, SI - -candidate_match_encodeBetterBlockAsm12B: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeBetterBlockAsm12B - -match_extend_back_loop_encodeBetterBlockAsm12B: - CMPL DX, DI - JBE match_extend_back_end_encodeBetterBlockAsm12B - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeBetterBlockAsm12B - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeBetterBlockAsm12B - JMP match_extend_back_loop_encodeBetterBlockAsm12B - -match_extend_back_end_encodeBetterBlockAsm12B: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 3(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeBetterBlockAsm12B - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeBetterBlockAsm12B: - MOVL DX, DI - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), R10 - - // matchLen - XORL R12, R12 - -matchlen_loopback_16_match_nolit_encodeBetterBlockAsm12B: - CMPL R8, $0x10 - JB matchlen_match8_match_nolit_encodeBetterBlockAsm12B - MOVQ (R9)(R12*1), R11 - MOVQ 8(R9)(R12*1), R13 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeBetterBlockAsm12B - XORQ 8(R10)(R12*1), R13 - JNZ matchlen_bsf_16match_nolit_encodeBetterBlockAsm12B - LEAL -16(R8), R8 - LEAL 16(R12), R12 - JMP matchlen_loopback_16_match_nolit_encodeBetterBlockAsm12B - -matchlen_bsf_16match_nolit_encodeBetterBlockAsm12B: -#ifdef GOAMD64_v3 - TZCNTQ R13, R13 - -#else - BSFQ R13, R13 - -#endif - SARQ $0x03, R13 - LEAL 8(R12)(R13*1), R12 - JMP match_nolit_end_encodeBetterBlockAsm12B - -matchlen_match8_match_nolit_encodeBetterBlockAsm12B: - CMPL R8, $0x08 - JB matchlen_match4_match_nolit_encodeBetterBlockAsm12B - MOVQ (R9)(R12*1), R11 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeBetterBlockAsm12B - LEAL -8(R8), R8 - LEAL 8(R12), R12 - JMP matchlen_match4_match_nolit_encodeBetterBlockAsm12B - -matchlen_bsf_8_match_nolit_encodeBetterBlockAsm12B: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL (R12)(R11*1), R12 - JMP match_nolit_end_encodeBetterBlockAsm12B - -matchlen_match4_match_nolit_encodeBetterBlockAsm12B: - CMPL R8, $0x04 - JB matchlen_match2_match_nolit_encodeBetterBlockAsm12B - MOVL (R9)(R12*1), R11 - CMPL (R10)(R12*1), R11 - JNE matchlen_match2_match_nolit_encodeBetterBlockAsm12B - LEAL -4(R8), R8 - LEAL 4(R12), R12 - -matchlen_match2_match_nolit_encodeBetterBlockAsm12B: - CMPL R8, $0x01 - JE matchlen_match1_match_nolit_encodeBetterBlockAsm12B - JB match_nolit_end_encodeBetterBlockAsm12B - MOVW (R9)(R12*1), R11 - CMPW (R10)(R12*1), R11 - JNE matchlen_match1_match_nolit_encodeBetterBlockAsm12B - LEAL 2(R12), R12 - SUBL $0x02, R8 - JZ match_nolit_end_encodeBetterBlockAsm12B - -matchlen_match1_match_nolit_encodeBetterBlockAsm12B: - MOVB (R9)(R12*1), R11 - CMPB (R10)(R12*1), R11 - JNE match_nolit_end_encodeBetterBlockAsm12B - LEAL 1(R12), R12 - -match_nolit_end_encodeBetterBlockAsm12B: - MOVL DX, R8 - SUBL SI, R8 - - // Check if repeat - CMPL 16(SP), R8 - JEQ match_is_repeat_encodeBetterBlockAsm12B - MOVL R8, 16(SP) - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_match_emit_encodeBetterBlockAsm12B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_match_emit_encodeBetterBlockAsm12B - CMPL SI, $0x00000100 - JB two_bytes_match_emit_encodeBetterBlockAsm12B - JB three_bytes_match_emit_encodeBetterBlockAsm12B - -three_bytes_match_emit_encodeBetterBlockAsm12B: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeBetterBlockAsm12B - -two_bytes_match_emit_encodeBetterBlockAsm12B: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_match_emit_encodeBetterBlockAsm12B - JMP memmove_long_match_emit_encodeBetterBlockAsm12B - -one_byte_match_emit_encodeBetterBlockAsm12B: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeBetterBlockAsm12B: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x04 - JBE emit_lit_memmove_match_emit_encodeBetterBlockAsm12B_memmove_move_4 - CMPQ R9, $0x08 - JB emit_lit_memmove_match_emit_encodeBetterBlockAsm12B_memmove_move_4through7 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeBetterBlockAsm12B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeBetterBlockAsm12B_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeBetterBlockAsm12B_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeBetterBlockAsm12B_memmove_move_4: - MOVL (R10), R11 - MOVL R11, (CX) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm12B - -emit_lit_memmove_match_emit_encodeBetterBlockAsm12B_memmove_move_4through7: - MOVL (R10), R11 - MOVL -4(R10)(R9*1), R10 - MOVL R11, (CX) - MOVL R10, -4(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm12B - -emit_lit_memmove_match_emit_encodeBetterBlockAsm12B_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm12B - -emit_lit_memmove_match_emit_encodeBetterBlockAsm12B_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm12B - -emit_lit_memmove_match_emit_encodeBetterBlockAsm12B_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeBetterBlockAsm12B: - MOVQ SI, CX - JMP emit_literal_done_match_emit_encodeBetterBlockAsm12B - -memmove_long_match_emit_encodeBetterBlockAsm12B: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R14 - SUBQ R11, R14 - DECQ R13 - JA emit_lit_memmove_long_match_emit_encodeBetterBlockAsm12Blarge_forward_sse_loop_32 - LEAQ -32(R10)(R14*1), R11 - LEAQ -32(CX)(R14*1), R15 - -emit_lit_memmove_long_match_emit_encodeBetterBlockAsm12Blarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R11 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_match_emit_encodeBetterBlockAsm12Blarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeBetterBlockAsm12Blarge_forward_sse_loop_32: - MOVOU -32(R10)(R14*1), X4 - MOVOU -16(R10)(R14*1), X5 - MOVOA X4, -32(CX)(R14*1) - MOVOA X5, -16(CX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_match_emit_encodeBetterBlockAsm12Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_match_emit_encodeBetterBlockAsm12B: - ADDL R12, DX - ADDL $0x04, R12 - MOVL DX, 12(SP) - - // emitCopy - CMPL R12, $0x40 - JBE two_byte_offset_short_match_nolit_encodeBetterBlockAsm12B - CMPL R8, $0x00000800 - JAE long_offset_short_match_nolit_encodeBetterBlockAsm12B - MOVL $0x00000001, SI - LEAL 16(SI), SI - MOVB R8, 1(CX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, SI - MOVB SI, (CX) - ADDQ $0x02, CX - SUBL $0x08, R12 - - // emitRepeat - LEAL -4(R12), R12 - JMP cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm12B_emit_copy_short_2b - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_encodeBetterBlockAsm12B_emit_copy_short_2b - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm12B_emit_copy_short_2b - CMPL R8, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBetterBlockAsm12B_emit_copy_short_2b - -cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm12B_emit_copy_short_2b: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_encodeBetterBlockAsm12B_emit_copy_short_2b - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm12B - -repeat_three_match_nolit_encodeBetterBlockAsm12B_emit_copy_short_2b: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm12B - -repeat_two_match_nolit_encodeBetterBlockAsm12B_emit_copy_short_2b: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm12B - -repeat_two_offset_match_nolit_encodeBetterBlockAsm12B_emit_copy_short_2b: - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm12B - -long_offset_short_match_nolit_encodeBetterBlockAsm12B: - MOVB $0xee, (CX) - MOVW R8, 1(CX) - LEAL -60(R12), R12 - ADDQ $0x03, CX - - // emitRepeat - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_encodeBetterBlockAsm12B_emit_copy_short - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm12B_emit_copy_short - CMPL R8, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBetterBlockAsm12B_emit_copy_short - -cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm12B_emit_copy_short: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_encodeBetterBlockAsm12B_emit_copy_short - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm12B - -repeat_three_match_nolit_encodeBetterBlockAsm12B_emit_copy_short: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm12B - -repeat_two_match_nolit_encodeBetterBlockAsm12B_emit_copy_short: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm12B - -repeat_two_offset_match_nolit_encodeBetterBlockAsm12B_emit_copy_short: - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm12B - -two_byte_offset_short_match_nolit_encodeBetterBlockAsm12B: - MOVL R12, SI - SHLL $0x02, SI - CMPL R12, $0x0c - JAE emit_copy_three_match_nolit_encodeBetterBlockAsm12B - CMPL R8, $0x00000800 - JAE emit_copy_three_match_nolit_encodeBetterBlockAsm12B - LEAL -15(SI), SI - MOVB R8, 1(CX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm12B - -emit_copy_three_match_nolit_encodeBetterBlockAsm12B: - LEAL -2(SI), SI - MOVB SI, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm12B - -match_is_repeat_encodeBetterBlockAsm12B: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_match_emit_repeat_encodeBetterBlockAsm12B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_match_emit_repeat_encodeBetterBlockAsm12B - CMPL SI, $0x00000100 - JB two_bytes_match_emit_repeat_encodeBetterBlockAsm12B - JB three_bytes_match_emit_repeat_encodeBetterBlockAsm12B - -three_bytes_match_emit_repeat_encodeBetterBlockAsm12B: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_repeat_encodeBetterBlockAsm12B - -two_bytes_match_emit_repeat_encodeBetterBlockAsm12B: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_match_emit_repeat_encodeBetterBlockAsm12B - JMP memmove_long_match_emit_repeat_encodeBetterBlockAsm12B - -one_byte_match_emit_repeat_encodeBetterBlockAsm12B: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_match_emit_repeat_encodeBetterBlockAsm12B: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x04 - JBE emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm12B_memmove_move_4 - CMPQ R9, $0x08 - JB emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm12B_memmove_move_4through7 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm12B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm12B_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm12B_memmove_move_33through64 - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm12B_memmove_move_4: - MOVL (R10), R11 - MOVL R11, (CX) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm12B - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm12B_memmove_move_4through7: - MOVL (R10), R11 - MOVL -4(R10)(R9*1), R10 - MOVL R11, (CX) - MOVL R10, -4(CX)(R9*1) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm12B - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm12B_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm12B - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm12B_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm12B - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm12B_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm12B: - MOVQ SI, CX - JMP emit_literal_done_match_emit_repeat_encodeBetterBlockAsm12B - -memmove_long_match_emit_repeat_encodeBetterBlockAsm12B: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R14 - SUBQ R11, R14 - DECQ R13 - JA emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm12Blarge_forward_sse_loop_32 - LEAQ -32(R10)(R14*1), R11 - LEAQ -32(CX)(R14*1), R15 - -emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm12Blarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R11 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm12Blarge_big_loop_back - -emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm12Blarge_forward_sse_loop_32: - MOVOU -32(R10)(R14*1), X4 - MOVOU -16(R10)(R14*1), X5 - MOVOA X4, -32(CX)(R14*1) - MOVOA X5, -16(CX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm12Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_match_emit_repeat_encodeBetterBlockAsm12B: - ADDL R12, DX - ADDL $0x04, R12 - MOVL DX, 12(SP) - - // emitRepeat - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_repeat_encodeBetterBlockAsm12B - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm12B - CMPL R8, $0x00000800 - JB repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm12B - -cant_repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm12B: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_repeat_encodeBetterBlockAsm12B - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm12B - -repeat_three_match_nolit_repeat_encodeBetterBlockAsm12B: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm12B - -repeat_two_match_nolit_repeat_encodeBetterBlockAsm12B: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm12B - -repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm12B: - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - -match_nolit_emitcopy_end_encodeBetterBlockAsm12B: - CMPL DX, 8(SP) - JAE emit_remainder_encodeBetterBlockAsm12B - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeBetterBlockAsm12B - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeBetterBlockAsm12B: - MOVQ $0x0000cf1bbcdcbf9b, SI - MOVQ $0x9e3779b1, R8 - LEAQ 1(DI), DI - LEAQ -2(DX), R9 - MOVQ (BX)(DI*1), R10 - MOVQ 1(BX)(DI*1), R11 - MOVQ (BX)(R9*1), R12 - MOVQ 1(BX)(R9*1), R13 - SHLQ $0x10, R10 - IMULQ SI, R10 - SHRQ $0x32, R10 - SHLQ $0x20, R11 - IMULQ R8, R11 - SHRQ $0x34, R11 - SHLQ $0x10, R12 - IMULQ SI, R12 - SHRQ $0x32, R12 - SHLQ $0x20, R13 - IMULQ R8, R13 - SHRQ $0x34, R13 - LEAQ 1(DI), R8 - LEAQ 1(R9), R14 - MOVL DI, (AX)(R10*4) - MOVL R9, (AX)(R12*4) - MOVL R8, 65536(AX)(R11*4) - MOVL R14, 65536(AX)(R13*4) - LEAQ 1(R9)(DI*1), R8 - SHRQ $0x01, R8 - ADDQ $0x01, DI - SUBQ $0x01, R9 - -index_loop_encodeBetterBlockAsm12B: - CMPQ R8, R9 - JAE search_loop_encodeBetterBlockAsm12B - MOVQ (BX)(DI*1), R10 - MOVQ (BX)(R8*1), R11 - SHLQ $0x10, R10 - IMULQ SI, R10 - SHRQ $0x32, R10 - SHLQ $0x10, R11 - IMULQ SI, R11 - SHRQ $0x32, R11 - MOVL DI, (AX)(R10*4) - MOVL R8, (AX)(R11*4) - ADDQ $0x02, DI - ADDQ $0x02, R8 - JMP index_loop_encodeBetterBlockAsm12B - -emit_remainder_encodeBetterBlockAsm12B: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 3(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeBetterBlockAsm12B - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeBetterBlockAsm12B: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeBetterBlockAsm12B - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeBetterBlockAsm12B - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeBetterBlockAsm12B - JB three_bytes_emit_remainder_encodeBetterBlockAsm12B - -three_bytes_emit_remainder_encodeBetterBlockAsm12B: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeBetterBlockAsm12B - -two_bytes_emit_remainder_encodeBetterBlockAsm12B: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeBetterBlockAsm12B - JMP memmove_long_emit_remainder_encodeBetterBlockAsm12B - -one_byte_emit_remainder_encodeBetterBlockAsm12B: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeBetterBlockAsm12B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeBetterBlockAsm12B_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeBetterBlockAsm12B_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeBetterBlockAsm12B_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeBetterBlockAsm12B_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeBetterBlockAsm12B_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeBetterBlockAsm12B_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm12B_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm12B_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm12B_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm12B_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm12B_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm12B_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeBetterBlockAsm12B: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeBetterBlockAsm12B - -memmove_long_emit_remainder_encodeBetterBlockAsm12B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm12Blarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm12Blarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm12Blarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm12Blarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm12Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeBetterBlockAsm12B: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeBetterBlockAsm10B(dst []byte, src []byte, tmp *[20480]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeBetterBlockAsm10B(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x000000a0, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeBetterBlockAsm10B: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeBetterBlockAsm10B - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -6(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL $0x00000000, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeBetterBlockAsm10B: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x05, SI - LEAL 1(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeBetterBlockAsm10B - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ $0x9e3779b1, SI - MOVQ DI, R10 - MOVQ DI, R11 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x34, R10 - SHLQ $0x20, R11 - IMULQ SI, R11 - SHRQ $0x36, R11 - MOVL (AX)(R10*4), SI - MOVL 16384(AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - MOVL DX, 16384(AX)(R11*4) - MOVQ (BX)(SI*1), R10 - MOVQ (BX)(R8*1), R11 - CMPQ R10, DI - JEQ candidate_match_encodeBetterBlockAsm10B - CMPQ R11, DI - JNE no_short_found_encodeBetterBlockAsm10B - MOVL R8, SI - JMP candidate_match_encodeBetterBlockAsm10B - -no_short_found_encodeBetterBlockAsm10B: - CMPL R10, DI - JEQ candidate_match_encodeBetterBlockAsm10B - CMPL R11, DI - JEQ candidateS_match_encodeBetterBlockAsm10B - MOVL 20(SP), DX - JMP search_loop_encodeBetterBlockAsm10B - -candidateS_match_encodeBetterBlockAsm10B: - SHRQ $0x08, DI - MOVQ DI, R10 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x34, R10 - MOVL (AX)(R10*4), SI - INCL DX - MOVL DX, (AX)(R10*4) - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeBetterBlockAsm10B - DECL DX - MOVL R8, SI - -candidate_match_encodeBetterBlockAsm10B: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeBetterBlockAsm10B - -match_extend_back_loop_encodeBetterBlockAsm10B: - CMPL DX, DI - JBE match_extend_back_end_encodeBetterBlockAsm10B - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeBetterBlockAsm10B - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeBetterBlockAsm10B - JMP match_extend_back_loop_encodeBetterBlockAsm10B - -match_extend_back_end_encodeBetterBlockAsm10B: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 3(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeBetterBlockAsm10B - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeBetterBlockAsm10B: - MOVL DX, DI - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), R10 - - // matchLen - XORL R12, R12 - -matchlen_loopback_16_match_nolit_encodeBetterBlockAsm10B: - CMPL R8, $0x10 - JB matchlen_match8_match_nolit_encodeBetterBlockAsm10B - MOVQ (R9)(R12*1), R11 - MOVQ 8(R9)(R12*1), R13 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeBetterBlockAsm10B - XORQ 8(R10)(R12*1), R13 - JNZ matchlen_bsf_16match_nolit_encodeBetterBlockAsm10B - LEAL -16(R8), R8 - LEAL 16(R12), R12 - JMP matchlen_loopback_16_match_nolit_encodeBetterBlockAsm10B - -matchlen_bsf_16match_nolit_encodeBetterBlockAsm10B: -#ifdef GOAMD64_v3 - TZCNTQ R13, R13 - -#else - BSFQ R13, R13 - -#endif - SARQ $0x03, R13 - LEAL 8(R12)(R13*1), R12 - JMP match_nolit_end_encodeBetterBlockAsm10B - -matchlen_match8_match_nolit_encodeBetterBlockAsm10B: - CMPL R8, $0x08 - JB matchlen_match4_match_nolit_encodeBetterBlockAsm10B - MOVQ (R9)(R12*1), R11 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeBetterBlockAsm10B - LEAL -8(R8), R8 - LEAL 8(R12), R12 - JMP matchlen_match4_match_nolit_encodeBetterBlockAsm10B - -matchlen_bsf_8_match_nolit_encodeBetterBlockAsm10B: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL (R12)(R11*1), R12 - JMP match_nolit_end_encodeBetterBlockAsm10B - -matchlen_match4_match_nolit_encodeBetterBlockAsm10B: - CMPL R8, $0x04 - JB matchlen_match2_match_nolit_encodeBetterBlockAsm10B - MOVL (R9)(R12*1), R11 - CMPL (R10)(R12*1), R11 - JNE matchlen_match2_match_nolit_encodeBetterBlockAsm10B - LEAL -4(R8), R8 - LEAL 4(R12), R12 - -matchlen_match2_match_nolit_encodeBetterBlockAsm10B: - CMPL R8, $0x01 - JE matchlen_match1_match_nolit_encodeBetterBlockAsm10B - JB match_nolit_end_encodeBetterBlockAsm10B - MOVW (R9)(R12*1), R11 - CMPW (R10)(R12*1), R11 - JNE matchlen_match1_match_nolit_encodeBetterBlockAsm10B - LEAL 2(R12), R12 - SUBL $0x02, R8 - JZ match_nolit_end_encodeBetterBlockAsm10B - -matchlen_match1_match_nolit_encodeBetterBlockAsm10B: - MOVB (R9)(R12*1), R11 - CMPB (R10)(R12*1), R11 - JNE match_nolit_end_encodeBetterBlockAsm10B - LEAL 1(R12), R12 - -match_nolit_end_encodeBetterBlockAsm10B: - MOVL DX, R8 - SUBL SI, R8 - - // Check if repeat - CMPL 16(SP), R8 - JEQ match_is_repeat_encodeBetterBlockAsm10B - MOVL R8, 16(SP) - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_match_emit_encodeBetterBlockAsm10B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_match_emit_encodeBetterBlockAsm10B - CMPL SI, $0x00000100 - JB two_bytes_match_emit_encodeBetterBlockAsm10B - JB three_bytes_match_emit_encodeBetterBlockAsm10B - -three_bytes_match_emit_encodeBetterBlockAsm10B: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeBetterBlockAsm10B - -two_bytes_match_emit_encodeBetterBlockAsm10B: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_match_emit_encodeBetterBlockAsm10B - JMP memmove_long_match_emit_encodeBetterBlockAsm10B - -one_byte_match_emit_encodeBetterBlockAsm10B: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeBetterBlockAsm10B: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x04 - JBE emit_lit_memmove_match_emit_encodeBetterBlockAsm10B_memmove_move_4 - CMPQ R9, $0x08 - JB emit_lit_memmove_match_emit_encodeBetterBlockAsm10B_memmove_move_4through7 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeBetterBlockAsm10B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeBetterBlockAsm10B_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeBetterBlockAsm10B_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeBetterBlockAsm10B_memmove_move_4: - MOVL (R10), R11 - MOVL R11, (CX) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm10B - -emit_lit_memmove_match_emit_encodeBetterBlockAsm10B_memmove_move_4through7: - MOVL (R10), R11 - MOVL -4(R10)(R9*1), R10 - MOVL R11, (CX) - MOVL R10, -4(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm10B - -emit_lit_memmove_match_emit_encodeBetterBlockAsm10B_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm10B - -emit_lit_memmove_match_emit_encodeBetterBlockAsm10B_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm10B - -emit_lit_memmove_match_emit_encodeBetterBlockAsm10B_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeBetterBlockAsm10B: - MOVQ SI, CX - JMP emit_literal_done_match_emit_encodeBetterBlockAsm10B - -memmove_long_match_emit_encodeBetterBlockAsm10B: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R14 - SUBQ R11, R14 - DECQ R13 - JA emit_lit_memmove_long_match_emit_encodeBetterBlockAsm10Blarge_forward_sse_loop_32 - LEAQ -32(R10)(R14*1), R11 - LEAQ -32(CX)(R14*1), R15 - -emit_lit_memmove_long_match_emit_encodeBetterBlockAsm10Blarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R11 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_match_emit_encodeBetterBlockAsm10Blarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeBetterBlockAsm10Blarge_forward_sse_loop_32: - MOVOU -32(R10)(R14*1), X4 - MOVOU -16(R10)(R14*1), X5 - MOVOA X4, -32(CX)(R14*1) - MOVOA X5, -16(CX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_match_emit_encodeBetterBlockAsm10Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_match_emit_encodeBetterBlockAsm10B: - ADDL R12, DX - ADDL $0x04, R12 - MOVL DX, 12(SP) - - // emitCopy - CMPL R12, $0x40 - JBE two_byte_offset_short_match_nolit_encodeBetterBlockAsm10B - CMPL R8, $0x00000800 - JAE long_offset_short_match_nolit_encodeBetterBlockAsm10B - MOVL $0x00000001, SI - LEAL 16(SI), SI - MOVB R8, 1(CX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, SI - MOVB SI, (CX) - ADDQ $0x02, CX - SUBL $0x08, R12 - - // emitRepeat - LEAL -4(R12), R12 - JMP cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm10B_emit_copy_short_2b - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_encodeBetterBlockAsm10B_emit_copy_short_2b - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm10B_emit_copy_short_2b - CMPL R8, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBetterBlockAsm10B_emit_copy_short_2b - -cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm10B_emit_copy_short_2b: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_encodeBetterBlockAsm10B_emit_copy_short_2b - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm10B - -repeat_three_match_nolit_encodeBetterBlockAsm10B_emit_copy_short_2b: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm10B - -repeat_two_match_nolit_encodeBetterBlockAsm10B_emit_copy_short_2b: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm10B - -repeat_two_offset_match_nolit_encodeBetterBlockAsm10B_emit_copy_short_2b: - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm10B - -long_offset_short_match_nolit_encodeBetterBlockAsm10B: - MOVB $0xee, (CX) - MOVW R8, 1(CX) - LEAL -60(R12), R12 - ADDQ $0x03, CX - - // emitRepeat - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_encodeBetterBlockAsm10B_emit_copy_short - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm10B_emit_copy_short - CMPL R8, $0x00000800 - JB repeat_two_offset_match_nolit_encodeBetterBlockAsm10B_emit_copy_short - -cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm10B_emit_copy_short: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_encodeBetterBlockAsm10B_emit_copy_short - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm10B - -repeat_three_match_nolit_encodeBetterBlockAsm10B_emit_copy_short: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm10B - -repeat_two_match_nolit_encodeBetterBlockAsm10B_emit_copy_short: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm10B - -repeat_two_offset_match_nolit_encodeBetterBlockAsm10B_emit_copy_short: - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm10B - -two_byte_offset_short_match_nolit_encodeBetterBlockAsm10B: - MOVL R12, SI - SHLL $0x02, SI - CMPL R12, $0x0c - JAE emit_copy_three_match_nolit_encodeBetterBlockAsm10B - CMPL R8, $0x00000800 - JAE emit_copy_three_match_nolit_encodeBetterBlockAsm10B - LEAL -15(SI), SI - MOVB R8, 1(CX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm10B - -emit_copy_three_match_nolit_encodeBetterBlockAsm10B: - LEAL -2(SI), SI - MOVB SI, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm10B - -match_is_repeat_encodeBetterBlockAsm10B: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_match_emit_repeat_encodeBetterBlockAsm10B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_match_emit_repeat_encodeBetterBlockAsm10B - CMPL SI, $0x00000100 - JB two_bytes_match_emit_repeat_encodeBetterBlockAsm10B - JB three_bytes_match_emit_repeat_encodeBetterBlockAsm10B - -three_bytes_match_emit_repeat_encodeBetterBlockAsm10B: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_repeat_encodeBetterBlockAsm10B - -two_bytes_match_emit_repeat_encodeBetterBlockAsm10B: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_match_emit_repeat_encodeBetterBlockAsm10B - JMP memmove_long_match_emit_repeat_encodeBetterBlockAsm10B - -one_byte_match_emit_repeat_encodeBetterBlockAsm10B: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_match_emit_repeat_encodeBetterBlockAsm10B: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x04 - JBE emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm10B_memmove_move_4 - CMPQ R9, $0x08 - JB emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm10B_memmove_move_4through7 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm10B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm10B_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm10B_memmove_move_33through64 - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm10B_memmove_move_4: - MOVL (R10), R11 - MOVL R11, (CX) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm10B - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm10B_memmove_move_4through7: - MOVL (R10), R11 - MOVL -4(R10)(R9*1), R10 - MOVL R11, (CX) - MOVL R10, -4(CX)(R9*1) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm10B - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm10B_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm10B - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm10B_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm10B - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm10B_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm10B: - MOVQ SI, CX - JMP emit_literal_done_match_emit_repeat_encodeBetterBlockAsm10B - -memmove_long_match_emit_repeat_encodeBetterBlockAsm10B: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R14 - SUBQ R11, R14 - DECQ R13 - JA emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm10Blarge_forward_sse_loop_32 - LEAQ -32(R10)(R14*1), R11 - LEAQ -32(CX)(R14*1), R15 - -emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm10Blarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R11 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm10Blarge_big_loop_back - -emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm10Blarge_forward_sse_loop_32: - MOVOU -32(R10)(R14*1), X4 - MOVOU -16(R10)(R14*1), X5 - MOVOA X4, -32(CX)(R14*1) - MOVOA X5, -16(CX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm10Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_match_emit_repeat_encodeBetterBlockAsm10B: - ADDL R12, DX - ADDL $0x04, R12 - MOVL DX, 12(SP) - - // emitRepeat - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_repeat_encodeBetterBlockAsm10B - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm10B - CMPL R8, $0x00000800 - JB repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm10B - -cant_repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm10B: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_repeat_encodeBetterBlockAsm10B - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm10B - -repeat_three_match_nolit_repeat_encodeBetterBlockAsm10B: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm10B - -repeat_two_match_nolit_repeat_encodeBetterBlockAsm10B: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm10B - -repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm10B: - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - -match_nolit_emitcopy_end_encodeBetterBlockAsm10B: - CMPL DX, 8(SP) - JAE emit_remainder_encodeBetterBlockAsm10B - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeBetterBlockAsm10B - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeBetterBlockAsm10B: - MOVQ $0x0000cf1bbcdcbf9b, SI - MOVQ $0x9e3779b1, R8 - LEAQ 1(DI), DI - LEAQ -2(DX), R9 - MOVQ (BX)(DI*1), R10 - MOVQ 1(BX)(DI*1), R11 - MOVQ (BX)(R9*1), R12 - MOVQ 1(BX)(R9*1), R13 - SHLQ $0x10, R10 - IMULQ SI, R10 - SHRQ $0x34, R10 - SHLQ $0x20, R11 - IMULQ R8, R11 - SHRQ $0x36, R11 - SHLQ $0x10, R12 - IMULQ SI, R12 - SHRQ $0x34, R12 - SHLQ $0x20, R13 - IMULQ R8, R13 - SHRQ $0x36, R13 - LEAQ 1(DI), R8 - LEAQ 1(R9), R14 - MOVL DI, (AX)(R10*4) - MOVL R9, (AX)(R12*4) - MOVL R8, 16384(AX)(R11*4) - MOVL R14, 16384(AX)(R13*4) - LEAQ 1(R9)(DI*1), R8 - SHRQ $0x01, R8 - ADDQ $0x01, DI - SUBQ $0x01, R9 - -index_loop_encodeBetterBlockAsm10B: - CMPQ R8, R9 - JAE search_loop_encodeBetterBlockAsm10B - MOVQ (BX)(DI*1), R10 - MOVQ (BX)(R8*1), R11 - SHLQ $0x10, R10 - IMULQ SI, R10 - SHRQ $0x34, R10 - SHLQ $0x10, R11 - IMULQ SI, R11 - SHRQ $0x34, R11 - MOVL DI, (AX)(R10*4) - MOVL R8, (AX)(R11*4) - ADDQ $0x02, DI - ADDQ $0x02, R8 - JMP index_loop_encodeBetterBlockAsm10B - -emit_remainder_encodeBetterBlockAsm10B: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 3(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeBetterBlockAsm10B - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeBetterBlockAsm10B: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeBetterBlockAsm10B - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeBetterBlockAsm10B - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeBetterBlockAsm10B - JB three_bytes_emit_remainder_encodeBetterBlockAsm10B - -three_bytes_emit_remainder_encodeBetterBlockAsm10B: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeBetterBlockAsm10B - -two_bytes_emit_remainder_encodeBetterBlockAsm10B: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeBetterBlockAsm10B - JMP memmove_long_emit_remainder_encodeBetterBlockAsm10B - -one_byte_emit_remainder_encodeBetterBlockAsm10B: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeBetterBlockAsm10B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeBetterBlockAsm10B_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeBetterBlockAsm10B_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeBetterBlockAsm10B_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeBetterBlockAsm10B_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeBetterBlockAsm10B_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeBetterBlockAsm10B_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm10B_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm10B_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm10B_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm10B_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm10B_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm10B_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeBetterBlockAsm10B: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeBetterBlockAsm10B - -memmove_long_emit_remainder_encodeBetterBlockAsm10B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm10Blarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm10Blarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm10Blarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm10Blarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm10Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeBetterBlockAsm10B: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeBetterBlockAsm8B(dst []byte, src []byte, tmp *[5120]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeBetterBlockAsm8B(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00000028, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeBetterBlockAsm8B: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeBetterBlockAsm8B - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -6(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL $0x00000000, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeBetterBlockAsm8B: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x04, SI - LEAL 1(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeBetterBlockAsm8B - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ $0x9e3779b1, SI - MOVQ DI, R10 - MOVQ DI, R11 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x36, R10 - SHLQ $0x20, R11 - IMULQ SI, R11 - SHRQ $0x38, R11 - MOVL (AX)(R10*4), SI - MOVL 4096(AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - MOVL DX, 4096(AX)(R11*4) - MOVQ (BX)(SI*1), R10 - MOVQ (BX)(R8*1), R11 - CMPQ R10, DI - JEQ candidate_match_encodeBetterBlockAsm8B - CMPQ R11, DI - JNE no_short_found_encodeBetterBlockAsm8B - MOVL R8, SI - JMP candidate_match_encodeBetterBlockAsm8B - -no_short_found_encodeBetterBlockAsm8B: - CMPL R10, DI - JEQ candidate_match_encodeBetterBlockAsm8B - CMPL R11, DI - JEQ candidateS_match_encodeBetterBlockAsm8B - MOVL 20(SP), DX - JMP search_loop_encodeBetterBlockAsm8B - -candidateS_match_encodeBetterBlockAsm8B: - SHRQ $0x08, DI - MOVQ DI, R10 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x36, R10 - MOVL (AX)(R10*4), SI - INCL DX - MOVL DX, (AX)(R10*4) - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeBetterBlockAsm8B - DECL DX - MOVL R8, SI - -candidate_match_encodeBetterBlockAsm8B: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeBetterBlockAsm8B - -match_extend_back_loop_encodeBetterBlockAsm8B: - CMPL DX, DI - JBE match_extend_back_end_encodeBetterBlockAsm8B - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeBetterBlockAsm8B - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeBetterBlockAsm8B - JMP match_extend_back_loop_encodeBetterBlockAsm8B - -match_extend_back_end_encodeBetterBlockAsm8B: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 3(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeBetterBlockAsm8B - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeBetterBlockAsm8B: - MOVL DX, DI - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), R10 - - // matchLen - XORL R12, R12 - -matchlen_loopback_16_match_nolit_encodeBetterBlockAsm8B: - CMPL R8, $0x10 - JB matchlen_match8_match_nolit_encodeBetterBlockAsm8B - MOVQ (R9)(R12*1), R11 - MOVQ 8(R9)(R12*1), R13 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeBetterBlockAsm8B - XORQ 8(R10)(R12*1), R13 - JNZ matchlen_bsf_16match_nolit_encodeBetterBlockAsm8B - LEAL -16(R8), R8 - LEAL 16(R12), R12 - JMP matchlen_loopback_16_match_nolit_encodeBetterBlockAsm8B - -matchlen_bsf_16match_nolit_encodeBetterBlockAsm8B: -#ifdef GOAMD64_v3 - TZCNTQ R13, R13 - -#else - BSFQ R13, R13 - -#endif - SARQ $0x03, R13 - LEAL 8(R12)(R13*1), R12 - JMP match_nolit_end_encodeBetterBlockAsm8B - -matchlen_match8_match_nolit_encodeBetterBlockAsm8B: - CMPL R8, $0x08 - JB matchlen_match4_match_nolit_encodeBetterBlockAsm8B - MOVQ (R9)(R12*1), R11 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeBetterBlockAsm8B - LEAL -8(R8), R8 - LEAL 8(R12), R12 - JMP matchlen_match4_match_nolit_encodeBetterBlockAsm8B - -matchlen_bsf_8_match_nolit_encodeBetterBlockAsm8B: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL (R12)(R11*1), R12 - JMP match_nolit_end_encodeBetterBlockAsm8B - -matchlen_match4_match_nolit_encodeBetterBlockAsm8B: - CMPL R8, $0x04 - JB matchlen_match2_match_nolit_encodeBetterBlockAsm8B - MOVL (R9)(R12*1), R11 - CMPL (R10)(R12*1), R11 - JNE matchlen_match2_match_nolit_encodeBetterBlockAsm8B - LEAL -4(R8), R8 - LEAL 4(R12), R12 - -matchlen_match2_match_nolit_encodeBetterBlockAsm8B: - CMPL R8, $0x01 - JE matchlen_match1_match_nolit_encodeBetterBlockAsm8B - JB match_nolit_end_encodeBetterBlockAsm8B - MOVW (R9)(R12*1), R11 - CMPW (R10)(R12*1), R11 - JNE matchlen_match1_match_nolit_encodeBetterBlockAsm8B - LEAL 2(R12), R12 - SUBL $0x02, R8 - JZ match_nolit_end_encodeBetterBlockAsm8B - -matchlen_match1_match_nolit_encodeBetterBlockAsm8B: - MOVB (R9)(R12*1), R11 - CMPB (R10)(R12*1), R11 - JNE match_nolit_end_encodeBetterBlockAsm8B - LEAL 1(R12), R12 - -match_nolit_end_encodeBetterBlockAsm8B: - MOVL DX, R8 - SUBL SI, R8 - - // Check if repeat - CMPL 16(SP), R8 - JEQ match_is_repeat_encodeBetterBlockAsm8B - MOVL R8, 16(SP) - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_match_emit_encodeBetterBlockAsm8B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_match_emit_encodeBetterBlockAsm8B - CMPL SI, $0x00000100 - JB two_bytes_match_emit_encodeBetterBlockAsm8B - JB three_bytes_match_emit_encodeBetterBlockAsm8B - -three_bytes_match_emit_encodeBetterBlockAsm8B: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeBetterBlockAsm8B - -two_bytes_match_emit_encodeBetterBlockAsm8B: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_match_emit_encodeBetterBlockAsm8B - JMP memmove_long_match_emit_encodeBetterBlockAsm8B - -one_byte_match_emit_encodeBetterBlockAsm8B: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeBetterBlockAsm8B: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x04 - JBE emit_lit_memmove_match_emit_encodeBetterBlockAsm8B_memmove_move_4 - CMPQ R9, $0x08 - JB emit_lit_memmove_match_emit_encodeBetterBlockAsm8B_memmove_move_4through7 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeBetterBlockAsm8B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeBetterBlockAsm8B_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeBetterBlockAsm8B_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeBetterBlockAsm8B_memmove_move_4: - MOVL (R10), R11 - MOVL R11, (CX) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm8B - -emit_lit_memmove_match_emit_encodeBetterBlockAsm8B_memmove_move_4through7: - MOVL (R10), R11 - MOVL -4(R10)(R9*1), R10 - MOVL R11, (CX) - MOVL R10, -4(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm8B - -emit_lit_memmove_match_emit_encodeBetterBlockAsm8B_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm8B - -emit_lit_memmove_match_emit_encodeBetterBlockAsm8B_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeBetterBlockAsm8B - -emit_lit_memmove_match_emit_encodeBetterBlockAsm8B_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeBetterBlockAsm8B: - MOVQ SI, CX - JMP emit_literal_done_match_emit_encodeBetterBlockAsm8B - -memmove_long_match_emit_encodeBetterBlockAsm8B: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R14 - SUBQ R11, R14 - DECQ R13 - JA emit_lit_memmove_long_match_emit_encodeBetterBlockAsm8Blarge_forward_sse_loop_32 - LEAQ -32(R10)(R14*1), R11 - LEAQ -32(CX)(R14*1), R15 - -emit_lit_memmove_long_match_emit_encodeBetterBlockAsm8Blarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R11 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_match_emit_encodeBetterBlockAsm8Blarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeBetterBlockAsm8Blarge_forward_sse_loop_32: - MOVOU -32(R10)(R14*1), X4 - MOVOU -16(R10)(R14*1), X5 - MOVOA X4, -32(CX)(R14*1) - MOVOA X5, -16(CX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_match_emit_encodeBetterBlockAsm8Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_match_emit_encodeBetterBlockAsm8B: - ADDL R12, DX - ADDL $0x04, R12 - MOVL DX, 12(SP) - - // emitCopy - CMPL R12, $0x40 - JBE two_byte_offset_short_match_nolit_encodeBetterBlockAsm8B - CMPL R8, $0x00000800 - JAE long_offset_short_match_nolit_encodeBetterBlockAsm8B - MOVL $0x00000001, SI - LEAL 16(SI), SI - MOVB R8, 1(CX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, SI - MOVB SI, (CX) - ADDQ $0x02, CX - SUBL $0x08, R12 - - // emitRepeat - LEAL -4(R12), R12 - JMP cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm8B_emit_copy_short_2b - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_encodeBetterBlockAsm8B_emit_copy_short_2b - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm8B_emit_copy_short_2b - -cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm8B_emit_copy_short_2b: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_encodeBetterBlockAsm8B_emit_copy_short_2b - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm8B - -repeat_three_match_nolit_encodeBetterBlockAsm8B_emit_copy_short_2b: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm8B - -repeat_two_match_nolit_encodeBetterBlockAsm8B_emit_copy_short_2b: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm8B - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm8B - -long_offset_short_match_nolit_encodeBetterBlockAsm8B: - MOVB $0xee, (CX) - MOVW R8, 1(CX) - LEAL -60(R12), R12 - ADDQ $0x03, CX - - // emitRepeat - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_encodeBetterBlockAsm8B_emit_copy_short - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm8B_emit_copy_short - -cant_repeat_two_offset_match_nolit_encodeBetterBlockAsm8B_emit_copy_short: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_encodeBetterBlockAsm8B_emit_copy_short - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm8B - -repeat_three_match_nolit_encodeBetterBlockAsm8B_emit_copy_short: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm8B - -repeat_two_match_nolit_encodeBetterBlockAsm8B_emit_copy_short: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm8B - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm8B - -two_byte_offset_short_match_nolit_encodeBetterBlockAsm8B: - MOVL R12, SI - SHLL $0x02, SI - CMPL R12, $0x0c - JAE emit_copy_three_match_nolit_encodeBetterBlockAsm8B - LEAL -15(SI), SI - MOVB R8, 1(CX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm8B - -emit_copy_three_match_nolit_encodeBetterBlockAsm8B: - LEAL -2(SI), SI - MOVB SI, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm8B - -match_is_repeat_encodeBetterBlockAsm8B: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_match_emit_repeat_encodeBetterBlockAsm8B - MOVL DI, R8 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R9 - SUBL SI, R8 - LEAL -1(R8), SI - CMPL SI, $0x3c - JB one_byte_match_emit_repeat_encodeBetterBlockAsm8B - CMPL SI, $0x00000100 - JB two_bytes_match_emit_repeat_encodeBetterBlockAsm8B - JB three_bytes_match_emit_repeat_encodeBetterBlockAsm8B - -three_bytes_match_emit_repeat_encodeBetterBlockAsm8B: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_repeat_encodeBetterBlockAsm8B - -two_bytes_match_emit_repeat_encodeBetterBlockAsm8B: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_match_emit_repeat_encodeBetterBlockAsm8B - JMP memmove_long_match_emit_repeat_encodeBetterBlockAsm8B - -one_byte_match_emit_repeat_encodeBetterBlockAsm8B: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_match_emit_repeat_encodeBetterBlockAsm8B: - LEAQ (CX)(R8*1), SI - - // genMemMoveShort - CMPQ R8, $0x04 - JBE emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm8B_memmove_move_4 - CMPQ R8, $0x08 - JB emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm8B_memmove_move_4through7 - CMPQ R8, $0x10 - JBE emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm8B_memmove_move_8through16 - CMPQ R8, $0x20 - JBE emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm8B_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm8B_memmove_move_33through64 - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm8B_memmove_move_4: - MOVL (R9), R10 - MOVL R10, (CX) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm8B - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm8B_memmove_move_4through7: - MOVL (R9), R10 - MOVL -4(R9)(R8*1), R9 - MOVL R10, (CX) - MOVL R9, -4(CX)(R8*1) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm8B - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm8B_memmove_move_8through16: - MOVQ (R9), R10 - MOVQ -8(R9)(R8*1), R9 - MOVQ R10, (CX) - MOVQ R9, -8(CX)(R8*1) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm8B - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm8B_memmove_move_17through32: - MOVOU (R9), X0 - MOVOU -16(R9)(R8*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R8*1) - JMP memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm8B - -emit_lit_memmove_match_emit_repeat_encodeBetterBlockAsm8B_memmove_move_33through64: - MOVOU (R9), X0 - MOVOU 16(R9), X1 - MOVOU -32(R9)(R8*1), X2 - MOVOU -16(R9)(R8*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R8*1) - MOVOU X3, -16(CX)(R8*1) - -memmove_end_copy_match_emit_repeat_encodeBetterBlockAsm8B: - MOVQ SI, CX - JMP emit_literal_done_match_emit_repeat_encodeBetterBlockAsm8B - -memmove_long_match_emit_repeat_encodeBetterBlockAsm8B: - LEAQ (CX)(R8*1), SI - - // genMemMoveLong - MOVOU (R9), X0 - MOVOU 16(R9), X1 - MOVOU -32(R9)(R8*1), X2 - MOVOU -16(R9)(R8*1), X3 - MOVQ R8, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R13 - SUBQ R10, R13 - DECQ R11 - JA emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm8Blarge_forward_sse_loop_32 - LEAQ -32(R9)(R13*1), R10 - LEAQ -32(CX)(R13*1), R14 - -emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm8Blarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R14) - MOVOA X5, 16(R14) - ADDQ $0x20, R14 - ADDQ $0x20, R10 - ADDQ $0x20, R13 - DECQ R11 - JNA emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm8Blarge_big_loop_back - -emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm8Blarge_forward_sse_loop_32: - MOVOU -32(R9)(R13*1), X4 - MOVOU -16(R9)(R13*1), X5 - MOVOA X4, -32(CX)(R13*1) - MOVOA X5, -16(CX)(R13*1) - ADDQ $0x20, R13 - CMPQ R8, R13 - JAE emit_lit_memmove_long_match_emit_repeat_encodeBetterBlockAsm8Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R8*1) - MOVOU X3, -16(CX)(R8*1) - MOVQ SI, CX - -emit_literal_done_match_emit_repeat_encodeBetterBlockAsm8B: - ADDL R12, DX - ADDL $0x04, R12 - MOVL DX, 12(SP) - - // emitRepeat - MOVL R12, SI - LEAL -4(R12), R12 - CMPL SI, $0x08 - JBE repeat_two_match_nolit_repeat_encodeBetterBlockAsm8B - CMPL SI, $0x0c - JAE cant_repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm8B - -cant_repeat_two_offset_match_nolit_repeat_encodeBetterBlockAsm8B: - CMPL R12, $0x00000104 - JB repeat_three_match_nolit_repeat_encodeBetterBlockAsm8B - LEAL -256(R12), R12 - MOVW $0x0019, (CX) - MOVW R12, 2(CX) - ADDQ $0x04, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm8B - -repeat_three_match_nolit_repeat_encodeBetterBlockAsm8B: - LEAL -4(R12), R12 - MOVW $0x0015, (CX) - MOVB R12, 2(CX) - ADDQ $0x03, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm8B - -repeat_two_match_nolit_repeat_encodeBetterBlockAsm8B: - SHLL $0x02, R12 - ORL $0x01, R12 - MOVW R12, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeBetterBlockAsm8B - XORQ SI, SI - LEAL 1(SI)(R12*4), R12 - MOVB R8, 1(CX) - SARL $0x08, R8 - SHLL $0x05, R8 - ORL R8, R12 - MOVB R12, (CX) - ADDQ $0x02, CX - -match_nolit_emitcopy_end_encodeBetterBlockAsm8B: - CMPL DX, 8(SP) - JAE emit_remainder_encodeBetterBlockAsm8B - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeBetterBlockAsm8B - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeBetterBlockAsm8B: - MOVQ $0x0000cf1bbcdcbf9b, SI - MOVQ $0x9e3779b1, R8 - LEAQ 1(DI), DI - LEAQ -2(DX), R9 - MOVQ (BX)(DI*1), R10 - MOVQ 1(BX)(DI*1), R11 - MOVQ (BX)(R9*1), R12 - MOVQ 1(BX)(R9*1), R13 - SHLQ $0x10, R10 - IMULQ SI, R10 - SHRQ $0x36, R10 - SHLQ $0x20, R11 - IMULQ R8, R11 - SHRQ $0x38, R11 - SHLQ $0x10, R12 - IMULQ SI, R12 - SHRQ $0x36, R12 - SHLQ $0x20, R13 - IMULQ R8, R13 - SHRQ $0x38, R13 - LEAQ 1(DI), R8 - LEAQ 1(R9), R14 - MOVL DI, (AX)(R10*4) - MOVL R9, (AX)(R12*4) - MOVL R8, 4096(AX)(R11*4) - MOVL R14, 4096(AX)(R13*4) - LEAQ 1(R9)(DI*1), R8 - SHRQ $0x01, R8 - ADDQ $0x01, DI - SUBQ $0x01, R9 - -index_loop_encodeBetterBlockAsm8B: - CMPQ R8, R9 - JAE search_loop_encodeBetterBlockAsm8B - MOVQ (BX)(DI*1), R10 - MOVQ (BX)(R8*1), R11 - SHLQ $0x10, R10 - IMULQ SI, R10 - SHRQ $0x36, R10 - SHLQ $0x10, R11 - IMULQ SI, R11 - SHRQ $0x36, R11 - MOVL DI, (AX)(R10*4) - MOVL R8, (AX)(R11*4) - ADDQ $0x02, DI - ADDQ $0x02, R8 - JMP index_loop_encodeBetterBlockAsm8B - -emit_remainder_encodeBetterBlockAsm8B: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 3(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeBetterBlockAsm8B - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeBetterBlockAsm8B: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeBetterBlockAsm8B - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeBetterBlockAsm8B - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeBetterBlockAsm8B - JB three_bytes_emit_remainder_encodeBetterBlockAsm8B - -three_bytes_emit_remainder_encodeBetterBlockAsm8B: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeBetterBlockAsm8B - -two_bytes_emit_remainder_encodeBetterBlockAsm8B: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeBetterBlockAsm8B - JMP memmove_long_emit_remainder_encodeBetterBlockAsm8B - -one_byte_emit_remainder_encodeBetterBlockAsm8B: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeBetterBlockAsm8B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeBetterBlockAsm8B_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeBetterBlockAsm8B_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeBetterBlockAsm8B_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeBetterBlockAsm8B_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeBetterBlockAsm8B_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeBetterBlockAsm8B_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm8B_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm8B_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm8B_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm8B_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm8B_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeBetterBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeBetterBlockAsm8B_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeBetterBlockAsm8B: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeBetterBlockAsm8B - -memmove_long_emit_remainder_encodeBetterBlockAsm8B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm8Blarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm8Blarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm8Blarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm8Blarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeBetterBlockAsm8Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeBetterBlockAsm8B: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeSnappyBlockAsm(dst []byte, src []byte, tmp *[65536]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeSnappyBlockAsm(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00000200, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeSnappyBlockAsm: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeSnappyBlockAsm - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL DX, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeSnappyBlockAsm: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x06, SI - LEAL 4(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeSnappyBlockAsm - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ DI, R10 - MOVQ DI, R11 - SHRQ $0x08, R11 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x32, R10 - SHLQ $0x10, R11 - IMULQ R9, R11 - SHRQ $0x32, R11 - MOVL (AX)(R10*4), SI - MOVL (AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - LEAL 1(DX), R10 - MOVL R10, (AX)(R11*4) - MOVQ DI, R10 - SHRQ $0x10, R10 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x32, R10 - MOVL DX, R9 - SUBL 16(SP), R9 - MOVL 1(BX)(R9*1), R11 - MOVQ DI, R9 - SHRQ $0x08, R9 - CMPL R9, R11 - JNE no_repeat_found_encodeSnappyBlockAsm - LEAL 1(DX), DI - MOVL 12(SP), SI - MOVL DI, R8 - SUBL 16(SP), R8 - JZ repeat_extend_back_end_encodeSnappyBlockAsm - -repeat_extend_back_loop_encodeSnappyBlockAsm: - CMPL DI, SI - JBE repeat_extend_back_end_encodeSnappyBlockAsm - MOVB -1(BX)(R8*1), R9 - MOVB -1(BX)(DI*1), R10 - CMPB R9, R10 - JNE repeat_extend_back_end_encodeSnappyBlockAsm - LEAL -1(DI), DI - DECL R8 - JNZ repeat_extend_back_loop_encodeSnappyBlockAsm - -repeat_extend_back_end_encodeSnappyBlockAsm: - MOVL DI, SI - SUBL 12(SP), SI - LEAQ 5(CX)(SI*1), SI - CMPQ SI, (SP) - JB repeat_dst_size_check_encodeSnappyBlockAsm - MOVQ $0x00000000, ret+56(FP) - RET - -repeat_dst_size_check_encodeSnappyBlockAsm: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_repeat_emit_encodeSnappyBlockAsm - MOVL DI, R8 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R9 - SUBL SI, R8 - LEAL -1(R8), SI - CMPL SI, $0x3c - JB one_byte_repeat_emit_encodeSnappyBlockAsm - CMPL SI, $0x00000100 - JB two_bytes_repeat_emit_encodeSnappyBlockAsm - CMPL SI, $0x00010000 - JB three_bytes_repeat_emit_encodeSnappyBlockAsm - CMPL SI, $0x01000000 - JB four_bytes_repeat_emit_encodeSnappyBlockAsm - MOVB $0xfc, (CX) - MOVL SI, 1(CX) - ADDQ $0x05, CX - JMP memmove_long_repeat_emit_encodeSnappyBlockAsm - -four_bytes_repeat_emit_encodeSnappyBlockAsm: - MOVL SI, R10 - SHRL $0x10, R10 - MOVB $0xf8, (CX) - MOVW SI, 1(CX) - MOVB R10, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_repeat_emit_encodeSnappyBlockAsm - -three_bytes_repeat_emit_encodeSnappyBlockAsm: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_repeat_emit_encodeSnappyBlockAsm - -two_bytes_repeat_emit_encodeSnappyBlockAsm: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_repeat_emit_encodeSnappyBlockAsm - JMP memmove_long_repeat_emit_encodeSnappyBlockAsm - -one_byte_repeat_emit_encodeSnappyBlockAsm: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_repeat_emit_encodeSnappyBlockAsm: - LEAQ (CX)(R8*1), SI - - // genMemMoveShort - CMPQ R8, $0x08 - JBE emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm_memmove_move_8 - CMPQ R8, $0x10 - JBE emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm_memmove_move_8through16 - CMPQ R8, $0x20 - JBE emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm_memmove_move_17through32 - JMP emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm_memmove_move_33through64 - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm_memmove_move_8: - MOVQ (R9), R10 - MOVQ R10, (CX) - JMP memmove_end_copy_repeat_emit_encodeSnappyBlockAsm - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm_memmove_move_8through16: - MOVQ (R9), R10 - MOVQ -8(R9)(R8*1), R9 - MOVQ R10, (CX) - MOVQ R9, -8(CX)(R8*1) - JMP memmove_end_copy_repeat_emit_encodeSnappyBlockAsm - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm_memmove_move_17through32: - MOVOU (R9), X0 - MOVOU -16(R9)(R8*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R8*1) - JMP memmove_end_copy_repeat_emit_encodeSnappyBlockAsm - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm_memmove_move_33through64: - MOVOU (R9), X0 - MOVOU 16(R9), X1 - MOVOU -32(R9)(R8*1), X2 - MOVOU -16(R9)(R8*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R8*1) - MOVOU X3, -16(CX)(R8*1) - -memmove_end_copy_repeat_emit_encodeSnappyBlockAsm: - MOVQ SI, CX - JMP emit_literal_done_repeat_emit_encodeSnappyBlockAsm - -memmove_long_repeat_emit_encodeSnappyBlockAsm: - LEAQ (CX)(R8*1), SI - - // genMemMoveLong - MOVOU (R9), X0 - MOVOU 16(R9), X1 - MOVOU -32(R9)(R8*1), X2 - MOVOU -16(R9)(R8*1), X3 - MOVQ R8, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R12 - SUBQ R10, R12 - DECQ R11 - JA emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsmlarge_forward_sse_loop_32 - LEAQ -32(R9)(R12*1), R10 - LEAQ -32(CX)(R12*1), R13 - -emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsmlarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R13) - MOVOA X5, 16(R13) - ADDQ $0x20, R13 - ADDQ $0x20, R10 - ADDQ $0x20, R12 - DECQ R11 - JNA emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsmlarge_big_loop_back - -emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsmlarge_forward_sse_loop_32: - MOVOU -32(R9)(R12*1), X4 - MOVOU -16(R9)(R12*1), X5 - MOVOA X4, -32(CX)(R12*1) - MOVOA X5, -16(CX)(R12*1) - ADDQ $0x20, R12 - CMPQ R8, R12 - JAE emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsmlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R8*1) - MOVOU X3, -16(CX)(R8*1) - MOVQ SI, CX - -emit_literal_done_repeat_emit_encodeSnappyBlockAsm: - ADDL $0x05, DX - MOVL DX, SI - SUBL 16(SP), SI - MOVQ src_len+32(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R11, R11 - -matchlen_loopback_16_repeat_extend_encodeSnappyBlockAsm: - CMPL R8, $0x10 - JB matchlen_match8_repeat_extend_encodeSnappyBlockAsm - MOVQ (R9)(R11*1), R10 - MOVQ 8(R9)(R11*1), R12 - XORQ (SI)(R11*1), R10 - JNZ matchlen_bsf_8_repeat_extend_encodeSnappyBlockAsm - XORQ 8(SI)(R11*1), R12 - JNZ matchlen_bsf_16repeat_extend_encodeSnappyBlockAsm - LEAL -16(R8), R8 - LEAL 16(R11), R11 - JMP matchlen_loopback_16_repeat_extend_encodeSnappyBlockAsm - -matchlen_bsf_16repeat_extend_encodeSnappyBlockAsm: -#ifdef GOAMD64_v3 - TZCNTQ R12, R12 - -#else - BSFQ R12, R12 - -#endif - SARQ $0x03, R12 - LEAL 8(R11)(R12*1), R11 - JMP repeat_extend_forward_end_encodeSnappyBlockAsm - -matchlen_match8_repeat_extend_encodeSnappyBlockAsm: - CMPL R8, $0x08 - JB matchlen_match4_repeat_extend_encodeSnappyBlockAsm - MOVQ (R9)(R11*1), R10 - XORQ (SI)(R11*1), R10 - JNZ matchlen_bsf_8_repeat_extend_encodeSnappyBlockAsm - LEAL -8(R8), R8 - LEAL 8(R11), R11 - JMP matchlen_match4_repeat_extend_encodeSnappyBlockAsm - -matchlen_bsf_8_repeat_extend_encodeSnappyBlockAsm: -#ifdef GOAMD64_v3 - TZCNTQ R10, R10 - -#else - BSFQ R10, R10 - -#endif - SARQ $0x03, R10 - LEAL (R11)(R10*1), R11 - JMP repeat_extend_forward_end_encodeSnappyBlockAsm - -matchlen_match4_repeat_extend_encodeSnappyBlockAsm: - CMPL R8, $0x04 - JB matchlen_match2_repeat_extend_encodeSnappyBlockAsm - MOVL (R9)(R11*1), R10 - CMPL (SI)(R11*1), R10 - JNE matchlen_match2_repeat_extend_encodeSnappyBlockAsm - LEAL -4(R8), R8 - LEAL 4(R11), R11 - -matchlen_match2_repeat_extend_encodeSnappyBlockAsm: - CMPL R8, $0x01 - JE matchlen_match1_repeat_extend_encodeSnappyBlockAsm - JB repeat_extend_forward_end_encodeSnappyBlockAsm - MOVW (R9)(R11*1), R10 - CMPW (SI)(R11*1), R10 - JNE matchlen_match1_repeat_extend_encodeSnappyBlockAsm - LEAL 2(R11), R11 - SUBL $0x02, R8 - JZ repeat_extend_forward_end_encodeSnappyBlockAsm - -matchlen_match1_repeat_extend_encodeSnappyBlockAsm: - MOVB (R9)(R11*1), R10 - CMPB (SI)(R11*1), R10 - JNE repeat_extend_forward_end_encodeSnappyBlockAsm - LEAL 1(R11), R11 - -repeat_extend_forward_end_encodeSnappyBlockAsm: - ADDL R11, DX - MOVL DX, SI - SUBL DI, SI - MOVL 16(SP), DI - - // emitCopy - CMPL DI, $0x00010000 - JB two_byte_offset_repeat_as_copy_encodeSnappyBlockAsm - -four_bytes_loop_back_repeat_as_copy_encodeSnappyBlockAsm: - CMPL SI, $0x40 - JBE four_bytes_remain_repeat_as_copy_encodeSnappyBlockAsm - MOVB $0xff, (CX) - MOVL DI, 1(CX) - LEAL -64(SI), SI - ADDQ $0x05, CX - CMPL SI, $0x04 - JB four_bytes_remain_repeat_as_copy_encodeSnappyBlockAsm - JMP four_bytes_loop_back_repeat_as_copy_encodeSnappyBlockAsm - -four_bytes_remain_repeat_as_copy_encodeSnappyBlockAsm: - TESTL SI, SI - JZ repeat_end_emit_encodeSnappyBlockAsm - XORL R8, R8 - LEAL -1(R8)(SI*4), SI - MOVB SI, (CX) - MOVL DI, 1(CX) - ADDQ $0x05, CX - JMP repeat_end_emit_encodeSnappyBlockAsm - -two_byte_offset_repeat_as_copy_encodeSnappyBlockAsm: - CMPL SI, $0x40 - JBE two_byte_offset_short_repeat_as_copy_encodeSnappyBlockAsm - MOVB $0xee, (CX) - MOVW DI, 1(CX) - LEAL -60(SI), SI - ADDQ $0x03, CX - JMP two_byte_offset_repeat_as_copy_encodeSnappyBlockAsm - -two_byte_offset_short_repeat_as_copy_encodeSnappyBlockAsm: - MOVL SI, R8 - SHLL $0x02, R8 - CMPL SI, $0x0c - JAE emit_copy_three_repeat_as_copy_encodeSnappyBlockAsm - CMPL DI, $0x00000800 - JAE emit_copy_three_repeat_as_copy_encodeSnappyBlockAsm - LEAL -15(R8), R8 - MOVB DI, 1(CX) - SHRL $0x08, DI - SHLL $0x05, DI - ORL DI, R8 - MOVB R8, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeSnappyBlockAsm - -emit_copy_three_repeat_as_copy_encodeSnappyBlockAsm: - LEAL -2(R8), R8 - MOVB R8, (CX) - MOVW DI, 1(CX) - ADDQ $0x03, CX - -repeat_end_emit_encodeSnappyBlockAsm: - MOVL DX, 12(SP) - JMP search_loop_encodeSnappyBlockAsm - -no_repeat_found_encodeSnappyBlockAsm: - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeSnappyBlockAsm - SHRQ $0x08, DI - MOVL (AX)(R10*4), SI - LEAL 2(DX), R9 - CMPL (BX)(R8*1), DI - JEQ candidate2_match_encodeSnappyBlockAsm - MOVL R9, (AX)(R10*4) - SHRQ $0x08, DI - CMPL (BX)(SI*1), DI - JEQ candidate3_match_encodeSnappyBlockAsm - MOVL 20(SP), DX - JMP search_loop_encodeSnappyBlockAsm - -candidate3_match_encodeSnappyBlockAsm: - ADDL $0x02, DX - JMP candidate_match_encodeSnappyBlockAsm - -candidate2_match_encodeSnappyBlockAsm: - MOVL R9, (AX)(R10*4) - INCL DX - MOVL R8, SI - -candidate_match_encodeSnappyBlockAsm: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeSnappyBlockAsm - -match_extend_back_loop_encodeSnappyBlockAsm: - CMPL DX, DI - JBE match_extend_back_end_encodeSnappyBlockAsm - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeSnappyBlockAsm - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeSnappyBlockAsm - JMP match_extend_back_loop_encodeSnappyBlockAsm - -match_extend_back_end_encodeSnappyBlockAsm: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 5(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeSnappyBlockAsm - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeSnappyBlockAsm: - MOVL DX, DI - MOVL 12(SP), R8 - CMPL R8, DI - JEQ emit_literal_done_match_emit_encodeSnappyBlockAsm - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(R8*1), DI - SUBL R8, R9 - LEAL -1(R9), R8 - CMPL R8, $0x3c - JB one_byte_match_emit_encodeSnappyBlockAsm - CMPL R8, $0x00000100 - JB two_bytes_match_emit_encodeSnappyBlockAsm - CMPL R8, $0x00010000 - JB three_bytes_match_emit_encodeSnappyBlockAsm - CMPL R8, $0x01000000 - JB four_bytes_match_emit_encodeSnappyBlockAsm - MOVB $0xfc, (CX) - MOVL R8, 1(CX) - ADDQ $0x05, CX - JMP memmove_long_match_emit_encodeSnappyBlockAsm - -four_bytes_match_emit_encodeSnappyBlockAsm: - MOVL R8, R10 - SHRL $0x10, R10 - MOVB $0xf8, (CX) - MOVW R8, 1(CX) - MOVB R10, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_match_emit_encodeSnappyBlockAsm - -three_bytes_match_emit_encodeSnappyBlockAsm: - MOVB $0xf4, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeSnappyBlockAsm - -two_bytes_match_emit_encodeSnappyBlockAsm: - MOVB $0xf0, (CX) - MOVB R8, 1(CX) - ADDQ $0x02, CX - CMPL R8, $0x40 - JB memmove_match_emit_encodeSnappyBlockAsm - JMP memmove_long_match_emit_encodeSnappyBlockAsm - -one_byte_match_emit_encodeSnappyBlockAsm: - SHLB $0x02, R8 - MOVB R8, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeSnappyBlockAsm: - LEAQ (CX)(R9*1), R8 - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_match_emit_encodeSnappyBlockAsm_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeSnappyBlockAsm_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeSnappyBlockAsm_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeSnappyBlockAsm_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm_memmove_move_8: - MOVQ (DI), R10 - MOVQ R10, (CX) - JMP memmove_end_copy_match_emit_encodeSnappyBlockAsm - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm_memmove_move_8through16: - MOVQ (DI), R10 - MOVQ -8(DI)(R9*1), DI - MOVQ R10, (CX) - MOVQ DI, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBlockAsm - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm_memmove_move_17through32: - MOVOU (DI), X0 - MOVOU -16(DI)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBlockAsm - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm_memmove_move_33through64: - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeSnappyBlockAsm: - MOVQ R8, CX - JMP emit_literal_done_match_emit_encodeSnappyBlockAsm - -memmove_long_match_emit_encodeSnappyBlockAsm: - LEAQ (CX)(R9*1), R8 - - // genMemMoveLong - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVQ R9, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R12 - SUBQ R10, R12 - DECQ R11 - JA emit_lit_memmove_long_match_emit_encodeSnappyBlockAsmlarge_forward_sse_loop_32 - LEAQ -32(DI)(R12*1), R10 - LEAQ -32(CX)(R12*1), R13 - -emit_lit_memmove_long_match_emit_encodeSnappyBlockAsmlarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R13) - MOVOA X5, 16(R13) - ADDQ $0x20, R13 - ADDQ $0x20, R10 - ADDQ $0x20, R12 - DECQ R11 - JNA emit_lit_memmove_long_match_emit_encodeSnappyBlockAsmlarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeSnappyBlockAsmlarge_forward_sse_loop_32: - MOVOU -32(DI)(R12*1), X4 - MOVOU -16(DI)(R12*1), X5 - MOVOA X4, -32(CX)(R12*1) - MOVOA X5, -16(CX)(R12*1) - ADDQ $0x20, R12 - CMPQ R9, R12 - JAE emit_lit_memmove_long_match_emit_encodeSnappyBlockAsmlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ R8, CX - -emit_literal_done_match_emit_encodeSnappyBlockAsm: -match_nolit_loop_encodeSnappyBlockAsm: - MOVL DX, DI - SUBL SI, DI - MOVL DI, 16(SP) - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), DI - SUBL DX, DI - LEAQ (BX)(DX*1), R8 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R10, R10 - -matchlen_loopback_16_match_nolit_encodeSnappyBlockAsm: - CMPL DI, $0x10 - JB matchlen_match8_match_nolit_encodeSnappyBlockAsm - MOVQ (R8)(R10*1), R9 - MOVQ 8(R8)(R10*1), R11 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBlockAsm - XORQ 8(SI)(R10*1), R11 - JNZ matchlen_bsf_16match_nolit_encodeSnappyBlockAsm - LEAL -16(DI), DI - LEAL 16(R10), R10 - JMP matchlen_loopback_16_match_nolit_encodeSnappyBlockAsm - -matchlen_bsf_16match_nolit_encodeSnappyBlockAsm: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL 8(R10)(R11*1), R10 - JMP match_nolit_end_encodeSnappyBlockAsm - -matchlen_match8_match_nolit_encodeSnappyBlockAsm: - CMPL DI, $0x08 - JB matchlen_match4_match_nolit_encodeSnappyBlockAsm - MOVQ (R8)(R10*1), R9 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBlockAsm - LEAL -8(DI), DI - LEAL 8(R10), R10 - JMP matchlen_match4_match_nolit_encodeSnappyBlockAsm - -matchlen_bsf_8_match_nolit_encodeSnappyBlockAsm: -#ifdef GOAMD64_v3 - TZCNTQ R9, R9 - -#else - BSFQ R9, R9 - -#endif - SARQ $0x03, R9 - LEAL (R10)(R9*1), R10 - JMP match_nolit_end_encodeSnappyBlockAsm - -matchlen_match4_match_nolit_encodeSnappyBlockAsm: - CMPL DI, $0x04 - JB matchlen_match2_match_nolit_encodeSnappyBlockAsm - MOVL (R8)(R10*1), R9 - CMPL (SI)(R10*1), R9 - JNE matchlen_match2_match_nolit_encodeSnappyBlockAsm - LEAL -4(DI), DI - LEAL 4(R10), R10 - -matchlen_match2_match_nolit_encodeSnappyBlockAsm: - CMPL DI, $0x01 - JE matchlen_match1_match_nolit_encodeSnappyBlockAsm - JB match_nolit_end_encodeSnappyBlockAsm - MOVW (R8)(R10*1), R9 - CMPW (SI)(R10*1), R9 - JNE matchlen_match1_match_nolit_encodeSnappyBlockAsm - LEAL 2(R10), R10 - SUBL $0x02, DI - JZ match_nolit_end_encodeSnappyBlockAsm - -matchlen_match1_match_nolit_encodeSnappyBlockAsm: - MOVB (R8)(R10*1), R9 - CMPB (SI)(R10*1), R9 - JNE match_nolit_end_encodeSnappyBlockAsm - LEAL 1(R10), R10 - -match_nolit_end_encodeSnappyBlockAsm: - ADDL R10, DX - MOVL 16(SP), SI - ADDL $0x04, R10 - MOVL DX, 12(SP) - - // emitCopy - CMPL SI, $0x00010000 - JB two_byte_offset_match_nolit_encodeSnappyBlockAsm - -four_bytes_loop_back_match_nolit_encodeSnappyBlockAsm: - CMPL R10, $0x40 - JBE four_bytes_remain_match_nolit_encodeSnappyBlockAsm - MOVB $0xff, (CX) - MOVL SI, 1(CX) - LEAL -64(R10), R10 - ADDQ $0x05, CX - CMPL R10, $0x04 - JB four_bytes_remain_match_nolit_encodeSnappyBlockAsm - JMP four_bytes_loop_back_match_nolit_encodeSnappyBlockAsm - -four_bytes_remain_match_nolit_encodeSnappyBlockAsm: - TESTL R10, R10 - JZ match_nolit_emitcopy_end_encodeSnappyBlockAsm - XORL DI, DI - LEAL -1(DI)(R10*4), R10 - MOVB R10, (CX) - MOVL SI, 1(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeSnappyBlockAsm - -two_byte_offset_match_nolit_encodeSnappyBlockAsm: - CMPL R10, $0x40 - JBE two_byte_offset_short_match_nolit_encodeSnappyBlockAsm - MOVB $0xee, (CX) - MOVW SI, 1(CX) - LEAL -60(R10), R10 - ADDQ $0x03, CX - JMP two_byte_offset_match_nolit_encodeSnappyBlockAsm - -two_byte_offset_short_match_nolit_encodeSnappyBlockAsm: - MOVL R10, DI - SHLL $0x02, DI - CMPL R10, $0x0c - JAE emit_copy_three_match_nolit_encodeSnappyBlockAsm - CMPL SI, $0x00000800 - JAE emit_copy_three_match_nolit_encodeSnappyBlockAsm - LEAL -15(DI), DI - MOVB SI, 1(CX) - SHRL $0x08, SI - SHLL $0x05, SI - ORL SI, DI - MOVB DI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeSnappyBlockAsm - -emit_copy_three_match_nolit_encodeSnappyBlockAsm: - LEAL -2(DI), DI - MOVB DI, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - -match_nolit_emitcopy_end_encodeSnappyBlockAsm: - CMPL DX, 8(SP) - JAE emit_remainder_encodeSnappyBlockAsm - MOVQ -2(BX)(DX*1), DI - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeSnappyBlockAsm - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeSnappyBlockAsm: - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ DI, R8 - SHRQ $0x10, DI - MOVQ DI, SI - SHLQ $0x10, R8 - IMULQ R9, R8 - SHRQ $0x32, R8 - SHLQ $0x10, SI - IMULQ R9, SI - SHRQ $0x32, SI - LEAL -2(DX), R9 - LEAQ (AX)(SI*4), R10 - MOVL (R10), SI - MOVL R9, (AX)(R8*4) - MOVL DX, (R10) - CMPL (BX)(SI*1), DI - JEQ match_nolit_loop_encodeSnappyBlockAsm - INCL DX - JMP search_loop_encodeSnappyBlockAsm - -emit_remainder_encodeSnappyBlockAsm: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 5(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeSnappyBlockAsm - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeSnappyBlockAsm: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeSnappyBlockAsm - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeSnappyBlockAsm - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeSnappyBlockAsm - CMPL DX, $0x00010000 - JB three_bytes_emit_remainder_encodeSnappyBlockAsm - CMPL DX, $0x01000000 - JB four_bytes_emit_remainder_encodeSnappyBlockAsm - MOVB $0xfc, (CX) - MOVL DX, 1(CX) - ADDQ $0x05, CX - JMP memmove_long_emit_remainder_encodeSnappyBlockAsm - -four_bytes_emit_remainder_encodeSnappyBlockAsm: - MOVL DX, BX - SHRL $0x10, BX - MOVB $0xf8, (CX) - MOVW DX, 1(CX) - MOVB BL, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_emit_remainder_encodeSnappyBlockAsm - -three_bytes_emit_remainder_encodeSnappyBlockAsm: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeSnappyBlockAsm - -two_bytes_emit_remainder_encodeSnappyBlockAsm: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeSnappyBlockAsm - JMP memmove_long_emit_remainder_encodeSnappyBlockAsm - -one_byte_emit_remainder_encodeSnappyBlockAsm: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeSnappyBlockAsm: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeSnappyBlockAsm: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeSnappyBlockAsm - -memmove_long_emit_remainder_encodeSnappyBlockAsm: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsmlarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsmlarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsmlarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsmlarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsmlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeSnappyBlockAsm: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeSnappyBlockAsm64K(dst []byte, src []byte, tmp *[65536]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeSnappyBlockAsm64K(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00000200, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeSnappyBlockAsm64K: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeSnappyBlockAsm64K - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL DX, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeSnappyBlockAsm64K: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x06, SI - LEAL 4(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeSnappyBlockAsm64K - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ DI, R10 - MOVQ DI, R11 - SHRQ $0x08, R11 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x32, R10 - SHLQ $0x10, R11 - IMULQ R9, R11 - SHRQ $0x32, R11 - MOVL (AX)(R10*4), SI - MOVL (AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - LEAL 1(DX), R10 - MOVL R10, (AX)(R11*4) - MOVQ DI, R10 - SHRQ $0x10, R10 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x32, R10 - MOVL DX, R9 - SUBL 16(SP), R9 - MOVL 1(BX)(R9*1), R11 - MOVQ DI, R9 - SHRQ $0x08, R9 - CMPL R9, R11 - JNE no_repeat_found_encodeSnappyBlockAsm64K - LEAL 1(DX), DI - MOVL 12(SP), SI - MOVL DI, R8 - SUBL 16(SP), R8 - JZ repeat_extend_back_end_encodeSnappyBlockAsm64K - -repeat_extend_back_loop_encodeSnappyBlockAsm64K: - CMPL DI, SI - JBE repeat_extend_back_end_encodeSnappyBlockAsm64K - MOVB -1(BX)(R8*1), R9 - MOVB -1(BX)(DI*1), R10 - CMPB R9, R10 - JNE repeat_extend_back_end_encodeSnappyBlockAsm64K - LEAL -1(DI), DI - DECL R8 - JNZ repeat_extend_back_loop_encodeSnappyBlockAsm64K - -repeat_extend_back_end_encodeSnappyBlockAsm64K: - MOVL DI, SI - SUBL 12(SP), SI - LEAQ 3(CX)(SI*1), SI - CMPQ SI, (SP) - JB repeat_dst_size_check_encodeSnappyBlockAsm64K - MOVQ $0x00000000, ret+56(FP) - RET - -repeat_dst_size_check_encodeSnappyBlockAsm64K: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_repeat_emit_encodeSnappyBlockAsm64K - MOVL DI, R8 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R9 - SUBL SI, R8 - LEAL -1(R8), SI - CMPL SI, $0x3c - JB one_byte_repeat_emit_encodeSnappyBlockAsm64K - CMPL SI, $0x00000100 - JB two_bytes_repeat_emit_encodeSnappyBlockAsm64K - JB three_bytes_repeat_emit_encodeSnappyBlockAsm64K - -three_bytes_repeat_emit_encodeSnappyBlockAsm64K: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_repeat_emit_encodeSnappyBlockAsm64K - -two_bytes_repeat_emit_encodeSnappyBlockAsm64K: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_repeat_emit_encodeSnappyBlockAsm64K - JMP memmove_long_repeat_emit_encodeSnappyBlockAsm64K - -one_byte_repeat_emit_encodeSnappyBlockAsm64K: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_repeat_emit_encodeSnappyBlockAsm64K: - LEAQ (CX)(R8*1), SI - - // genMemMoveShort - CMPQ R8, $0x08 - JBE emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm64K_memmove_move_8 - CMPQ R8, $0x10 - JBE emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm64K_memmove_move_8through16 - CMPQ R8, $0x20 - JBE emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm64K_memmove_move_17through32 - JMP emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm64K_memmove_move_33through64 - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm64K_memmove_move_8: - MOVQ (R9), R10 - MOVQ R10, (CX) - JMP memmove_end_copy_repeat_emit_encodeSnappyBlockAsm64K - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm64K_memmove_move_8through16: - MOVQ (R9), R10 - MOVQ -8(R9)(R8*1), R9 - MOVQ R10, (CX) - MOVQ R9, -8(CX)(R8*1) - JMP memmove_end_copy_repeat_emit_encodeSnappyBlockAsm64K - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm64K_memmove_move_17through32: - MOVOU (R9), X0 - MOVOU -16(R9)(R8*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R8*1) - JMP memmove_end_copy_repeat_emit_encodeSnappyBlockAsm64K - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm64K_memmove_move_33through64: - MOVOU (R9), X0 - MOVOU 16(R9), X1 - MOVOU -32(R9)(R8*1), X2 - MOVOU -16(R9)(R8*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R8*1) - MOVOU X3, -16(CX)(R8*1) - -memmove_end_copy_repeat_emit_encodeSnappyBlockAsm64K: - MOVQ SI, CX - JMP emit_literal_done_repeat_emit_encodeSnappyBlockAsm64K - -memmove_long_repeat_emit_encodeSnappyBlockAsm64K: - LEAQ (CX)(R8*1), SI - - // genMemMoveLong - MOVOU (R9), X0 - MOVOU 16(R9), X1 - MOVOU -32(R9)(R8*1), X2 - MOVOU -16(R9)(R8*1), X3 - MOVQ R8, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R12 - SUBQ R10, R12 - DECQ R11 - JA emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm64Klarge_forward_sse_loop_32 - LEAQ -32(R9)(R12*1), R10 - LEAQ -32(CX)(R12*1), R13 - -emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm64Klarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R13) - MOVOA X5, 16(R13) - ADDQ $0x20, R13 - ADDQ $0x20, R10 - ADDQ $0x20, R12 - DECQ R11 - JNA emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm64Klarge_big_loop_back - -emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm64Klarge_forward_sse_loop_32: - MOVOU -32(R9)(R12*1), X4 - MOVOU -16(R9)(R12*1), X5 - MOVOA X4, -32(CX)(R12*1) - MOVOA X5, -16(CX)(R12*1) - ADDQ $0x20, R12 - CMPQ R8, R12 - JAE emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm64Klarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R8*1) - MOVOU X3, -16(CX)(R8*1) - MOVQ SI, CX - -emit_literal_done_repeat_emit_encodeSnappyBlockAsm64K: - ADDL $0x05, DX - MOVL DX, SI - SUBL 16(SP), SI - MOVQ src_len+32(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R11, R11 - -matchlen_loopback_16_repeat_extend_encodeSnappyBlockAsm64K: - CMPL R8, $0x10 - JB matchlen_match8_repeat_extend_encodeSnappyBlockAsm64K - MOVQ (R9)(R11*1), R10 - MOVQ 8(R9)(R11*1), R12 - XORQ (SI)(R11*1), R10 - JNZ matchlen_bsf_8_repeat_extend_encodeSnappyBlockAsm64K - XORQ 8(SI)(R11*1), R12 - JNZ matchlen_bsf_16repeat_extend_encodeSnappyBlockAsm64K - LEAL -16(R8), R8 - LEAL 16(R11), R11 - JMP matchlen_loopback_16_repeat_extend_encodeSnappyBlockAsm64K - -matchlen_bsf_16repeat_extend_encodeSnappyBlockAsm64K: -#ifdef GOAMD64_v3 - TZCNTQ R12, R12 - -#else - BSFQ R12, R12 - -#endif - SARQ $0x03, R12 - LEAL 8(R11)(R12*1), R11 - JMP repeat_extend_forward_end_encodeSnappyBlockAsm64K - -matchlen_match8_repeat_extend_encodeSnappyBlockAsm64K: - CMPL R8, $0x08 - JB matchlen_match4_repeat_extend_encodeSnappyBlockAsm64K - MOVQ (R9)(R11*1), R10 - XORQ (SI)(R11*1), R10 - JNZ matchlen_bsf_8_repeat_extend_encodeSnappyBlockAsm64K - LEAL -8(R8), R8 - LEAL 8(R11), R11 - JMP matchlen_match4_repeat_extend_encodeSnappyBlockAsm64K - -matchlen_bsf_8_repeat_extend_encodeSnappyBlockAsm64K: -#ifdef GOAMD64_v3 - TZCNTQ R10, R10 - -#else - BSFQ R10, R10 - -#endif - SARQ $0x03, R10 - LEAL (R11)(R10*1), R11 - JMP repeat_extend_forward_end_encodeSnappyBlockAsm64K - -matchlen_match4_repeat_extend_encodeSnappyBlockAsm64K: - CMPL R8, $0x04 - JB matchlen_match2_repeat_extend_encodeSnappyBlockAsm64K - MOVL (R9)(R11*1), R10 - CMPL (SI)(R11*1), R10 - JNE matchlen_match2_repeat_extend_encodeSnappyBlockAsm64K - LEAL -4(R8), R8 - LEAL 4(R11), R11 - -matchlen_match2_repeat_extend_encodeSnappyBlockAsm64K: - CMPL R8, $0x01 - JE matchlen_match1_repeat_extend_encodeSnappyBlockAsm64K - JB repeat_extend_forward_end_encodeSnappyBlockAsm64K - MOVW (R9)(R11*1), R10 - CMPW (SI)(R11*1), R10 - JNE matchlen_match1_repeat_extend_encodeSnappyBlockAsm64K - LEAL 2(R11), R11 - SUBL $0x02, R8 - JZ repeat_extend_forward_end_encodeSnappyBlockAsm64K - -matchlen_match1_repeat_extend_encodeSnappyBlockAsm64K: - MOVB (R9)(R11*1), R10 - CMPB (SI)(R11*1), R10 - JNE repeat_extend_forward_end_encodeSnappyBlockAsm64K - LEAL 1(R11), R11 - -repeat_extend_forward_end_encodeSnappyBlockAsm64K: - ADDL R11, DX - MOVL DX, SI - SUBL DI, SI - MOVL 16(SP), DI - - // emitCopy -two_byte_offset_repeat_as_copy_encodeSnappyBlockAsm64K: - CMPL SI, $0x40 - JBE two_byte_offset_short_repeat_as_copy_encodeSnappyBlockAsm64K - MOVB $0xee, (CX) - MOVW DI, 1(CX) - LEAL -60(SI), SI - ADDQ $0x03, CX - JMP two_byte_offset_repeat_as_copy_encodeSnappyBlockAsm64K - -two_byte_offset_short_repeat_as_copy_encodeSnappyBlockAsm64K: - MOVL SI, R8 - SHLL $0x02, R8 - CMPL SI, $0x0c - JAE emit_copy_three_repeat_as_copy_encodeSnappyBlockAsm64K - CMPL DI, $0x00000800 - JAE emit_copy_three_repeat_as_copy_encodeSnappyBlockAsm64K - LEAL -15(R8), R8 - MOVB DI, 1(CX) - SHRL $0x08, DI - SHLL $0x05, DI - ORL DI, R8 - MOVB R8, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeSnappyBlockAsm64K - -emit_copy_three_repeat_as_copy_encodeSnappyBlockAsm64K: - LEAL -2(R8), R8 - MOVB R8, (CX) - MOVW DI, 1(CX) - ADDQ $0x03, CX - -repeat_end_emit_encodeSnappyBlockAsm64K: - MOVL DX, 12(SP) - JMP search_loop_encodeSnappyBlockAsm64K - -no_repeat_found_encodeSnappyBlockAsm64K: - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeSnappyBlockAsm64K - SHRQ $0x08, DI - MOVL (AX)(R10*4), SI - LEAL 2(DX), R9 - CMPL (BX)(R8*1), DI - JEQ candidate2_match_encodeSnappyBlockAsm64K - MOVL R9, (AX)(R10*4) - SHRQ $0x08, DI - CMPL (BX)(SI*1), DI - JEQ candidate3_match_encodeSnappyBlockAsm64K - MOVL 20(SP), DX - JMP search_loop_encodeSnappyBlockAsm64K - -candidate3_match_encodeSnappyBlockAsm64K: - ADDL $0x02, DX - JMP candidate_match_encodeSnappyBlockAsm64K - -candidate2_match_encodeSnappyBlockAsm64K: - MOVL R9, (AX)(R10*4) - INCL DX - MOVL R8, SI - -candidate_match_encodeSnappyBlockAsm64K: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeSnappyBlockAsm64K - -match_extend_back_loop_encodeSnappyBlockAsm64K: - CMPL DX, DI - JBE match_extend_back_end_encodeSnappyBlockAsm64K - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeSnappyBlockAsm64K - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeSnappyBlockAsm64K - JMP match_extend_back_loop_encodeSnappyBlockAsm64K - -match_extend_back_end_encodeSnappyBlockAsm64K: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 3(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeSnappyBlockAsm64K - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeSnappyBlockAsm64K: - MOVL DX, DI - MOVL 12(SP), R8 - CMPL R8, DI - JEQ emit_literal_done_match_emit_encodeSnappyBlockAsm64K - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(R8*1), DI - SUBL R8, R9 - LEAL -1(R9), R8 - CMPL R8, $0x3c - JB one_byte_match_emit_encodeSnappyBlockAsm64K - CMPL R8, $0x00000100 - JB two_bytes_match_emit_encodeSnappyBlockAsm64K - JB three_bytes_match_emit_encodeSnappyBlockAsm64K - -three_bytes_match_emit_encodeSnappyBlockAsm64K: - MOVB $0xf4, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeSnappyBlockAsm64K - -two_bytes_match_emit_encodeSnappyBlockAsm64K: - MOVB $0xf0, (CX) - MOVB R8, 1(CX) - ADDQ $0x02, CX - CMPL R8, $0x40 - JB memmove_match_emit_encodeSnappyBlockAsm64K - JMP memmove_long_match_emit_encodeSnappyBlockAsm64K - -one_byte_match_emit_encodeSnappyBlockAsm64K: - SHLB $0x02, R8 - MOVB R8, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeSnappyBlockAsm64K: - LEAQ (CX)(R9*1), R8 - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_match_emit_encodeSnappyBlockAsm64K_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeSnappyBlockAsm64K_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeSnappyBlockAsm64K_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeSnappyBlockAsm64K_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm64K_memmove_move_8: - MOVQ (DI), R10 - MOVQ R10, (CX) - JMP memmove_end_copy_match_emit_encodeSnappyBlockAsm64K - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm64K_memmove_move_8through16: - MOVQ (DI), R10 - MOVQ -8(DI)(R9*1), DI - MOVQ R10, (CX) - MOVQ DI, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBlockAsm64K - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm64K_memmove_move_17through32: - MOVOU (DI), X0 - MOVOU -16(DI)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBlockAsm64K - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm64K_memmove_move_33through64: - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeSnappyBlockAsm64K: - MOVQ R8, CX - JMP emit_literal_done_match_emit_encodeSnappyBlockAsm64K - -memmove_long_match_emit_encodeSnappyBlockAsm64K: - LEAQ (CX)(R9*1), R8 - - // genMemMoveLong - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVQ R9, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R12 - SUBQ R10, R12 - DECQ R11 - JA emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm64Klarge_forward_sse_loop_32 - LEAQ -32(DI)(R12*1), R10 - LEAQ -32(CX)(R12*1), R13 - -emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm64Klarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R13) - MOVOA X5, 16(R13) - ADDQ $0x20, R13 - ADDQ $0x20, R10 - ADDQ $0x20, R12 - DECQ R11 - JNA emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm64Klarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm64Klarge_forward_sse_loop_32: - MOVOU -32(DI)(R12*1), X4 - MOVOU -16(DI)(R12*1), X5 - MOVOA X4, -32(CX)(R12*1) - MOVOA X5, -16(CX)(R12*1) - ADDQ $0x20, R12 - CMPQ R9, R12 - JAE emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm64Klarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ R8, CX - -emit_literal_done_match_emit_encodeSnappyBlockAsm64K: -match_nolit_loop_encodeSnappyBlockAsm64K: - MOVL DX, DI - SUBL SI, DI - MOVL DI, 16(SP) - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), DI - SUBL DX, DI - LEAQ (BX)(DX*1), R8 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R10, R10 - -matchlen_loopback_16_match_nolit_encodeSnappyBlockAsm64K: - CMPL DI, $0x10 - JB matchlen_match8_match_nolit_encodeSnappyBlockAsm64K - MOVQ (R8)(R10*1), R9 - MOVQ 8(R8)(R10*1), R11 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBlockAsm64K - XORQ 8(SI)(R10*1), R11 - JNZ matchlen_bsf_16match_nolit_encodeSnappyBlockAsm64K - LEAL -16(DI), DI - LEAL 16(R10), R10 - JMP matchlen_loopback_16_match_nolit_encodeSnappyBlockAsm64K - -matchlen_bsf_16match_nolit_encodeSnappyBlockAsm64K: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL 8(R10)(R11*1), R10 - JMP match_nolit_end_encodeSnappyBlockAsm64K - -matchlen_match8_match_nolit_encodeSnappyBlockAsm64K: - CMPL DI, $0x08 - JB matchlen_match4_match_nolit_encodeSnappyBlockAsm64K - MOVQ (R8)(R10*1), R9 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBlockAsm64K - LEAL -8(DI), DI - LEAL 8(R10), R10 - JMP matchlen_match4_match_nolit_encodeSnappyBlockAsm64K - -matchlen_bsf_8_match_nolit_encodeSnappyBlockAsm64K: -#ifdef GOAMD64_v3 - TZCNTQ R9, R9 - -#else - BSFQ R9, R9 - -#endif - SARQ $0x03, R9 - LEAL (R10)(R9*1), R10 - JMP match_nolit_end_encodeSnappyBlockAsm64K - -matchlen_match4_match_nolit_encodeSnappyBlockAsm64K: - CMPL DI, $0x04 - JB matchlen_match2_match_nolit_encodeSnappyBlockAsm64K - MOVL (R8)(R10*1), R9 - CMPL (SI)(R10*1), R9 - JNE matchlen_match2_match_nolit_encodeSnappyBlockAsm64K - LEAL -4(DI), DI - LEAL 4(R10), R10 - -matchlen_match2_match_nolit_encodeSnappyBlockAsm64K: - CMPL DI, $0x01 - JE matchlen_match1_match_nolit_encodeSnappyBlockAsm64K - JB match_nolit_end_encodeSnappyBlockAsm64K - MOVW (R8)(R10*1), R9 - CMPW (SI)(R10*1), R9 - JNE matchlen_match1_match_nolit_encodeSnappyBlockAsm64K - LEAL 2(R10), R10 - SUBL $0x02, DI - JZ match_nolit_end_encodeSnappyBlockAsm64K - -matchlen_match1_match_nolit_encodeSnappyBlockAsm64K: - MOVB (R8)(R10*1), R9 - CMPB (SI)(R10*1), R9 - JNE match_nolit_end_encodeSnappyBlockAsm64K - LEAL 1(R10), R10 - -match_nolit_end_encodeSnappyBlockAsm64K: - ADDL R10, DX - MOVL 16(SP), SI - ADDL $0x04, R10 - MOVL DX, 12(SP) - - // emitCopy -two_byte_offset_match_nolit_encodeSnappyBlockAsm64K: - CMPL R10, $0x40 - JBE two_byte_offset_short_match_nolit_encodeSnappyBlockAsm64K - MOVB $0xee, (CX) - MOVW SI, 1(CX) - LEAL -60(R10), R10 - ADDQ $0x03, CX - JMP two_byte_offset_match_nolit_encodeSnappyBlockAsm64K - -two_byte_offset_short_match_nolit_encodeSnappyBlockAsm64K: - MOVL R10, DI - SHLL $0x02, DI - CMPL R10, $0x0c - JAE emit_copy_three_match_nolit_encodeSnappyBlockAsm64K - CMPL SI, $0x00000800 - JAE emit_copy_three_match_nolit_encodeSnappyBlockAsm64K - LEAL -15(DI), DI - MOVB SI, 1(CX) - SHRL $0x08, SI - SHLL $0x05, SI - ORL SI, DI - MOVB DI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeSnappyBlockAsm64K - -emit_copy_three_match_nolit_encodeSnappyBlockAsm64K: - LEAL -2(DI), DI - MOVB DI, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - -match_nolit_emitcopy_end_encodeSnappyBlockAsm64K: - CMPL DX, 8(SP) - JAE emit_remainder_encodeSnappyBlockAsm64K - MOVQ -2(BX)(DX*1), DI - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeSnappyBlockAsm64K - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeSnappyBlockAsm64K: - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ DI, R8 - SHRQ $0x10, DI - MOVQ DI, SI - SHLQ $0x10, R8 - IMULQ R9, R8 - SHRQ $0x32, R8 - SHLQ $0x10, SI - IMULQ R9, SI - SHRQ $0x32, SI - LEAL -2(DX), R9 - LEAQ (AX)(SI*4), R10 - MOVL (R10), SI - MOVL R9, (AX)(R8*4) - MOVL DX, (R10) - CMPL (BX)(SI*1), DI - JEQ match_nolit_loop_encodeSnappyBlockAsm64K - INCL DX - JMP search_loop_encodeSnappyBlockAsm64K - -emit_remainder_encodeSnappyBlockAsm64K: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 3(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeSnappyBlockAsm64K - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeSnappyBlockAsm64K: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeSnappyBlockAsm64K - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeSnappyBlockAsm64K - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeSnappyBlockAsm64K - JB three_bytes_emit_remainder_encodeSnappyBlockAsm64K - -three_bytes_emit_remainder_encodeSnappyBlockAsm64K: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeSnappyBlockAsm64K - -two_bytes_emit_remainder_encodeSnappyBlockAsm64K: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeSnappyBlockAsm64K - JMP memmove_long_emit_remainder_encodeSnappyBlockAsm64K - -one_byte_emit_remainder_encodeSnappyBlockAsm64K: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeSnappyBlockAsm64K: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm64K_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm64K_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm64K_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm64K_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm64K_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm64K_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm64K_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm64K - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm64K_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm64K - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm64K_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm64K - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm64K_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm64K - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm64K_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm64K - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm64K_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeSnappyBlockAsm64K: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeSnappyBlockAsm64K - -memmove_long_emit_remainder_encodeSnappyBlockAsm64K: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm64Klarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm64Klarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm64Klarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm64Klarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm64Klarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeSnappyBlockAsm64K: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeSnappyBlockAsm12B(dst []byte, src []byte, tmp *[16384]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeSnappyBlockAsm12B(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00000080, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeSnappyBlockAsm12B: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeSnappyBlockAsm12B - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL DX, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeSnappyBlockAsm12B: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x05, SI - LEAL 4(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeSnappyBlockAsm12B - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x000000cf1bbcdcbb, R9 - MOVQ DI, R10 - MOVQ DI, R11 - SHRQ $0x08, R11 - SHLQ $0x18, R10 - IMULQ R9, R10 - SHRQ $0x34, R10 - SHLQ $0x18, R11 - IMULQ R9, R11 - SHRQ $0x34, R11 - MOVL (AX)(R10*4), SI - MOVL (AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - LEAL 1(DX), R10 - MOVL R10, (AX)(R11*4) - MOVQ DI, R10 - SHRQ $0x10, R10 - SHLQ $0x18, R10 - IMULQ R9, R10 - SHRQ $0x34, R10 - MOVL DX, R9 - SUBL 16(SP), R9 - MOVL 1(BX)(R9*1), R11 - MOVQ DI, R9 - SHRQ $0x08, R9 - CMPL R9, R11 - JNE no_repeat_found_encodeSnappyBlockAsm12B - LEAL 1(DX), DI - MOVL 12(SP), SI - MOVL DI, R8 - SUBL 16(SP), R8 - JZ repeat_extend_back_end_encodeSnappyBlockAsm12B - -repeat_extend_back_loop_encodeSnappyBlockAsm12B: - CMPL DI, SI - JBE repeat_extend_back_end_encodeSnappyBlockAsm12B - MOVB -1(BX)(R8*1), R9 - MOVB -1(BX)(DI*1), R10 - CMPB R9, R10 - JNE repeat_extend_back_end_encodeSnappyBlockAsm12B - LEAL -1(DI), DI - DECL R8 - JNZ repeat_extend_back_loop_encodeSnappyBlockAsm12B - -repeat_extend_back_end_encodeSnappyBlockAsm12B: - MOVL DI, SI - SUBL 12(SP), SI - LEAQ 3(CX)(SI*1), SI - CMPQ SI, (SP) - JB repeat_dst_size_check_encodeSnappyBlockAsm12B - MOVQ $0x00000000, ret+56(FP) - RET - -repeat_dst_size_check_encodeSnappyBlockAsm12B: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_repeat_emit_encodeSnappyBlockAsm12B - MOVL DI, R8 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R9 - SUBL SI, R8 - LEAL -1(R8), SI - CMPL SI, $0x3c - JB one_byte_repeat_emit_encodeSnappyBlockAsm12B - CMPL SI, $0x00000100 - JB two_bytes_repeat_emit_encodeSnappyBlockAsm12B - JB three_bytes_repeat_emit_encodeSnappyBlockAsm12B - -three_bytes_repeat_emit_encodeSnappyBlockAsm12B: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_repeat_emit_encodeSnappyBlockAsm12B - -two_bytes_repeat_emit_encodeSnappyBlockAsm12B: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_repeat_emit_encodeSnappyBlockAsm12B - JMP memmove_long_repeat_emit_encodeSnappyBlockAsm12B - -one_byte_repeat_emit_encodeSnappyBlockAsm12B: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_repeat_emit_encodeSnappyBlockAsm12B: - LEAQ (CX)(R8*1), SI - - // genMemMoveShort - CMPQ R8, $0x08 - JBE emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm12B_memmove_move_8 - CMPQ R8, $0x10 - JBE emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm12B_memmove_move_8through16 - CMPQ R8, $0x20 - JBE emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm12B_memmove_move_17through32 - JMP emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm12B_memmove_move_33through64 - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm12B_memmove_move_8: - MOVQ (R9), R10 - MOVQ R10, (CX) - JMP memmove_end_copy_repeat_emit_encodeSnappyBlockAsm12B - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm12B_memmove_move_8through16: - MOVQ (R9), R10 - MOVQ -8(R9)(R8*1), R9 - MOVQ R10, (CX) - MOVQ R9, -8(CX)(R8*1) - JMP memmove_end_copy_repeat_emit_encodeSnappyBlockAsm12B - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm12B_memmove_move_17through32: - MOVOU (R9), X0 - MOVOU -16(R9)(R8*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R8*1) - JMP memmove_end_copy_repeat_emit_encodeSnappyBlockAsm12B - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm12B_memmove_move_33through64: - MOVOU (R9), X0 - MOVOU 16(R9), X1 - MOVOU -32(R9)(R8*1), X2 - MOVOU -16(R9)(R8*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R8*1) - MOVOU X3, -16(CX)(R8*1) - -memmove_end_copy_repeat_emit_encodeSnappyBlockAsm12B: - MOVQ SI, CX - JMP emit_literal_done_repeat_emit_encodeSnappyBlockAsm12B - -memmove_long_repeat_emit_encodeSnappyBlockAsm12B: - LEAQ (CX)(R8*1), SI - - // genMemMoveLong - MOVOU (R9), X0 - MOVOU 16(R9), X1 - MOVOU -32(R9)(R8*1), X2 - MOVOU -16(R9)(R8*1), X3 - MOVQ R8, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R12 - SUBQ R10, R12 - DECQ R11 - JA emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm12Blarge_forward_sse_loop_32 - LEAQ -32(R9)(R12*1), R10 - LEAQ -32(CX)(R12*1), R13 - -emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm12Blarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R13) - MOVOA X5, 16(R13) - ADDQ $0x20, R13 - ADDQ $0x20, R10 - ADDQ $0x20, R12 - DECQ R11 - JNA emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm12Blarge_big_loop_back - -emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm12Blarge_forward_sse_loop_32: - MOVOU -32(R9)(R12*1), X4 - MOVOU -16(R9)(R12*1), X5 - MOVOA X4, -32(CX)(R12*1) - MOVOA X5, -16(CX)(R12*1) - ADDQ $0x20, R12 - CMPQ R8, R12 - JAE emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm12Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R8*1) - MOVOU X3, -16(CX)(R8*1) - MOVQ SI, CX - -emit_literal_done_repeat_emit_encodeSnappyBlockAsm12B: - ADDL $0x05, DX - MOVL DX, SI - SUBL 16(SP), SI - MOVQ src_len+32(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R11, R11 - -matchlen_loopback_16_repeat_extend_encodeSnappyBlockAsm12B: - CMPL R8, $0x10 - JB matchlen_match8_repeat_extend_encodeSnappyBlockAsm12B - MOVQ (R9)(R11*1), R10 - MOVQ 8(R9)(R11*1), R12 - XORQ (SI)(R11*1), R10 - JNZ matchlen_bsf_8_repeat_extend_encodeSnappyBlockAsm12B - XORQ 8(SI)(R11*1), R12 - JNZ matchlen_bsf_16repeat_extend_encodeSnappyBlockAsm12B - LEAL -16(R8), R8 - LEAL 16(R11), R11 - JMP matchlen_loopback_16_repeat_extend_encodeSnappyBlockAsm12B - -matchlen_bsf_16repeat_extend_encodeSnappyBlockAsm12B: -#ifdef GOAMD64_v3 - TZCNTQ R12, R12 - -#else - BSFQ R12, R12 - -#endif - SARQ $0x03, R12 - LEAL 8(R11)(R12*1), R11 - JMP repeat_extend_forward_end_encodeSnappyBlockAsm12B - -matchlen_match8_repeat_extend_encodeSnappyBlockAsm12B: - CMPL R8, $0x08 - JB matchlen_match4_repeat_extend_encodeSnappyBlockAsm12B - MOVQ (R9)(R11*1), R10 - XORQ (SI)(R11*1), R10 - JNZ matchlen_bsf_8_repeat_extend_encodeSnappyBlockAsm12B - LEAL -8(R8), R8 - LEAL 8(R11), R11 - JMP matchlen_match4_repeat_extend_encodeSnappyBlockAsm12B - -matchlen_bsf_8_repeat_extend_encodeSnappyBlockAsm12B: -#ifdef GOAMD64_v3 - TZCNTQ R10, R10 - -#else - BSFQ R10, R10 - -#endif - SARQ $0x03, R10 - LEAL (R11)(R10*1), R11 - JMP repeat_extend_forward_end_encodeSnappyBlockAsm12B - -matchlen_match4_repeat_extend_encodeSnappyBlockAsm12B: - CMPL R8, $0x04 - JB matchlen_match2_repeat_extend_encodeSnappyBlockAsm12B - MOVL (R9)(R11*1), R10 - CMPL (SI)(R11*1), R10 - JNE matchlen_match2_repeat_extend_encodeSnappyBlockAsm12B - LEAL -4(R8), R8 - LEAL 4(R11), R11 - -matchlen_match2_repeat_extend_encodeSnappyBlockAsm12B: - CMPL R8, $0x01 - JE matchlen_match1_repeat_extend_encodeSnappyBlockAsm12B - JB repeat_extend_forward_end_encodeSnappyBlockAsm12B - MOVW (R9)(R11*1), R10 - CMPW (SI)(R11*1), R10 - JNE matchlen_match1_repeat_extend_encodeSnappyBlockAsm12B - LEAL 2(R11), R11 - SUBL $0x02, R8 - JZ repeat_extend_forward_end_encodeSnappyBlockAsm12B - -matchlen_match1_repeat_extend_encodeSnappyBlockAsm12B: - MOVB (R9)(R11*1), R10 - CMPB (SI)(R11*1), R10 - JNE repeat_extend_forward_end_encodeSnappyBlockAsm12B - LEAL 1(R11), R11 - -repeat_extend_forward_end_encodeSnappyBlockAsm12B: - ADDL R11, DX - MOVL DX, SI - SUBL DI, SI - MOVL 16(SP), DI - - // emitCopy -two_byte_offset_repeat_as_copy_encodeSnappyBlockAsm12B: - CMPL SI, $0x40 - JBE two_byte_offset_short_repeat_as_copy_encodeSnappyBlockAsm12B - MOVB $0xee, (CX) - MOVW DI, 1(CX) - LEAL -60(SI), SI - ADDQ $0x03, CX - JMP two_byte_offset_repeat_as_copy_encodeSnappyBlockAsm12B - -two_byte_offset_short_repeat_as_copy_encodeSnappyBlockAsm12B: - MOVL SI, R8 - SHLL $0x02, R8 - CMPL SI, $0x0c - JAE emit_copy_three_repeat_as_copy_encodeSnappyBlockAsm12B - CMPL DI, $0x00000800 - JAE emit_copy_three_repeat_as_copy_encodeSnappyBlockAsm12B - LEAL -15(R8), R8 - MOVB DI, 1(CX) - SHRL $0x08, DI - SHLL $0x05, DI - ORL DI, R8 - MOVB R8, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeSnappyBlockAsm12B - -emit_copy_three_repeat_as_copy_encodeSnappyBlockAsm12B: - LEAL -2(R8), R8 - MOVB R8, (CX) - MOVW DI, 1(CX) - ADDQ $0x03, CX - -repeat_end_emit_encodeSnappyBlockAsm12B: - MOVL DX, 12(SP) - JMP search_loop_encodeSnappyBlockAsm12B - -no_repeat_found_encodeSnappyBlockAsm12B: - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeSnappyBlockAsm12B - SHRQ $0x08, DI - MOVL (AX)(R10*4), SI - LEAL 2(DX), R9 - CMPL (BX)(R8*1), DI - JEQ candidate2_match_encodeSnappyBlockAsm12B - MOVL R9, (AX)(R10*4) - SHRQ $0x08, DI - CMPL (BX)(SI*1), DI - JEQ candidate3_match_encodeSnappyBlockAsm12B - MOVL 20(SP), DX - JMP search_loop_encodeSnappyBlockAsm12B - -candidate3_match_encodeSnappyBlockAsm12B: - ADDL $0x02, DX - JMP candidate_match_encodeSnappyBlockAsm12B - -candidate2_match_encodeSnappyBlockAsm12B: - MOVL R9, (AX)(R10*4) - INCL DX - MOVL R8, SI - -candidate_match_encodeSnappyBlockAsm12B: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeSnappyBlockAsm12B - -match_extend_back_loop_encodeSnappyBlockAsm12B: - CMPL DX, DI - JBE match_extend_back_end_encodeSnappyBlockAsm12B - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeSnappyBlockAsm12B - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeSnappyBlockAsm12B - JMP match_extend_back_loop_encodeSnappyBlockAsm12B - -match_extend_back_end_encodeSnappyBlockAsm12B: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 3(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeSnappyBlockAsm12B - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeSnappyBlockAsm12B: - MOVL DX, DI - MOVL 12(SP), R8 - CMPL R8, DI - JEQ emit_literal_done_match_emit_encodeSnappyBlockAsm12B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(R8*1), DI - SUBL R8, R9 - LEAL -1(R9), R8 - CMPL R8, $0x3c - JB one_byte_match_emit_encodeSnappyBlockAsm12B - CMPL R8, $0x00000100 - JB two_bytes_match_emit_encodeSnappyBlockAsm12B - JB three_bytes_match_emit_encodeSnappyBlockAsm12B - -three_bytes_match_emit_encodeSnappyBlockAsm12B: - MOVB $0xf4, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeSnappyBlockAsm12B - -two_bytes_match_emit_encodeSnappyBlockAsm12B: - MOVB $0xf0, (CX) - MOVB R8, 1(CX) - ADDQ $0x02, CX - CMPL R8, $0x40 - JB memmove_match_emit_encodeSnappyBlockAsm12B - JMP memmove_long_match_emit_encodeSnappyBlockAsm12B - -one_byte_match_emit_encodeSnappyBlockAsm12B: - SHLB $0x02, R8 - MOVB R8, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeSnappyBlockAsm12B: - LEAQ (CX)(R9*1), R8 - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_match_emit_encodeSnappyBlockAsm12B_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeSnappyBlockAsm12B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeSnappyBlockAsm12B_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeSnappyBlockAsm12B_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm12B_memmove_move_8: - MOVQ (DI), R10 - MOVQ R10, (CX) - JMP memmove_end_copy_match_emit_encodeSnappyBlockAsm12B - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm12B_memmove_move_8through16: - MOVQ (DI), R10 - MOVQ -8(DI)(R9*1), DI - MOVQ R10, (CX) - MOVQ DI, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBlockAsm12B - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm12B_memmove_move_17through32: - MOVOU (DI), X0 - MOVOU -16(DI)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBlockAsm12B - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm12B_memmove_move_33through64: - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeSnappyBlockAsm12B: - MOVQ R8, CX - JMP emit_literal_done_match_emit_encodeSnappyBlockAsm12B - -memmove_long_match_emit_encodeSnappyBlockAsm12B: - LEAQ (CX)(R9*1), R8 - - // genMemMoveLong - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVQ R9, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R12 - SUBQ R10, R12 - DECQ R11 - JA emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm12Blarge_forward_sse_loop_32 - LEAQ -32(DI)(R12*1), R10 - LEAQ -32(CX)(R12*1), R13 - -emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm12Blarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R13) - MOVOA X5, 16(R13) - ADDQ $0x20, R13 - ADDQ $0x20, R10 - ADDQ $0x20, R12 - DECQ R11 - JNA emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm12Blarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm12Blarge_forward_sse_loop_32: - MOVOU -32(DI)(R12*1), X4 - MOVOU -16(DI)(R12*1), X5 - MOVOA X4, -32(CX)(R12*1) - MOVOA X5, -16(CX)(R12*1) - ADDQ $0x20, R12 - CMPQ R9, R12 - JAE emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm12Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ R8, CX - -emit_literal_done_match_emit_encodeSnappyBlockAsm12B: -match_nolit_loop_encodeSnappyBlockAsm12B: - MOVL DX, DI - SUBL SI, DI - MOVL DI, 16(SP) - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), DI - SUBL DX, DI - LEAQ (BX)(DX*1), R8 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R10, R10 - -matchlen_loopback_16_match_nolit_encodeSnappyBlockAsm12B: - CMPL DI, $0x10 - JB matchlen_match8_match_nolit_encodeSnappyBlockAsm12B - MOVQ (R8)(R10*1), R9 - MOVQ 8(R8)(R10*1), R11 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBlockAsm12B - XORQ 8(SI)(R10*1), R11 - JNZ matchlen_bsf_16match_nolit_encodeSnappyBlockAsm12B - LEAL -16(DI), DI - LEAL 16(R10), R10 - JMP matchlen_loopback_16_match_nolit_encodeSnappyBlockAsm12B - -matchlen_bsf_16match_nolit_encodeSnappyBlockAsm12B: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL 8(R10)(R11*1), R10 - JMP match_nolit_end_encodeSnappyBlockAsm12B - -matchlen_match8_match_nolit_encodeSnappyBlockAsm12B: - CMPL DI, $0x08 - JB matchlen_match4_match_nolit_encodeSnappyBlockAsm12B - MOVQ (R8)(R10*1), R9 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBlockAsm12B - LEAL -8(DI), DI - LEAL 8(R10), R10 - JMP matchlen_match4_match_nolit_encodeSnappyBlockAsm12B - -matchlen_bsf_8_match_nolit_encodeSnappyBlockAsm12B: -#ifdef GOAMD64_v3 - TZCNTQ R9, R9 - -#else - BSFQ R9, R9 - -#endif - SARQ $0x03, R9 - LEAL (R10)(R9*1), R10 - JMP match_nolit_end_encodeSnappyBlockAsm12B - -matchlen_match4_match_nolit_encodeSnappyBlockAsm12B: - CMPL DI, $0x04 - JB matchlen_match2_match_nolit_encodeSnappyBlockAsm12B - MOVL (R8)(R10*1), R9 - CMPL (SI)(R10*1), R9 - JNE matchlen_match2_match_nolit_encodeSnappyBlockAsm12B - LEAL -4(DI), DI - LEAL 4(R10), R10 - -matchlen_match2_match_nolit_encodeSnappyBlockAsm12B: - CMPL DI, $0x01 - JE matchlen_match1_match_nolit_encodeSnappyBlockAsm12B - JB match_nolit_end_encodeSnappyBlockAsm12B - MOVW (R8)(R10*1), R9 - CMPW (SI)(R10*1), R9 - JNE matchlen_match1_match_nolit_encodeSnappyBlockAsm12B - LEAL 2(R10), R10 - SUBL $0x02, DI - JZ match_nolit_end_encodeSnappyBlockAsm12B - -matchlen_match1_match_nolit_encodeSnappyBlockAsm12B: - MOVB (R8)(R10*1), R9 - CMPB (SI)(R10*1), R9 - JNE match_nolit_end_encodeSnappyBlockAsm12B - LEAL 1(R10), R10 - -match_nolit_end_encodeSnappyBlockAsm12B: - ADDL R10, DX - MOVL 16(SP), SI - ADDL $0x04, R10 - MOVL DX, 12(SP) - - // emitCopy -two_byte_offset_match_nolit_encodeSnappyBlockAsm12B: - CMPL R10, $0x40 - JBE two_byte_offset_short_match_nolit_encodeSnappyBlockAsm12B - MOVB $0xee, (CX) - MOVW SI, 1(CX) - LEAL -60(R10), R10 - ADDQ $0x03, CX - JMP two_byte_offset_match_nolit_encodeSnappyBlockAsm12B - -two_byte_offset_short_match_nolit_encodeSnappyBlockAsm12B: - MOVL R10, DI - SHLL $0x02, DI - CMPL R10, $0x0c - JAE emit_copy_three_match_nolit_encodeSnappyBlockAsm12B - CMPL SI, $0x00000800 - JAE emit_copy_three_match_nolit_encodeSnappyBlockAsm12B - LEAL -15(DI), DI - MOVB SI, 1(CX) - SHRL $0x08, SI - SHLL $0x05, SI - ORL SI, DI - MOVB DI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeSnappyBlockAsm12B - -emit_copy_three_match_nolit_encodeSnappyBlockAsm12B: - LEAL -2(DI), DI - MOVB DI, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - -match_nolit_emitcopy_end_encodeSnappyBlockAsm12B: - CMPL DX, 8(SP) - JAE emit_remainder_encodeSnappyBlockAsm12B - MOVQ -2(BX)(DX*1), DI - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeSnappyBlockAsm12B - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeSnappyBlockAsm12B: - MOVQ $0x000000cf1bbcdcbb, R9 - MOVQ DI, R8 - SHRQ $0x10, DI - MOVQ DI, SI - SHLQ $0x18, R8 - IMULQ R9, R8 - SHRQ $0x34, R8 - SHLQ $0x18, SI - IMULQ R9, SI - SHRQ $0x34, SI - LEAL -2(DX), R9 - LEAQ (AX)(SI*4), R10 - MOVL (R10), SI - MOVL R9, (AX)(R8*4) - MOVL DX, (R10) - CMPL (BX)(SI*1), DI - JEQ match_nolit_loop_encodeSnappyBlockAsm12B - INCL DX - JMP search_loop_encodeSnappyBlockAsm12B - -emit_remainder_encodeSnappyBlockAsm12B: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 3(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeSnappyBlockAsm12B - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeSnappyBlockAsm12B: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeSnappyBlockAsm12B - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeSnappyBlockAsm12B - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeSnappyBlockAsm12B - JB three_bytes_emit_remainder_encodeSnappyBlockAsm12B - -three_bytes_emit_remainder_encodeSnappyBlockAsm12B: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeSnappyBlockAsm12B - -two_bytes_emit_remainder_encodeSnappyBlockAsm12B: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeSnappyBlockAsm12B - JMP memmove_long_emit_remainder_encodeSnappyBlockAsm12B - -one_byte_emit_remainder_encodeSnappyBlockAsm12B: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeSnappyBlockAsm12B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm12B_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm12B_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm12B_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm12B_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm12B_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm12B_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm12B_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm12B_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm12B_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm12B_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm12B_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm12B_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeSnappyBlockAsm12B: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeSnappyBlockAsm12B - -memmove_long_emit_remainder_encodeSnappyBlockAsm12B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm12Blarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm12Blarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm12Blarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm12Blarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm12Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeSnappyBlockAsm12B: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeSnappyBlockAsm10B(dst []byte, src []byte, tmp *[4096]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeSnappyBlockAsm10B(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00000020, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeSnappyBlockAsm10B: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeSnappyBlockAsm10B - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL DX, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeSnappyBlockAsm10B: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x05, SI - LEAL 4(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeSnappyBlockAsm10B - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x9e3779b1, R9 - MOVQ DI, R10 - MOVQ DI, R11 - SHRQ $0x08, R11 - SHLQ $0x20, R10 - IMULQ R9, R10 - SHRQ $0x36, R10 - SHLQ $0x20, R11 - IMULQ R9, R11 - SHRQ $0x36, R11 - MOVL (AX)(R10*4), SI - MOVL (AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - LEAL 1(DX), R10 - MOVL R10, (AX)(R11*4) - MOVQ DI, R10 - SHRQ $0x10, R10 - SHLQ $0x20, R10 - IMULQ R9, R10 - SHRQ $0x36, R10 - MOVL DX, R9 - SUBL 16(SP), R9 - MOVL 1(BX)(R9*1), R11 - MOVQ DI, R9 - SHRQ $0x08, R9 - CMPL R9, R11 - JNE no_repeat_found_encodeSnappyBlockAsm10B - LEAL 1(DX), DI - MOVL 12(SP), SI - MOVL DI, R8 - SUBL 16(SP), R8 - JZ repeat_extend_back_end_encodeSnappyBlockAsm10B - -repeat_extend_back_loop_encodeSnappyBlockAsm10B: - CMPL DI, SI - JBE repeat_extend_back_end_encodeSnappyBlockAsm10B - MOVB -1(BX)(R8*1), R9 - MOVB -1(BX)(DI*1), R10 - CMPB R9, R10 - JNE repeat_extend_back_end_encodeSnappyBlockAsm10B - LEAL -1(DI), DI - DECL R8 - JNZ repeat_extend_back_loop_encodeSnappyBlockAsm10B - -repeat_extend_back_end_encodeSnappyBlockAsm10B: - MOVL DI, SI - SUBL 12(SP), SI - LEAQ 3(CX)(SI*1), SI - CMPQ SI, (SP) - JB repeat_dst_size_check_encodeSnappyBlockAsm10B - MOVQ $0x00000000, ret+56(FP) - RET - -repeat_dst_size_check_encodeSnappyBlockAsm10B: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_repeat_emit_encodeSnappyBlockAsm10B - MOVL DI, R8 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R9 - SUBL SI, R8 - LEAL -1(R8), SI - CMPL SI, $0x3c - JB one_byte_repeat_emit_encodeSnappyBlockAsm10B - CMPL SI, $0x00000100 - JB two_bytes_repeat_emit_encodeSnappyBlockAsm10B - JB three_bytes_repeat_emit_encodeSnappyBlockAsm10B - -three_bytes_repeat_emit_encodeSnappyBlockAsm10B: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_repeat_emit_encodeSnappyBlockAsm10B - -two_bytes_repeat_emit_encodeSnappyBlockAsm10B: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_repeat_emit_encodeSnappyBlockAsm10B - JMP memmove_long_repeat_emit_encodeSnappyBlockAsm10B - -one_byte_repeat_emit_encodeSnappyBlockAsm10B: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_repeat_emit_encodeSnappyBlockAsm10B: - LEAQ (CX)(R8*1), SI - - // genMemMoveShort - CMPQ R8, $0x08 - JBE emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm10B_memmove_move_8 - CMPQ R8, $0x10 - JBE emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm10B_memmove_move_8through16 - CMPQ R8, $0x20 - JBE emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm10B_memmove_move_17through32 - JMP emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm10B_memmove_move_33through64 - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm10B_memmove_move_8: - MOVQ (R9), R10 - MOVQ R10, (CX) - JMP memmove_end_copy_repeat_emit_encodeSnappyBlockAsm10B - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm10B_memmove_move_8through16: - MOVQ (R9), R10 - MOVQ -8(R9)(R8*1), R9 - MOVQ R10, (CX) - MOVQ R9, -8(CX)(R8*1) - JMP memmove_end_copy_repeat_emit_encodeSnappyBlockAsm10B - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm10B_memmove_move_17through32: - MOVOU (R9), X0 - MOVOU -16(R9)(R8*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R8*1) - JMP memmove_end_copy_repeat_emit_encodeSnappyBlockAsm10B - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm10B_memmove_move_33through64: - MOVOU (R9), X0 - MOVOU 16(R9), X1 - MOVOU -32(R9)(R8*1), X2 - MOVOU -16(R9)(R8*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R8*1) - MOVOU X3, -16(CX)(R8*1) - -memmove_end_copy_repeat_emit_encodeSnappyBlockAsm10B: - MOVQ SI, CX - JMP emit_literal_done_repeat_emit_encodeSnappyBlockAsm10B - -memmove_long_repeat_emit_encodeSnappyBlockAsm10B: - LEAQ (CX)(R8*1), SI - - // genMemMoveLong - MOVOU (R9), X0 - MOVOU 16(R9), X1 - MOVOU -32(R9)(R8*1), X2 - MOVOU -16(R9)(R8*1), X3 - MOVQ R8, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R12 - SUBQ R10, R12 - DECQ R11 - JA emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm10Blarge_forward_sse_loop_32 - LEAQ -32(R9)(R12*1), R10 - LEAQ -32(CX)(R12*1), R13 - -emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm10Blarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R13) - MOVOA X5, 16(R13) - ADDQ $0x20, R13 - ADDQ $0x20, R10 - ADDQ $0x20, R12 - DECQ R11 - JNA emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm10Blarge_big_loop_back - -emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm10Blarge_forward_sse_loop_32: - MOVOU -32(R9)(R12*1), X4 - MOVOU -16(R9)(R12*1), X5 - MOVOA X4, -32(CX)(R12*1) - MOVOA X5, -16(CX)(R12*1) - ADDQ $0x20, R12 - CMPQ R8, R12 - JAE emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm10Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R8*1) - MOVOU X3, -16(CX)(R8*1) - MOVQ SI, CX - -emit_literal_done_repeat_emit_encodeSnappyBlockAsm10B: - ADDL $0x05, DX - MOVL DX, SI - SUBL 16(SP), SI - MOVQ src_len+32(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R11, R11 - -matchlen_loopback_16_repeat_extend_encodeSnappyBlockAsm10B: - CMPL R8, $0x10 - JB matchlen_match8_repeat_extend_encodeSnappyBlockAsm10B - MOVQ (R9)(R11*1), R10 - MOVQ 8(R9)(R11*1), R12 - XORQ (SI)(R11*1), R10 - JNZ matchlen_bsf_8_repeat_extend_encodeSnappyBlockAsm10B - XORQ 8(SI)(R11*1), R12 - JNZ matchlen_bsf_16repeat_extend_encodeSnappyBlockAsm10B - LEAL -16(R8), R8 - LEAL 16(R11), R11 - JMP matchlen_loopback_16_repeat_extend_encodeSnappyBlockAsm10B - -matchlen_bsf_16repeat_extend_encodeSnappyBlockAsm10B: -#ifdef GOAMD64_v3 - TZCNTQ R12, R12 - -#else - BSFQ R12, R12 - -#endif - SARQ $0x03, R12 - LEAL 8(R11)(R12*1), R11 - JMP repeat_extend_forward_end_encodeSnappyBlockAsm10B - -matchlen_match8_repeat_extend_encodeSnappyBlockAsm10B: - CMPL R8, $0x08 - JB matchlen_match4_repeat_extend_encodeSnappyBlockAsm10B - MOVQ (R9)(R11*1), R10 - XORQ (SI)(R11*1), R10 - JNZ matchlen_bsf_8_repeat_extend_encodeSnappyBlockAsm10B - LEAL -8(R8), R8 - LEAL 8(R11), R11 - JMP matchlen_match4_repeat_extend_encodeSnappyBlockAsm10B - -matchlen_bsf_8_repeat_extend_encodeSnappyBlockAsm10B: -#ifdef GOAMD64_v3 - TZCNTQ R10, R10 - -#else - BSFQ R10, R10 - -#endif - SARQ $0x03, R10 - LEAL (R11)(R10*1), R11 - JMP repeat_extend_forward_end_encodeSnappyBlockAsm10B - -matchlen_match4_repeat_extend_encodeSnappyBlockAsm10B: - CMPL R8, $0x04 - JB matchlen_match2_repeat_extend_encodeSnappyBlockAsm10B - MOVL (R9)(R11*1), R10 - CMPL (SI)(R11*1), R10 - JNE matchlen_match2_repeat_extend_encodeSnappyBlockAsm10B - LEAL -4(R8), R8 - LEAL 4(R11), R11 - -matchlen_match2_repeat_extend_encodeSnappyBlockAsm10B: - CMPL R8, $0x01 - JE matchlen_match1_repeat_extend_encodeSnappyBlockAsm10B - JB repeat_extend_forward_end_encodeSnappyBlockAsm10B - MOVW (R9)(R11*1), R10 - CMPW (SI)(R11*1), R10 - JNE matchlen_match1_repeat_extend_encodeSnappyBlockAsm10B - LEAL 2(R11), R11 - SUBL $0x02, R8 - JZ repeat_extend_forward_end_encodeSnappyBlockAsm10B - -matchlen_match1_repeat_extend_encodeSnappyBlockAsm10B: - MOVB (R9)(R11*1), R10 - CMPB (SI)(R11*1), R10 - JNE repeat_extend_forward_end_encodeSnappyBlockAsm10B - LEAL 1(R11), R11 - -repeat_extend_forward_end_encodeSnappyBlockAsm10B: - ADDL R11, DX - MOVL DX, SI - SUBL DI, SI - MOVL 16(SP), DI - - // emitCopy -two_byte_offset_repeat_as_copy_encodeSnappyBlockAsm10B: - CMPL SI, $0x40 - JBE two_byte_offset_short_repeat_as_copy_encodeSnappyBlockAsm10B - MOVB $0xee, (CX) - MOVW DI, 1(CX) - LEAL -60(SI), SI - ADDQ $0x03, CX - JMP two_byte_offset_repeat_as_copy_encodeSnappyBlockAsm10B - -two_byte_offset_short_repeat_as_copy_encodeSnappyBlockAsm10B: - MOVL SI, R8 - SHLL $0x02, R8 - CMPL SI, $0x0c - JAE emit_copy_three_repeat_as_copy_encodeSnappyBlockAsm10B - CMPL DI, $0x00000800 - JAE emit_copy_three_repeat_as_copy_encodeSnappyBlockAsm10B - LEAL -15(R8), R8 - MOVB DI, 1(CX) - SHRL $0x08, DI - SHLL $0x05, DI - ORL DI, R8 - MOVB R8, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeSnappyBlockAsm10B - -emit_copy_three_repeat_as_copy_encodeSnappyBlockAsm10B: - LEAL -2(R8), R8 - MOVB R8, (CX) - MOVW DI, 1(CX) - ADDQ $0x03, CX - -repeat_end_emit_encodeSnappyBlockAsm10B: - MOVL DX, 12(SP) - JMP search_loop_encodeSnappyBlockAsm10B - -no_repeat_found_encodeSnappyBlockAsm10B: - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeSnappyBlockAsm10B - SHRQ $0x08, DI - MOVL (AX)(R10*4), SI - LEAL 2(DX), R9 - CMPL (BX)(R8*1), DI - JEQ candidate2_match_encodeSnappyBlockAsm10B - MOVL R9, (AX)(R10*4) - SHRQ $0x08, DI - CMPL (BX)(SI*1), DI - JEQ candidate3_match_encodeSnappyBlockAsm10B - MOVL 20(SP), DX - JMP search_loop_encodeSnappyBlockAsm10B - -candidate3_match_encodeSnappyBlockAsm10B: - ADDL $0x02, DX - JMP candidate_match_encodeSnappyBlockAsm10B - -candidate2_match_encodeSnappyBlockAsm10B: - MOVL R9, (AX)(R10*4) - INCL DX - MOVL R8, SI - -candidate_match_encodeSnappyBlockAsm10B: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeSnappyBlockAsm10B - -match_extend_back_loop_encodeSnappyBlockAsm10B: - CMPL DX, DI - JBE match_extend_back_end_encodeSnappyBlockAsm10B - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeSnappyBlockAsm10B - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeSnappyBlockAsm10B - JMP match_extend_back_loop_encodeSnappyBlockAsm10B - -match_extend_back_end_encodeSnappyBlockAsm10B: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 3(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeSnappyBlockAsm10B - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeSnappyBlockAsm10B: - MOVL DX, DI - MOVL 12(SP), R8 - CMPL R8, DI - JEQ emit_literal_done_match_emit_encodeSnappyBlockAsm10B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(R8*1), DI - SUBL R8, R9 - LEAL -1(R9), R8 - CMPL R8, $0x3c - JB one_byte_match_emit_encodeSnappyBlockAsm10B - CMPL R8, $0x00000100 - JB two_bytes_match_emit_encodeSnappyBlockAsm10B - JB three_bytes_match_emit_encodeSnappyBlockAsm10B - -three_bytes_match_emit_encodeSnappyBlockAsm10B: - MOVB $0xf4, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeSnappyBlockAsm10B - -two_bytes_match_emit_encodeSnappyBlockAsm10B: - MOVB $0xf0, (CX) - MOVB R8, 1(CX) - ADDQ $0x02, CX - CMPL R8, $0x40 - JB memmove_match_emit_encodeSnappyBlockAsm10B - JMP memmove_long_match_emit_encodeSnappyBlockAsm10B - -one_byte_match_emit_encodeSnappyBlockAsm10B: - SHLB $0x02, R8 - MOVB R8, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeSnappyBlockAsm10B: - LEAQ (CX)(R9*1), R8 - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_match_emit_encodeSnappyBlockAsm10B_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeSnappyBlockAsm10B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeSnappyBlockAsm10B_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeSnappyBlockAsm10B_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm10B_memmove_move_8: - MOVQ (DI), R10 - MOVQ R10, (CX) - JMP memmove_end_copy_match_emit_encodeSnappyBlockAsm10B - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm10B_memmove_move_8through16: - MOVQ (DI), R10 - MOVQ -8(DI)(R9*1), DI - MOVQ R10, (CX) - MOVQ DI, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBlockAsm10B - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm10B_memmove_move_17through32: - MOVOU (DI), X0 - MOVOU -16(DI)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBlockAsm10B - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm10B_memmove_move_33through64: - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeSnappyBlockAsm10B: - MOVQ R8, CX - JMP emit_literal_done_match_emit_encodeSnappyBlockAsm10B - -memmove_long_match_emit_encodeSnappyBlockAsm10B: - LEAQ (CX)(R9*1), R8 - - // genMemMoveLong - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVQ R9, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R12 - SUBQ R10, R12 - DECQ R11 - JA emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm10Blarge_forward_sse_loop_32 - LEAQ -32(DI)(R12*1), R10 - LEAQ -32(CX)(R12*1), R13 - -emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm10Blarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R13) - MOVOA X5, 16(R13) - ADDQ $0x20, R13 - ADDQ $0x20, R10 - ADDQ $0x20, R12 - DECQ R11 - JNA emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm10Blarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm10Blarge_forward_sse_loop_32: - MOVOU -32(DI)(R12*1), X4 - MOVOU -16(DI)(R12*1), X5 - MOVOA X4, -32(CX)(R12*1) - MOVOA X5, -16(CX)(R12*1) - ADDQ $0x20, R12 - CMPQ R9, R12 - JAE emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm10Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ R8, CX - -emit_literal_done_match_emit_encodeSnappyBlockAsm10B: -match_nolit_loop_encodeSnappyBlockAsm10B: - MOVL DX, DI - SUBL SI, DI - MOVL DI, 16(SP) - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), DI - SUBL DX, DI - LEAQ (BX)(DX*1), R8 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R10, R10 - -matchlen_loopback_16_match_nolit_encodeSnappyBlockAsm10B: - CMPL DI, $0x10 - JB matchlen_match8_match_nolit_encodeSnappyBlockAsm10B - MOVQ (R8)(R10*1), R9 - MOVQ 8(R8)(R10*1), R11 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBlockAsm10B - XORQ 8(SI)(R10*1), R11 - JNZ matchlen_bsf_16match_nolit_encodeSnappyBlockAsm10B - LEAL -16(DI), DI - LEAL 16(R10), R10 - JMP matchlen_loopback_16_match_nolit_encodeSnappyBlockAsm10B - -matchlen_bsf_16match_nolit_encodeSnappyBlockAsm10B: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL 8(R10)(R11*1), R10 - JMP match_nolit_end_encodeSnappyBlockAsm10B - -matchlen_match8_match_nolit_encodeSnappyBlockAsm10B: - CMPL DI, $0x08 - JB matchlen_match4_match_nolit_encodeSnappyBlockAsm10B - MOVQ (R8)(R10*1), R9 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBlockAsm10B - LEAL -8(DI), DI - LEAL 8(R10), R10 - JMP matchlen_match4_match_nolit_encodeSnappyBlockAsm10B - -matchlen_bsf_8_match_nolit_encodeSnappyBlockAsm10B: -#ifdef GOAMD64_v3 - TZCNTQ R9, R9 - -#else - BSFQ R9, R9 - -#endif - SARQ $0x03, R9 - LEAL (R10)(R9*1), R10 - JMP match_nolit_end_encodeSnappyBlockAsm10B - -matchlen_match4_match_nolit_encodeSnappyBlockAsm10B: - CMPL DI, $0x04 - JB matchlen_match2_match_nolit_encodeSnappyBlockAsm10B - MOVL (R8)(R10*1), R9 - CMPL (SI)(R10*1), R9 - JNE matchlen_match2_match_nolit_encodeSnappyBlockAsm10B - LEAL -4(DI), DI - LEAL 4(R10), R10 - -matchlen_match2_match_nolit_encodeSnappyBlockAsm10B: - CMPL DI, $0x01 - JE matchlen_match1_match_nolit_encodeSnappyBlockAsm10B - JB match_nolit_end_encodeSnappyBlockAsm10B - MOVW (R8)(R10*1), R9 - CMPW (SI)(R10*1), R9 - JNE matchlen_match1_match_nolit_encodeSnappyBlockAsm10B - LEAL 2(R10), R10 - SUBL $0x02, DI - JZ match_nolit_end_encodeSnappyBlockAsm10B - -matchlen_match1_match_nolit_encodeSnappyBlockAsm10B: - MOVB (R8)(R10*1), R9 - CMPB (SI)(R10*1), R9 - JNE match_nolit_end_encodeSnappyBlockAsm10B - LEAL 1(R10), R10 - -match_nolit_end_encodeSnappyBlockAsm10B: - ADDL R10, DX - MOVL 16(SP), SI - ADDL $0x04, R10 - MOVL DX, 12(SP) - - // emitCopy -two_byte_offset_match_nolit_encodeSnappyBlockAsm10B: - CMPL R10, $0x40 - JBE two_byte_offset_short_match_nolit_encodeSnappyBlockAsm10B - MOVB $0xee, (CX) - MOVW SI, 1(CX) - LEAL -60(R10), R10 - ADDQ $0x03, CX - JMP two_byte_offset_match_nolit_encodeSnappyBlockAsm10B - -two_byte_offset_short_match_nolit_encodeSnappyBlockAsm10B: - MOVL R10, DI - SHLL $0x02, DI - CMPL R10, $0x0c - JAE emit_copy_three_match_nolit_encodeSnappyBlockAsm10B - CMPL SI, $0x00000800 - JAE emit_copy_three_match_nolit_encodeSnappyBlockAsm10B - LEAL -15(DI), DI - MOVB SI, 1(CX) - SHRL $0x08, SI - SHLL $0x05, SI - ORL SI, DI - MOVB DI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeSnappyBlockAsm10B - -emit_copy_three_match_nolit_encodeSnappyBlockAsm10B: - LEAL -2(DI), DI - MOVB DI, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - -match_nolit_emitcopy_end_encodeSnappyBlockAsm10B: - CMPL DX, 8(SP) - JAE emit_remainder_encodeSnappyBlockAsm10B - MOVQ -2(BX)(DX*1), DI - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeSnappyBlockAsm10B - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeSnappyBlockAsm10B: - MOVQ $0x9e3779b1, R9 - MOVQ DI, R8 - SHRQ $0x10, DI - MOVQ DI, SI - SHLQ $0x20, R8 - IMULQ R9, R8 - SHRQ $0x36, R8 - SHLQ $0x20, SI - IMULQ R9, SI - SHRQ $0x36, SI - LEAL -2(DX), R9 - LEAQ (AX)(SI*4), R10 - MOVL (R10), SI - MOVL R9, (AX)(R8*4) - MOVL DX, (R10) - CMPL (BX)(SI*1), DI - JEQ match_nolit_loop_encodeSnappyBlockAsm10B - INCL DX - JMP search_loop_encodeSnappyBlockAsm10B - -emit_remainder_encodeSnappyBlockAsm10B: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 3(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeSnappyBlockAsm10B - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeSnappyBlockAsm10B: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeSnappyBlockAsm10B - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeSnappyBlockAsm10B - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeSnappyBlockAsm10B - JB three_bytes_emit_remainder_encodeSnappyBlockAsm10B - -three_bytes_emit_remainder_encodeSnappyBlockAsm10B: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeSnappyBlockAsm10B - -two_bytes_emit_remainder_encodeSnappyBlockAsm10B: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeSnappyBlockAsm10B - JMP memmove_long_emit_remainder_encodeSnappyBlockAsm10B - -one_byte_emit_remainder_encodeSnappyBlockAsm10B: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeSnappyBlockAsm10B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm10B_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm10B_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm10B_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm10B_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm10B_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm10B_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm10B_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm10B_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm10B_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm10B_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm10B_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm10B_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeSnappyBlockAsm10B: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeSnappyBlockAsm10B - -memmove_long_emit_remainder_encodeSnappyBlockAsm10B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm10Blarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm10Blarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm10Blarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm10Blarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm10Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeSnappyBlockAsm10B: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeSnappyBlockAsm8B(dst []byte, src []byte, tmp *[1024]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeSnappyBlockAsm8B(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00000008, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeSnappyBlockAsm8B: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeSnappyBlockAsm8B - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL DX, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeSnappyBlockAsm8B: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x04, SI - LEAL 4(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeSnappyBlockAsm8B - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x9e3779b1, R9 - MOVQ DI, R10 - MOVQ DI, R11 - SHRQ $0x08, R11 - SHLQ $0x20, R10 - IMULQ R9, R10 - SHRQ $0x38, R10 - SHLQ $0x20, R11 - IMULQ R9, R11 - SHRQ $0x38, R11 - MOVL (AX)(R10*4), SI - MOVL (AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - LEAL 1(DX), R10 - MOVL R10, (AX)(R11*4) - MOVQ DI, R10 - SHRQ $0x10, R10 - SHLQ $0x20, R10 - IMULQ R9, R10 - SHRQ $0x38, R10 - MOVL DX, R9 - SUBL 16(SP), R9 - MOVL 1(BX)(R9*1), R11 - MOVQ DI, R9 - SHRQ $0x08, R9 - CMPL R9, R11 - JNE no_repeat_found_encodeSnappyBlockAsm8B - LEAL 1(DX), DI - MOVL 12(SP), SI - MOVL DI, R8 - SUBL 16(SP), R8 - JZ repeat_extend_back_end_encodeSnappyBlockAsm8B - -repeat_extend_back_loop_encodeSnappyBlockAsm8B: - CMPL DI, SI - JBE repeat_extend_back_end_encodeSnappyBlockAsm8B - MOVB -1(BX)(R8*1), R9 - MOVB -1(BX)(DI*1), R10 - CMPB R9, R10 - JNE repeat_extend_back_end_encodeSnappyBlockAsm8B - LEAL -1(DI), DI - DECL R8 - JNZ repeat_extend_back_loop_encodeSnappyBlockAsm8B - -repeat_extend_back_end_encodeSnappyBlockAsm8B: - MOVL DI, SI - SUBL 12(SP), SI - LEAQ 3(CX)(SI*1), SI - CMPQ SI, (SP) - JB repeat_dst_size_check_encodeSnappyBlockAsm8B - MOVQ $0x00000000, ret+56(FP) - RET - -repeat_dst_size_check_encodeSnappyBlockAsm8B: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_repeat_emit_encodeSnappyBlockAsm8B - MOVL DI, R8 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R9 - SUBL SI, R8 - LEAL -1(R8), SI - CMPL SI, $0x3c - JB one_byte_repeat_emit_encodeSnappyBlockAsm8B - CMPL SI, $0x00000100 - JB two_bytes_repeat_emit_encodeSnappyBlockAsm8B - JB three_bytes_repeat_emit_encodeSnappyBlockAsm8B - -three_bytes_repeat_emit_encodeSnappyBlockAsm8B: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_repeat_emit_encodeSnappyBlockAsm8B - -two_bytes_repeat_emit_encodeSnappyBlockAsm8B: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_repeat_emit_encodeSnappyBlockAsm8B - JMP memmove_long_repeat_emit_encodeSnappyBlockAsm8B - -one_byte_repeat_emit_encodeSnappyBlockAsm8B: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_repeat_emit_encodeSnappyBlockAsm8B: - LEAQ (CX)(R8*1), SI - - // genMemMoveShort - CMPQ R8, $0x08 - JBE emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm8B_memmove_move_8 - CMPQ R8, $0x10 - JBE emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm8B_memmove_move_8through16 - CMPQ R8, $0x20 - JBE emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm8B_memmove_move_17through32 - JMP emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm8B_memmove_move_33through64 - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm8B_memmove_move_8: - MOVQ (R9), R10 - MOVQ R10, (CX) - JMP memmove_end_copy_repeat_emit_encodeSnappyBlockAsm8B - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm8B_memmove_move_8through16: - MOVQ (R9), R10 - MOVQ -8(R9)(R8*1), R9 - MOVQ R10, (CX) - MOVQ R9, -8(CX)(R8*1) - JMP memmove_end_copy_repeat_emit_encodeSnappyBlockAsm8B - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm8B_memmove_move_17through32: - MOVOU (R9), X0 - MOVOU -16(R9)(R8*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R8*1) - JMP memmove_end_copy_repeat_emit_encodeSnappyBlockAsm8B - -emit_lit_memmove_repeat_emit_encodeSnappyBlockAsm8B_memmove_move_33through64: - MOVOU (R9), X0 - MOVOU 16(R9), X1 - MOVOU -32(R9)(R8*1), X2 - MOVOU -16(R9)(R8*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R8*1) - MOVOU X3, -16(CX)(R8*1) - -memmove_end_copy_repeat_emit_encodeSnappyBlockAsm8B: - MOVQ SI, CX - JMP emit_literal_done_repeat_emit_encodeSnappyBlockAsm8B - -memmove_long_repeat_emit_encodeSnappyBlockAsm8B: - LEAQ (CX)(R8*1), SI - - // genMemMoveLong - MOVOU (R9), X0 - MOVOU 16(R9), X1 - MOVOU -32(R9)(R8*1), X2 - MOVOU -16(R9)(R8*1), X3 - MOVQ R8, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R12 - SUBQ R10, R12 - DECQ R11 - JA emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm8Blarge_forward_sse_loop_32 - LEAQ -32(R9)(R12*1), R10 - LEAQ -32(CX)(R12*1), R13 - -emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm8Blarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R13) - MOVOA X5, 16(R13) - ADDQ $0x20, R13 - ADDQ $0x20, R10 - ADDQ $0x20, R12 - DECQ R11 - JNA emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm8Blarge_big_loop_back - -emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm8Blarge_forward_sse_loop_32: - MOVOU -32(R9)(R12*1), X4 - MOVOU -16(R9)(R12*1), X5 - MOVOA X4, -32(CX)(R12*1) - MOVOA X5, -16(CX)(R12*1) - ADDQ $0x20, R12 - CMPQ R8, R12 - JAE emit_lit_memmove_long_repeat_emit_encodeSnappyBlockAsm8Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R8*1) - MOVOU X3, -16(CX)(R8*1) - MOVQ SI, CX - -emit_literal_done_repeat_emit_encodeSnappyBlockAsm8B: - ADDL $0x05, DX - MOVL DX, SI - SUBL 16(SP), SI - MOVQ src_len+32(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R11, R11 - -matchlen_loopback_16_repeat_extend_encodeSnappyBlockAsm8B: - CMPL R8, $0x10 - JB matchlen_match8_repeat_extend_encodeSnappyBlockAsm8B - MOVQ (R9)(R11*1), R10 - MOVQ 8(R9)(R11*1), R12 - XORQ (SI)(R11*1), R10 - JNZ matchlen_bsf_8_repeat_extend_encodeSnappyBlockAsm8B - XORQ 8(SI)(R11*1), R12 - JNZ matchlen_bsf_16repeat_extend_encodeSnappyBlockAsm8B - LEAL -16(R8), R8 - LEAL 16(R11), R11 - JMP matchlen_loopback_16_repeat_extend_encodeSnappyBlockAsm8B - -matchlen_bsf_16repeat_extend_encodeSnappyBlockAsm8B: -#ifdef GOAMD64_v3 - TZCNTQ R12, R12 - -#else - BSFQ R12, R12 - -#endif - SARQ $0x03, R12 - LEAL 8(R11)(R12*1), R11 - JMP repeat_extend_forward_end_encodeSnappyBlockAsm8B - -matchlen_match8_repeat_extend_encodeSnappyBlockAsm8B: - CMPL R8, $0x08 - JB matchlen_match4_repeat_extend_encodeSnappyBlockAsm8B - MOVQ (R9)(R11*1), R10 - XORQ (SI)(R11*1), R10 - JNZ matchlen_bsf_8_repeat_extend_encodeSnappyBlockAsm8B - LEAL -8(R8), R8 - LEAL 8(R11), R11 - JMP matchlen_match4_repeat_extend_encodeSnappyBlockAsm8B - -matchlen_bsf_8_repeat_extend_encodeSnappyBlockAsm8B: -#ifdef GOAMD64_v3 - TZCNTQ R10, R10 - -#else - BSFQ R10, R10 - -#endif - SARQ $0x03, R10 - LEAL (R11)(R10*1), R11 - JMP repeat_extend_forward_end_encodeSnappyBlockAsm8B - -matchlen_match4_repeat_extend_encodeSnappyBlockAsm8B: - CMPL R8, $0x04 - JB matchlen_match2_repeat_extend_encodeSnappyBlockAsm8B - MOVL (R9)(R11*1), R10 - CMPL (SI)(R11*1), R10 - JNE matchlen_match2_repeat_extend_encodeSnappyBlockAsm8B - LEAL -4(R8), R8 - LEAL 4(R11), R11 - -matchlen_match2_repeat_extend_encodeSnappyBlockAsm8B: - CMPL R8, $0x01 - JE matchlen_match1_repeat_extend_encodeSnappyBlockAsm8B - JB repeat_extend_forward_end_encodeSnappyBlockAsm8B - MOVW (R9)(R11*1), R10 - CMPW (SI)(R11*1), R10 - JNE matchlen_match1_repeat_extend_encodeSnappyBlockAsm8B - LEAL 2(R11), R11 - SUBL $0x02, R8 - JZ repeat_extend_forward_end_encodeSnappyBlockAsm8B - -matchlen_match1_repeat_extend_encodeSnappyBlockAsm8B: - MOVB (R9)(R11*1), R10 - CMPB (SI)(R11*1), R10 - JNE repeat_extend_forward_end_encodeSnappyBlockAsm8B - LEAL 1(R11), R11 - -repeat_extend_forward_end_encodeSnappyBlockAsm8B: - ADDL R11, DX - MOVL DX, SI - SUBL DI, SI - MOVL 16(SP), DI - - // emitCopy -two_byte_offset_repeat_as_copy_encodeSnappyBlockAsm8B: - CMPL SI, $0x40 - JBE two_byte_offset_short_repeat_as_copy_encodeSnappyBlockAsm8B - MOVB $0xee, (CX) - MOVW DI, 1(CX) - LEAL -60(SI), SI - ADDQ $0x03, CX - JMP two_byte_offset_repeat_as_copy_encodeSnappyBlockAsm8B - -two_byte_offset_short_repeat_as_copy_encodeSnappyBlockAsm8B: - MOVL SI, R8 - SHLL $0x02, R8 - CMPL SI, $0x0c - JAE emit_copy_three_repeat_as_copy_encodeSnappyBlockAsm8B - LEAL -15(R8), R8 - MOVB DI, 1(CX) - SHRL $0x08, DI - SHLL $0x05, DI - ORL DI, R8 - MOVB R8, (CX) - ADDQ $0x02, CX - JMP repeat_end_emit_encodeSnappyBlockAsm8B - -emit_copy_three_repeat_as_copy_encodeSnappyBlockAsm8B: - LEAL -2(R8), R8 - MOVB R8, (CX) - MOVW DI, 1(CX) - ADDQ $0x03, CX - -repeat_end_emit_encodeSnappyBlockAsm8B: - MOVL DX, 12(SP) - JMP search_loop_encodeSnappyBlockAsm8B - -no_repeat_found_encodeSnappyBlockAsm8B: - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeSnappyBlockAsm8B - SHRQ $0x08, DI - MOVL (AX)(R10*4), SI - LEAL 2(DX), R9 - CMPL (BX)(R8*1), DI - JEQ candidate2_match_encodeSnappyBlockAsm8B - MOVL R9, (AX)(R10*4) - SHRQ $0x08, DI - CMPL (BX)(SI*1), DI - JEQ candidate3_match_encodeSnappyBlockAsm8B - MOVL 20(SP), DX - JMP search_loop_encodeSnappyBlockAsm8B - -candidate3_match_encodeSnappyBlockAsm8B: - ADDL $0x02, DX - JMP candidate_match_encodeSnappyBlockAsm8B - -candidate2_match_encodeSnappyBlockAsm8B: - MOVL R9, (AX)(R10*4) - INCL DX - MOVL R8, SI - -candidate_match_encodeSnappyBlockAsm8B: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeSnappyBlockAsm8B - -match_extend_back_loop_encodeSnappyBlockAsm8B: - CMPL DX, DI - JBE match_extend_back_end_encodeSnappyBlockAsm8B - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeSnappyBlockAsm8B - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeSnappyBlockAsm8B - JMP match_extend_back_loop_encodeSnappyBlockAsm8B - -match_extend_back_end_encodeSnappyBlockAsm8B: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 3(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeSnappyBlockAsm8B - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeSnappyBlockAsm8B: - MOVL DX, DI - MOVL 12(SP), R8 - CMPL R8, DI - JEQ emit_literal_done_match_emit_encodeSnappyBlockAsm8B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(R8*1), DI - SUBL R8, R9 - LEAL -1(R9), R8 - CMPL R8, $0x3c - JB one_byte_match_emit_encodeSnappyBlockAsm8B - CMPL R8, $0x00000100 - JB two_bytes_match_emit_encodeSnappyBlockAsm8B - JB three_bytes_match_emit_encodeSnappyBlockAsm8B - -three_bytes_match_emit_encodeSnappyBlockAsm8B: - MOVB $0xf4, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeSnappyBlockAsm8B - -two_bytes_match_emit_encodeSnappyBlockAsm8B: - MOVB $0xf0, (CX) - MOVB R8, 1(CX) - ADDQ $0x02, CX - CMPL R8, $0x40 - JB memmove_match_emit_encodeSnappyBlockAsm8B - JMP memmove_long_match_emit_encodeSnappyBlockAsm8B - -one_byte_match_emit_encodeSnappyBlockAsm8B: - SHLB $0x02, R8 - MOVB R8, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeSnappyBlockAsm8B: - LEAQ (CX)(R9*1), R8 - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_match_emit_encodeSnappyBlockAsm8B_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeSnappyBlockAsm8B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeSnappyBlockAsm8B_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeSnappyBlockAsm8B_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm8B_memmove_move_8: - MOVQ (DI), R10 - MOVQ R10, (CX) - JMP memmove_end_copy_match_emit_encodeSnappyBlockAsm8B - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm8B_memmove_move_8through16: - MOVQ (DI), R10 - MOVQ -8(DI)(R9*1), DI - MOVQ R10, (CX) - MOVQ DI, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBlockAsm8B - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm8B_memmove_move_17through32: - MOVOU (DI), X0 - MOVOU -16(DI)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBlockAsm8B - -emit_lit_memmove_match_emit_encodeSnappyBlockAsm8B_memmove_move_33through64: - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeSnappyBlockAsm8B: - MOVQ R8, CX - JMP emit_literal_done_match_emit_encodeSnappyBlockAsm8B - -memmove_long_match_emit_encodeSnappyBlockAsm8B: - LEAQ (CX)(R9*1), R8 - - // genMemMoveLong - MOVOU (DI), X0 - MOVOU 16(DI), X1 - MOVOU -32(DI)(R9*1), X2 - MOVOU -16(DI)(R9*1), X3 - MOVQ R9, R11 - SHRQ $0x05, R11 - MOVQ CX, R10 - ANDL $0x0000001f, R10 - MOVQ $0x00000040, R12 - SUBQ R10, R12 - DECQ R11 - JA emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm8Blarge_forward_sse_loop_32 - LEAQ -32(DI)(R12*1), R10 - LEAQ -32(CX)(R12*1), R13 - -emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm8Blarge_big_loop_back: - MOVOU (R10), X4 - MOVOU 16(R10), X5 - MOVOA X4, (R13) - MOVOA X5, 16(R13) - ADDQ $0x20, R13 - ADDQ $0x20, R10 - ADDQ $0x20, R12 - DECQ R11 - JNA emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm8Blarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm8Blarge_forward_sse_loop_32: - MOVOU -32(DI)(R12*1), X4 - MOVOU -16(DI)(R12*1), X5 - MOVOA X4, -32(CX)(R12*1) - MOVOA X5, -16(CX)(R12*1) - ADDQ $0x20, R12 - CMPQ R9, R12 - JAE emit_lit_memmove_long_match_emit_encodeSnappyBlockAsm8Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ R8, CX - -emit_literal_done_match_emit_encodeSnappyBlockAsm8B: -match_nolit_loop_encodeSnappyBlockAsm8B: - MOVL DX, DI - SUBL SI, DI - MOVL DI, 16(SP) - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), DI - SUBL DX, DI - LEAQ (BX)(DX*1), R8 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R10, R10 - -matchlen_loopback_16_match_nolit_encodeSnappyBlockAsm8B: - CMPL DI, $0x10 - JB matchlen_match8_match_nolit_encodeSnappyBlockAsm8B - MOVQ (R8)(R10*1), R9 - MOVQ 8(R8)(R10*1), R11 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBlockAsm8B - XORQ 8(SI)(R10*1), R11 - JNZ matchlen_bsf_16match_nolit_encodeSnappyBlockAsm8B - LEAL -16(DI), DI - LEAL 16(R10), R10 - JMP matchlen_loopback_16_match_nolit_encodeSnappyBlockAsm8B - -matchlen_bsf_16match_nolit_encodeSnappyBlockAsm8B: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL 8(R10)(R11*1), R10 - JMP match_nolit_end_encodeSnappyBlockAsm8B - -matchlen_match8_match_nolit_encodeSnappyBlockAsm8B: - CMPL DI, $0x08 - JB matchlen_match4_match_nolit_encodeSnappyBlockAsm8B - MOVQ (R8)(R10*1), R9 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBlockAsm8B - LEAL -8(DI), DI - LEAL 8(R10), R10 - JMP matchlen_match4_match_nolit_encodeSnappyBlockAsm8B - -matchlen_bsf_8_match_nolit_encodeSnappyBlockAsm8B: -#ifdef GOAMD64_v3 - TZCNTQ R9, R9 - -#else - BSFQ R9, R9 - -#endif - SARQ $0x03, R9 - LEAL (R10)(R9*1), R10 - JMP match_nolit_end_encodeSnappyBlockAsm8B - -matchlen_match4_match_nolit_encodeSnappyBlockAsm8B: - CMPL DI, $0x04 - JB matchlen_match2_match_nolit_encodeSnappyBlockAsm8B - MOVL (R8)(R10*1), R9 - CMPL (SI)(R10*1), R9 - JNE matchlen_match2_match_nolit_encodeSnappyBlockAsm8B - LEAL -4(DI), DI - LEAL 4(R10), R10 - -matchlen_match2_match_nolit_encodeSnappyBlockAsm8B: - CMPL DI, $0x01 - JE matchlen_match1_match_nolit_encodeSnappyBlockAsm8B - JB match_nolit_end_encodeSnappyBlockAsm8B - MOVW (R8)(R10*1), R9 - CMPW (SI)(R10*1), R9 - JNE matchlen_match1_match_nolit_encodeSnappyBlockAsm8B - LEAL 2(R10), R10 - SUBL $0x02, DI - JZ match_nolit_end_encodeSnappyBlockAsm8B - -matchlen_match1_match_nolit_encodeSnappyBlockAsm8B: - MOVB (R8)(R10*1), R9 - CMPB (SI)(R10*1), R9 - JNE match_nolit_end_encodeSnappyBlockAsm8B - LEAL 1(R10), R10 - -match_nolit_end_encodeSnappyBlockAsm8B: - ADDL R10, DX - MOVL 16(SP), SI - ADDL $0x04, R10 - MOVL DX, 12(SP) - - // emitCopy -two_byte_offset_match_nolit_encodeSnappyBlockAsm8B: - CMPL R10, $0x40 - JBE two_byte_offset_short_match_nolit_encodeSnappyBlockAsm8B - MOVB $0xee, (CX) - MOVW SI, 1(CX) - LEAL -60(R10), R10 - ADDQ $0x03, CX - JMP two_byte_offset_match_nolit_encodeSnappyBlockAsm8B - -two_byte_offset_short_match_nolit_encodeSnappyBlockAsm8B: - MOVL R10, DI - SHLL $0x02, DI - CMPL R10, $0x0c - JAE emit_copy_three_match_nolit_encodeSnappyBlockAsm8B - LEAL -15(DI), DI - MOVB SI, 1(CX) - SHRL $0x08, SI - SHLL $0x05, SI - ORL SI, DI - MOVB DI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeSnappyBlockAsm8B - -emit_copy_three_match_nolit_encodeSnappyBlockAsm8B: - LEAL -2(DI), DI - MOVB DI, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - -match_nolit_emitcopy_end_encodeSnappyBlockAsm8B: - CMPL DX, 8(SP) - JAE emit_remainder_encodeSnappyBlockAsm8B - MOVQ -2(BX)(DX*1), DI - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeSnappyBlockAsm8B - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeSnappyBlockAsm8B: - MOVQ $0x9e3779b1, R9 - MOVQ DI, R8 - SHRQ $0x10, DI - MOVQ DI, SI - SHLQ $0x20, R8 - IMULQ R9, R8 - SHRQ $0x38, R8 - SHLQ $0x20, SI - IMULQ R9, SI - SHRQ $0x38, SI - LEAL -2(DX), R9 - LEAQ (AX)(SI*4), R10 - MOVL (R10), SI - MOVL R9, (AX)(R8*4) - MOVL DX, (R10) - CMPL (BX)(SI*1), DI - JEQ match_nolit_loop_encodeSnappyBlockAsm8B - INCL DX - JMP search_loop_encodeSnappyBlockAsm8B - -emit_remainder_encodeSnappyBlockAsm8B: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 3(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeSnappyBlockAsm8B - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeSnappyBlockAsm8B: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeSnappyBlockAsm8B - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeSnappyBlockAsm8B - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeSnappyBlockAsm8B - JB three_bytes_emit_remainder_encodeSnappyBlockAsm8B - -three_bytes_emit_remainder_encodeSnappyBlockAsm8B: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeSnappyBlockAsm8B - -two_bytes_emit_remainder_encodeSnappyBlockAsm8B: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeSnappyBlockAsm8B - JMP memmove_long_emit_remainder_encodeSnappyBlockAsm8B - -one_byte_emit_remainder_encodeSnappyBlockAsm8B: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeSnappyBlockAsm8B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm8B_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm8B_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm8B_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm8B_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm8B_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm8B_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm8B_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm8B_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm8B_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm8B_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm8B_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeSnappyBlockAsm8B_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeSnappyBlockAsm8B: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeSnappyBlockAsm8B - -memmove_long_emit_remainder_encodeSnappyBlockAsm8B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm8Blarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm8Blarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm8Blarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm8Blarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeSnappyBlockAsm8Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeSnappyBlockAsm8B: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeSnappyBetterBlockAsm(dst []byte, src []byte, tmp *[589824]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeSnappyBetterBlockAsm(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00001200, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeSnappyBetterBlockAsm: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeSnappyBetterBlockAsm - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL $0x00000000, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeSnappyBetterBlockAsm: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x07, SI - CMPL SI, $0x63 - JBE check_maxskip_ok_encodeSnappyBetterBlockAsm - LEAL 100(DX), SI - JMP check_maxskip_cont_encodeSnappyBetterBlockAsm - -check_maxskip_ok_encodeSnappyBetterBlockAsm: - LEAL 1(DX)(SI*1), SI - -check_maxskip_cont_encodeSnappyBetterBlockAsm: - CMPL SI, 8(SP) - JAE emit_remainder_encodeSnappyBetterBlockAsm - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x00cf1bbcdcbfa563, R9 - MOVQ $0x9e3779b1, SI - MOVQ DI, R10 - MOVQ DI, R11 - SHLQ $0x08, R10 - IMULQ R9, R10 - SHRQ $0x2f, R10 - SHLQ $0x20, R11 - IMULQ SI, R11 - SHRQ $0x32, R11 - MOVL (AX)(R10*4), SI - MOVL 524288(AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - MOVL DX, 524288(AX)(R11*4) - MOVQ (BX)(SI*1), R10 - MOVQ (BX)(R8*1), R11 - CMPQ R10, DI - JEQ candidate_match_encodeSnappyBetterBlockAsm - CMPQ R11, DI - JNE no_short_found_encodeSnappyBetterBlockAsm - MOVL R8, SI - JMP candidate_match_encodeSnappyBetterBlockAsm - -no_short_found_encodeSnappyBetterBlockAsm: - CMPL R10, DI - JEQ candidate_match_encodeSnappyBetterBlockAsm - CMPL R11, DI - JEQ candidateS_match_encodeSnappyBetterBlockAsm - MOVL 20(SP), DX - JMP search_loop_encodeSnappyBetterBlockAsm - -candidateS_match_encodeSnappyBetterBlockAsm: - SHRQ $0x08, DI - MOVQ DI, R10 - SHLQ $0x08, R10 - IMULQ R9, R10 - SHRQ $0x2f, R10 - MOVL (AX)(R10*4), SI - INCL DX - MOVL DX, (AX)(R10*4) - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeSnappyBetterBlockAsm - DECL DX - MOVL R8, SI - -candidate_match_encodeSnappyBetterBlockAsm: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeSnappyBetterBlockAsm - -match_extend_back_loop_encodeSnappyBetterBlockAsm: - CMPL DX, DI - JBE match_extend_back_end_encodeSnappyBetterBlockAsm - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeSnappyBetterBlockAsm - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeSnappyBetterBlockAsm - JMP match_extend_back_loop_encodeSnappyBetterBlockAsm - -match_extend_back_end_encodeSnappyBetterBlockAsm: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 5(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeSnappyBetterBlockAsm - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeSnappyBetterBlockAsm: - MOVL DX, DI - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), R10 - - // matchLen - XORL R12, R12 - -matchlen_loopback_16_match_nolit_encodeSnappyBetterBlockAsm: - CMPL R8, $0x10 - JB matchlen_match8_match_nolit_encodeSnappyBetterBlockAsm - MOVQ (R9)(R12*1), R11 - MOVQ 8(R9)(R12*1), R13 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBetterBlockAsm - XORQ 8(R10)(R12*1), R13 - JNZ matchlen_bsf_16match_nolit_encodeSnappyBetterBlockAsm - LEAL -16(R8), R8 - LEAL 16(R12), R12 - JMP matchlen_loopback_16_match_nolit_encodeSnappyBetterBlockAsm - -matchlen_bsf_16match_nolit_encodeSnappyBetterBlockAsm: -#ifdef GOAMD64_v3 - TZCNTQ R13, R13 - -#else - BSFQ R13, R13 - -#endif - SARQ $0x03, R13 - LEAL 8(R12)(R13*1), R12 - JMP match_nolit_end_encodeSnappyBetterBlockAsm - -matchlen_match8_match_nolit_encodeSnappyBetterBlockAsm: - CMPL R8, $0x08 - JB matchlen_match4_match_nolit_encodeSnappyBetterBlockAsm - MOVQ (R9)(R12*1), R11 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBetterBlockAsm - LEAL -8(R8), R8 - LEAL 8(R12), R12 - JMP matchlen_match4_match_nolit_encodeSnappyBetterBlockAsm - -matchlen_bsf_8_match_nolit_encodeSnappyBetterBlockAsm: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL (R12)(R11*1), R12 - JMP match_nolit_end_encodeSnappyBetterBlockAsm - -matchlen_match4_match_nolit_encodeSnappyBetterBlockAsm: - CMPL R8, $0x04 - JB matchlen_match2_match_nolit_encodeSnappyBetterBlockAsm - MOVL (R9)(R12*1), R11 - CMPL (R10)(R12*1), R11 - JNE matchlen_match2_match_nolit_encodeSnappyBetterBlockAsm - LEAL -4(R8), R8 - LEAL 4(R12), R12 - -matchlen_match2_match_nolit_encodeSnappyBetterBlockAsm: - CMPL R8, $0x01 - JE matchlen_match1_match_nolit_encodeSnappyBetterBlockAsm - JB match_nolit_end_encodeSnappyBetterBlockAsm - MOVW (R9)(R12*1), R11 - CMPW (R10)(R12*1), R11 - JNE matchlen_match1_match_nolit_encodeSnappyBetterBlockAsm - LEAL 2(R12), R12 - SUBL $0x02, R8 - JZ match_nolit_end_encodeSnappyBetterBlockAsm - -matchlen_match1_match_nolit_encodeSnappyBetterBlockAsm: - MOVB (R9)(R12*1), R11 - CMPB (R10)(R12*1), R11 - JNE match_nolit_end_encodeSnappyBetterBlockAsm - LEAL 1(R12), R12 - -match_nolit_end_encodeSnappyBetterBlockAsm: - MOVL DX, R8 - SUBL SI, R8 - - // Check if repeat - CMPL R12, $0x01 - JA match_length_ok_encodeSnappyBetterBlockAsm - CMPL R8, $0x0000ffff - JBE match_length_ok_encodeSnappyBetterBlockAsm - MOVL 20(SP), DX - INCL DX - JMP search_loop_encodeSnappyBetterBlockAsm - -match_length_ok_encodeSnappyBetterBlockAsm: - MOVL R8, 16(SP) - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_match_emit_encodeSnappyBetterBlockAsm - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_match_emit_encodeSnappyBetterBlockAsm - CMPL SI, $0x00000100 - JB two_bytes_match_emit_encodeSnappyBetterBlockAsm - CMPL SI, $0x00010000 - JB three_bytes_match_emit_encodeSnappyBetterBlockAsm - CMPL SI, $0x01000000 - JB four_bytes_match_emit_encodeSnappyBetterBlockAsm - MOVB $0xfc, (CX) - MOVL SI, 1(CX) - ADDQ $0x05, CX - JMP memmove_long_match_emit_encodeSnappyBetterBlockAsm - -four_bytes_match_emit_encodeSnappyBetterBlockAsm: - MOVL SI, R11 - SHRL $0x10, R11 - MOVB $0xf8, (CX) - MOVW SI, 1(CX) - MOVB R11, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_match_emit_encodeSnappyBetterBlockAsm - -three_bytes_match_emit_encodeSnappyBetterBlockAsm: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeSnappyBetterBlockAsm - -two_bytes_match_emit_encodeSnappyBetterBlockAsm: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_match_emit_encodeSnappyBetterBlockAsm - JMP memmove_long_match_emit_encodeSnappyBetterBlockAsm - -one_byte_match_emit_encodeSnappyBetterBlockAsm: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeSnappyBetterBlockAsm: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm_memmove_move_8: - MOVQ (R10), R11 - MOVQ R11, (CX) - JMP memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm: - MOVQ SI, CX - JMP emit_literal_done_match_emit_encodeSnappyBetterBlockAsm - -memmove_long_match_emit_encodeSnappyBetterBlockAsm: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R14 - SUBQ R11, R14 - DECQ R13 - JA emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsmlarge_forward_sse_loop_32 - LEAQ -32(R10)(R14*1), R11 - LEAQ -32(CX)(R14*1), R15 - -emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsmlarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R11 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsmlarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsmlarge_forward_sse_loop_32: - MOVOU -32(R10)(R14*1), X4 - MOVOU -16(R10)(R14*1), X5 - MOVOA X4, -32(CX)(R14*1) - MOVOA X5, -16(CX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsmlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_match_emit_encodeSnappyBetterBlockAsm: - ADDL R12, DX - ADDL $0x04, R12 - MOVL DX, 12(SP) - - // emitCopy - CMPL R8, $0x00010000 - JB two_byte_offset_match_nolit_encodeSnappyBetterBlockAsm - -four_bytes_loop_back_match_nolit_encodeSnappyBetterBlockAsm: - CMPL R12, $0x40 - JBE four_bytes_remain_match_nolit_encodeSnappyBetterBlockAsm - MOVB $0xff, (CX) - MOVL R8, 1(CX) - LEAL -64(R12), R12 - ADDQ $0x05, CX - CMPL R12, $0x04 - JB four_bytes_remain_match_nolit_encodeSnappyBetterBlockAsm - JMP four_bytes_loop_back_match_nolit_encodeSnappyBetterBlockAsm - -four_bytes_remain_match_nolit_encodeSnappyBetterBlockAsm: - TESTL R12, R12 - JZ match_nolit_emitcopy_end_encodeSnappyBetterBlockAsm - XORL SI, SI - LEAL -1(SI)(R12*4), R12 - MOVB R12, (CX) - MOVL R8, 1(CX) - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_encodeSnappyBetterBlockAsm - -two_byte_offset_match_nolit_encodeSnappyBetterBlockAsm: - CMPL R12, $0x40 - JBE two_byte_offset_short_match_nolit_encodeSnappyBetterBlockAsm - MOVB $0xee, (CX) - MOVW R8, 1(CX) - LEAL -60(R12), R12 - ADDQ $0x03, CX - JMP two_byte_offset_match_nolit_encodeSnappyBetterBlockAsm - -two_byte_offset_short_match_nolit_encodeSnappyBetterBlockAsm: - MOVL R12, SI - SHLL $0x02, SI - CMPL R12, $0x0c - JAE emit_copy_three_match_nolit_encodeSnappyBetterBlockAsm - CMPL R8, $0x00000800 - JAE emit_copy_three_match_nolit_encodeSnappyBetterBlockAsm - LEAL -15(SI), SI - MOVB R8, 1(CX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeSnappyBetterBlockAsm - -emit_copy_three_match_nolit_encodeSnappyBetterBlockAsm: - LEAL -2(SI), SI - MOVB SI, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - -match_nolit_emitcopy_end_encodeSnappyBetterBlockAsm: - CMPL DX, 8(SP) - JAE emit_remainder_encodeSnappyBetterBlockAsm - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeSnappyBetterBlockAsm - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeSnappyBetterBlockAsm: - MOVQ $0x00cf1bbcdcbfa563, SI - MOVQ $0x9e3779b1, R8 - LEAQ 1(DI), DI - LEAQ -2(DX), R9 - MOVQ (BX)(DI*1), R10 - MOVQ 1(BX)(DI*1), R11 - MOVQ (BX)(R9*1), R12 - MOVQ 1(BX)(R9*1), R13 - SHLQ $0x08, R10 - IMULQ SI, R10 - SHRQ $0x2f, R10 - SHLQ $0x20, R11 - IMULQ R8, R11 - SHRQ $0x32, R11 - SHLQ $0x08, R12 - IMULQ SI, R12 - SHRQ $0x2f, R12 - SHLQ $0x20, R13 - IMULQ R8, R13 - SHRQ $0x32, R13 - LEAQ 1(DI), R8 - LEAQ 1(R9), R14 - MOVL DI, (AX)(R10*4) - MOVL R9, (AX)(R12*4) - MOVL R8, 524288(AX)(R11*4) - MOVL R14, 524288(AX)(R13*4) - LEAQ 1(R9)(DI*1), R8 - SHRQ $0x01, R8 - ADDQ $0x01, DI - SUBQ $0x01, R9 - -index_loop_encodeSnappyBetterBlockAsm: - CMPQ R8, R9 - JAE search_loop_encodeSnappyBetterBlockAsm - MOVQ (BX)(DI*1), R10 - MOVQ (BX)(R8*1), R11 - SHLQ $0x08, R10 - IMULQ SI, R10 - SHRQ $0x2f, R10 - SHLQ $0x08, R11 - IMULQ SI, R11 - SHRQ $0x2f, R11 - MOVL DI, (AX)(R10*4) - MOVL R8, (AX)(R11*4) - ADDQ $0x02, DI - ADDQ $0x02, R8 - JMP index_loop_encodeSnappyBetterBlockAsm - -emit_remainder_encodeSnappyBetterBlockAsm: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 5(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeSnappyBetterBlockAsm - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeSnappyBetterBlockAsm: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeSnappyBetterBlockAsm - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeSnappyBetterBlockAsm - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeSnappyBetterBlockAsm - CMPL DX, $0x00010000 - JB three_bytes_emit_remainder_encodeSnappyBetterBlockAsm - CMPL DX, $0x01000000 - JB four_bytes_emit_remainder_encodeSnappyBetterBlockAsm - MOVB $0xfc, (CX) - MOVL DX, 1(CX) - ADDQ $0x05, CX - JMP memmove_long_emit_remainder_encodeSnappyBetterBlockAsm - -four_bytes_emit_remainder_encodeSnappyBetterBlockAsm: - MOVL DX, BX - SHRL $0x10, BX - MOVB $0xf8, (CX) - MOVW DX, 1(CX) - MOVB BL, 3(CX) - ADDQ $0x04, CX - JMP memmove_long_emit_remainder_encodeSnappyBetterBlockAsm - -three_bytes_emit_remainder_encodeSnappyBetterBlockAsm: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeSnappyBetterBlockAsm - -two_bytes_emit_remainder_encodeSnappyBetterBlockAsm: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeSnappyBetterBlockAsm - JMP memmove_long_emit_remainder_encodeSnappyBetterBlockAsm - -one_byte_emit_remainder_encodeSnappyBetterBlockAsm: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeSnappyBetterBlockAsm: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeSnappyBetterBlockAsm - -memmove_long_emit_remainder_encodeSnappyBetterBlockAsm: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsmlarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsmlarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsmlarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsmlarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsmlarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeSnappyBetterBlockAsm: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeSnappyBetterBlockAsm64K(dst []byte, src []byte, tmp *[294912]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeSnappyBetterBlockAsm64K(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00000900, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeSnappyBetterBlockAsm64K: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeSnappyBetterBlockAsm64K - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL $0x00000000, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeSnappyBetterBlockAsm64K: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x07, SI - LEAL 1(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeSnappyBetterBlockAsm64K - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x00cf1bbcdcbfa563, R9 - MOVQ $0x9e3779b1, SI - MOVQ DI, R10 - MOVQ DI, R11 - SHLQ $0x08, R10 - IMULQ R9, R10 - SHRQ $0x30, R10 - SHLQ $0x20, R11 - IMULQ SI, R11 - SHRQ $0x33, R11 - MOVL (AX)(R10*4), SI - MOVL 262144(AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - MOVL DX, 262144(AX)(R11*4) - MOVQ (BX)(SI*1), R10 - MOVQ (BX)(R8*1), R11 - CMPQ R10, DI - JEQ candidate_match_encodeSnappyBetterBlockAsm64K - CMPQ R11, DI - JNE no_short_found_encodeSnappyBetterBlockAsm64K - MOVL R8, SI - JMP candidate_match_encodeSnappyBetterBlockAsm64K - -no_short_found_encodeSnappyBetterBlockAsm64K: - CMPL R10, DI - JEQ candidate_match_encodeSnappyBetterBlockAsm64K - CMPL R11, DI - JEQ candidateS_match_encodeSnappyBetterBlockAsm64K - MOVL 20(SP), DX - JMP search_loop_encodeSnappyBetterBlockAsm64K - -candidateS_match_encodeSnappyBetterBlockAsm64K: - SHRQ $0x08, DI - MOVQ DI, R10 - SHLQ $0x08, R10 - IMULQ R9, R10 - SHRQ $0x30, R10 - MOVL (AX)(R10*4), SI - INCL DX - MOVL DX, (AX)(R10*4) - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeSnappyBetterBlockAsm64K - DECL DX - MOVL R8, SI - -candidate_match_encodeSnappyBetterBlockAsm64K: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeSnappyBetterBlockAsm64K - -match_extend_back_loop_encodeSnappyBetterBlockAsm64K: - CMPL DX, DI - JBE match_extend_back_end_encodeSnappyBetterBlockAsm64K - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeSnappyBetterBlockAsm64K - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeSnappyBetterBlockAsm64K - JMP match_extend_back_loop_encodeSnappyBetterBlockAsm64K - -match_extend_back_end_encodeSnappyBetterBlockAsm64K: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 3(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeSnappyBetterBlockAsm64K - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeSnappyBetterBlockAsm64K: - MOVL DX, DI - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), R10 - - // matchLen - XORL R12, R12 - -matchlen_loopback_16_match_nolit_encodeSnappyBetterBlockAsm64K: - CMPL R8, $0x10 - JB matchlen_match8_match_nolit_encodeSnappyBetterBlockAsm64K - MOVQ (R9)(R12*1), R11 - MOVQ 8(R9)(R12*1), R13 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBetterBlockAsm64K - XORQ 8(R10)(R12*1), R13 - JNZ matchlen_bsf_16match_nolit_encodeSnappyBetterBlockAsm64K - LEAL -16(R8), R8 - LEAL 16(R12), R12 - JMP matchlen_loopback_16_match_nolit_encodeSnappyBetterBlockAsm64K - -matchlen_bsf_16match_nolit_encodeSnappyBetterBlockAsm64K: -#ifdef GOAMD64_v3 - TZCNTQ R13, R13 - -#else - BSFQ R13, R13 - -#endif - SARQ $0x03, R13 - LEAL 8(R12)(R13*1), R12 - JMP match_nolit_end_encodeSnappyBetterBlockAsm64K - -matchlen_match8_match_nolit_encodeSnappyBetterBlockAsm64K: - CMPL R8, $0x08 - JB matchlen_match4_match_nolit_encodeSnappyBetterBlockAsm64K - MOVQ (R9)(R12*1), R11 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBetterBlockAsm64K - LEAL -8(R8), R8 - LEAL 8(R12), R12 - JMP matchlen_match4_match_nolit_encodeSnappyBetterBlockAsm64K - -matchlen_bsf_8_match_nolit_encodeSnappyBetterBlockAsm64K: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL (R12)(R11*1), R12 - JMP match_nolit_end_encodeSnappyBetterBlockAsm64K - -matchlen_match4_match_nolit_encodeSnappyBetterBlockAsm64K: - CMPL R8, $0x04 - JB matchlen_match2_match_nolit_encodeSnappyBetterBlockAsm64K - MOVL (R9)(R12*1), R11 - CMPL (R10)(R12*1), R11 - JNE matchlen_match2_match_nolit_encodeSnappyBetterBlockAsm64K - LEAL -4(R8), R8 - LEAL 4(R12), R12 - -matchlen_match2_match_nolit_encodeSnappyBetterBlockAsm64K: - CMPL R8, $0x01 - JE matchlen_match1_match_nolit_encodeSnappyBetterBlockAsm64K - JB match_nolit_end_encodeSnappyBetterBlockAsm64K - MOVW (R9)(R12*1), R11 - CMPW (R10)(R12*1), R11 - JNE matchlen_match1_match_nolit_encodeSnappyBetterBlockAsm64K - LEAL 2(R12), R12 - SUBL $0x02, R8 - JZ match_nolit_end_encodeSnappyBetterBlockAsm64K - -matchlen_match1_match_nolit_encodeSnappyBetterBlockAsm64K: - MOVB (R9)(R12*1), R11 - CMPB (R10)(R12*1), R11 - JNE match_nolit_end_encodeSnappyBetterBlockAsm64K - LEAL 1(R12), R12 - -match_nolit_end_encodeSnappyBetterBlockAsm64K: - MOVL DX, R8 - SUBL SI, R8 - - // Check if repeat - MOVL R8, 16(SP) - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_match_emit_encodeSnappyBetterBlockAsm64K - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_match_emit_encodeSnappyBetterBlockAsm64K - CMPL SI, $0x00000100 - JB two_bytes_match_emit_encodeSnappyBetterBlockAsm64K - JB three_bytes_match_emit_encodeSnappyBetterBlockAsm64K - -three_bytes_match_emit_encodeSnappyBetterBlockAsm64K: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeSnappyBetterBlockAsm64K - -two_bytes_match_emit_encodeSnappyBetterBlockAsm64K: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_match_emit_encodeSnappyBetterBlockAsm64K - JMP memmove_long_match_emit_encodeSnappyBetterBlockAsm64K - -one_byte_match_emit_encodeSnappyBetterBlockAsm64K: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeSnappyBetterBlockAsm64K: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm64K_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm64K_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm64K_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm64K_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm64K_memmove_move_8: - MOVQ (R10), R11 - MOVQ R11, (CX) - JMP memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm64K - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm64K_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm64K - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm64K_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm64K - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm64K_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm64K: - MOVQ SI, CX - JMP emit_literal_done_match_emit_encodeSnappyBetterBlockAsm64K - -memmove_long_match_emit_encodeSnappyBetterBlockAsm64K: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R14 - SUBQ R11, R14 - DECQ R13 - JA emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm64Klarge_forward_sse_loop_32 - LEAQ -32(R10)(R14*1), R11 - LEAQ -32(CX)(R14*1), R15 - -emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm64Klarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R11 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm64Klarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm64Klarge_forward_sse_loop_32: - MOVOU -32(R10)(R14*1), X4 - MOVOU -16(R10)(R14*1), X5 - MOVOA X4, -32(CX)(R14*1) - MOVOA X5, -16(CX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm64Klarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_match_emit_encodeSnappyBetterBlockAsm64K: - ADDL R12, DX - ADDL $0x04, R12 - MOVL DX, 12(SP) - - // emitCopy -two_byte_offset_match_nolit_encodeSnappyBetterBlockAsm64K: - CMPL R12, $0x40 - JBE two_byte_offset_short_match_nolit_encodeSnappyBetterBlockAsm64K - MOVB $0xee, (CX) - MOVW R8, 1(CX) - LEAL -60(R12), R12 - ADDQ $0x03, CX - JMP two_byte_offset_match_nolit_encodeSnappyBetterBlockAsm64K - -two_byte_offset_short_match_nolit_encodeSnappyBetterBlockAsm64K: - MOVL R12, SI - SHLL $0x02, SI - CMPL R12, $0x0c - JAE emit_copy_three_match_nolit_encodeSnappyBetterBlockAsm64K - CMPL R8, $0x00000800 - JAE emit_copy_three_match_nolit_encodeSnappyBetterBlockAsm64K - LEAL -15(SI), SI - MOVB R8, 1(CX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeSnappyBetterBlockAsm64K - -emit_copy_three_match_nolit_encodeSnappyBetterBlockAsm64K: - LEAL -2(SI), SI - MOVB SI, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - -match_nolit_emitcopy_end_encodeSnappyBetterBlockAsm64K: - CMPL DX, 8(SP) - JAE emit_remainder_encodeSnappyBetterBlockAsm64K - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeSnappyBetterBlockAsm64K - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeSnappyBetterBlockAsm64K: - MOVQ $0x00cf1bbcdcbfa563, SI - MOVQ $0x9e3779b1, R8 - LEAQ 1(DI), DI - LEAQ -2(DX), R9 - MOVQ (BX)(DI*1), R10 - MOVQ 1(BX)(DI*1), R11 - MOVQ (BX)(R9*1), R12 - MOVQ 1(BX)(R9*1), R13 - SHLQ $0x08, R10 - IMULQ SI, R10 - SHRQ $0x30, R10 - SHLQ $0x20, R11 - IMULQ R8, R11 - SHRQ $0x33, R11 - SHLQ $0x08, R12 - IMULQ SI, R12 - SHRQ $0x30, R12 - SHLQ $0x20, R13 - IMULQ R8, R13 - SHRQ $0x33, R13 - LEAQ 1(DI), R8 - LEAQ 1(R9), R14 - MOVL DI, (AX)(R10*4) - MOVL R9, (AX)(R12*4) - MOVL R8, 262144(AX)(R11*4) - MOVL R14, 262144(AX)(R13*4) - LEAQ 1(R9)(DI*1), R8 - SHRQ $0x01, R8 - ADDQ $0x01, DI - SUBQ $0x01, R9 - -index_loop_encodeSnappyBetterBlockAsm64K: - CMPQ R8, R9 - JAE search_loop_encodeSnappyBetterBlockAsm64K - MOVQ (BX)(DI*1), R10 - MOVQ (BX)(R8*1), R11 - SHLQ $0x08, R10 - IMULQ SI, R10 - SHRQ $0x30, R10 - SHLQ $0x08, R11 - IMULQ SI, R11 - SHRQ $0x30, R11 - MOVL DI, (AX)(R10*4) - MOVL R8, (AX)(R11*4) - ADDQ $0x02, DI - ADDQ $0x02, R8 - JMP index_loop_encodeSnappyBetterBlockAsm64K - -emit_remainder_encodeSnappyBetterBlockAsm64K: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 3(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeSnappyBetterBlockAsm64K - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeSnappyBetterBlockAsm64K: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeSnappyBetterBlockAsm64K - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeSnappyBetterBlockAsm64K - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeSnappyBetterBlockAsm64K - JB three_bytes_emit_remainder_encodeSnappyBetterBlockAsm64K - -three_bytes_emit_remainder_encodeSnappyBetterBlockAsm64K: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeSnappyBetterBlockAsm64K - -two_bytes_emit_remainder_encodeSnappyBetterBlockAsm64K: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeSnappyBetterBlockAsm64K - JMP memmove_long_emit_remainder_encodeSnappyBetterBlockAsm64K - -one_byte_emit_remainder_encodeSnappyBetterBlockAsm64K: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeSnappyBetterBlockAsm64K: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm64K_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm64K_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm64K_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm64K_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm64K_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm64K_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm64K_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm64K - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm64K_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm64K - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm64K_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm64K - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm64K_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm64K - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm64K_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm64K - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm64K_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm64K: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeSnappyBetterBlockAsm64K - -memmove_long_emit_remainder_encodeSnappyBetterBlockAsm64K: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm64Klarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm64Klarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm64Klarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm64Klarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm64Klarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeSnappyBetterBlockAsm64K: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeSnappyBetterBlockAsm12B(dst []byte, src []byte, tmp *[81920]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeSnappyBetterBlockAsm12B(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00000280, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeSnappyBetterBlockAsm12B: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeSnappyBetterBlockAsm12B - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL $0x00000000, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeSnappyBetterBlockAsm12B: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x06, SI - LEAL 1(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeSnappyBetterBlockAsm12B - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ $0x9e3779b1, SI - MOVQ DI, R10 - MOVQ DI, R11 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x32, R10 - SHLQ $0x20, R11 - IMULQ SI, R11 - SHRQ $0x34, R11 - MOVL (AX)(R10*4), SI - MOVL 65536(AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - MOVL DX, 65536(AX)(R11*4) - MOVQ (BX)(SI*1), R10 - MOVQ (BX)(R8*1), R11 - CMPQ R10, DI - JEQ candidate_match_encodeSnappyBetterBlockAsm12B - CMPQ R11, DI - JNE no_short_found_encodeSnappyBetterBlockAsm12B - MOVL R8, SI - JMP candidate_match_encodeSnappyBetterBlockAsm12B - -no_short_found_encodeSnappyBetterBlockAsm12B: - CMPL R10, DI - JEQ candidate_match_encodeSnappyBetterBlockAsm12B - CMPL R11, DI - JEQ candidateS_match_encodeSnappyBetterBlockAsm12B - MOVL 20(SP), DX - JMP search_loop_encodeSnappyBetterBlockAsm12B - -candidateS_match_encodeSnappyBetterBlockAsm12B: - SHRQ $0x08, DI - MOVQ DI, R10 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x32, R10 - MOVL (AX)(R10*4), SI - INCL DX - MOVL DX, (AX)(R10*4) - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeSnappyBetterBlockAsm12B - DECL DX - MOVL R8, SI - -candidate_match_encodeSnappyBetterBlockAsm12B: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeSnappyBetterBlockAsm12B - -match_extend_back_loop_encodeSnappyBetterBlockAsm12B: - CMPL DX, DI - JBE match_extend_back_end_encodeSnappyBetterBlockAsm12B - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeSnappyBetterBlockAsm12B - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeSnappyBetterBlockAsm12B - JMP match_extend_back_loop_encodeSnappyBetterBlockAsm12B - -match_extend_back_end_encodeSnappyBetterBlockAsm12B: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 3(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeSnappyBetterBlockAsm12B - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeSnappyBetterBlockAsm12B: - MOVL DX, DI - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), R10 - - // matchLen - XORL R12, R12 - -matchlen_loopback_16_match_nolit_encodeSnappyBetterBlockAsm12B: - CMPL R8, $0x10 - JB matchlen_match8_match_nolit_encodeSnappyBetterBlockAsm12B - MOVQ (R9)(R12*1), R11 - MOVQ 8(R9)(R12*1), R13 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBetterBlockAsm12B - XORQ 8(R10)(R12*1), R13 - JNZ matchlen_bsf_16match_nolit_encodeSnappyBetterBlockAsm12B - LEAL -16(R8), R8 - LEAL 16(R12), R12 - JMP matchlen_loopback_16_match_nolit_encodeSnappyBetterBlockAsm12B - -matchlen_bsf_16match_nolit_encodeSnappyBetterBlockAsm12B: -#ifdef GOAMD64_v3 - TZCNTQ R13, R13 - -#else - BSFQ R13, R13 - -#endif - SARQ $0x03, R13 - LEAL 8(R12)(R13*1), R12 - JMP match_nolit_end_encodeSnappyBetterBlockAsm12B - -matchlen_match8_match_nolit_encodeSnappyBetterBlockAsm12B: - CMPL R8, $0x08 - JB matchlen_match4_match_nolit_encodeSnappyBetterBlockAsm12B - MOVQ (R9)(R12*1), R11 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBetterBlockAsm12B - LEAL -8(R8), R8 - LEAL 8(R12), R12 - JMP matchlen_match4_match_nolit_encodeSnappyBetterBlockAsm12B - -matchlen_bsf_8_match_nolit_encodeSnappyBetterBlockAsm12B: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL (R12)(R11*1), R12 - JMP match_nolit_end_encodeSnappyBetterBlockAsm12B - -matchlen_match4_match_nolit_encodeSnappyBetterBlockAsm12B: - CMPL R8, $0x04 - JB matchlen_match2_match_nolit_encodeSnappyBetterBlockAsm12B - MOVL (R9)(R12*1), R11 - CMPL (R10)(R12*1), R11 - JNE matchlen_match2_match_nolit_encodeSnappyBetterBlockAsm12B - LEAL -4(R8), R8 - LEAL 4(R12), R12 - -matchlen_match2_match_nolit_encodeSnappyBetterBlockAsm12B: - CMPL R8, $0x01 - JE matchlen_match1_match_nolit_encodeSnappyBetterBlockAsm12B - JB match_nolit_end_encodeSnappyBetterBlockAsm12B - MOVW (R9)(R12*1), R11 - CMPW (R10)(R12*1), R11 - JNE matchlen_match1_match_nolit_encodeSnappyBetterBlockAsm12B - LEAL 2(R12), R12 - SUBL $0x02, R8 - JZ match_nolit_end_encodeSnappyBetterBlockAsm12B - -matchlen_match1_match_nolit_encodeSnappyBetterBlockAsm12B: - MOVB (R9)(R12*1), R11 - CMPB (R10)(R12*1), R11 - JNE match_nolit_end_encodeSnappyBetterBlockAsm12B - LEAL 1(R12), R12 - -match_nolit_end_encodeSnappyBetterBlockAsm12B: - MOVL DX, R8 - SUBL SI, R8 - - // Check if repeat - MOVL R8, 16(SP) - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_match_emit_encodeSnappyBetterBlockAsm12B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_match_emit_encodeSnappyBetterBlockAsm12B - CMPL SI, $0x00000100 - JB two_bytes_match_emit_encodeSnappyBetterBlockAsm12B - JB three_bytes_match_emit_encodeSnappyBetterBlockAsm12B - -three_bytes_match_emit_encodeSnappyBetterBlockAsm12B: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeSnappyBetterBlockAsm12B - -two_bytes_match_emit_encodeSnappyBetterBlockAsm12B: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_match_emit_encodeSnappyBetterBlockAsm12B - JMP memmove_long_match_emit_encodeSnappyBetterBlockAsm12B - -one_byte_match_emit_encodeSnappyBetterBlockAsm12B: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeSnappyBetterBlockAsm12B: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm12B_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm12B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm12B_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm12B_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm12B_memmove_move_8: - MOVQ (R10), R11 - MOVQ R11, (CX) - JMP memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm12B - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm12B_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm12B - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm12B_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm12B - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm12B_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm12B: - MOVQ SI, CX - JMP emit_literal_done_match_emit_encodeSnappyBetterBlockAsm12B - -memmove_long_match_emit_encodeSnappyBetterBlockAsm12B: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R14 - SUBQ R11, R14 - DECQ R13 - JA emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm12Blarge_forward_sse_loop_32 - LEAQ -32(R10)(R14*1), R11 - LEAQ -32(CX)(R14*1), R15 - -emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm12Blarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R11 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm12Blarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm12Blarge_forward_sse_loop_32: - MOVOU -32(R10)(R14*1), X4 - MOVOU -16(R10)(R14*1), X5 - MOVOA X4, -32(CX)(R14*1) - MOVOA X5, -16(CX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm12Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_match_emit_encodeSnappyBetterBlockAsm12B: - ADDL R12, DX - ADDL $0x04, R12 - MOVL DX, 12(SP) - - // emitCopy -two_byte_offset_match_nolit_encodeSnappyBetterBlockAsm12B: - CMPL R12, $0x40 - JBE two_byte_offset_short_match_nolit_encodeSnappyBetterBlockAsm12B - MOVB $0xee, (CX) - MOVW R8, 1(CX) - LEAL -60(R12), R12 - ADDQ $0x03, CX - JMP two_byte_offset_match_nolit_encodeSnappyBetterBlockAsm12B - -two_byte_offset_short_match_nolit_encodeSnappyBetterBlockAsm12B: - MOVL R12, SI - SHLL $0x02, SI - CMPL R12, $0x0c - JAE emit_copy_three_match_nolit_encodeSnappyBetterBlockAsm12B - CMPL R8, $0x00000800 - JAE emit_copy_three_match_nolit_encodeSnappyBetterBlockAsm12B - LEAL -15(SI), SI - MOVB R8, 1(CX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeSnappyBetterBlockAsm12B - -emit_copy_three_match_nolit_encodeSnappyBetterBlockAsm12B: - LEAL -2(SI), SI - MOVB SI, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - -match_nolit_emitcopy_end_encodeSnappyBetterBlockAsm12B: - CMPL DX, 8(SP) - JAE emit_remainder_encodeSnappyBetterBlockAsm12B - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeSnappyBetterBlockAsm12B - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeSnappyBetterBlockAsm12B: - MOVQ $0x0000cf1bbcdcbf9b, SI - MOVQ $0x9e3779b1, R8 - LEAQ 1(DI), DI - LEAQ -2(DX), R9 - MOVQ (BX)(DI*1), R10 - MOVQ 1(BX)(DI*1), R11 - MOVQ (BX)(R9*1), R12 - MOVQ 1(BX)(R9*1), R13 - SHLQ $0x10, R10 - IMULQ SI, R10 - SHRQ $0x32, R10 - SHLQ $0x20, R11 - IMULQ R8, R11 - SHRQ $0x34, R11 - SHLQ $0x10, R12 - IMULQ SI, R12 - SHRQ $0x32, R12 - SHLQ $0x20, R13 - IMULQ R8, R13 - SHRQ $0x34, R13 - LEAQ 1(DI), R8 - LEAQ 1(R9), R14 - MOVL DI, (AX)(R10*4) - MOVL R9, (AX)(R12*4) - MOVL R8, 65536(AX)(R11*4) - MOVL R14, 65536(AX)(R13*4) - LEAQ 1(R9)(DI*1), R8 - SHRQ $0x01, R8 - ADDQ $0x01, DI - SUBQ $0x01, R9 - -index_loop_encodeSnappyBetterBlockAsm12B: - CMPQ R8, R9 - JAE search_loop_encodeSnappyBetterBlockAsm12B - MOVQ (BX)(DI*1), R10 - MOVQ (BX)(R8*1), R11 - SHLQ $0x10, R10 - IMULQ SI, R10 - SHRQ $0x32, R10 - SHLQ $0x10, R11 - IMULQ SI, R11 - SHRQ $0x32, R11 - MOVL DI, (AX)(R10*4) - MOVL R8, (AX)(R11*4) - ADDQ $0x02, DI - ADDQ $0x02, R8 - JMP index_loop_encodeSnappyBetterBlockAsm12B - -emit_remainder_encodeSnappyBetterBlockAsm12B: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 3(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeSnappyBetterBlockAsm12B - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeSnappyBetterBlockAsm12B: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeSnappyBetterBlockAsm12B - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeSnappyBetterBlockAsm12B - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeSnappyBetterBlockAsm12B - JB three_bytes_emit_remainder_encodeSnappyBetterBlockAsm12B - -three_bytes_emit_remainder_encodeSnappyBetterBlockAsm12B: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeSnappyBetterBlockAsm12B - -two_bytes_emit_remainder_encodeSnappyBetterBlockAsm12B: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeSnappyBetterBlockAsm12B - JMP memmove_long_emit_remainder_encodeSnappyBetterBlockAsm12B - -one_byte_emit_remainder_encodeSnappyBetterBlockAsm12B: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeSnappyBetterBlockAsm12B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm12B_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm12B_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm12B_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm12B_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm12B_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm12B_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm12B_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm12B_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm12B_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm12B_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm12B_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm12B - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm12B_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm12B: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeSnappyBetterBlockAsm12B - -memmove_long_emit_remainder_encodeSnappyBetterBlockAsm12B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm12Blarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm12Blarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm12Blarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm12Blarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm12Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeSnappyBetterBlockAsm12B: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeSnappyBetterBlockAsm10B(dst []byte, src []byte, tmp *[20480]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeSnappyBetterBlockAsm10B(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x000000a0, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeSnappyBetterBlockAsm10B: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeSnappyBetterBlockAsm10B - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL $0x00000000, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeSnappyBetterBlockAsm10B: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x05, SI - LEAL 1(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeSnappyBetterBlockAsm10B - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ $0x9e3779b1, SI - MOVQ DI, R10 - MOVQ DI, R11 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x34, R10 - SHLQ $0x20, R11 - IMULQ SI, R11 - SHRQ $0x36, R11 - MOVL (AX)(R10*4), SI - MOVL 16384(AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - MOVL DX, 16384(AX)(R11*4) - MOVQ (BX)(SI*1), R10 - MOVQ (BX)(R8*1), R11 - CMPQ R10, DI - JEQ candidate_match_encodeSnappyBetterBlockAsm10B - CMPQ R11, DI - JNE no_short_found_encodeSnappyBetterBlockAsm10B - MOVL R8, SI - JMP candidate_match_encodeSnappyBetterBlockAsm10B - -no_short_found_encodeSnappyBetterBlockAsm10B: - CMPL R10, DI - JEQ candidate_match_encodeSnappyBetterBlockAsm10B - CMPL R11, DI - JEQ candidateS_match_encodeSnappyBetterBlockAsm10B - MOVL 20(SP), DX - JMP search_loop_encodeSnappyBetterBlockAsm10B - -candidateS_match_encodeSnappyBetterBlockAsm10B: - SHRQ $0x08, DI - MOVQ DI, R10 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x34, R10 - MOVL (AX)(R10*4), SI - INCL DX - MOVL DX, (AX)(R10*4) - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeSnappyBetterBlockAsm10B - DECL DX - MOVL R8, SI - -candidate_match_encodeSnappyBetterBlockAsm10B: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeSnappyBetterBlockAsm10B - -match_extend_back_loop_encodeSnappyBetterBlockAsm10B: - CMPL DX, DI - JBE match_extend_back_end_encodeSnappyBetterBlockAsm10B - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeSnappyBetterBlockAsm10B - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeSnappyBetterBlockAsm10B - JMP match_extend_back_loop_encodeSnappyBetterBlockAsm10B - -match_extend_back_end_encodeSnappyBetterBlockAsm10B: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 3(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeSnappyBetterBlockAsm10B - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeSnappyBetterBlockAsm10B: - MOVL DX, DI - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), R10 - - // matchLen - XORL R12, R12 - -matchlen_loopback_16_match_nolit_encodeSnappyBetterBlockAsm10B: - CMPL R8, $0x10 - JB matchlen_match8_match_nolit_encodeSnappyBetterBlockAsm10B - MOVQ (R9)(R12*1), R11 - MOVQ 8(R9)(R12*1), R13 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBetterBlockAsm10B - XORQ 8(R10)(R12*1), R13 - JNZ matchlen_bsf_16match_nolit_encodeSnappyBetterBlockAsm10B - LEAL -16(R8), R8 - LEAL 16(R12), R12 - JMP matchlen_loopback_16_match_nolit_encodeSnappyBetterBlockAsm10B - -matchlen_bsf_16match_nolit_encodeSnappyBetterBlockAsm10B: -#ifdef GOAMD64_v3 - TZCNTQ R13, R13 - -#else - BSFQ R13, R13 - -#endif - SARQ $0x03, R13 - LEAL 8(R12)(R13*1), R12 - JMP match_nolit_end_encodeSnappyBetterBlockAsm10B - -matchlen_match8_match_nolit_encodeSnappyBetterBlockAsm10B: - CMPL R8, $0x08 - JB matchlen_match4_match_nolit_encodeSnappyBetterBlockAsm10B - MOVQ (R9)(R12*1), R11 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBetterBlockAsm10B - LEAL -8(R8), R8 - LEAL 8(R12), R12 - JMP matchlen_match4_match_nolit_encodeSnappyBetterBlockAsm10B - -matchlen_bsf_8_match_nolit_encodeSnappyBetterBlockAsm10B: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL (R12)(R11*1), R12 - JMP match_nolit_end_encodeSnappyBetterBlockAsm10B - -matchlen_match4_match_nolit_encodeSnappyBetterBlockAsm10B: - CMPL R8, $0x04 - JB matchlen_match2_match_nolit_encodeSnappyBetterBlockAsm10B - MOVL (R9)(R12*1), R11 - CMPL (R10)(R12*1), R11 - JNE matchlen_match2_match_nolit_encodeSnappyBetterBlockAsm10B - LEAL -4(R8), R8 - LEAL 4(R12), R12 - -matchlen_match2_match_nolit_encodeSnappyBetterBlockAsm10B: - CMPL R8, $0x01 - JE matchlen_match1_match_nolit_encodeSnappyBetterBlockAsm10B - JB match_nolit_end_encodeSnappyBetterBlockAsm10B - MOVW (R9)(R12*1), R11 - CMPW (R10)(R12*1), R11 - JNE matchlen_match1_match_nolit_encodeSnappyBetterBlockAsm10B - LEAL 2(R12), R12 - SUBL $0x02, R8 - JZ match_nolit_end_encodeSnappyBetterBlockAsm10B - -matchlen_match1_match_nolit_encodeSnappyBetterBlockAsm10B: - MOVB (R9)(R12*1), R11 - CMPB (R10)(R12*1), R11 - JNE match_nolit_end_encodeSnappyBetterBlockAsm10B - LEAL 1(R12), R12 - -match_nolit_end_encodeSnappyBetterBlockAsm10B: - MOVL DX, R8 - SUBL SI, R8 - - // Check if repeat - MOVL R8, 16(SP) - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_match_emit_encodeSnappyBetterBlockAsm10B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_match_emit_encodeSnappyBetterBlockAsm10B - CMPL SI, $0x00000100 - JB two_bytes_match_emit_encodeSnappyBetterBlockAsm10B - JB three_bytes_match_emit_encodeSnappyBetterBlockAsm10B - -three_bytes_match_emit_encodeSnappyBetterBlockAsm10B: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeSnappyBetterBlockAsm10B - -two_bytes_match_emit_encodeSnappyBetterBlockAsm10B: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_match_emit_encodeSnappyBetterBlockAsm10B - JMP memmove_long_match_emit_encodeSnappyBetterBlockAsm10B - -one_byte_match_emit_encodeSnappyBetterBlockAsm10B: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeSnappyBetterBlockAsm10B: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm10B_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm10B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm10B_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm10B_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm10B_memmove_move_8: - MOVQ (R10), R11 - MOVQ R11, (CX) - JMP memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm10B - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm10B_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm10B - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm10B_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm10B - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm10B_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm10B: - MOVQ SI, CX - JMP emit_literal_done_match_emit_encodeSnappyBetterBlockAsm10B - -memmove_long_match_emit_encodeSnappyBetterBlockAsm10B: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R14 - SUBQ R11, R14 - DECQ R13 - JA emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm10Blarge_forward_sse_loop_32 - LEAQ -32(R10)(R14*1), R11 - LEAQ -32(CX)(R14*1), R15 - -emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm10Blarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R11 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm10Blarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm10Blarge_forward_sse_loop_32: - MOVOU -32(R10)(R14*1), X4 - MOVOU -16(R10)(R14*1), X5 - MOVOA X4, -32(CX)(R14*1) - MOVOA X5, -16(CX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm10Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_match_emit_encodeSnappyBetterBlockAsm10B: - ADDL R12, DX - ADDL $0x04, R12 - MOVL DX, 12(SP) - - // emitCopy -two_byte_offset_match_nolit_encodeSnappyBetterBlockAsm10B: - CMPL R12, $0x40 - JBE two_byte_offset_short_match_nolit_encodeSnappyBetterBlockAsm10B - MOVB $0xee, (CX) - MOVW R8, 1(CX) - LEAL -60(R12), R12 - ADDQ $0x03, CX - JMP two_byte_offset_match_nolit_encodeSnappyBetterBlockAsm10B - -two_byte_offset_short_match_nolit_encodeSnappyBetterBlockAsm10B: - MOVL R12, SI - SHLL $0x02, SI - CMPL R12, $0x0c - JAE emit_copy_three_match_nolit_encodeSnappyBetterBlockAsm10B - CMPL R8, $0x00000800 - JAE emit_copy_three_match_nolit_encodeSnappyBetterBlockAsm10B - LEAL -15(SI), SI - MOVB R8, 1(CX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeSnappyBetterBlockAsm10B - -emit_copy_three_match_nolit_encodeSnappyBetterBlockAsm10B: - LEAL -2(SI), SI - MOVB SI, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - -match_nolit_emitcopy_end_encodeSnappyBetterBlockAsm10B: - CMPL DX, 8(SP) - JAE emit_remainder_encodeSnappyBetterBlockAsm10B - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeSnappyBetterBlockAsm10B - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeSnappyBetterBlockAsm10B: - MOVQ $0x0000cf1bbcdcbf9b, SI - MOVQ $0x9e3779b1, R8 - LEAQ 1(DI), DI - LEAQ -2(DX), R9 - MOVQ (BX)(DI*1), R10 - MOVQ 1(BX)(DI*1), R11 - MOVQ (BX)(R9*1), R12 - MOVQ 1(BX)(R9*1), R13 - SHLQ $0x10, R10 - IMULQ SI, R10 - SHRQ $0x34, R10 - SHLQ $0x20, R11 - IMULQ R8, R11 - SHRQ $0x36, R11 - SHLQ $0x10, R12 - IMULQ SI, R12 - SHRQ $0x34, R12 - SHLQ $0x20, R13 - IMULQ R8, R13 - SHRQ $0x36, R13 - LEAQ 1(DI), R8 - LEAQ 1(R9), R14 - MOVL DI, (AX)(R10*4) - MOVL R9, (AX)(R12*4) - MOVL R8, 16384(AX)(R11*4) - MOVL R14, 16384(AX)(R13*4) - LEAQ 1(R9)(DI*1), R8 - SHRQ $0x01, R8 - ADDQ $0x01, DI - SUBQ $0x01, R9 - -index_loop_encodeSnappyBetterBlockAsm10B: - CMPQ R8, R9 - JAE search_loop_encodeSnappyBetterBlockAsm10B - MOVQ (BX)(DI*1), R10 - MOVQ (BX)(R8*1), R11 - SHLQ $0x10, R10 - IMULQ SI, R10 - SHRQ $0x34, R10 - SHLQ $0x10, R11 - IMULQ SI, R11 - SHRQ $0x34, R11 - MOVL DI, (AX)(R10*4) - MOVL R8, (AX)(R11*4) - ADDQ $0x02, DI - ADDQ $0x02, R8 - JMP index_loop_encodeSnappyBetterBlockAsm10B - -emit_remainder_encodeSnappyBetterBlockAsm10B: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 3(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeSnappyBetterBlockAsm10B - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeSnappyBetterBlockAsm10B: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeSnappyBetterBlockAsm10B - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeSnappyBetterBlockAsm10B - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeSnappyBetterBlockAsm10B - JB three_bytes_emit_remainder_encodeSnappyBetterBlockAsm10B - -three_bytes_emit_remainder_encodeSnappyBetterBlockAsm10B: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeSnappyBetterBlockAsm10B - -two_bytes_emit_remainder_encodeSnappyBetterBlockAsm10B: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeSnappyBetterBlockAsm10B - JMP memmove_long_emit_remainder_encodeSnappyBetterBlockAsm10B - -one_byte_emit_remainder_encodeSnappyBetterBlockAsm10B: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeSnappyBetterBlockAsm10B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm10B_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm10B_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm10B_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm10B_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm10B_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm10B_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm10B_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm10B_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm10B_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm10B_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm10B_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm10B - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm10B_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm10B: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeSnappyBetterBlockAsm10B - -memmove_long_emit_remainder_encodeSnappyBetterBlockAsm10B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm10Blarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm10Blarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm10Blarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm10Blarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm10Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeSnappyBetterBlockAsm10B: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func encodeSnappyBetterBlockAsm8B(dst []byte, src []byte, tmp *[5120]byte) int -// Requires: BMI, SSE2 -TEXT ·encodeSnappyBetterBlockAsm8B(SB), $24-64 - MOVQ tmp+48(FP), AX - MOVQ dst_base+0(FP), CX - MOVQ $0x00000028, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_encodeSnappyBetterBlockAsm8B: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_encodeSnappyBetterBlockAsm8B - MOVL $0x00000000, 12(SP) - MOVQ src_len+32(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL $0x00000000, 16(SP) - MOVQ src_base+24(FP), BX - -search_loop_encodeSnappyBetterBlockAsm8B: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x04, SI - LEAL 1(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_encodeSnappyBetterBlockAsm8B - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ $0x9e3779b1, SI - MOVQ DI, R10 - MOVQ DI, R11 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x36, R10 - SHLQ $0x20, R11 - IMULQ SI, R11 - SHRQ $0x38, R11 - MOVL (AX)(R10*4), SI - MOVL 4096(AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - MOVL DX, 4096(AX)(R11*4) - MOVQ (BX)(SI*1), R10 - MOVQ (BX)(R8*1), R11 - CMPQ R10, DI - JEQ candidate_match_encodeSnappyBetterBlockAsm8B - CMPQ R11, DI - JNE no_short_found_encodeSnappyBetterBlockAsm8B - MOVL R8, SI - JMP candidate_match_encodeSnappyBetterBlockAsm8B - -no_short_found_encodeSnappyBetterBlockAsm8B: - CMPL R10, DI - JEQ candidate_match_encodeSnappyBetterBlockAsm8B - CMPL R11, DI - JEQ candidateS_match_encodeSnappyBetterBlockAsm8B - MOVL 20(SP), DX - JMP search_loop_encodeSnappyBetterBlockAsm8B - -candidateS_match_encodeSnappyBetterBlockAsm8B: - SHRQ $0x08, DI - MOVQ DI, R10 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x36, R10 - MOVL (AX)(R10*4), SI - INCL DX - MOVL DX, (AX)(R10*4) - CMPL (BX)(SI*1), DI - JEQ candidate_match_encodeSnappyBetterBlockAsm8B - DECL DX - MOVL R8, SI - -candidate_match_encodeSnappyBetterBlockAsm8B: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_encodeSnappyBetterBlockAsm8B - -match_extend_back_loop_encodeSnappyBetterBlockAsm8B: - CMPL DX, DI - JBE match_extend_back_end_encodeSnappyBetterBlockAsm8B - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_encodeSnappyBetterBlockAsm8B - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_encodeSnappyBetterBlockAsm8B - JMP match_extend_back_loop_encodeSnappyBetterBlockAsm8B - -match_extend_back_end_encodeSnappyBetterBlockAsm8B: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 3(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_encodeSnappyBetterBlockAsm8B - MOVQ $0x00000000, ret+56(FP) - RET - -match_dst_size_check_encodeSnappyBetterBlockAsm8B: - MOVL DX, DI - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+32(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), R10 - - // matchLen - XORL R12, R12 - -matchlen_loopback_16_match_nolit_encodeSnappyBetterBlockAsm8B: - CMPL R8, $0x10 - JB matchlen_match8_match_nolit_encodeSnappyBetterBlockAsm8B - MOVQ (R9)(R12*1), R11 - MOVQ 8(R9)(R12*1), R13 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBetterBlockAsm8B - XORQ 8(R10)(R12*1), R13 - JNZ matchlen_bsf_16match_nolit_encodeSnappyBetterBlockAsm8B - LEAL -16(R8), R8 - LEAL 16(R12), R12 - JMP matchlen_loopback_16_match_nolit_encodeSnappyBetterBlockAsm8B - -matchlen_bsf_16match_nolit_encodeSnappyBetterBlockAsm8B: -#ifdef GOAMD64_v3 - TZCNTQ R13, R13 - -#else - BSFQ R13, R13 - -#endif - SARQ $0x03, R13 - LEAL 8(R12)(R13*1), R12 - JMP match_nolit_end_encodeSnappyBetterBlockAsm8B - -matchlen_match8_match_nolit_encodeSnappyBetterBlockAsm8B: - CMPL R8, $0x08 - JB matchlen_match4_match_nolit_encodeSnappyBetterBlockAsm8B - MOVQ (R9)(R12*1), R11 - XORQ (R10)(R12*1), R11 - JNZ matchlen_bsf_8_match_nolit_encodeSnappyBetterBlockAsm8B - LEAL -8(R8), R8 - LEAL 8(R12), R12 - JMP matchlen_match4_match_nolit_encodeSnappyBetterBlockAsm8B - -matchlen_bsf_8_match_nolit_encodeSnappyBetterBlockAsm8B: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL (R12)(R11*1), R12 - JMP match_nolit_end_encodeSnappyBetterBlockAsm8B - -matchlen_match4_match_nolit_encodeSnappyBetterBlockAsm8B: - CMPL R8, $0x04 - JB matchlen_match2_match_nolit_encodeSnappyBetterBlockAsm8B - MOVL (R9)(R12*1), R11 - CMPL (R10)(R12*1), R11 - JNE matchlen_match2_match_nolit_encodeSnappyBetterBlockAsm8B - LEAL -4(R8), R8 - LEAL 4(R12), R12 - -matchlen_match2_match_nolit_encodeSnappyBetterBlockAsm8B: - CMPL R8, $0x01 - JE matchlen_match1_match_nolit_encodeSnappyBetterBlockAsm8B - JB match_nolit_end_encodeSnappyBetterBlockAsm8B - MOVW (R9)(R12*1), R11 - CMPW (R10)(R12*1), R11 - JNE matchlen_match1_match_nolit_encodeSnappyBetterBlockAsm8B - LEAL 2(R12), R12 - SUBL $0x02, R8 - JZ match_nolit_end_encodeSnappyBetterBlockAsm8B - -matchlen_match1_match_nolit_encodeSnappyBetterBlockAsm8B: - MOVB (R9)(R12*1), R11 - CMPB (R10)(R12*1), R11 - JNE match_nolit_end_encodeSnappyBetterBlockAsm8B - LEAL 1(R12), R12 - -match_nolit_end_encodeSnappyBetterBlockAsm8B: - MOVL DX, R8 - SUBL SI, R8 - - // Check if repeat - MOVL R8, 16(SP) - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_match_emit_encodeSnappyBetterBlockAsm8B - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R10 - SUBL SI, R9 - LEAL -1(R9), SI - CMPL SI, $0x3c - JB one_byte_match_emit_encodeSnappyBetterBlockAsm8B - CMPL SI, $0x00000100 - JB two_bytes_match_emit_encodeSnappyBetterBlockAsm8B - JB three_bytes_match_emit_encodeSnappyBetterBlockAsm8B - -three_bytes_match_emit_encodeSnappyBetterBlockAsm8B: - MOVB $0xf4, (CX) - MOVW SI, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_match_emit_encodeSnappyBetterBlockAsm8B - -two_bytes_match_emit_encodeSnappyBetterBlockAsm8B: - MOVB $0xf0, (CX) - MOVB SI, 1(CX) - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_match_emit_encodeSnappyBetterBlockAsm8B - JMP memmove_long_match_emit_encodeSnappyBetterBlockAsm8B - -one_byte_match_emit_encodeSnappyBetterBlockAsm8B: - SHLB $0x02, SI - MOVB SI, (CX) - ADDQ $0x01, CX - -memmove_match_emit_encodeSnappyBetterBlockAsm8B: - LEAQ (CX)(R9*1), SI - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm8B_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm8B_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm8B_memmove_move_17through32 - JMP emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm8B_memmove_move_33through64 - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm8B_memmove_move_8: - MOVQ (R10), R11 - MOVQ R11, (CX) - JMP memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm8B - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm8B_memmove_move_8through16: - MOVQ (R10), R11 - MOVQ -8(R10)(R9*1), R10 - MOVQ R11, (CX) - MOVQ R10, -8(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm8B - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm8B_memmove_move_17through32: - MOVOU (R10), X0 - MOVOU -16(R10)(R9*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(R9*1) - JMP memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm8B - -emit_lit_memmove_match_emit_encodeSnappyBetterBlockAsm8B_memmove_move_33through64: - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - -memmove_end_copy_match_emit_encodeSnappyBetterBlockAsm8B: - MOVQ SI, CX - JMP emit_literal_done_match_emit_encodeSnappyBetterBlockAsm8B - -memmove_long_match_emit_encodeSnappyBetterBlockAsm8B: - LEAQ (CX)(R9*1), SI - - // genMemMoveLong - MOVOU (R10), X0 - MOVOU 16(R10), X1 - MOVOU -32(R10)(R9*1), X2 - MOVOU -16(R10)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ CX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R14 - SUBQ R11, R14 - DECQ R13 - JA emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm8Blarge_forward_sse_loop_32 - LEAQ -32(R10)(R14*1), R11 - LEAQ -32(CX)(R14*1), R15 - -emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm8Blarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R11 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm8Blarge_big_loop_back - -emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm8Blarge_forward_sse_loop_32: - MOVOU -32(R10)(R14*1), X4 - MOVOU -16(R10)(R14*1), X5 - MOVOA X4, -32(CX)(R14*1) - MOVOA X5, -16(CX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_match_emit_encodeSnappyBetterBlockAsm8Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(R9*1) - MOVOU X3, -16(CX)(R9*1) - MOVQ SI, CX - -emit_literal_done_match_emit_encodeSnappyBetterBlockAsm8B: - ADDL R12, DX - ADDL $0x04, R12 - MOVL DX, 12(SP) - - // emitCopy -two_byte_offset_match_nolit_encodeSnappyBetterBlockAsm8B: - CMPL R12, $0x40 - JBE two_byte_offset_short_match_nolit_encodeSnappyBetterBlockAsm8B - MOVB $0xee, (CX) - MOVW R8, 1(CX) - LEAL -60(R12), R12 - ADDQ $0x03, CX - JMP two_byte_offset_match_nolit_encodeSnappyBetterBlockAsm8B - -two_byte_offset_short_match_nolit_encodeSnappyBetterBlockAsm8B: - MOVL R12, SI - SHLL $0x02, SI - CMPL R12, $0x0c - JAE emit_copy_three_match_nolit_encodeSnappyBetterBlockAsm8B - LEAL -15(SI), SI - MOVB R8, 1(CX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, SI - MOVB SI, (CX) - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_encodeSnappyBetterBlockAsm8B - -emit_copy_three_match_nolit_encodeSnappyBetterBlockAsm8B: - LEAL -2(SI), SI - MOVB SI, (CX) - MOVW R8, 1(CX) - ADDQ $0x03, CX - -match_nolit_emitcopy_end_encodeSnappyBetterBlockAsm8B: - CMPL DX, 8(SP) - JAE emit_remainder_encodeSnappyBetterBlockAsm8B - CMPQ CX, (SP) - JB match_nolit_dst_ok_encodeSnappyBetterBlockAsm8B - MOVQ $0x00000000, ret+56(FP) - RET - -match_nolit_dst_ok_encodeSnappyBetterBlockAsm8B: - MOVQ $0x0000cf1bbcdcbf9b, SI - MOVQ $0x9e3779b1, R8 - LEAQ 1(DI), DI - LEAQ -2(DX), R9 - MOVQ (BX)(DI*1), R10 - MOVQ 1(BX)(DI*1), R11 - MOVQ (BX)(R9*1), R12 - MOVQ 1(BX)(R9*1), R13 - SHLQ $0x10, R10 - IMULQ SI, R10 - SHRQ $0x36, R10 - SHLQ $0x20, R11 - IMULQ R8, R11 - SHRQ $0x38, R11 - SHLQ $0x10, R12 - IMULQ SI, R12 - SHRQ $0x36, R12 - SHLQ $0x20, R13 - IMULQ R8, R13 - SHRQ $0x38, R13 - LEAQ 1(DI), R8 - LEAQ 1(R9), R14 - MOVL DI, (AX)(R10*4) - MOVL R9, (AX)(R12*4) - MOVL R8, 4096(AX)(R11*4) - MOVL R14, 4096(AX)(R13*4) - LEAQ 1(R9)(DI*1), R8 - SHRQ $0x01, R8 - ADDQ $0x01, DI - SUBQ $0x01, R9 - -index_loop_encodeSnappyBetterBlockAsm8B: - CMPQ R8, R9 - JAE search_loop_encodeSnappyBetterBlockAsm8B - MOVQ (BX)(DI*1), R10 - MOVQ (BX)(R8*1), R11 - SHLQ $0x10, R10 - IMULQ SI, R10 - SHRQ $0x36, R10 - SHLQ $0x10, R11 - IMULQ SI, R11 - SHRQ $0x36, R11 - MOVL DI, (AX)(R10*4) - MOVL R8, (AX)(R11*4) - ADDQ $0x02, DI - ADDQ $0x02, R8 - JMP index_loop_encodeSnappyBetterBlockAsm8B - -emit_remainder_encodeSnappyBetterBlockAsm8B: - MOVQ src_len+32(FP), AX - SUBL 12(SP), AX - LEAQ 3(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_encodeSnappyBetterBlockAsm8B - MOVQ $0x00000000, ret+56(FP) - RET - -emit_remainder_ok_encodeSnappyBetterBlockAsm8B: - MOVQ src_len+32(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_encodeSnappyBetterBlockAsm8B - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), DX - CMPL DX, $0x3c - JB one_byte_emit_remainder_encodeSnappyBetterBlockAsm8B - CMPL DX, $0x00000100 - JB two_bytes_emit_remainder_encodeSnappyBetterBlockAsm8B - JB three_bytes_emit_remainder_encodeSnappyBetterBlockAsm8B - -three_bytes_emit_remainder_encodeSnappyBetterBlockAsm8B: - MOVB $0xf4, (CX) - MOVW DX, 1(CX) - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_encodeSnappyBetterBlockAsm8B - -two_bytes_emit_remainder_encodeSnappyBetterBlockAsm8B: - MOVB $0xf0, (CX) - MOVB DL, 1(CX) - ADDQ $0x02, CX - CMPL DX, $0x40 - JB memmove_emit_remainder_encodeSnappyBetterBlockAsm8B - JMP memmove_long_emit_remainder_encodeSnappyBetterBlockAsm8B - -one_byte_emit_remainder_encodeSnappyBetterBlockAsm8B: - SHLB $0x02, DL - MOVB DL, (CX) - ADDQ $0x01, CX - -memmove_emit_remainder_encodeSnappyBetterBlockAsm8B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveShort - CMPQ BX, $0x03 - JB emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm8B_memmove_move_1or2 - JE emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm8B_memmove_move_3 - CMPQ BX, $0x08 - JB emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm8B_memmove_move_4through7 - CMPQ BX, $0x10 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm8B_memmove_move_8through16 - CMPQ BX, $0x20 - JBE emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm8B_memmove_move_17through32 - JMP emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm8B_memmove_move_33through64 - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm8B_memmove_move_1or2: - MOVB (AX), SI - MOVB -1(AX)(BX*1), AL - MOVB SI, (CX) - MOVB AL, -1(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm8B_memmove_move_3: - MOVW (AX), SI - MOVB 2(AX), AL - MOVW SI, (CX) - MOVB AL, 2(CX) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm8B_memmove_move_4through7: - MOVL (AX), SI - MOVL -4(AX)(BX*1), AX - MOVL SI, (CX) - MOVL AX, -4(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm8B_memmove_move_8through16: - MOVQ (AX), SI - MOVQ -8(AX)(BX*1), AX - MOVQ SI, (CX) - MOVQ AX, -8(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm8B_memmove_move_17through32: - MOVOU (AX), X0 - MOVOU -16(AX)(BX*1), X1 - MOVOU X0, (CX) - MOVOU X1, -16(CX)(BX*1) - JMP memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm8B - -emit_lit_memmove_emit_remainder_encodeSnappyBetterBlockAsm8B_memmove_move_33through64: - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - -memmove_end_copy_emit_remainder_encodeSnappyBetterBlockAsm8B: - MOVQ DX, CX - JMP emit_literal_done_emit_remainder_encodeSnappyBetterBlockAsm8B - -memmove_long_emit_remainder_encodeSnappyBetterBlockAsm8B: - LEAQ (CX)(SI*1), DX - MOVL SI, BX - - // genMemMoveLong - MOVOU (AX), X0 - MOVOU 16(AX), X1 - MOVOU -32(AX)(BX*1), X2 - MOVOU -16(AX)(BX*1), X3 - MOVQ BX, DI - SHRQ $0x05, DI - MOVQ CX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm8Blarge_forward_sse_loop_32 - LEAQ -32(AX)(R8*1), SI - LEAQ -32(CX)(R8*1), R9 - -emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm8Blarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm8Blarge_big_loop_back - -emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm8Blarge_forward_sse_loop_32: - MOVOU -32(AX)(R8*1), X4 - MOVOU -16(AX)(R8*1), X5 - MOVOA X4, -32(CX)(R8*1) - MOVOA X5, -16(CX)(R8*1) - ADDQ $0x20, R8 - CMPQ BX, R8 - JAE emit_lit_memmove_long_emit_remainder_encodeSnappyBetterBlockAsm8Blarge_forward_sse_loop_32 - MOVOU X0, (CX) - MOVOU X1, 16(CX) - MOVOU X2, -32(CX)(BX*1) - MOVOU X3, -16(CX)(BX*1) - MOVQ DX, CX - -emit_literal_done_emit_remainder_encodeSnappyBetterBlockAsm8B: - MOVQ dst_base+0(FP), AX - SUBQ AX, CX - MOVQ CX, ret+56(FP) - RET - -// func calcBlockSize(src []byte, tmp *[32768]byte) int -// Requires: BMI, SSE2 -TEXT ·calcBlockSize(SB), $24-40 - MOVQ tmp+24(FP), AX - XORQ CX, CX - MOVQ $0x00000100, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_calcBlockSize: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_calcBlockSize - MOVL $0x00000000, 12(SP) - MOVQ src_len+8(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL DX, 16(SP) - MOVQ src_base+0(FP), BX - -search_loop_calcBlockSize: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x05, SI - LEAL 4(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_calcBlockSize - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ DI, R10 - MOVQ DI, R11 - SHRQ $0x08, R11 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x33, R10 - SHLQ $0x10, R11 - IMULQ R9, R11 - SHRQ $0x33, R11 - MOVL (AX)(R10*4), SI - MOVL (AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - LEAL 1(DX), R10 - MOVL R10, (AX)(R11*4) - MOVQ DI, R10 - SHRQ $0x10, R10 - SHLQ $0x10, R10 - IMULQ R9, R10 - SHRQ $0x33, R10 - MOVL DX, R9 - SUBL 16(SP), R9 - MOVL 1(BX)(R9*1), R11 - MOVQ DI, R9 - SHRQ $0x08, R9 - CMPL R9, R11 - JNE no_repeat_found_calcBlockSize - LEAL 1(DX), DI - MOVL 12(SP), SI - MOVL DI, R8 - SUBL 16(SP), R8 - JZ repeat_extend_back_end_calcBlockSize - -repeat_extend_back_loop_calcBlockSize: - CMPL DI, SI - JBE repeat_extend_back_end_calcBlockSize - MOVB -1(BX)(R8*1), R9 - MOVB -1(BX)(DI*1), R10 - CMPB R9, R10 - JNE repeat_extend_back_end_calcBlockSize - LEAL -1(DI), DI - DECL R8 - JNZ repeat_extend_back_loop_calcBlockSize - -repeat_extend_back_end_calcBlockSize: - MOVL DI, SI - SUBL 12(SP), SI - LEAQ 5(CX)(SI*1), SI - CMPQ SI, (SP) - JB repeat_dst_size_check_calcBlockSize - MOVQ $0x00000000, ret+32(FP) - RET - -repeat_dst_size_check_calcBlockSize: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_repeat_emit_calcBlockSize - MOVL DI, R8 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R9 - SUBL SI, R8 - LEAL -1(R8), SI - CMPL SI, $0x3c - JB one_byte_repeat_emit_calcBlockSize - CMPL SI, $0x00000100 - JB two_bytes_repeat_emit_calcBlockSize - CMPL SI, $0x00010000 - JB three_bytes_repeat_emit_calcBlockSize - CMPL SI, $0x01000000 - JB four_bytes_repeat_emit_calcBlockSize - ADDQ $0x05, CX - JMP memmove_long_repeat_emit_calcBlockSize - -four_bytes_repeat_emit_calcBlockSize: - ADDQ $0x04, CX - JMP memmove_long_repeat_emit_calcBlockSize - -three_bytes_repeat_emit_calcBlockSize: - ADDQ $0x03, CX - JMP memmove_long_repeat_emit_calcBlockSize - -two_bytes_repeat_emit_calcBlockSize: - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_repeat_emit_calcBlockSize - JMP memmove_long_repeat_emit_calcBlockSize - -one_byte_repeat_emit_calcBlockSize: - ADDQ $0x01, CX - -memmove_repeat_emit_calcBlockSize: - LEAQ (CX)(R8*1), CX - JMP emit_literal_done_repeat_emit_calcBlockSize - -memmove_long_repeat_emit_calcBlockSize: - LEAQ (CX)(R8*1), CX - -emit_literal_done_repeat_emit_calcBlockSize: - ADDL $0x05, DX - MOVL DX, SI - SUBL 16(SP), SI - MOVQ src_len+8(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R11, R11 - -matchlen_loopback_16_repeat_extend_calcBlockSize: - CMPL R8, $0x10 - JB matchlen_match8_repeat_extend_calcBlockSize - MOVQ (R9)(R11*1), R10 - MOVQ 8(R9)(R11*1), R12 - XORQ (SI)(R11*1), R10 - JNZ matchlen_bsf_8_repeat_extend_calcBlockSize - XORQ 8(SI)(R11*1), R12 - JNZ matchlen_bsf_16repeat_extend_calcBlockSize - LEAL -16(R8), R8 - LEAL 16(R11), R11 - JMP matchlen_loopback_16_repeat_extend_calcBlockSize - -matchlen_bsf_16repeat_extend_calcBlockSize: -#ifdef GOAMD64_v3 - TZCNTQ R12, R12 - -#else - BSFQ R12, R12 - -#endif - SARQ $0x03, R12 - LEAL 8(R11)(R12*1), R11 - JMP repeat_extend_forward_end_calcBlockSize - -matchlen_match8_repeat_extend_calcBlockSize: - CMPL R8, $0x08 - JB matchlen_match4_repeat_extend_calcBlockSize - MOVQ (R9)(R11*1), R10 - XORQ (SI)(R11*1), R10 - JNZ matchlen_bsf_8_repeat_extend_calcBlockSize - LEAL -8(R8), R8 - LEAL 8(R11), R11 - JMP matchlen_match4_repeat_extend_calcBlockSize - -matchlen_bsf_8_repeat_extend_calcBlockSize: -#ifdef GOAMD64_v3 - TZCNTQ R10, R10 - -#else - BSFQ R10, R10 - -#endif - SARQ $0x03, R10 - LEAL (R11)(R10*1), R11 - JMP repeat_extend_forward_end_calcBlockSize - -matchlen_match4_repeat_extend_calcBlockSize: - CMPL R8, $0x04 - JB matchlen_match2_repeat_extend_calcBlockSize - MOVL (R9)(R11*1), R10 - CMPL (SI)(R11*1), R10 - JNE matchlen_match2_repeat_extend_calcBlockSize - LEAL -4(R8), R8 - LEAL 4(R11), R11 - -matchlen_match2_repeat_extend_calcBlockSize: - CMPL R8, $0x01 - JE matchlen_match1_repeat_extend_calcBlockSize - JB repeat_extend_forward_end_calcBlockSize - MOVW (R9)(R11*1), R10 - CMPW (SI)(R11*1), R10 - JNE matchlen_match1_repeat_extend_calcBlockSize - LEAL 2(R11), R11 - SUBL $0x02, R8 - JZ repeat_extend_forward_end_calcBlockSize - -matchlen_match1_repeat_extend_calcBlockSize: - MOVB (R9)(R11*1), R10 - CMPB (SI)(R11*1), R10 - JNE repeat_extend_forward_end_calcBlockSize - LEAL 1(R11), R11 - -repeat_extend_forward_end_calcBlockSize: - ADDL R11, DX - MOVL DX, SI - SUBL DI, SI - MOVL 16(SP), DI - - // emitCopy - CMPL DI, $0x00010000 - JB two_byte_offset_repeat_as_copy_calcBlockSize - -four_bytes_loop_back_repeat_as_copy_calcBlockSize: - CMPL SI, $0x40 - JBE four_bytes_remain_repeat_as_copy_calcBlockSize - LEAL -64(SI), SI - ADDQ $0x05, CX - CMPL SI, $0x04 - JB four_bytes_remain_repeat_as_copy_calcBlockSize - JMP four_bytes_loop_back_repeat_as_copy_calcBlockSize - -four_bytes_remain_repeat_as_copy_calcBlockSize: - TESTL SI, SI - JZ repeat_end_emit_calcBlockSize - XORL SI, SI - ADDQ $0x05, CX - JMP repeat_end_emit_calcBlockSize - -two_byte_offset_repeat_as_copy_calcBlockSize: - CMPL SI, $0x40 - JBE two_byte_offset_short_repeat_as_copy_calcBlockSize - LEAL -60(SI), SI - ADDQ $0x03, CX - JMP two_byte_offset_repeat_as_copy_calcBlockSize - -two_byte_offset_short_repeat_as_copy_calcBlockSize: - MOVL SI, R8 - SHLL $0x02, R8 - CMPL SI, $0x0c - JAE emit_copy_three_repeat_as_copy_calcBlockSize - CMPL DI, $0x00000800 - JAE emit_copy_three_repeat_as_copy_calcBlockSize - ADDQ $0x02, CX - JMP repeat_end_emit_calcBlockSize - -emit_copy_three_repeat_as_copy_calcBlockSize: - ADDQ $0x03, CX - -repeat_end_emit_calcBlockSize: - MOVL DX, 12(SP) - JMP search_loop_calcBlockSize - -no_repeat_found_calcBlockSize: - CMPL (BX)(SI*1), DI - JEQ candidate_match_calcBlockSize - SHRQ $0x08, DI - MOVL (AX)(R10*4), SI - LEAL 2(DX), R9 - CMPL (BX)(R8*1), DI - JEQ candidate2_match_calcBlockSize - MOVL R9, (AX)(R10*4) - SHRQ $0x08, DI - CMPL (BX)(SI*1), DI - JEQ candidate3_match_calcBlockSize - MOVL 20(SP), DX - JMP search_loop_calcBlockSize - -candidate3_match_calcBlockSize: - ADDL $0x02, DX - JMP candidate_match_calcBlockSize - -candidate2_match_calcBlockSize: - MOVL R9, (AX)(R10*4) - INCL DX - MOVL R8, SI - -candidate_match_calcBlockSize: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_calcBlockSize - -match_extend_back_loop_calcBlockSize: - CMPL DX, DI - JBE match_extend_back_end_calcBlockSize - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_calcBlockSize - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_calcBlockSize - JMP match_extend_back_loop_calcBlockSize - -match_extend_back_end_calcBlockSize: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 5(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_calcBlockSize - MOVQ $0x00000000, ret+32(FP) - RET - -match_dst_size_check_calcBlockSize: - MOVL DX, DI - MOVL 12(SP), R8 - CMPL R8, DI - JEQ emit_literal_done_match_emit_calcBlockSize - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(R8*1), DI - SUBL R8, R9 - LEAL -1(R9), DI - CMPL DI, $0x3c - JB one_byte_match_emit_calcBlockSize - CMPL DI, $0x00000100 - JB two_bytes_match_emit_calcBlockSize - CMPL DI, $0x00010000 - JB three_bytes_match_emit_calcBlockSize - CMPL DI, $0x01000000 - JB four_bytes_match_emit_calcBlockSize - ADDQ $0x05, CX - JMP memmove_long_match_emit_calcBlockSize - -four_bytes_match_emit_calcBlockSize: - ADDQ $0x04, CX - JMP memmove_long_match_emit_calcBlockSize - -three_bytes_match_emit_calcBlockSize: - ADDQ $0x03, CX - JMP memmove_long_match_emit_calcBlockSize - -two_bytes_match_emit_calcBlockSize: - ADDQ $0x02, CX - CMPL DI, $0x40 - JB memmove_match_emit_calcBlockSize - JMP memmove_long_match_emit_calcBlockSize - -one_byte_match_emit_calcBlockSize: - ADDQ $0x01, CX - -memmove_match_emit_calcBlockSize: - LEAQ (CX)(R9*1), CX - JMP emit_literal_done_match_emit_calcBlockSize - -memmove_long_match_emit_calcBlockSize: - LEAQ (CX)(R9*1), CX - -emit_literal_done_match_emit_calcBlockSize: -match_nolit_loop_calcBlockSize: - MOVL DX, DI - SUBL SI, DI - MOVL DI, 16(SP) - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+8(FP), DI - SUBL DX, DI - LEAQ (BX)(DX*1), R8 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R10, R10 - -matchlen_loopback_16_match_nolit_calcBlockSize: - CMPL DI, $0x10 - JB matchlen_match8_match_nolit_calcBlockSize - MOVQ (R8)(R10*1), R9 - MOVQ 8(R8)(R10*1), R11 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_calcBlockSize - XORQ 8(SI)(R10*1), R11 - JNZ matchlen_bsf_16match_nolit_calcBlockSize - LEAL -16(DI), DI - LEAL 16(R10), R10 - JMP matchlen_loopback_16_match_nolit_calcBlockSize - -matchlen_bsf_16match_nolit_calcBlockSize: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL 8(R10)(R11*1), R10 - JMP match_nolit_end_calcBlockSize - -matchlen_match8_match_nolit_calcBlockSize: - CMPL DI, $0x08 - JB matchlen_match4_match_nolit_calcBlockSize - MOVQ (R8)(R10*1), R9 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_calcBlockSize - LEAL -8(DI), DI - LEAL 8(R10), R10 - JMP matchlen_match4_match_nolit_calcBlockSize - -matchlen_bsf_8_match_nolit_calcBlockSize: -#ifdef GOAMD64_v3 - TZCNTQ R9, R9 - -#else - BSFQ R9, R9 - -#endif - SARQ $0x03, R9 - LEAL (R10)(R9*1), R10 - JMP match_nolit_end_calcBlockSize - -matchlen_match4_match_nolit_calcBlockSize: - CMPL DI, $0x04 - JB matchlen_match2_match_nolit_calcBlockSize - MOVL (R8)(R10*1), R9 - CMPL (SI)(R10*1), R9 - JNE matchlen_match2_match_nolit_calcBlockSize - LEAL -4(DI), DI - LEAL 4(R10), R10 - -matchlen_match2_match_nolit_calcBlockSize: - CMPL DI, $0x01 - JE matchlen_match1_match_nolit_calcBlockSize - JB match_nolit_end_calcBlockSize - MOVW (R8)(R10*1), R9 - CMPW (SI)(R10*1), R9 - JNE matchlen_match1_match_nolit_calcBlockSize - LEAL 2(R10), R10 - SUBL $0x02, DI - JZ match_nolit_end_calcBlockSize - -matchlen_match1_match_nolit_calcBlockSize: - MOVB (R8)(R10*1), R9 - CMPB (SI)(R10*1), R9 - JNE match_nolit_end_calcBlockSize - LEAL 1(R10), R10 - -match_nolit_end_calcBlockSize: - ADDL R10, DX - MOVL 16(SP), SI - ADDL $0x04, R10 - MOVL DX, 12(SP) - - // emitCopy - CMPL SI, $0x00010000 - JB two_byte_offset_match_nolit_calcBlockSize - -four_bytes_loop_back_match_nolit_calcBlockSize: - CMPL R10, $0x40 - JBE four_bytes_remain_match_nolit_calcBlockSize - LEAL -64(R10), R10 - ADDQ $0x05, CX - CMPL R10, $0x04 - JB four_bytes_remain_match_nolit_calcBlockSize - JMP four_bytes_loop_back_match_nolit_calcBlockSize - -four_bytes_remain_match_nolit_calcBlockSize: - TESTL R10, R10 - JZ match_nolit_emitcopy_end_calcBlockSize - XORL SI, SI - ADDQ $0x05, CX - JMP match_nolit_emitcopy_end_calcBlockSize - -two_byte_offset_match_nolit_calcBlockSize: - CMPL R10, $0x40 - JBE two_byte_offset_short_match_nolit_calcBlockSize - LEAL -60(R10), R10 - ADDQ $0x03, CX - JMP two_byte_offset_match_nolit_calcBlockSize - -two_byte_offset_short_match_nolit_calcBlockSize: - MOVL R10, DI - SHLL $0x02, DI - CMPL R10, $0x0c - JAE emit_copy_three_match_nolit_calcBlockSize - CMPL SI, $0x00000800 - JAE emit_copy_three_match_nolit_calcBlockSize - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_calcBlockSize - -emit_copy_three_match_nolit_calcBlockSize: - ADDQ $0x03, CX - -match_nolit_emitcopy_end_calcBlockSize: - CMPL DX, 8(SP) - JAE emit_remainder_calcBlockSize - MOVQ -2(BX)(DX*1), DI - CMPQ CX, (SP) - JB match_nolit_dst_ok_calcBlockSize - MOVQ $0x00000000, ret+32(FP) - RET - -match_nolit_dst_ok_calcBlockSize: - MOVQ $0x0000cf1bbcdcbf9b, R9 - MOVQ DI, R8 - SHRQ $0x10, DI - MOVQ DI, SI - SHLQ $0x10, R8 - IMULQ R9, R8 - SHRQ $0x33, R8 - SHLQ $0x10, SI - IMULQ R9, SI - SHRQ $0x33, SI - LEAL -2(DX), R9 - LEAQ (AX)(SI*4), R10 - MOVL (R10), SI - MOVL R9, (AX)(R8*4) - MOVL DX, (R10) - CMPL (BX)(SI*1), DI - JEQ match_nolit_loop_calcBlockSize - INCL DX - JMP search_loop_calcBlockSize - -emit_remainder_calcBlockSize: - MOVQ src_len+8(FP), AX - SUBL 12(SP), AX - LEAQ 5(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_calcBlockSize - MOVQ $0x00000000, ret+32(FP) - RET - -emit_remainder_ok_calcBlockSize: - MOVQ src_len+8(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_calcBlockSize - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), AX - CMPL AX, $0x3c - JB one_byte_emit_remainder_calcBlockSize - CMPL AX, $0x00000100 - JB two_bytes_emit_remainder_calcBlockSize - CMPL AX, $0x00010000 - JB three_bytes_emit_remainder_calcBlockSize - CMPL AX, $0x01000000 - JB four_bytes_emit_remainder_calcBlockSize - ADDQ $0x05, CX - JMP memmove_long_emit_remainder_calcBlockSize - -four_bytes_emit_remainder_calcBlockSize: - ADDQ $0x04, CX - JMP memmove_long_emit_remainder_calcBlockSize - -three_bytes_emit_remainder_calcBlockSize: - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_calcBlockSize - -two_bytes_emit_remainder_calcBlockSize: - ADDQ $0x02, CX - CMPL AX, $0x40 - JB memmove_emit_remainder_calcBlockSize - JMP memmove_long_emit_remainder_calcBlockSize - -one_byte_emit_remainder_calcBlockSize: - ADDQ $0x01, CX - -memmove_emit_remainder_calcBlockSize: - LEAQ (CX)(SI*1), AX - MOVQ AX, CX - JMP emit_literal_done_emit_remainder_calcBlockSize - -memmove_long_emit_remainder_calcBlockSize: - LEAQ (CX)(SI*1), AX - MOVQ AX, CX - -emit_literal_done_emit_remainder_calcBlockSize: - MOVQ CX, ret+32(FP) - RET - -// func calcBlockSizeSmall(src []byte, tmp *[2048]byte) int -// Requires: BMI, SSE2 -TEXT ·calcBlockSizeSmall(SB), $24-40 - MOVQ tmp+24(FP), AX - XORQ CX, CX - MOVQ $0x00000010, DX - MOVQ AX, BX - PXOR X0, X0 - -zero_loop_calcBlockSizeSmall: - MOVOU X0, (BX) - MOVOU X0, 16(BX) - MOVOU X0, 32(BX) - MOVOU X0, 48(BX) - MOVOU X0, 64(BX) - MOVOU X0, 80(BX) - MOVOU X0, 96(BX) - MOVOU X0, 112(BX) - ADDQ $0x80, BX - DECQ DX - JNZ zero_loop_calcBlockSizeSmall - MOVL $0x00000000, 12(SP) - MOVQ src_len+8(FP), DX - LEAQ -9(DX), BX - LEAQ -8(DX), SI - MOVL SI, 8(SP) - SHRQ $0x05, DX - SUBL DX, BX - LEAQ (CX)(BX*1), BX - MOVQ BX, (SP) - MOVL $0x00000001, DX - MOVL DX, 16(SP) - MOVQ src_base+0(FP), BX - -search_loop_calcBlockSizeSmall: - MOVL DX, SI - SUBL 12(SP), SI - SHRL $0x04, SI - LEAL 4(DX)(SI*1), SI - CMPL SI, 8(SP) - JAE emit_remainder_calcBlockSizeSmall - MOVQ (BX)(DX*1), DI - MOVL SI, 20(SP) - MOVQ $0x9e3779b1, R9 - MOVQ DI, R10 - MOVQ DI, R11 - SHRQ $0x08, R11 - SHLQ $0x20, R10 - IMULQ R9, R10 - SHRQ $0x37, R10 - SHLQ $0x20, R11 - IMULQ R9, R11 - SHRQ $0x37, R11 - MOVL (AX)(R10*4), SI - MOVL (AX)(R11*4), R8 - MOVL DX, (AX)(R10*4) - LEAL 1(DX), R10 - MOVL R10, (AX)(R11*4) - MOVQ DI, R10 - SHRQ $0x10, R10 - SHLQ $0x20, R10 - IMULQ R9, R10 - SHRQ $0x37, R10 - MOVL DX, R9 - SUBL 16(SP), R9 - MOVL 1(BX)(R9*1), R11 - MOVQ DI, R9 - SHRQ $0x08, R9 - CMPL R9, R11 - JNE no_repeat_found_calcBlockSizeSmall - LEAL 1(DX), DI - MOVL 12(SP), SI - MOVL DI, R8 - SUBL 16(SP), R8 - JZ repeat_extend_back_end_calcBlockSizeSmall - -repeat_extend_back_loop_calcBlockSizeSmall: - CMPL DI, SI - JBE repeat_extend_back_end_calcBlockSizeSmall - MOVB -1(BX)(R8*1), R9 - MOVB -1(BX)(DI*1), R10 - CMPB R9, R10 - JNE repeat_extend_back_end_calcBlockSizeSmall - LEAL -1(DI), DI - DECL R8 - JNZ repeat_extend_back_loop_calcBlockSizeSmall - -repeat_extend_back_end_calcBlockSizeSmall: - MOVL DI, SI - SUBL 12(SP), SI - LEAQ 3(CX)(SI*1), SI - CMPQ SI, (SP) - JB repeat_dst_size_check_calcBlockSizeSmall - MOVQ $0x00000000, ret+32(FP) - RET - -repeat_dst_size_check_calcBlockSizeSmall: - MOVL 12(SP), SI - CMPL SI, DI - JEQ emit_literal_done_repeat_emit_calcBlockSizeSmall - MOVL DI, R8 - MOVL DI, 12(SP) - LEAQ (BX)(SI*1), R9 - SUBL SI, R8 - LEAL -1(R8), SI - CMPL SI, $0x3c - JB one_byte_repeat_emit_calcBlockSizeSmall - CMPL SI, $0x00000100 - JB two_bytes_repeat_emit_calcBlockSizeSmall - JB three_bytes_repeat_emit_calcBlockSizeSmall - -three_bytes_repeat_emit_calcBlockSizeSmall: - ADDQ $0x03, CX - JMP memmove_long_repeat_emit_calcBlockSizeSmall - -two_bytes_repeat_emit_calcBlockSizeSmall: - ADDQ $0x02, CX - CMPL SI, $0x40 - JB memmove_repeat_emit_calcBlockSizeSmall - JMP memmove_long_repeat_emit_calcBlockSizeSmall - -one_byte_repeat_emit_calcBlockSizeSmall: - ADDQ $0x01, CX - -memmove_repeat_emit_calcBlockSizeSmall: - LEAQ (CX)(R8*1), CX - JMP emit_literal_done_repeat_emit_calcBlockSizeSmall - -memmove_long_repeat_emit_calcBlockSizeSmall: - LEAQ (CX)(R8*1), CX - -emit_literal_done_repeat_emit_calcBlockSizeSmall: - ADDL $0x05, DX - MOVL DX, SI - SUBL 16(SP), SI - MOVQ src_len+8(FP), R8 - SUBL DX, R8 - LEAQ (BX)(DX*1), R9 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R11, R11 - -matchlen_loopback_16_repeat_extend_calcBlockSizeSmall: - CMPL R8, $0x10 - JB matchlen_match8_repeat_extend_calcBlockSizeSmall - MOVQ (R9)(R11*1), R10 - MOVQ 8(R9)(R11*1), R12 - XORQ (SI)(R11*1), R10 - JNZ matchlen_bsf_8_repeat_extend_calcBlockSizeSmall - XORQ 8(SI)(R11*1), R12 - JNZ matchlen_bsf_16repeat_extend_calcBlockSizeSmall - LEAL -16(R8), R8 - LEAL 16(R11), R11 - JMP matchlen_loopback_16_repeat_extend_calcBlockSizeSmall - -matchlen_bsf_16repeat_extend_calcBlockSizeSmall: -#ifdef GOAMD64_v3 - TZCNTQ R12, R12 - -#else - BSFQ R12, R12 - -#endif - SARQ $0x03, R12 - LEAL 8(R11)(R12*1), R11 - JMP repeat_extend_forward_end_calcBlockSizeSmall - -matchlen_match8_repeat_extend_calcBlockSizeSmall: - CMPL R8, $0x08 - JB matchlen_match4_repeat_extend_calcBlockSizeSmall - MOVQ (R9)(R11*1), R10 - XORQ (SI)(R11*1), R10 - JNZ matchlen_bsf_8_repeat_extend_calcBlockSizeSmall - LEAL -8(R8), R8 - LEAL 8(R11), R11 - JMP matchlen_match4_repeat_extend_calcBlockSizeSmall - -matchlen_bsf_8_repeat_extend_calcBlockSizeSmall: -#ifdef GOAMD64_v3 - TZCNTQ R10, R10 - -#else - BSFQ R10, R10 - -#endif - SARQ $0x03, R10 - LEAL (R11)(R10*1), R11 - JMP repeat_extend_forward_end_calcBlockSizeSmall - -matchlen_match4_repeat_extend_calcBlockSizeSmall: - CMPL R8, $0x04 - JB matchlen_match2_repeat_extend_calcBlockSizeSmall - MOVL (R9)(R11*1), R10 - CMPL (SI)(R11*1), R10 - JNE matchlen_match2_repeat_extend_calcBlockSizeSmall - LEAL -4(R8), R8 - LEAL 4(R11), R11 - -matchlen_match2_repeat_extend_calcBlockSizeSmall: - CMPL R8, $0x01 - JE matchlen_match1_repeat_extend_calcBlockSizeSmall - JB repeat_extend_forward_end_calcBlockSizeSmall - MOVW (R9)(R11*1), R10 - CMPW (SI)(R11*1), R10 - JNE matchlen_match1_repeat_extend_calcBlockSizeSmall - LEAL 2(R11), R11 - SUBL $0x02, R8 - JZ repeat_extend_forward_end_calcBlockSizeSmall - -matchlen_match1_repeat_extend_calcBlockSizeSmall: - MOVB (R9)(R11*1), R10 - CMPB (SI)(R11*1), R10 - JNE repeat_extend_forward_end_calcBlockSizeSmall - LEAL 1(R11), R11 - -repeat_extend_forward_end_calcBlockSizeSmall: - ADDL R11, DX - MOVL DX, SI - SUBL DI, SI - MOVL 16(SP), DI - - // emitCopy -two_byte_offset_repeat_as_copy_calcBlockSizeSmall: - CMPL SI, $0x40 - JBE two_byte_offset_short_repeat_as_copy_calcBlockSizeSmall - LEAL -60(SI), SI - ADDQ $0x03, CX - JMP two_byte_offset_repeat_as_copy_calcBlockSizeSmall - -two_byte_offset_short_repeat_as_copy_calcBlockSizeSmall: - MOVL SI, DI - SHLL $0x02, DI - CMPL SI, $0x0c - JAE emit_copy_three_repeat_as_copy_calcBlockSizeSmall - ADDQ $0x02, CX - JMP repeat_end_emit_calcBlockSizeSmall - -emit_copy_three_repeat_as_copy_calcBlockSizeSmall: - ADDQ $0x03, CX - -repeat_end_emit_calcBlockSizeSmall: - MOVL DX, 12(SP) - JMP search_loop_calcBlockSizeSmall - -no_repeat_found_calcBlockSizeSmall: - CMPL (BX)(SI*1), DI - JEQ candidate_match_calcBlockSizeSmall - SHRQ $0x08, DI - MOVL (AX)(R10*4), SI - LEAL 2(DX), R9 - CMPL (BX)(R8*1), DI - JEQ candidate2_match_calcBlockSizeSmall - MOVL R9, (AX)(R10*4) - SHRQ $0x08, DI - CMPL (BX)(SI*1), DI - JEQ candidate3_match_calcBlockSizeSmall - MOVL 20(SP), DX - JMP search_loop_calcBlockSizeSmall - -candidate3_match_calcBlockSizeSmall: - ADDL $0x02, DX - JMP candidate_match_calcBlockSizeSmall - -candidate2_match_calcBlockSizeSmall: - MOVL R9, (AX)(R10*4) - INCL DX - MOVL R8, SI - -candidate_match_calcBlockSizeSmall: - MOVL 12(SP), DI - TESTL SI, SI - JZ match_extend_back_end_calcBlockSizeSmall - -match_extend_back_loop_calcBlockSizeSmall: - CMPL DX, DI - JBE match_extend_back_end_calcBlockSizeSmall - MOVB -1(BX)(SI*1), R8 - MOVB -1(BX)(DX*1), R9 - CMPB R8, R9 - JNE match_extend_back_end_calcBlockSizeSmall - LEAL -1(DX), DX - DECL SI - JZ match_extend_back_end_calcBlockSizeSmall - JMP match_extend_back_loop_calcBlockSizeSmall - -match_extend_back_end_calcBlockSizeSmall: - MOVL DX, DI - SUBL 12(SP), DI - LEAQ 3(CX)(DI*1), DI - CMPQ DI, (SP) - JB match_dst_size_check_calcBlockSizeSmall - MOVQ $0x00000000, ret+32(FP) - RET - -match_dst_size_check_calcBlockSizeSmall: - MOVL DX, DI - MOVL 12(SP), R8 - CMPL R8, DI - JEQ emit_literal_done_match_emit_calcBlockSizeSmall - MOVL DI, R9 - MOVL DI, 12(SP) - LEAQ (BX)(R8*1), DI - SUBL R8, R9 - LEAL -1(R9), DI - CMPL DI, $0x3c - JB one_byte_match_emit_calcBlockSizeSmall - CMPL DI, $0x00000100 - JB two_bytes_match_emit_calcBlockSizeSmall - JB three_bytes_match_emit_calcBlockSizeSmall - -three_bytes_match_emit_calcBlockSizeSmall: - ADDQ $0x03, CX - JMP memmove_long_match_emit_calcBlockSizeSmall - -two_bytes_match_emit_calcBlockSizeSmall: - ADDQ $0x02, CX - CMPL DI, $0x40 - JB memmove_match_emit_calcBlockSizeSmall - JMP memmove_long_match_emit_calcBlockSizeSmall - -one_byte_match_emit_calcBlockSizeSmall: - ADDQ $0x01, CX - -memmove_match_emit_calcBlockSizeSmall: - LEAQ (CX)(R9*1), CX - JMP emit_literal_done_match_emit_calcBlockSizeSmall - -memmove_long_match_emit_calcBlockSizeSmall: - LEAQ (CX)(R9*1), CX - -emit_literal_done_match_emit_calcBlockSizeSmall: -match_nolit_loop_calcBlockSizeSmall: - MOVL DX, DI - SUBL SI, DI - MOVL DI, 16(SP) - ADDL $0x04, DX - ADDL $0x04, SI - MOVQ src_len+8(FP), DI - SUBL DX, DI - LEAQ (BX)(DX*1), R8 - LEAQ (BX)(SI*1), SI - - // matchLen - XORL R10, R10 - -matchlen_loopback_16_match_nolit_calcBlockSizeSmall: - CMPL DI, $0x10 - JB matchlen_match8_match_nolit_calcBlockSizeSmall - MOVQ (R8)(R10*1), R9 - MOVQ 8(R8)(R10*1), R11 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_calcBlockSizeSmall - XORQ 8(SI)(R10*1), R11 - JNZ matchlen_bsf_16match_nolit_calcBlockSizeSmall - LEAL -16(DI), DI - LEAL 16(R10), R10 - JMP matchlen_loopback_16_match_nolit_calcBlockSizeSmall - -matchlen_bsf_16match_nolit_calcBlockSizeSmall: -#ifdef GOAMD64_v3 - TZCNTQ R11, R11 - -#else - BSFQ R11, R11 - -#endif - SARQ $0x03, R11 - LEAL 8(R10)(R11*1), R10 - JMP match_nolit_end_calcBlockSizeSmall - -matchlen_match8_match_nolit_calcBlockSizeSmall: - CMPL DI, $0x08 - JB matchlen_match4_match_nolit_calcBlockSizeSmall - MOVQ (R8)(R10*1), R9 - XORQ (SI)(R10*1), R9 - JNZ matchlen_bsf_8_match_nolit_calcBlockSizeSmall - LEAL -8(DI), DI - LEAL 8(R10), R10 - JMP matchlen_match4_match_nolit_calcBlockSizeSmall - -matchlen_bsf_8_match_nolit_calcBlockSizeSmall: -#ifdef GOAMD64_v3 - TZCNTQ R9, R9 - -#else - BSFQ R9, R9 - -#endif - SARQ $0x03, R9 - LEAL (R10)(R9*1), R10 - JMP match_nolit_end_calcBlockSizeSmall - -matchlen_match4_match_nolit_calcBlockSizeSmall: - CMPL DI, $0x04 - JB matchlen_match2_match_nolit_calcBlockSizeSmall - MOVL (R8)(R10*1), R9 - CMPL (SI)(R10*1), R9 - JNE matchlen_match2_match_nolit_calcBlockSizeSmall - LEAL -4(DI), DI - LEAL 4(R10), R10 - -matchlen_match2_match_nolit_calcBlockSizeSmall: - CMPL DI, $0x01 - JE matchlen_match1_match_nolit_calcBlockSizeSmall - JB match_nolit_end_calcBlockSizeSmall - MOVW (R8)(R10*1), R9 - CMPW (SI)(R10*1), R9 - JNE matchlen_match1_match_nolit_calcBlockSizeSmall - LEAL 2(R10), R10 - SUBL $0x02, DI - JZ match_nolit_end_calcBlockSizeSmall - -matchlen_match1_match_nolit_calcBlockSizeSmall: - MOVB (R8)(R10*1), R9 - CMPB (SI)(R10*1), R9 - JNE match_nolit_end_calcBlockSizeSmall - LEAL 1(R10), R10 - -match_nolit_end_calcBlockSizeSmall: - ADDL R10, DX - MOVL 16(SP), SI - ADDL $0x04, R10 - MOVL DX, 12(SP) - - // emitCopy -two_byte_offset_match_nolit_calcBlockSizeSmall: - CMPL R10, $0x40 - JBE two_byte_offset_short_match_nolit_calcBlockSizeSmall - LEAL -60(R10), R10 - ADDQ $0x03, CX - JMP two_byte_offset_match_nolit_calcBlockSizeSmall - -two_byte_offset_short_match_nolit_calcBlockSizeSmall: - MOVL R10, SI - SHLL $0x02, SI - CMPL R10, $0x0c - JAE emit_copy_three_match_nolit_calcBlockSizeSmall - ADDQ $0x02, CX - JMP match_nolit_emitcopy_end_calcBlockSizeSmall - -emit_copy_three_match_nolit_calcBlockSizeSmall: - ADDQ $0x03, CX - -match_nolit_emitcopy_end_calcBlockSizeSmall: - CMPL DX, 8(SP) - JAE emit_remainder_calcBlockSizeSmall - MOVQ -2(BX)(DX*1), DI - CMPQ CX, (SP) - JB match_nolit_dst_ok_calcBlockSizeSmall - MOVQ $0x00000000, ret+32(FP) - RET - -match_nolit_dst_ok_calcBlockSizeSmall: - MOVQ $0x9e3779b1, R9 - MOVQ DI, R8 - SHRQ $0x10, DI - MOVQ DI, SI - SHLQ $0x20, R8 - IMULQ R9, R8 - SHRQ $0x37, R8 - SHLQ $0x20, SI - IMULQ R9, SI - SHRQ $0x37, SI - LEAL -2(DX), R9 - LEAQ (AX)(SI*4), R10 - MOVL (R10), SI - MOVL R9, (AX)(R8*4) - MOVL DX, (R10) - CMPL (BX)(SI*1), DI - JEQ match_nolit_loop_calcBlockSizeSmall - INCL DX - JMP search_loop_calcBlockSizeSmall - -emit_remainder_calcBlockSizeSmall: - MOVQ src_len+8(FP), AX - SUBL 12(SP), AX - LEAQ 3(CX)(AX*1), AX - CMPQ AX, (SP) - JB emit_remainder_ok_calcBlockSizeSmall - MOVQ $0x00000000, ret+32(FP) - RET - -emit_remainder_ok_calcBlockSizeSmall: - MOVQ src_len+8(FP), AX - MOVL 12(SP), DX - CMPL DX, AX - JEQ emit_literal_done_emit_remainder_calcBlockSizeSmall - MOVL AX, SI - MOVL AX, 12(SP) - LEAQ (BX)(DX*1), AX - SUBL DX, SI - LEAL -1(SI), AX - CMPL AX, $0x3c - JB one_byte_emit_remainder_calcBlockSizeSmall - CMPL AX, $0x00000100 - JB two_bytes_emit_remainder_calcBlockSizeSmall - JB three_bytes_emit_remainder_calcBlockSizeSmall - -three_bytes_emit_remainder_calcBlockSizeSmall: - ADDQ $0x03, CX - JMP memmove_long_emit_remainder_calcBlockSizeSmall - -two_bytes_emit_remainder_calcBlockSizeSmall: - ADDQ $0x02, CX - CMPL AX, $0x40 - JB memmove_emit_remainder_calcBlockSizeSmall - JMP memmove_long_emit_remainder_calcBlockSizeSmall - -one_byte_emit_remainder_calcBlockSizeSmall: - ADDQ $0x01, CX - -memmove_emit_remainder_calcBlockSizeSmall: - LEAQ (CX)(SI*1), AX - MOVQ AX, CX - JMP emit_literal_done_emit_remainder_calcBlockSizeSmall - -memmove_long_emit_remainder_calcBlockSizeSmall: - LEAQ (CX)(SI*1), AX - MOVQ AX, CX - -emit_literal_done_emit_remainder_calcBlockSizeSmall: - MOVQ CX, ret+32(FP) - RET - -// func emitLiteral(dst []byte, lit []byte) int -// Requires: SSE2 -TEXT ·emitLiteral(SB), NOSPLIT, $0-56 - MOVQ lit_len+32(FP), DX - MOVQ dst_base+0(FP), AX - MOVQ lit_base+24(FP), CX - TESTQ DX, DX - JZ emit_literal_end_standalone_skip - MOVL DX, BX - LEAL -1(DX), SI - CMPL SI, $0x3c - JB one_byte_standalone - CMPL SI, $0x00000100 - JB two_bytes_standalone - CMPL SI, $0x00010000 - JB three_bytes_standalone - CMPL SI, $0x01000000 - JB four_bytes_standalone - MOVB $0xfc, (AX) - MOVL SI, 1(AX) - ADDQ $0x05, BX - ADDQ $0x05, AX - JMP memmove_long_standalone - -four_bytes_standalone: - MOVL SI, DI - SHRL $0x10, DI - MOVB $0xf8, (AX) - MOVW SI, 1(AX) - MOVB DI, 3(AX) - ADDQ $0x04, BX - ADDQ $0x04, AX - JMP memmove_long_standalone - -three_bytes_standalone: - MOVB $0xf4, (AX) - MOVW SI, 1(AX) - ADDQ $0x03, BX - ADDQ $0x03, AX - JMP memmove_long_standalone - -two_bytes_standalone: - MOVB $0xf0, (AX) - MOVB SI, 1(AX) - ADDQ $0x02, BX - ADDQ $0x02, AX - CMPL SI, $0x40 - JB memmove_standalone - JMP memmove_long_standalone - -one_byte_standalone: - SHLB $0x02, SI - MOVB SI, (AX) - ADDQ $0x01, BX - ADDQ $0x01, AX - -memmove_standalone: - // genMemMoveShort - CMPQ DX, $0x03 - JB emit_lit_memmove_standalone_memmove_move_1or2 - JE emit_lit_memmove_standalone_memmove_move_3 - CMPQ DX, $0x08 - JB emit_lit_memmove_standalone_memmove_move_4through7 - CMPQ DX, $0x10 - JBE emit_lit_memmove_standalone_memmove_move_8through16 - CMPQ DX, $0x20 - JBE emit_lit_memmove_standalone_memmove_move_17through32 - JMP emit_lit_memmove_standalone_memmove_move_33through64 - -emit_lit_memmove_standalone_memmove_move_1or2: - MOVB (CX), SI - MOVB -1(CX)(DX*1), CL - MOVB SI, (AX) - MOVB CL, -1(AX)(DX*1) - JMP emit_literal_end_standalone - -emit_lit_memmove_standalone_memmove_move_3: - MOVW (CX), SI - MOVB 2(CX), CL - MOVW SI, (AX) - MOVB CL, 2(AX) - JMP emit_literal_end_standalone - -emit_lit_memmove_standalone_memmove_move_4through7: - MOVL (CX), SI - MOVL -4(CX)(DX*1), CX - MOVL SI, (AX) - MOVL CX, -4(AX)(DX*1) - JMP emit_literal_end_standalone - -emit_lit_memmove_standalone_memmove_move_8through16: - MOVQ (CX), SI - MOVQ -8(CX)(DX*1), CX - MOVQ SI, (AX) - MOVQ CX, -8(AX)(DX*1) - JMP emit_literal_end_standalone - -emit_lit_memmove_standalone_memmove_move_17through32: - MOVOU (CX), X0 - MOVOU -16(CX)(DX*1), X1 - MOVOU X0, (AX) - MOVOU X1, -16(AX)(DX*1) - JMP emit_literal_end_standalone - -emit_lit_memmove_standalone_memmove_move_33through64: - MOVOU (CX), X0 - MOVOU 16(CX), X1 - MOVOU -32(CX)(DX*1), X2 - MOVOU -16(CX)(DX*1), X3 - MOVOU X0, (AX) - MOVOU X1, 16(AX) - MOVOU X2, -32(AX)(DX*1) - MOVOU X3, -16(AX)(DX*1) - JMP emit_literal_end_standalone - JMP emit_literal_end_standalone - -memmove_long_standalone: - // genMemMoveLong - MOVOU (CX), X0 - MOVOU 16(CX), X1 - MOVOU -32(CX)(DX*1), X2 - MOVOU -16(CX)(DX*1), X3 - MOVQ DX, DI - SHRQ $0x05, DI - MOVQ AX, SI - ANDL $0x0000001f, SI - MOVQ $0x00000040, R8 - SUBQ SI, R8 - DECQ DI - JA emit_lit_memmove_long_standalonelarge_forward_sse_loop_32 - LEAQ -32(CX)(R8*1), SI - LEAQ -32(AX)(R8*1), R9 - -emit_lit_memmove_long_standalonelarge_big_loop_back: - MOVOU (SI), X4 - MOVOU 16(SI), X5 - MOVOA X4, (R9) - MOVOA X5, 16(R9) - ADDQ $0x20, R9 - ADDQ $0x20, SI - ADDQ $0x20, R8 - DECQ DI - JNA emit_lit_memmove_long_standalonelarge_big_loop_back - -emit_lit_memmove_long_standalonelarge_forward_sse_loop_32: - MOVOU -32(CX)(R8*1), X4 - MOVOU -16(CX)(R8*1), X5 - MOVOA X4, -32(AX)(R8*1) - MOVOA X5, -16(AX)(R8*1) - ADDQ $0x20, R8 - CMPQ DX, R8 - JAE emit_lit_memmove_long_standalonelarge_forward_sse_loop_32 - MOVOU X0, (AX) - MOVOU X1, 16(AX) - MOVOU X2, -32(AX)(DX*1) - MOVOU X3, -16(AX)(DX*1) - JMP emit_literal_end_standalone - JMP emit_literal_end_standalone - -emit_literal_end_standalone_skip: - XORQ BX, BX - -emit_literal_end_standalone: - MOVQ BX, ret+48(FP) - RET - -// func emitRepeat(dst []byte, offset int, length int) int -TEXT ·emitRepeat(SB), NOSPLIT, $0-48 - XORQ BX, BX - MOVQ dst_base+0(FP), AX - MOVQ offset+24(FP), CX - MOVQ length+32(FP), DX - - // emitRepeat -emit_repeat_again_standalone: - MOVL DX, SI - LEAL -4(DX), DX - CMPL SI, $0x08 - JBE repeat_two_standalone - CMPL SI, $0x0c - JAE cant_repeat_two_offset_standalone - CMPL CX, $0x00000800 - JB repeat_two_offset_standalone - -cant_repeat_two_offset_standalone: - CMPL DX, $0x00000104 - JB repeat_three_standalone - CMPL DX, $0x00010100 - JB repeat_four_standalone - CMPL DX, $0x0100ffff - JB repeat_five_standalone - LEAL -16842747(DX), DX - MOVL $0xfffb001d, (AX) - MOVB $0xff, 4(AX) - ADDQ $0x05, AX - ADDQ $0x05, BX - JMP emit_repeat_again_standalone - -repeat_five_standalone: - LEAL -65536(DX), DX - MOVL DX, CX - MOVW $0x001d, (AX) - MOVW DX, 2(AX) - SARL $0x10, CX - MOVB CL, 4(AX) - ADDQ $0x05, BX - ADDQ $0x05, AX - JMP gen_emit_repeat_end - -repeat_four_standalone: - LEAL -256(DX), DX - MOVW $0x0019, (AX) - MOVW DX, 2(AX) - ADDQ $0x04, BX - ADDQ $0x04, AX - JMP gen_emit_repeat_end - -repeat_three_standalone: - LEAL -4(DX), DX - MOVW $0x0015, (AX) - MOVB DL, 2(AX) - ADDQ $0x03, BX - ADDQ $0x03, AX - JMP gen_emit_repeat_end - -repeat_two_standalone: - SHLL $0x02, DX - ORL $0x01, DX - MOVW DX, (AX) - ADDQ $0x02, BX - ADDQ $0x02, AX - JMP gen_emit_repeat_end - -repeat_two_offset_standalone: - XORQ SI, SI - LEAL 1(SI)(DX*4), DX - MOVB CL, 1(AX) - SARL $0x08, CX - SHLL $0x05, CX - ORL CX, DX - MOVB DL, (AX) - ADDQ $0x02, BX - ADDQ $0x02, AX - -gen_emit_repeat_end: - MOVQ BX, ret+40(FP) - RET - -// func emitCopy(dst []byte, offset int, length int) int -TEXT ·emitCopy(SB), NOSPLIT, $0-48 - XORQ BX, BX - MOVQ dst_base+0(FP), AX - MOVQ offset+24(FP), CX - MOVQ length+32(FP), DX - - // emitCopy - CMPL CX, $0x00010000 - JB two_byte_offset_standalone - CMPL DX, $0x40 - JBE four_bytes_remain_standalone - MOVB $0xff, (AX) - MOVL CX, 1(AX) - LEAL -64(DX), DX - ADDQ $0x05, BX - ADDQ $0x05, AX - CMPL DX, $0x04 - JB four_bytes_remain_standalone - - // emitRepeat -emit_repeat_again_standalone_emit_copy: - MOVL DX, SI - LEAL -4(DX), DX - CMPL SI, $0x08 - JBE repeat_two_standalone_emit_copy - CMPL SI, $0x0c - JAE cant_repeat_two_offset_standalone_emit_copy - CMPL CX, $0x00000800 - JB repeat_two_offset_standalone_emit_copy - -cant_repeat_two_offset_standalone_emit_copy: - CMPL DX, $0x00000104 - JB repeat_three_standalone_emit_copy - CMPL DX, $0x00010100 - JB repeat_four_standalone_emit_copy - CMPL DX, $0x0100ffff - JB repeat_five_standalone_emit_copy - LEAL -16842747(DX), DX - MOVL $0xfffb001d, (AX) - MOVB $0xff, 4(AX) - ADDQ $0x05, AX - ADDQ $0x05, BX - JMP emit_repeat_again_standalone_emit_copy - -repeat_five_standalone_emit_copy: - LEAL -65536(DX), DX - MOVL DX, CX - MOVW $0x001d, (AX) - MOVW DX, 2(AX) - SARL $0x10, CX - MOVB CL, 4(AX) - ADDQ $0x05, BX - ADDQ $0x05, AX - JMP gen_emit_copy_end - -repeat_four_standalone_emit_copy: - LEAL -256(DX), DX - MOVW $0x0019, (AX) - MOVW DX, 2(AX) - ADDQ $0x04, BX - ADDQ $0x04, AX - JMP gen_emit_copy_end - -repeat_three_standalone_emit_copy: - LEAL -4(DX), DX - MOVW $0x0015, (AX) - MOVB DL, 2(AX) - ADDQ $0x03, BX - ADDQ $0x03, AX - JMP gen_emit_copy_end - -repeat_two_standalone_emit_copy: - SHLL $0x02, DX - ORL $0x01, DX - MOVW DX, (AX) - ADDQ $0x02, BX - ADDQ $0x02, AX - JMP gen_emit_copy_end - -repeat_two_offset_standalone_emit_copy: - XORQ SI, SI - LEAL 1(SI)(DX*4), DX - MOVB CL, 1(AX) - SARL $0x08, CX - SHLL $0x05, CX - ORL CX, DX - MOVB DL, (AX) - ADDQ $0x02, BX - ADDQ $0x02, AX - JMP gen_emit_copy_end - -four_bytes_remain_standalone: - TESTL DX, DX - JZ gen_emit_copy_end - XORL SI, SI - LEAL -1(SI)(DX*4), DX - MOVB DL, (AX) - MOVL CX, 1(AX) - ADDQ $0x05, BX - ADDQ $0x05, AX - JMP gen_emit_copy_end - -two_byte_offset_standalone: - CMPL DX, $0x40 - JBE two_byte_offset_short_standalone - CMPL CX, $0x00000800 - JAE long_offset_short_standalone - MOVL $0x00000001, SI - LEAL 16(SI), SI - MOVB CL, 1(AX) - MOVL CX, DI - SHRL $0x08, DI - SHLL $0x05, DI - ORL DI, SI - MOVB SI, (AX) - ADDQ $0x02, BX - ADDQ $0x02, AX - SUBL $0x08, DX - - // emitRepeat - LEAL -4(DX), DX - JMP cant_repeat_two_offset_standalone_emit_copy_short_2b - -emit_repeat_again_standalone_emit_copy_short_2b: - MOVL DX, SI - LEAL -4(DX), DX - CMPL SI, $0x08 - JBE repeat_two_standalone_emit_copy_short_2b - CMPL SI, $0x0c - JAE cant_repeat_two_offset_standalone_emit_copy_short_2b - CMPL CX, $0x00000800 - JB repeat_two_offset_standalone_emit_copy_short_2b - -cant_repeat_two_offset_standalone_emit_copy_short_2b: - CMPL DX, $0x00000104 - JB repeat_three_standalone_emit_copy_short_2b - CMPL DX, $0x00010100 - JB repeat_four_standalone_emit_copy_short_2b - CMPL DX, $0x0100ffff - JB repeat_five_standalone_emit_copy_short_2b - LEAL -16842747(DX), DX - MOVL $0xfffb001d, (AX) - MOVB $0xff, 4(AX) - ADDQ $0x05, AX - ADDQ $0x05, BX - JMP emit_repeat_again_standalone_emit_copy_short_2b - -repeat_five_standalone_emit_copy_short_2b: - LEAL -65536(DX), DX - MOVL DX, CX - MOVW $0x001d, (AX) - MOVW DX, 2(AX) - SARL $0x10, CX - MOVB CL, 4(AX) - ADDQ $0x05, BX - ADDQ $0x05, AX - JMP gen_emit_copy_end - -repeat_four_standalone_emit_copy_short_2b: - LEAL -256(DX), DX - MOVW $0x0019, (AX) - MOVW DX, 2(AX) - ADDQ $0x04, BX - ADDQ $0x04, AX - JMP gen_emit_copy_end - -repeat_three_standalone_emit_copy_short_2b: - LEAL -4(DX), DX - MOVW $0x0015, (AX) - MOVB DL, 2(AX) - ADDQ $0x03, BX - ADDQ $0x03, AX - JMP gen_emit_copy_end - -repeat_two_standalone_emit_copy_short_2b: - SHLL $0x02, DX - ORL $0x01, DX - MOVW DX, (AX) - ADDQ $0x02, BX - ADDQ $0x02, AX - JMP gen_emit_copy_end - -repeat_two_offset_standalone_emit_copy_short_2b: - XORQ SI, SI - LEAL 1(SI)(DX*4), DX - MOVB CL, 1(AX) - SARL $0x08, CX - SHLL $0x05, CX - ORL CX, DX - MOVB DL, (AX) - ADDQ $0x02, BX - ADDQ $0x02, AX - JMP gen_emit_copy_end - -long_offset_short_standalone: - MOVB $0xee, (AX) - MOVW CX, 1(AX) - LEAL -60(DX), DX - ADDQ $0x03, AX - ADDQ $0x03, BX - - // emitRepeat -emit_repeat_again_standalone_emit_copy_short: - MOVL DX, SI - LEAL -4(DX), DX - CMPL SI, $0x08 - JBE repeat_two_standalone_emit_copy_short - CMPL SI, $0x0c - JAE cant_repeat_two_offset_standalone_emit_copy_short - CMPL CX, $0x00000800 - JB repeat_two_offset_standalone_emit_copy_short - -cant_repeat_two_offset_standalone_emit_copy_short: - CMPL DX, $0x00000104 - JB repeat_three_standalone_emit_copy_short - CMPL DX, $0x00010100 - JB repeat_four_standalone_emit_copy_short - CMPL DX, $0x0100ffff - JB repeat_five_standalone_emit_copy_short - LEAL -16842747(DX), DX - MOVL $0xfffb001d, (AX) - MOVB $0xff, 4(AX) - ADDQ $0x05, AX - ADDQ $0x05, BX - JMP emit_repeat_again_standalone_emit_copy_short - -repeat_five_standalone_emit_copy_short: - LEAL -65536(DX), DX - MOVL DX, CX - MOVW $0x001d, (AX) - MOVW DX, 2(AX) - SARL $0x10, CX - MOVB CL, 4(AX) - ADDQ $0x05, BX - ADDQ $0x05, AX - JMP gen_emit_copy_end - -repeat_four_standalone_emit_copy_short: - LEAL -256(DX), DX - MOVW $0x0019, (AX) - MOVW DX, 2(AX) - ADDQ $0x04, BX - ADDQ $0x04, AX - JMP gen_emit_copy_end - -repeat_three_standalone_emit_copy_short: - LEAL -4(DX), DX - MOVW $0x0015, (AX) - MOVB DL, 2(AX) - ADDQ $0x03, BX - ADDQ $0x03, AX - JMP gen_emit_copy_end - -repeat_two_standalone_emit_copy_short: - SHLL $0x02, DX - ORL $0x01, DX - MOVW DX, (AX) - ADDQ $0x02, BX - ADDQ $0x02, AX - JMP gen_emit_copy_end - -repeat_two_offset_standalone_emit_copy_short: - XORQ SI, SI - LEAL 1(SI)(DX*4), DX - MOVB CL, 1(AX) - SARL $0x08, CX - SHLL $0x05, CX - ORL CX, DX - MOVB DL, (AX) - ADDQ $0x02, BX - ADDQ $0x02, AX - JMP gen_emit_copy_end - -two_byte_offset_short_standalone: - MOVL DX, SI - SHLL $0x02, SI - CMPL DX, $0x0c - JAE emit_copy_three_standalone - CMPL CX, $0x00000800 - JAE emit_copy_three_standalone - LEAL -15(SI), SI - MOVB CL, 1(AX) - SHRL $0x08, CX - SHLL $0x05, CX - ORL CX, SI - MOVB SI, (AX) - ADDQ $0x02, BX - ADDQ $0x02, AX - JMP gen_emit_copy_end - -emit_copy_three_standalone: - LEAL -2(SI), SI - MOVB SI, (AX) - MOVW CX, 1(AX) - ADDQ $0x03, BX - ADDQ $0x03, AX - -gen_emit_copy_end: - MOVQ BX, ret+40(FP) - RET - -// func emitCopyNoRepeat(dst []byte, offset int, length int) int -TEXT ·emitCopyNoRepeat(SB), NOSPLIT, $0-48 - XORQ BX, BX - MOVQ dst_base+0(FP), AX - MOVQ offset+24(FP), CX - MOVQ length+32(FP), DX - - // emitCopy - CMPL CX, $0x00010000 - JB two_byte_offset_standalone_snappy - -four_bytes_loop_back_standalone_snappy: - CMPL DX, $0x40 - JBE four_bytes_remain_standalone_snappy - MOVB $0xff, (AX) - MOVL CX, 1(AX) - LEAL -64(DX), DX - ADDQ $0x05, BX - ADDQ $0x05, AX - CMPL DX, $0x04 - JB four_bytes_remain_standalone_snappy - JMP four_bytes_loop_back_standalone_snappy - -four_bytes_remain_standalone_snappy: - TESTL DX, DX - JZ gen_emit_copy_end_snappy - XORL SI, SI - LEAL -1(SI)(DX*4), DX - MOVB DL, (AX) - MOVL CX, 1(AX) - ADDQ $0x05, BX - ADDQ $0x05, AX - JMP gen_emit_copy_end_snappy - -two_byte_offset_standalone_snappy: - CMPL DX, $0x40 - JBE two_byte_offset_short_standalone_snappy - MOVB $0xee, (AX) - MOVW CX, 1(AX) - LEAL -60(DX), DX - ADDQ $0x03, AX - ADDQ $0x03, BX - JMP two_byte_offset_standalone_snappy - -two_byte_offset_short_standalone_snappy: - MOVL DX, SI - SHLL $0x02, SI - CMPL DX, $0x0c - JAE emit_copy_three_standalone_snappy - CMPL CX, $0x00000800 - JAE emit_copy_three_standalone_snappy - LEAL -15(SI), SI - MOVB CL, 1(AX) - SHRL $0x08, CX - SHLL $0x05, CX - ORL CX, SI - MOVB SI, (AX) - ADDQ $0x02, BX - ADDQ $0x02, AX - JMP gen_emit_copy_end_snappy - -emit_copy_three_standalone_snappy: - LEAL -2(SI), SI - MOVB SI, (AX) - MOVW CX, 1(AX) - ADDQ $0x03, BX - ADDQ $0x03, AX - -gen_emit_copy_end_snappy: - MOVQ BX, ret+40(FP) - RET - -// func matchLen(a []byte, b []byte) int -// Requires: BMI -TEXT ·matchLen(SB), NOSPLIT, $0-56 - MOVQ a_base+0(FP), AX - MOVQ b_base+24(FP), CX - MOVQ a_len+8(FP), DX - - // matchLen - XORL SI, SI - -matchlen_loopback_16_standalone: - CMPL DX, $0x10 - JB matchlen_match8_standalone - MOVQ (AX)(SI*1), BX - MOVQ 8(AX)(SI*1), DI - XORQ (CX)(SI*1), BX - JNZ matchlen_bsf_8_standalone - XORQ 8(CX)(SI*1), DI - JNZ matchlen_bsf_16standalone - LEAL -16(DX), DX - LEAL 16(SI), SI - JMP matchlen_loopback_16_standalone - -matchlen_bsf_16standalone: -#ifdef GOAMD64_v3 - TZCNTQ DI, DI - -#else - BSFQ DI, DI - -#endif - SARQ $0x03, DI - LEAL 8(SI)(DI*1), SI - JMP gen_match_len_end - -matchlen_match8_standalone: - CMPL DX, $0x08 - JB matchlen_match4_standalone - MOVQ (AX)(SI*1), BX - XORQ (CX)(SI*1), BX - JNZ matchlen_bsf_8_standalone - LEAL -8(DX), DX - LEAL 8(SI), SI - JMP matchlen_match4_standalone - -matchlen_bsf_8_standalone: -#ifdef GOAMD64_v3 - TZCNTQ BX, BX - -#else - BSFQ BX, BX - -#endif - SARQ $0x03, BX - LEAL (SI)(BX*1), SI - JMP gen_match_len_end - -matchlen_match4_standalone: - CMPL DX, $0x04 - JB matchlen_match2_standalone - MOVL (AX)(SI*1), BX - CMPL (CX)(SI*1), BX - JNE matchlen_match2_standalone - LEAL -4(DX), DX - LEAL 4(SI), SI - -matchlen_match2_standalone: - CMPL DX, $0x01 - JE matchlen_match1_standalone - JB gen_match_len_end - MOVW (AX)(SI*1), BX - CMPW (CX)(SI*1), BX - JNE matchlen_match1_standalone - LEAL 2(SI), SI - SUBL $0x02, DX - JZ gen_match_len_end - -matchlen_match1_standalone: - MOVB (AX)(SI*1), BL - CMPB (CX)(SI*1), BL - JNE gen_match_len_end - LEAL 1(SI), SI - -gen_match_len_end: - MOVQ SI, ret+48(FP) - RET - -// func cvtLZ4BlockAsm(dst []byte, src []byte) (uncompressed int, dstUsed int) -// Requires: SSE2 -TEXT ·cvtLZ4BlockAsm(SB), NOSPLIT, $0-64 - XORQ SI, SI - MOVQ dst_base+0(FP), AX - MOVQ dst_len+8(FP), CX - MOVQ src_base+24(FP), DX - MOVQ src_len+32(FP), BX - LEAQ (DX)(BX*1), BX - LEAQ -8(AX)(CX*1), CX - XORQ DI, DI - -lz4_s2_loop: - CMPQ DX, BX - JAE lz4_s2_corrupt - CMPQ AX, CX - JAE lz4_s2_dstfull - MOVBQZX (DX), R8 - MOVQ R8, R9 - MOVQ R8, R10 - SHRQ $0x04, R9 - ANDQ $0x0f, R10 - CMPQ R8, $0xf0 - JB lz4_s2_ll_end - -lz4_s2_ll_loop: - INCQ DX - CMPQ DX, BX - JAE lz4_s2_corrupt - MOVBQZX (DX), R8 - ADDQ R8, R9 - CMPQ R8, $0xff - JEQ lz4_s2_ll_loop - -lz4_s2_ll_end: - LEAQ (DX)(R9*1), R8 - ADDQ $0x04, R10 - CMPQ R8, BX - JAE lz4_s2_corrupt - INCQ DX - INCQ R8 - TESTQ R9, R9 - JZ lz4_s2_lits_done - LEAQ (AX)(R9*1), R11 - CMPQ R11, CX - JAE lz4_s2_dstfull - ADDQ R9, SI - LEAL -1(R9), R11 - CMPL R11, $0x3c - JB one_byte_lz4_s2 - CMPL R11, $0x00000100 - JB two_bytes_lz4_s2 - CMPL R11, $0x00010000 - JB three_bytes_lz4_s2 - CMPL R11, $0x01000000 - JB four_bytes_lz4_s2 - MOVB $0xfc, (AX) - MOVL R11, 1(AX) - ADDQ $0x05, AX - JMP memmove_long_lz4_s2 - -four_bytes_lz4_s2: - MOVL R11, R12 - SHRL $0x10, R12 - MOVB $0xf8, (AX) - MOVW R11, 1(AX) - MOVB R12, 3(AX) - ADDQ $0x04, AX - JMP memmove_long_lz4_s2 - -three_bytes_lz4_s2: - MOVB $0xf4, (AX) - MOVW R11, 1(AX) - ADDQ $0x03, AX - JMP memmove_long_lz4_s2 - -two_bytes_lz4_s2: - MOVB $0xf0, (AX) - MOVB R11, 1(AX) - ADDQ $0x02, AX - CMPL R11, $0x40 - JB memmove_lz4_s2 - JMP memmove_long_lz4_s2 - -one_byte_lz4_s2: - SHLB $0x02, R11 - MOVB R11, (AX) - ADDQ $0x01, AX - -memmove_lz4_s2: - LEAQ (AX)(R9*1), R11 - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_lz4_s2_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_lz4_s2_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_lz4_s2_memmove_move_17through32 - JMP emit_lit_memmove_lz4_s2_memmove_move_33through64 - -emit_lit_memmove_lz4_s2_memmove_move_8: - MOVQ (DX), R12 - MOVQ R12, (AX) - JMP memmove_end_copy_lz4_s2 - -emit_lit_memmove_lz4_s2_memmove_move_8through16: - MOVQ (DX), R12 - MOVQ -8(DX)(R9*1), DX - MOVQ R12, (AX) - MOVQ DX, -8(AX)(R9*1) - JMP memmove_end_copy_lz4_s2 - -emit_lit_memmove_lz4_s2_memmove_move_17through32: - MOVOU (DX), X0 - MOVOU -16(DX)(R9*1), X1 - MOVOU X0, (AX) - MOVOU X1, -16(AX)(R9*1) - JMP memmove_end_copy_lz4_s2 - -emit_lit_memmove_lz4_s2_memmove_move_33through64: - MOVOU (DX), X0 - MOVOU 16(DX), X1 - MOVOU -32(DX)(R9*1), X2 - MOVOU -16(DX)(R9*1), X3 - MOVOU X0, (AX) - MOVOU X1, 16(AX) - MOVOU X2, -32(AX)(R9*1) - MOVOU X3, -16(AX)(R9*1) - -memmove_end_copy_lz4_s2: - MOVQ R11, AX - JMP lz4_s2_lits_emit_done - -memmove_long_lz4_s2: - LEAQ (AX)(R9*1), R11 - - // genMemMoveLong - MOVOU (DX), X0 - MOVOU 16(DX), X1 - MOVOU -32(DX)(R9*1), X2 - MOVOU -16(DX)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ AX, R12 - ANDL $0x0000001f, R12 - MOVQ $0x00000040, R14 - SUBQ R12, R14 - DECQ R13 - JA emit_lit_memmove_long_lz4_s2large_forward_sse_loop_32 - LEAQ -32(DX)(R14*1), R12 - LEAQ -32(AX)(R14*1), R15 - -emit_lit_memmove_long_lz4_s2large_big_loop_back: - MOVOU (R12), X4 - MOVOU 16(R12), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R12 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_lz4_s2large_big_loop_back - -emit_lit_memmove_long_lz4_s2large_forward_sse_loop_32: - MOVOU -32(DX)(R14*1), X4 - MOVOU -16(DX)(R14*1), X5 - MOVOA X4, -32(AX)(R14*1) - MOVOA X5, -16(AX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_lz4_s2large_forward_sse_loop_32 - MOVOU X0, (AX) - MOVOU X1, 16(AX) - MOVOU X2, -32(AX)(R9*1) - MOVOU X3, -16(AX)(R9*1) - MOVQ R11, AX - -lz4_s2_lits_emit_done: - MOVQ R8, DX - -lz4_s2_lits_done: - CMPQ DX, BX - JNE lz4_s2_match - CMPQ R10, $0x04 - JEQ lz4_s2_done - JMP lz4_s2_corrupt - -lz4_s2_match: - LEAQ 2(DX), R8 - CMPQ R8, BX - JAE lz4_s2_corrupt - MOVWQZX (DX), R9 - MOVQ R8, DX - TESTQ R9, R9 - JZ lz4_s2_corrupt - CMPQ R9, SI - JA lz4_s2_corrupt - CMPQ R10, $0x13 - JNE lz4_s2_ml_done - -lz4_s2_ml_loop: - MOVBQZX (DX), R8 - INCQ DX - ADDQ R8, R10 - CMPQ DX, BX - JAE lz4_s2_corrupt - CMPQ R8, $0xff - JEQ lz4_s2_ml_loop - -lz4_s2_ml_done: - ADDQ R10, SI - CMPQ R9, DI - JNE lz4_s2_docopy - - // emitRepeat -emit_repeat_again_lz4_s2: - MOVL R10, R8 - LEAL -4(R10), R10 - CMPL R8, $0x08 - JBE repeat_two_lz4_s2 - CMPL R8, $0x0c - JAE cant_repeat_two_offset_lz4_s2 - CMPL R9, $0x00000800 - JB repeat_two_offset_lz4_s2 - -cant_repeat_two_offset_lz4_s2: - CMPL R10, $0x00000104 - JB repeat_three_lz4_s2 - CMPL R10, $0x00010100 - JB repeat_four_lz4_s2 - CMPL R10, $0x0100ffff - JB repeat_five_lz4_s2 - LEAL -16842747(R10), R10 - MOVL $0xfffb001d, (AX) - MOVB $0xff, 4(AX) - ADDQ $0x05, AX - JMP emit_repeat_again_lz4_s2 - -repeat_five_lz4_s2: - LEAL -65536(R10), R10 - MOVL R10, R9 - MOVW $0x001d, (AX) - MOVW R10, 2(AX) - SARL $0x10, R9 - MOVB R9, 4(AX) - ADDQ $0x05, AX - JMP lz4_s2_loop - -repeat_four_lz4_s2: - LEAL -256(R10), R10 - MOVW $0x0019, (AX) - MOVW R10, 2(AX) - ADDQ $0x04, AX - JMP lz4_s2_loop - -repeat_three_lz4_s2: - LEAL -4(R10), R10 - MOVW $0x0015, (AX) - MOVB R10, 2(AX) - ADDQ $0x03, AX - JMP lz4_s2_loop - -repeat_two_lz4_s2: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (AX) - ADDQ $0x02, AX - JMP lz4_s2_loop - -repeat_two_offset_lz4_s2: - XORQ R8, R8 - LEAL 1(R8)(R10*4), R10 - MOVB R9, 1(AX) - SARL $0x08, R9 - SHLL $0x05, R9 - ORL R9, R10 - MOVB R10, (AX) - ADDQ $0x02, AX - JMP lz4_s2_loop - -lz4_s2_docopy: - MOVQ R9, DI - - // emitCopy - CMPL R10, $0x40 - JBE two_byte_offset_short_lz4_s2 - CMPL R9, $0x00000800 - JAE long_offset_short_lz4_s2 - MOVL $0x00000001, R8 - LEAL 16(R8), R8 - MOVB R9, 1(AX) - MOVL R9, R11 - SHRL $0x08, R11 - SHLL $0x05, R11 - ORL R11, R8 - MOVB R8, (AX) - ADDQ $0x02, AX - SUBL $0x08, R10 - - // emitRepeat - LEAL -4(R10), R10 - JMP cant_repeat_two_offset_lz4_s2_emit_copy_short_2b - -emit_repeat_again_lz4_s2_emit_copy_short_2b: - MOVL R10, R8 - LEAL -4(R10), R10 - CMPL R8, $0x08 - JBE repeat_two_lz4_s2_emit_copy_short_2b - CMPL R8, $0x0c - JAE cant_repeat_two_offset_lz4_s2_emit_copy_short_2b - CMPL R9, $0x00000800 - JB repeat_two_offset_lz4_s2_emit_copy_short_2b - -cant_repeat_two_offset_lz4_s2_emit_copy_short_2b: - CMPL R10, $0x00000104 - JB repeat_three_lz4_s2_emit_copy_short_2b - CMPL R10, $0x00010100 - JB repeat_four_lz4_s2_emit_copy_short_2b - CMPL R10, $0x0100ffff - JB repeat_five_lz4_s2_emit_copy_short_2b - LEAL -16842747(R10), R10 - MOVL $0xfffb001d, (AX) - MOVB $0xff, 4(AX) - ADDQ $0x05, AX - JMP emit_repeat_again_lz4_s2_emit_copy_short_2b - -repeat_five_lz4_s2_emit_copy_short_2b: - LEAL -65536(R10), R10 - MOVL R10, R9 - MOVW $0x001d, (AX) - MOVW R10, 2(AX) - SARL $0x10, R9 - MOVB R9, 4(AX) - ADDQ $0x05, AX - JMP lz4_s2_loop - -repeat_four_lz4_s2_emit_copy_short_2b: - LEAL -256(R10), R10 - MOVW $0x0019, (AX) - MOVW R10, 2(AX) - ADDQ $0x04, AX - JMP lz4_s2_loop - -repeat_three_lz4_s2_emit_copy_short_2b: - LEAL -4(R10), R10 - MOVW $0x0015, (AX) - MOVB R10, 2(AX) - ADDQ $0x03, AX - JMP lz4_s2_loop - -repeat_two_lz4_s2_emit_copy_short_2b: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (AX) - ADDQ $0x02, AX - JMP lz4_s2_loop - -repeat_two_offset_lz4_s2_emit_copy_short_2b: - XORQ R8, R8 - LEAL 1(R8)(R10*4), R10 - MOVB R9, 1(AX) - SARL $0x08, R9 - SHLL $0x05, R9 - ORL R9, R10 - MOVB R10, (AX) - ADDQ $0x02, AX - JMP lz4_s2_loop - -long_offset_short_lz4_s2: - MOVB $0xee, (AX) - MOVW R9, 1(AX) - LEAL -60(R10), R10 - ADDQ $0x03, AX - - // emitRepeat -emit_repeat_again_lz4_s2_emit_copy_short: - MOVL R10, R8 - LEAL -4(R10), R10 - CMPL R8, $0x08 - JBE repeat_two_lz4_s2_emit_copy_short - CMPL R8, $0x0c - JAE cant_repeat_two_offset_lz4_s2_emit_copy_short - CMPL R9, $0x00000800 - JB repeat_two_offset_lz4_s2_emit_copy_short - -cant_repeat_two_offset_lz4_s2_emit_copy_short: - CMPL R10, $0x00000104 - JB repeat_three_lz4_s2_emit_copy_short - CMPL R10, $0x00010100 - JB repeat_four_lz4_s2_emit_copy_short - CMPL R10, $0x0100ffff - JB repeat_five_lz4_s2_emit_copy_short - LEAL -16842747(R10), R10 - MOVL $0xfffb001d, (AX) - MOVB $0xff, 4(AX) - ADDQ $0x05, AX - JMP emit_repeat_again_lz4_s2_emit_copy_short - -repeat_five_lz4_s2_emit_copy_short: - LEAL -65536(R10), R10 - MOVL R10, R9 - MOVW $0x001d, (AX) - MOVW R10, 2(AX) - SARL $0x10, R9 - MOVB R9, 4(AX) - ADDQ $0x05, AX - JMP lz4_s2_loop - -repeat_four_lz4_s2_emit_copy_short: - LEAL -256(R10), R10 - MOVW $0x0019, (AX) - MOVW R10, 2(AX) - ADDQ $0x04, AX - JMP lz4_s2_loop - -repeat_three_lz4_s2_emit_copy_short: - LEAL -4(R10), R10 - MOVW $0x0015, (AX) - MOVB R10, 2(AX) - ADDQ $0x03, AX - JMP lz4_s2_loop - -repeat_two_lz4_s2_emit_copy_short: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (AX) - ADDQ $0x02, AX - JMP lz4_s2_loop - -repeat_two_offset_lz4_s2_emit_copy_short: - XORQ R8, R8 - LEAL 1(R8)(R10*4), R10 - MOVB R9, 1(AX) - SARL $0x08, R9 - SHLL $0x05, R9 - ORL R9, R10 - MOVB R10, (AX) - ADDQ $0x02, AX - JMP lz4_s2_loop - -two_byte_offset_short_lz4_s2: - MOVL R10, R8 - SHLL $0x02, R8 - CMPL R10, $0x0c - JAE emit_copy_three_lz4_s2 - CMPL R9, $0x00000800 - JAE emit_copy_three_lz4_s2 - LEAL -15(R8), R8 - MOVB R9, 1(AX) - SHRL $0x08, R9 - SHLL $0x05, R9 - ORL R9, R8 - MOVB R8, (AX) - ADDQ $0x02, AX - JMP lz4_s2_loop - -emit_copy_three_lz4_s2: - LEAL -2(R8), R8 - MOVB R8, (AX) - MOVW R9, 1(AX) - ADDQ $0x03, AX - JMP lz4_s2_loop - -lz4_s2_done: - MOVQ dst_base+0(FP), CX - SUBQ CX, AX - MOVQ SI, uncompressed+48(FP) - MOVQ AX, dstUsed+56(FP) - RET - -lz4_s2_corrupt: - XORQ AX, AX - LEAQ -1(AX), SI - MOVQ SI, uncompressed+48(FP) - RET - -lz4_s2_dstfull: - XORQ AX, AX - LEAQ -2(AX), SI - MOVQ SI, uncompressed+48(FP) - RET - -// func cvtLZ4sBlockAsm(dst []byte, src []byte) (uncompressed int, dstUsed int) -// Requires: SSE2 -TEXT ·cvtLZ4sBlockAsm(SB), NOSPLIT, $0-64 - XORQ SI, SI - MOVQ dst_base+0(FP), AX - MOVQ dst_len+8(FP), CX - MOVQ src_base+24(FP), DX - MOVQ src_len+32(FP), BX - LEAQ (DX)(BX*1), BX - LEAQ -8(AX)(CX*1), CX - XORQ DI, DI - -lz4s_s2_loop: - CMPQ DX, BX - JAE lz4s_s2_corrupt - CMPQ AX, CX - JAE lz4s_s2_dstfull - MOVBQZX (DX), R8 - MOVQ R8, R9 - MOVQ R8, R10 - SHRQ $0x04, R9 - ANDQ $0x0f, R10 - CMPQ R8, $0xf0 - JB lz4s_s2_ll_end - -lz4s_s2_ll_loop: - INCQ DX - CMPQ DX, BX - JAE lz4s_s2_corrupt - MOVBQZX (DX), R8 - ADDQ R8, R9 - CMPQ R8, $0xff - JEQ lz4s_s2_ll_loop - -lz4s_s2_ll_end: - LEAQ (DX)(R9*1), R8 - ADDQ $0x03, R10 - CMPQ R8, BX - JAE lz4s_s2_corrupt - INCQ DX - INCQ R8 - TESTQ R9, R9 - JZ lz4s_s2_lits_done - LEAQ (AX)(R9*1), R11 - CMPQ R11, CX - JAE lz4s_s2_dstfull - ADDQ R9, SI - LEAL -1(R9), R11 - CMPL R11, $0x3c - JB one_byte_lz4s_s2 - CMPL R11, $0x00000100 - JB two_bytes_lz4s_s2 - CMPL R11, $0x00010000 - JB three_bytes_lz4s_s2 - CMPL R11, $0x01000000 - JB four_bytes_lz4s_s2 - MOVB $0xfc, (AX) - MOVL R11, 1(AX) - ADDQ $0x05, AX - JMP memmove_long_lz4s_s2 - -four_bytes_lz4s_s2: - MOVL R11, R12 - SHRL $0x10, R12 - MOVB $0xf8, (AX) - MOVW R11, 1(AX) - MOVB R12, 3(AX) - ADDQ $0x04, AX - JMP memmove_long_lz4s_s2 - -three_bytes_lz4s_s2: - MOVB $0xf4, (AX) - MOVW R11, 1(AX) - ADDQ $0x03, AX - JMP memmove_long_lz4s_s2 - -two_bytes_lz4s_s2: - MOVB $0xf0, (AX) - MOVB R11, 1(AX) - ADDQ $0x02, AX - CMPL R11, $0x40 - JB memmove_lz4s_s2 - JMP memmove_long_lz4s_s2 - -one_byte_lz4s_s2: - SHLB $0x02, R11 - MOVB R11, (AX) - ADDQ $0x01, AX - -memmove_lz4s_s2: - LEAQ (AX)(R9*1), R11 - - // genMemMoveShort - CMPQ R9, $0x08 - JBE emit_lit_memmove_lz4s_s2_memmove_move_8 - CMPQ R9, $0x10 - JBE emit_lit_memmove_lz4s_s2_memmove_move_8through16 - CMPQ R9, $0x20 - JBE emit_lit_memmove_lz4s_s2_memmove_move_17through32 - JMP emit_lit_memmove_lz4s_s2_memmove_move_33through64 - -emit_lit_memmove_lz4s_s2_memmove_move_8: - MOVQ (DX), R12 - MOVQ R12, (AX) - JMP memmove_end_copy_lz4s_s2 - -emit_lit_memmove_lz4s_s2_memmove_move_8through16: - MOVQ (DX), R12 - MOVQ -8(DX)(R9*1), DX - MOVQ R12, (AX) - MOVQ DX, -8(AX)(R9*1) - JMP memmove_end_copy_lz4s_s2 - -emit_lit_memmove_lz4s_s2_memmove_move_17through32: - MOVOU (DX), X0 - MOVOU -16(DX)(R9*1), X1 - MOVOU X0, (AX) - MOVOU X1, -16(AX)(R9*1) - JMP memmove_end_copy_lz4s_s2 - -emit_lit_memmove_lz4s_s2_memmove_move_33through64: - MOVOU (DX), X0 - MOVOU 16(DX), X1 - MOVOU -32(DX)(R9*1), X2 - MOVOU -16(DX)(R9*1), X3 - MOVOU X0, (AX) - MOVOU X1, 16(AX) - MOVOU X2, -32(AX)(R9*1) - MOVOU X3, -16(AX)(R9*1) - -memmove_end_copy_lz4s_s2: - MOVQ R11, AX - JMP lz4s_s2_lits_emit_done - -memmove_long_lz4s_s2: - LEAQ (AX)(R9*1), R11 - - // genMemMoveLong - MOVOU (DX), X0 - MOVOU 16(DX), X1 - MOVOU -32(DX)(R9*1), X2 - MOVOU -16(DX)(R9*1), X3 - MOVQ R9, R13 - SHRQ $0x05, R13 - MOVQ AX, R12 - ANDL $0x0000001f, R12 - MOVQ $0x00000040, R14 - SUBQ R12, R14 - DECQ R13 - JA emit_lit_memmove_long_lz4s_s2large_forward_sse_loop_32 - LEAQ -32(DX)(R14*1), R12 - LEAQ -32(AX)(R14*1), R15 - -emit_lit_memmove_long_lz4s_s2large_big_loop_back: - MOVOU (R12), X4 - MOVOU 16(R12), X5 - MOVOA X4, (R15) - MOVOA X5, 16(R15) - ADDQ $0x20, R15 - ADDQ $0x20, R12 - ADDQ $0x20, R14 - DECQ R13 - JNA emit_lit_memmove_long_lz4s_s2large_big_loop_back - -emit_lit_memmove_long_lz4s_s2large_forward_sse_loop_32: - MOVOU -32(DX)(R14*1), X4 - MOVOU -16(DX)(R14*1), X5 - MOVOA X4, -32(AX)(R14*1) - MOVOA X5, -16(AX)(R14*1) - ADDQ $0x20, R14 - CMPQ R9, R14 - JAE emit_lit_memmove_long_lz4s_s2large_forward_sse_loop_32 - MOVOU X0, (AX) - MOVOU X1, 16(AX) - MOVOU X2, -32(AX)(R9*1) - MOVOU X3, -16(AX)(R9*1) - MOVQ R11, AX - -lz4s_s2_lits_emit_done: - MOVQ R8, DX - -lz4s_s2_lits_done: - CMPQ DX, BX - JNE lz4s_s2_match - CMPQ R10, $0x03 - JEQ lz4s_s2_done - JMP lz4s_s2_corrupt - -lz4s_s2_match: - CMPQ R10, $0x03 - JEQ lz4s_s2_loop - LEAQ 2(DX), R8 - CMPQ R8, BX - JAE lz4s_s2_corrupt - MOVWQZX (DX), R9 - MOVQ R8, DX - TESTQ R9, R9 - JZ lz4s_s2_corrupt - CMPQ R9, SI - JA lz4s_s2_corrupt - CMPQ R10, $0x12 - JNE lz4s_s2_ml_done - -lz4s_s2_ml_loop: - MOVBQZX (DX), R8 - INCQ DX - ADDQ R8, R10 - CMPQ DX, BX - JAE lz4s_s2_corrupt - CMPQ R8, $0xff - JEQ lz4s_s2_ml_loop - -lz4s_s2_ml_done: - ADDQ R10, SI - CMPQ R9, DI - JNE lz4s_s2_docopy - - // emitRepeat -emit_repeat_again_lz4_s2: - MOVL R10, R8 - LEAL -4(R10), R10 - CMPL R8, $0x08 - JBE repeat_two_lz4_s2 - CMPL R8, $0x0c - JAE cant_repeat_two_offset_lz4_s2 - CMPL R9, $0x00000800 - JB repeat_two_offset_lz4_s2 - -cant_repeat_two_offset_lz4_s2: - CMPL R10, $0x00000104 - JB repeat_three_lz4_s2 - CMPL R10, $0x00010100 - JB repeat_four_lz4_s2 - CMPL R10, $0x0100ffff - JB repeat_five_lz4_s2 - LEAL -16842747(R10), R10 - MOVL $0xfffb001d, (AX) - MOVB $0xff, 4(AX) - ADDQ $0x05, AX - JMP emit_repeat_again_lz4_s2 - -repeat_five_lz4_s2: - LEAL -65536(R10), R10 - MOVL R10, R9 - MOVW $0x001d, (AX) - MOVW R10, 2(AX) - SARL $0x10, R9 - MOVB R9, 4(AX) - ADDQ $0x05, AX - JMP lz4s_s2_loop - -repeat_four_lz4_s2: - LEAL -256(R10), R10 - MOVW $0x0019, (AX) - MOVW R10, 2(AX) - ADDQ $0x04, AX - JMP lz4s_s2_loop - -repeat_three_lz4_s2: - LEAL -4(R10), R10 - MOVW $0x0015, (AX) - MOVB R10, 2(AX) - ADDQ $0x03, AX - JMP lz4s_s2_loop - -repeat_two_lz4_s2: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (AX) - ADDQ $0x02, AX - JMP lz4s_s2_loop - -repeat_two_offset_lz4_s2: - XORQ R8, R8 - LEAL 1(R8)(R10*4), R10 - MOVB R9, 1(AX) - SARL $0x08, R9 - SHLL $0x05, R9 - ORL R9, R10 - MOVB R10, (AX) - ADDQ $0x02, AX - JMP lz4s_s2_loop - -lz4s_s2_docopy: - MOVQ R9, DI - - // emitCopy - CMPL R10, $0x40 - JBE two_byte_offset_short_lz4_s2 - CMPL R9, $0x00000800 - JAE long_offset_short_lz4_s2 - MOVL $0x00000001, R8 - LEAL 16(R8), R8 - MOVB R9, 1(AX) - MOVL R9, R11 - SHRL $0x08, R11 - SHLL $0x05, R11 - ORL R11, R8 - MOVB R8, (AX) - ADDQ $0x02, AX - SUBL $0x08, R10 - - // emitRepeat - LEAL -4(R10), R10 - JMP cant_repeat_two_offset_lz4_s2_emit_copy_short_2b - -emit_repeat_again_lz4_s2_emit_copy_short_2b: - MOVL R10, R8 - LEAL -4(R10), R10 - CMPL R8, $0x08 - JBE repeat_two_lz4_s2_emit_copy_short_2b - CMPL R8, $0x0c - JAE cant_repeat_two_offset_lz4_s2_emit_copy_short_2b - CMPL R9, $0x00000800 - JB repeat_two_offset_lz4_s2_emit_copy_short_2b - -cant_repeat_two_offset_lz4_s2_emit_copy_short_2b: - CMPL R10, $0x00000104 - JB repeat_three_lz4_s2_emit_copy_short_2b - CMPL R10, $0x00010100 - JB repeat_four_lz4_s2_emit_copy_short_2b - CMPL R10, $0x0100ffff - JB repeat_five_lz4_s2_emit_copy_short_2b - LEAL -16842747(R10), R10 - MOVL $0xfffb001d, (AX) - MOVB $0xff, 4(AX) - ADDQ $0x05, AX - JMP emit_repeat_again_lz4_s2_emit_copy_short_2b - -repeat_five_lz4_s2_emit_copy_short_2b: - LEAL -65536(R10), R10 - MOVL R10, R9 - MOVW $0x001d, (AX) - MOVW R10, 2(AX) - SARL $0x10, R9 - MOVB R9, 4(AX) - ADDQ $0x05, AX - JMP lz4s_s2_loop - -repeat_four_lz4_s2_emit_copy_short_2b: - LEAL -256(R10), R10 - MOVW $0x0019, (AX) - MOVW R10, 2(AX) - ADDQ $0x04, AX - JMP lz4s_s2_loop - -repeat_three_lz4_s2_emit_copy_short_2b: - LEAL -4(R10), R10 - MOVW $0x0015, (AX) - MOVB R10, 2(AX) - ADDQ $0x03, AX - JMP lz4s_s2_loop - -repeat_two_lz4_s2_emit_copy_short_2b: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (AX) - ADDQ $0x02, AX - JMP lz4s_s2_loop - -repeat_two_offset_lz4_s2_emit_copy_short_2b: - XORQ R8, R8 - LEAL 1(R8)(R10*4), R10 - MOVB R9, 1(AX) - SARL $0x08, R9 - SHLL $0x05, R9 - ORL R9, R10 - MOVB R10, (AX) - ADDQ $0x02, AX - JMP lz4s_s2_loop - -long_offset_short_lz4_s2: - MOVB $0xee, (AX) - MOVW R9, 1(AX) - LEAL -60(R10), R10 - ADDQ $0x03, AX - - // emitRepeat -emit_repeat_again_lz4_s2_emit_copy_short: - MOVL R10, R8 - LEAL -4(R10), R10 - CMPL R8, $0x08 - JBE repeat_two_lz4_s2_emit_copy_short - CMPL R8, $0x0c - JAE cant_repeat_two_offset_lz4_s2_emit_copy_short - CMPL R9, $0x00000800 - JB repeat_two_offset_lz4_s2_emit_copy_short - -cant_repeat_two_offset_lz4_s2_emit_copy_short: - CMPL R10, $0x00000104 - JB repeat_three_lz4_s2_emit_copy_short - CMPL R10, $0x00010100 - JB repeat_four_lz4_s2_emit_copy_short - CMPL R10, $0x0100ffff - JB repeat_five_lz4_s2_emit_copy_short - LEAL -16842747(R10), R10 - MOVL $0xfffb001d, (AX) - MOVB $0xff, 4(AX) - ADDQ $0x05, AX - JMP emit_repeat_again_lz4_s2_emit_copy_short - -repeat_five_lz4_s2_emit_copy_short: - LEAL -65536(R10), R10 - MOVL R10, R9 - MOVW $0x001d, (AX) - MOVW R10, 2(AX) - SARL $0x10, R9 - MOVB R9, 4(AX) - ADDQ $0x05, AX - JMP lz4s_s2_loop - -repeat_four_lz4_s2_emit_copy_short: - LEAL -256(R10), R10 - MOVW $0x0019, (AX) - MOVW R10, 2(AX) - ADDQ $0x04, AX - JMP lz4s_s2_loop - -repeat_three_lz4_s2_emit_copy_short: - LEAL -4(R10), R10 - MOVW $0x0015, (AX) - MOVB R10, 2(AX) - ADDQ $0x03, AX - JMP lz4s_s2_loop - -repeat_two_lz4_s2_emit_copy_short: - SHLL $0x02, R10 - ORL $0x01, R10 - MOVW R10, (AX) - ADDQ $0x02, AX - JMP lz4s_s2_loop - -repeat_two_offset_lz4_s2_emit_copy_short: - XORQ R8, R8 - LEAL 1(R8)(R10*4), R10 - MOVB R9, 1(AX) - SARL $0x08, R9 - SHLL $0x05, R9 - ORL R9, R10 - MOVB R10, (AX) - ADDQ $0x02, AX - JMP lz4s_s2_loop - -two_byte_offset_short_lz4_s2: - MOVL R10, R8 - SHLL $0x02, R8 - CMPL R10, $0x0c - JAE emit_copy_three_lz4_s2 - CMPL R9, $0x00000800 - JAE emit_copy_three_lz4_s2 - LEAL -15(R8), R8 - MOVB R9, 1(AX) - SHRL $0x08, R9 - SHLL $0x05, R9 - ORL R9, R8 - MOVB R8, (AX) - ADDQ $0x02, AX - JMP lz4s_s2_loop - -emit_copy_three_lz4_s2: - LEAL -2(R8), R8 - MOVB R8, (AX) - MOVW R9, 1(AX) - ADDQ $0x03, AX - JMP lz4s_s2_loop - -lz4s_s2_done: - MOVQ dst_base+0(FP), CX - SUBQ CX, AX - MOVQ SI, uncompressed+48(FP) - MOVQ AX, dstUsed+56(FP) - RET - -lz4s_s2_corrupt: - XORQ AX, AX - LEAQ -1(AX), SI - MOVQ SI, uncompressed+48(FP) - RET - -lz4s_s2_dstfull: - XORQ AX, AX - LEAQ -2(AX), SI - MOVQ SI, uncompressed+48(FP) - RET - -// func cvtLZ4BlockSnappyAsm(dst []byte, src []byte) (uncompressed int, dstUsed int) -// Requires: SSE2 -TEXT ·cvtLZ4BlockSnappyAsm(SB), NOSPLIT, $0-64 - XORQ SI, SI - MOVQ dst_base+0(FP), AX - MOVQ dst_len+8(FP), CX - MOVQ src_base+24(FP), DX - MOVQ src_len+32(FP), BX - LEAQ (DX)(BX*1), BX - LEAQ -8(AX)(CX*1), CX - -lz4_snappy_loop: - CMPQ DX, BX - JAE lz4_snappy_corrupt - CMPQ AX, CX - JAE lz4_snappy_dstfull - MOVBQZX (DX), DI - MOVQ DI, R8 - MOVQ DI, R9 - SHRQ $0x04, R8 - ANDQ $0x0f, R9 - CMPQ DI, $0xf0 - JB lz4_snappy_ll_end - -lz4_snappy_ll_loop: - INCQ DX - CMPQ DX, BX - JAE lz4_snappy_corrupt - MOVBQZX (DX), DI - ADDQ DI, R8 - CMPQ DI, $0xff - JEQ lz4_snappy_ll_loop - -lz4_snappy_ll_end: - LEAQ (DX)(R8*1), DI - ADDQ $0x04, R9 - CMPQ DI, BX - JAE lz4_snappy_corrupt - INCQ DX - INCQ DI - TESTQ R8, R8 - JZ lz4_snappy_lits_done - LEAQ (AX)(R8*1), R10 - CMPQ R10, CX - JAE lz4_snappy_dstfull - ADDQ R8, SI - LEAL -1(R8), R10 - CMPL R10, $0x3c - JB one_byte_lz4_snappy - CMPL R10, $0x00000100 - JB two_bytes_lz4_snappy - CMPL R10, $0x00010000 - JB three_bytes_lz4_snappy - CMPL R10, $0x01000000 - JB four_bytes_lz4_snappy - MOVB $0xfc, (AX) - MOVL R10, 1(AX) - ADDQ $0x05, AX - JMP memmove_long_lz4_snappy - -four_bytes_lz4_snappy: - MOVL R10, R11 - SHRL $0x10, R11 - MOVB $0xf8, (AX) - MOVW R10, 1(AX) - MOVB R11, 3(AX) - ADDQ $0x04, AX - JMP memmove_long_lz4_snappy - -three_bytes_lz4_snappy: - MOVB $0xf4, (AX) - MOVW R10, 1(AX) - ADDQ $0x03, AX - JMP memmove_long_lz4_snappy - -two_bytes_lz4_snappy: - MOVB $0xf0, (AX) - MOVB R10, 1(AX) - ADDQ $0x02, AX - CMPL R10, $0x40 - JB memmove_lz4_snappy - JMP memmove_long_lz4_snappy - -one_byte_lz4_snappy: - SHLB $0x02, R10 - MOVB R10, (AX) - ADDQ $0x01, AX - -memmove_lz4_snappy: - LEAQ (AX)(R8*1), R10 - - // genMemMoveShort - CMPQ R8, $0x08 - JBE emit_lit_memmove_lz4_snappy_memmove_move_8 - CMPQ R8, $0x10 - JBE emit_lit_memmove_lz4_snappy_memmove_move_8through16 - CMPQ R8, $0x20 - JBE emit_lit_memmove_lz4_snappy_memmove_move_17through32 - JMP emit_lit_memmove_lz4_snappy_memmove_move_33through64 - -emit_lit_memmove_lz4_snappy_memmove_move_8: - MOVQ (DX), R11 - MOVQ R11, (AX) - JMP memmove_end_copy_lz4_snappy - -emit_lit_memmove_lz4_snappy_memmove_move_8through16: - MOVQ (DX), R11 - MOVQ -8(DX)(R8*1), DX - MOVQ R11, (AX) - MOVQ DX, -8(AX)(R8*1) - JMP memmove_end_copy_lz4_snappy - -emit_lit_memmove_lz4_snappy_memmove_move_17through32: - MOVOU (DX), X0 - MOVOU -16(DX)(R8*1), X1 - MOVOU X0, (AX) - MOVOU X1, -16(AX)(R8*1) - JMP memmove_end_copy_lz4_snappy - -emit_lit_memmove_lz4_snappy_memmove_move_33through64: - MOVOU (DX), X0 - MOVOU 16(DX), X1 - MOVOU -32(DX)(R8*1), X2 - MOVOU -16(DX)(R8*1), X3 - MOVOU X0, (AX) - MOVOU X1, 16(AX) - MOVOU X2, -32(AX)(R8*1) - MOVOU X3, -16(AX)(R8*1) - -memmove_end_copy_lz4_snappy: - MOVQ R10, AX - JMP lz4_snappy_lits_emit_done - -memmove_long_lz4_snappy: - LEAQ (AX)(R8*1), R10 - - // genMemMoveLong - MOVOU (DX), X0 - MOVOU 16(DX), X1 - MOVOU -32(DX)(R8*1), X2 - MOVOU -16(DX)(R8*1), X3 - MOVQ R8, R12 - SHRQ $0x05, R12 - MOVQ AX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R13 - SUBQ R11, R13 - DECQ R12 - JA emit_lit_memmove_long_lz4_snappylarge_forward_sse_loop_32 - LEAQ -32(DX)(R13*1), R11 - LEAQ -32(AX)(R13*1), R14 - -emit_lit_memmove_long_lz4_snappylarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R14) - MOVOA X5, 16(R14) - ADDQ $0x20, R14 - ADDQ $0x20, R11 - ADDQ $0x20, R13 - DECQ R12 - JNA emit_lit_memmove_long_lz4_snappylarge_big_loop_back - -emit_lit_memmove_long_lz4_snappylarge_forward_sse_loop_32: - MOVOU -32(DX)(R13*1), X4 - MOVOU -16(DX)(R13*1), X5 - MOVOA X4, -32(AX)(R13*1) - MOVOA X5, -16(AX)(R13*1) - ADDQ $0x20, R13 - CMPQ R8, R13 - JAE emit_lit_memmove_long_lz4_snappylarge_forward_sse_loop_32 - MOVOU X0, (AX) - MOVOU X1, 16(AX) - MOVOU X2, -32(AX)(R8*1) - MOVOU X3, -16(AX)(R8*1) - MOVQ R10, AX - -lz4_snappy_lits_emit_done: - MOVQ DI, DX - -lz4_snappy_lits_done: - CMPQ DX, BX - JNE lz4_snappy_match - CMPQ R9, $0x04 - JEQ lz4_snappy_done - JMP lz4_snappy_corrupt - -lz4_snappy_match: - LEAQ 2(DX), DI - CMPQ DI, BX - JAE lz4_snappy_corrupt - MOVWQZX (DX), R8 - MOVQ DI, DX - TESTQ R8, R8 - JZ lz4_snappy_corrupt - CMPQ R8, SI - JA lz4_snappy_corrupt - CMPQ R9, $0x13 - JNE lz4_snappy_ml_done - -lz4_snappy_ml_loop: - MOVBQZX (DX), DI - INCQ DX - ADDQ DI, R9 - CMPQ DX, BX - JAE lz4_snappy_corrupt - CMPQ DI, $0xff - JEQ lz4_snappy_ml_loop - -lz4_snappy_ml_done: - ADDQ R9, SI - - // emitCopy -two_byte_offset_lz4_s2: - CMPL R9, $0x40 - JBE two_byte_offset_short_lz4_s2 - MOVB $0xee, (AX) - MOVW R8, 1(AX) - LEAL -60(R9), R9 - ADDQ $0x03, AX - CMPQ AX, CX - JAE lz4_snappy_loop - JMP two_byte_offset_lz4_s2 - -two_byte_offset_short_lz4_s2: - MOVL R9, DI - SHLL $0x02, DI - CMPL R9, $0x0c - JAE emit_copy_three_lz4_s2 - CMPL R8, $0x00000800 - JAE emit_copy_three_lz4_s2 - LEAL -15(DI), DI - MOVB R8, 1(AX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, DI - MOVB DI, (AX) - ADDQ $0x02, AX - JMP lz4_snappy_loop - -emit_copy_three_lz4_s2: - LEAL -2(DI), DI - MOVB DI, (AX) - MOVW R8, 1(AX) - ADDQ $0x03, AX - JMP lz4_snappy_loop - -lz4_snappy_done: - MOVQ dst_base+0(FP), CX - SUBQ CX, AX - MOVQ SI, uncompressed+48(FP) - MOVQ AX, dstUsed+56(FP) - RET - -lz4_snappy_corrupt: - XORQ AX, AX - LEAQ -1(AX), SI - MOVQ SI, uncompressed+48(FP) - RET - -lz4_snappy_dstfull: - XORQ AX, AX - LEAQ -2(AX), SI - MOVQ SI, uncompressed+48(FP) - RET - -// func cvtLZ4sBlockSnappyAsm(dst []byte, src []byte) (uncompressed int, dstUsed int) -// Requires: SSE2 -TEXT ·cvtLZ4sBlockSnappyAsm(SB), NOSPLIT, $0-64 - XORQ SI, SI - MOVQ dst_base+0(FP), AX - MOVQ dst_len+8(FP), CX - MOVQ src_base+24(FP), DX - MOVQ src_len+32(FP), BX - LEAQ (DX)(BX*1), BX - LEAQ -8(AX)(CX*1), CX - -lz4s_snappy_loop: - CMPQ DX, BX - JAE lz4s_snappy_corrupt - CMPQ AX, CX - JAE lz4s_snappy_dstfull - MOVBQZX (DX), DI - MOVQ DI, R8 - MOVQ DI, R9 - SHRQ $0x04, R8 - ANDQ $0x0f, R9 - CMPQ DI, $0xf0 - JB lz4s_snappy_ll_end - -lz4s_snappy_ll_loop: - INCQ DX - CMPQ DX, BX - JAE lz4s_snappy_corrupt - MOVBQZX (DX), DI - ADDQ DI, R8 - CMPQ DI, $0xff - JEQ lz4s_snappy_ll_loop - -lz4s_snappy_ll_end: - LEAQ (DX)(R8*1), DI - ADDQ $0x03, R9 - CMPQ DI, BX - JAE lz4s_snappy_corrupt - INCQ DX - INCQ DI - TESTQ R8, R8 - JZ lz4s_snappy_lits_done - LEAQ (AX)(R8*1), R10 - CMPQ R10, CX - JAE lz4s_snappy_dstfull - ADDQ R8, SI - LEAL -1(R8), R10 - CMPL R10, $0x3c - JB one_byte_lz4s_snappy - CMPL R10, $0x00000100 - JB two_bytes_lz4s_snappy - CMPL R10, $0x00010000 - JB three_bytes_lz4s_snappy - CMPL R10, $0x01000000 - JB four_bytes_lz4s_snappy - MOVB $0xfc, (AX) - MOVL R10, 1(AX) - ADDQ $0x05, AX - JMP memmove_long_lz4s_snappy - -four_bytes_lz4s_snappy: - MOVL R10, R11 - SHRL $0x10, R11 - MOVB $0xf8, (AX) - MOVW R10, 1(AX) - MOVB R11, 3(AX) - ADDQ $0x04, AX - JMP memmove_long_lz4s_snappy - -three_bytes_lz4s_snappy: - MOVB $0xf4, (AX) - MOVW R10, 1(AX) - ADDQ $0x03, AX - JMP memmove_long_lz4s_snappy - -two_bytes_lz4s_snappy: - MOVB $0xf0, (AX) - MOVB R10, 1(AX) - ADDQ $0x02, AX - CMPL R10, $0x40 - JB memmove_lz4s_snappy - JMP memmove_long_lz4s_snappy - -one_byte_lz4s_snappy: - SHLB $0x02, R10 - MOVB R10, (AX) - ADDQ $0x01, AX - -memmove_lz4s_snappy: - LEAQ (AX)(R8*1), R10 - - // genMemMoveShort - CMPQ R8, $0x08 - JBE emit_lit_memmove_lz4s_snappy_memmove_move_8 - CMPQ R8, $0x10 - JBE emit_lit_memmove_lz4s_snappy_memmove_move_8through16 - CMPQ R8, $0x20 - JBE emit_lit_memmove_lz4s_snappy_memmove_move_17through32 - JMP emit_lit_memmove_lz4s_snappy_memmove_move_33through64 - -emit_lit_memmove_lz4s_snappy_memmove_move_8: - MOVQ (DX), R11 - MOVQ R11, (AX) - JMP memmove_end_copy_lz4s_snappy - -emit_lit_memmove_lz4s_snappy_memmove_move_8through16: - MOVQ (DX), R11 - MOVQ -8(DX)(R8*1), DX - MOVQ R11, (AX) - MOVQ DX, -8(AX)(R8*1) - JMP memmove_end_copy_lz4s_snappy - -emit_lit_memmove_lz4s_snappy_memmove_move_17through32: - MOVOU (DX), X0 - MOVOU -16(DX)(R8*1), X1 - MOVOU X0, (AX) - MOVOU X1, -16(AX)(R8*1) - JMP memmove_end_copy_lz4s_snappy - -emit_lit_memmove_lz4s_snappy_memmove_move_33through64: - MOVOU (DX), X0 - MOVOU 16(DX), X1 - MOVOU -32(DX)(R8*1), X2 - MOVOU -16(DX)(R8*1), X3 - MOVOU X0, (AX) - MOVOU X1, 16(AX) - MOVOU X2, -32(AX)(R8*1) - MOVOU X3, -16(AX)(R8*1) - -memmove_end_copy_lz4s_snappy: - MOVQ R10, AX - JMP lz4s_snappy_lits_emit_done - -memmove_long_lz4s_snappy: - LEAQ (AX)(R8*1), R10 - - // genMemMoveLong - MOVOU (DX), X0 - MOVOU 16(DX), X1 - MOVOU -32(DX)(R8*1), X2 - MOVOU -16(DX)(R8*1), X3 - MOVQ R8, R12 - SHRQ $0x05, R12 - MOVQ AX, R11 - ANDL $0x0000001f, R11 - MOVQ $0x00000040, R13 - SUBQ R11, R13 - DECQ R12 - JA emit_lit_memmove_long_lz4s_snappylarge_forward_sse_loop_32 - LEAQ -32(DX)(R13*1), R11 - LEAQ -32(AX)(R13*1), R14 - -emit_lit_memmove_long_lz4s_snappylarge_big_loop_back: - MOVOU (R11), X4 - MOVOU 16(R11), X5 - MOVOA X4, (R14) - MOVOA X5, 16(R14) - ADDQ $0x20, R14 - ADDQ $0x20, R11 - ADDQ $0x20, R13 - DECQ R12 - JNA emit_lit_memmove_long_lz4s_snappylarge_big_loop_back - -emit_lit_memmove_long_lz4s_snappylarge_forward_sse_loop_32: - MOVOU -32(DX)(R13*1), X4 - MOVOU -16(DX)(R13*1), X5 - MOVOA X4, -32(AX)(R13*1) - MOVOA X5, -16(AX)(R13*1) - ADDQ $0x20, R13 - CMPQ R8, R13 - JAE emit_lit_memmove_long_lz4s_snappylarge_forward_sse_loop_32 - MOVOU X0, (AX) - MOVOU X1, 16(AX) - MOVOU X2, -32(AX)(R8*1) - MOVOU X3, -16(AX)(R8*1) - MOVQ R10, AX - -lz4s_snappy_lits_emit_done: - MOVQ DI, DX - -lz4s_snappy_lits_done: - CMPQ DX, BX - JNE lz4s_snappy_match - CMPQ R9, $0x03 - JEQ lz4s_snappy_done - JMP lz4s_snappy_corrupt - -lz4s_snappy_match: - CMPQ R9, $0x03 - JEQ lz4s_snappy_loop - LEAQ 2(DX), DI - CMPQ DI, BX - JAE lz4s_snappy_corrupt - MOVWQZX (DX), R8 - MOVQ DI, DX - TESTQ R8, R8 - JZ lz4s_snappy_corrupt - CMPQ R8, SI - JA lz4s_snappy_corrupt - CMPQ R9, $0x12 - JNE lz4s_snappy_ml_done - -lz4s_snappy_ml_loop: - MOVBQZX (DX), DI - INCQ DX - ADDQ DI, R9 - CMPQ DX, BX - JAE lz4s_snappy_corrupt - CMPQ DI, $0xff - JEQ lz4s_snappy_ml_loop - -lz4s_snappy_ml_done: - ADDQ R9, SI - - // emitCopy -two_byte_offset_lz4_s2: - CMPL R9, $0x40 - JBE two_byte_offset_short_lz4_s2 - MOVB $0xee, (AX) - MOVW R8, 1(AX) - LEAL -60(R9), R9 - ADDQ $0x03, AX - CMPQ AX, CX - JAE lz4s_snappy_loop - JMP two_byte_offset_lz4_s2 - -two_byte_offset_short_lz4_s2: - MOVL R9, DI - SHLL $0x02, DI - CMPL R9, $0x0c - JAE emit_copy_three_lz4_s2 - CMPL R8, $0x00000800 - JAE emit_copy_three_lz4_s2 - LEAL -15(DI), DI - MOVB R8, 1(AX) - SHRL $0x08, R8 - SHLL $0x05, R8 - ORL R8, DI - MOVB DI, (AX) - ADDQ $0x02, AX - JMP lz4s_snappy_loop - -emit_copy_three_lz4_s2: - LEAL -2(DI), DI - MOVB DI, (AX) - MOVW R8, 1(AX) - ADDQ $0x03, AX - JMP lz4s_snappy_loop - -lz4s_snappy_done: - MOVQ dst_base+0(FP), CX - SUBQ CX, AX - MOVQ SI, uncompressed+48(FP) - MOVQ AX, dstUsed+56(FP) - RET - -lz4s_snappy_corrupt: - XORQ AX, AX - LEAQ -1(AX), SI - MOVQ SI, uncompressed+48(FP) - RET - -lz4s_snappy_dstfull: - XORQ AX, AX - LEAQ -2(AX), SI - MOVQ SI, uncompressed+48(FP) - RET diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/index.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/index.go deleted file mode 100644 index 4229957b..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/index.go +++ /dev/null @@ -1,602 +0,0 @@ -// Copyright (c) 2022+ Klaus Post. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package s2 - -import ( - "bytes" - "encoding/binary" - "encoding/json" - "fmt" - "io" - "sort" -) - -const ( - S2IndexHeader = "s2idx\x00" - S2IndexTrailer = "\x00xdi2s" - maxIndexEntries = 1 << 16 - // If distance is less than this, we do not add the entry. - minIndexDist = 1 << 20 -) - -// Index represents an S2/Snappy index. -type Index struct { - TotalUncompressed int64 // Total Uncompressed size if known. Will be -1 if unknown. - TotalCompressed int64 // Total Compressed size if known. Will be -1 if unknown. - info []struct { - compressedOffset int64 - uncompressedOffset int64 - } - estBlockUncomp int64 -} - -func (i *Index) reset(maxBlock int) { - i.estBlockUncomp = int64(maxBlock) - i.TotalCompressed = -1 - i.TotalUncompressed = -1 - if len(i.info) > 0 { - i.info = i.info[:0] - } -} - -// allocInfos will allocate an empty slice of infos. -func (i *Index) allocInfos(n int) { - if n > maxIndexEntries { - panic("n > maxIndexEntries") - } - i.info = make([]struct { - compressedOffset int64 - uncompressedOffset int64 - }, 0, n) -} - -// add an uncompressed and compressed pair. -// Entries must be sent in order. -func (i *Index) add(compressedOffset, uncompressedOffset int64) error { - if i == nil { - return nil - } - lastIdx := len(i.info) - 1 - if lastIdx >= 0 { - latest := i.info[lastIdx] - if latest.uncompressedOffset == uncompressedOffset { - // Uncompressed didn't change, don't add entry, - // but update start index. - latest.compressedOffset = compressedOffset - i.info[lastIdx] = latest - return nil - } - if latest.uncompressedOffset > uncompressedOffset { - return fmt.Errorf("internal error: Earlier uncompressed received (%d > %d)", latest.uncompressedOffset, uncompressedOffset) - } - if latest.compressedOffset > compressedOffset { - return fmt.Errorf("internal error: Earlier compressed received (%d > %d)", latest.uncompressedOffset, uncompressedOffset) - } - if latest.uncompressedOffset+minIndexDist > uncompressedOffset { - // Only add entry if distance is large enough. - return nil - } - } - i.info = append(i.info, struct { - compressedOffset int64 - uncompressedOffset int64 - }{compressedOffset: compressedOffset, uncompressedOffset: uncompressedOffset}) - return nil -} - -// Find the offset at or before the wanted (uncompressed) offset. -// If offset is 0 or positive it is the offset from the beginning of the file. -// If the uncompressed size is known, the offset must be within the file. -// If an offset outside the file is requested io.ErrUnexpectedEOF is returned. -// If the offset is negative, it is interpreted as the distance from the end of the file, -// where -1 represents the last byte. -// If offset from the end of the file is requested, but size is unknown, -// ErrUnsupported will be returned. -func (i *Index) Find(offset int64) (compressedOff, uncompressedOff int64, err error) { - if i.TotalUncompressed < 0 { - return 0, 0, ErrCorrupt - } - if offset < 0 { - offset = i.TotalUncompressed + offset - if offset < 0 { - return 0, 0, io.ErrUnexpectedEOF - } - } - if offset > i.TotalUncompressed { - return 0, 0, io.ErrUnexpectedEOF - } - if len(i.info) > 200 { - n := sort.Search(len(i.info), func(n int) bool { - return i.info[n].uncompressedOffset > offset - }) - if n == 0 { - n = 1 - } - return i.info[n-1].compressedOffset, i.info[n-1].uncompressedOffset, nil - } - for _, info := range i.info { - if info.uncompressedOffset > offset { - break - } - compressedOff = info.compressedOffset - uncompressedOff = info.uncompressedOffset - } - return compressedOff, uncompressedOff, nil -} - -// reduce to stay below maxIndexEntries -func (i *Index) reduce() { - if len(i.info) < maxIndexEntries && i.estBlockUncomp >= minIndexDist { - return - } - - // Algorithm, keep 1, remove removeN entries... - removeN := (len(i.info) + 1) / maxIndexEntries - src := i.info - j := 0 - - // Each block should be at least 1MB, but don't reduce below 1000 entries. - for i.estBlockUncomp*(int64(removeN)+1) < minIndexDist && len(i.info)/(removeN+1) > 1000 { - removeN++ - } - for idx := 0; idx < len(src); idx++ { - i.info[j] = src[idx] - j++ - idx += removeN - } - i.info = i.info[:j] - // Update maxblock estimate. - i.estBlockUncomp += i.estBlockUncomp * int64(removeN) -} - -func (i *Index) appendTo(b []byte, uncompTotal, compTotal int64) []byte { - i.reduce() - var tmp [binary.MaxVarintLen64]byte - - initSize := len(b) - // We make the start a skippable header+size. - b = append(b, ChunkTypeIndex, 0, 0, 0) - b = append(b, []byte(S2IndexHeader)...) - // Total Uncompressed size - n := binary.PutVarint(tmp[:], uncompTotal) - b = append(b, tmp[:n]...) - // Total Compressed size - n = binary.PutVarint(tmp[:], compTotal) - b = append(b, tmp[:n]...) - // Put EstBlockUncomp size - n = binary.PutVarint(tmp[:], i.estBlockUncomp) - b = append(b, tmp[:n]...) - // Put length - n = binary.PutVarint(tmp[:], int64(len(i.info))) - b = append(b, tmp[:n]...) - - // Check if we should add uncompressed offsets - var hasUncompressed byte - for idx, info := range i.info { - if idx == 0 { - if info.uncompressedOffset != 0 { - hasUncompressed = 1 - break - } - continue - } - if info.uncompressedOffset != i.info[idx-1].uncompressedOffset+i.estBlockUncomp { - hasUncompressed = 1 - break - } - } - b = append(b, hasUncompressed) - - // Add each entry - if hasUncompressed == 1 { - for idx, info := range i.info { - uOff := info.uncompressedOffset - if idx > 0 { - prev := i.info[idx-1] - uOff -= prev.uncompressedOffset + (i.estBlockUncomp) - } - n = binary.PutVarint(tmp[:], uOff) - b = append(b, tmp[:n]...) - } - } - - // Initial compressed size estimate. - cPredict := i.estBlockUncomp / 2 - - for idx, info := range i.info { - cOff := info.compressedOffset - if idx > 0 { - prev := i.info[idx-1] - cOff -= prev.compressedOffset + cPredict - // Update compressed size prediction, with half the error. - cPredict += cOff / 2 - } - n = binary.PutVarint(tmp[:], cOff) - b = append(b, tmp[:n]...) - } - - // Add Total Size. - // Stored as fixed size for easier reading. - binary.LittleEndian.PutUint32(tmp[:], uint32(len(b)-initSize+4+len(S2IndexTrailer))) - b = append(b, tmp[:4]...) - // Trailer - b = append(b, []byte(S2IndexTrailer)...) - - // Update size - chunkLen := len(b) - initSize - skippableFrameHeader - b[initSize+1] = uint8(chunkLen >> 0) - b[initSize+2] = uint8(chunkLen >> 8) - b[initSize+3] = uint8(chunkLen >> 16) - //fmt.Printf("chunklen: 0x%x Uncomp:%d, Comp:%d\n", chunkLen, uncompTotal, compTotal) - return b -} - -// Load a binary index. -// A zero value Index can be used or a previous one can be reused. -func (i *Index) Load(b []byte) ([]byte, error) { - if len(b) <= 4+len(S2IndexHeader)+len(S2IndexTrailer) { - return b, io.ErrUnexpectedEOF - } - if b[0] != ChunkTypeIndex { - return b, ErrCorrupt - } - chunkLen := int(b[1]) | int(b[2])<<8 | int(b[3])<<16 - b = b[4:] - - // Validate we have enough... - if len(b) < chunkLen { - return b, io.ErrUnexpectedEOF - } - if !bytes.Equal(b[:len(S2IndexHeader)], []byte(S2IndexHeader)) { - return b, ErrUnsupported - } - b = b[len(S2IndexHeader):] - - // Total Uncompressed - if v, n := binary.Varint(b); n <= 0 || v < 0 { - return b, ErrCorrupt - } else { - i.TotalUncompressed = v - b = b[n:] - } - - // Total Compressed - if v, n := binary.Varint(b); n <= 0 { - return b, ErrCorrupt - } else { - i.TotalCompressed = v - b = b[n:] - } - - // Read EstBlockUncomp - if v, n := binary.Varint(b); n <= 0 { - return b, ErrCorrupt - } else { - if v < 0 { - return b, ErrCorrupt - } - i.estBlockUncomp = v - b = b[n:] - } - - var entries int - if v, n := binary.Varint(b); n <= 0 { - return b, ErrCorrupt - } else { - if v < 0 || v > maxIndexEntries { - return b, ErrCorrupt - } - entries = int(v) - b = b[n:] - } - if cap(i.info) < entries { - i.allocInfos(entries) - } - i.info = i.info[:entries] - - if len(b) < 1 { - return b, io.ErrUnexpectedEOF - } - hasUncompressed := b[0] - b = b[1:] - if hasUncompressed&1 != hasUncompressed { - return b, ErrCorrupt - } - - // Add each uncompressed entry - for idx := range i.info { - var uOff int64 - if hasUncompressed != 0 { - // Load delta - if v, n := binary.Varint(b); n <= 0 { - return b, ErrCorrupt - } else { - uOff = v - b = b[n:] - } - } - - if idx > 0 { - prev := i.info[idx-1].uncompressedOffset - uOff += prev + (i.estBlockUncomp) - if uOff <= prev { - return b, ErrCorrupt - } - } - if uOff < 0 { - return b, ErrCorrupt - } - i.info[idx].uncompressedOffset = uOff - } - - // Initial compressed size estimate. - cPredict := i.estBlockUncomp / 2 - - // Add each compressed entry - for idx := range i.info { - var cOff int64 - if v, n := binary.Varint(b); n <= 0 { - return b, ErrCorrupt - } else { - cOff = v - b = b[n:] - } - - if idx > 0 { - // Update compressed size prediction, with half the error. - cPredictNew := cPredict + cOff/2 - - prev := i.info[idx-1].compressedOffset - cOff += prev + cPredict - if cOff <= prev { - return b, ErrCorrupt - } - cPredict = cPredictNew - } - if cOff < 0 { - return b, ErrCorrupt - } - i.info[idx].compressedOffset = cOff - } - if len(b) < 4+len(S2IndexTrailer) { - return b, io.ErrUnexpectedEOF - } - // Skip size... - b = b[4:] - - // Check trailer... - if !bytes.Equal(b[:len(S2IndexTrailer)], []byte(S2IndexTrailer)) { - return b, ErrCorrupt - } - return b[len(S2IndexTrailer):], nil -} - -// LoadStream will load an index from the end of the supplied stream. -// ErrUnsupported will be returned if the signature cannot be found. -// ErrCorrupt will be returned if unexpected values are found. -// io.ErrUnexpectedEOF is returned if there are too few bytes. -// IO errors are returned as-is. -func (i *Index) LoadStream(rs io.ReadSeeker) error { - // Go to end. - _, err := rs.Seek(-10, io.SeekEnd) - if err != nil { - return err - } - var tmp [10]byte - _, err = io.ReadFull(rs, tmp[:]) - if err != nil { - return err - } - // Check trailer... - if !bytes.Equal(tmp[4:4+len(S2IndexTrailer)], []byte(S2IndexTrailer)) { - return ErrUnsupported - } - sz := binary.LittleEndian.Uint32(tmp[:4]) - if sz > maxChunkSize+skippableFrameHeader { - return ErrCorrupt - } - _, err = rs.Seek(-int64(sz), io.SeekEnd) - if err != nil { - return err - } - - // Read index. - buf := make([]byte, sz) - _, err = io.ReadFull(rs, buf) - if err != nil { - return err - } - _, err = i.Load(buf) - return err -} - -// IndexStream will return an index for a stream. -// The stream structure will be checked, but -// data within blocks is not verified. -// The returned index can either be appended to the end of the stream -// or stored separately. -func IndexStream(r io.Reader) ([]byte, error) { - var i Index - var buf [maxChunkSize]byte - var readHeader bool - for { - _, err := io.ReadFull(r, buf[:4]) - if err != nil { - if err == io.EOF { - return i.appendTo(nil, i.TotalUncompressed, i.TotalCompressed), nil - } - return nil, err - } - // Start of this chunk. - startChunk := i.TotalCompressed - i.TotalCompressed += 4 - - chunkType := buf[0] - if !readHeader { - if chunkType != chunkTypeStreamIdentifier { - return nil, ErrCorrupt - } - readHeader = true - } - chunkLen := int(buf[1]) | int(buf[2])<<8 | int(buf[3])<<16 - if chunkLen < checksumSize { - return nil, ErrCorrupt - } - - i.TotalCompressed += int64(chunkLen) - _, err = io.ReadFull(r, buf[:chunkLen]) - if err != nil { - return nil, io.ErrUnexpectedEOF - } - // The chunk types are specified at - // https://github.com/google/snappy/blob/master/framing_format.txt - switch chunkType { - case chunkTypeCompressedData: - // Section 4.2. Compressed data (chunk type 0x00). - // Skip checksum. - dLen, err := DecodedLen(buf[checksumSize:]) - if err != nil { - return nil, err - } - if dLen > maxBlockSize { - return nil, ErrCorrupt - } - if i.estBlockUncomp == 0 { - // Use first block for estimate... - i.estBlockUncomp = int64(dLen) - } - err = i.add(startChunk, i.TotalUncompressed) - if err != nil { - return nil, err - } - i.TotalUncompressed += int64(dLen) - continue - case chunkTypeUncompressedData: - n2 := chunkLen - checksumSize - if n2 > maxBlockSize { - return nil, ErrCorrupt - } - if i.estBlockUncomp == 0 { - // Use first block for estimate... - i.estBlockUncomp = int64(n2) - } - err = i.add(startChunk, i.TotalUncompressed) - if err != nil { - return nil, err - } - i.TotalUncompressed += int64(n2) - continue - case chunkTypeStreamIdentifier: - // Section 4.1. Stream identifier (chunk type 0xff). - if chunkLen != len(magicBody) { - return nil, ErrCorrupt - } - - if string(buf[:len(magicBody)]) != magicBody { - if string(buf[:len(magicBody)]) != magicBodySnappy { - return nil, ErrCorrupt - } - } - - continue - } - - if chunkType <= 0x7f { - // Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f). - return nil, ErrUnsupported - } - if chunkLen > maxChunkSize { - return nil, ErrUnsupported - } - // Section 4.4 Padding (chunk type 0xfe). - // Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd). - } -} - -// JSON returns the index as JSON text. -func (i *Index) JSON() []byte { - type offset struct { - CompressedOffset int64 `json:"compressed"` - UncompressedOffset int64 `json:"uncompressed"` - } - x := struct { - TotalUncompressed int64 `json:"total_uncompressed"` // Total Uncompressed size if known. Will be -1 if unknown. - TotalCompressed int64 `json:"total_compressed"` // Total Compressed size if known. Will be -1 if unknown. - Offsets []offset `json:"offsets"` - EstBlockUncomp int64 `json:"est_block_uncompressed"` - }{ - TotalUncompressed: i.TotalUncompressed, - TotalCompressed: i.TotalCompressed, - EstBlockUncomp: i.estBlockUncomp, - } - for _, v := range i.info { - x.Offsets = append(x.Offsets, offset{CompressedOffset: v.compressedOffset, UncompressedOffset: v.uncompressedOffset}) - } - b, _ := json.MarshalIndent(x, "", " ") - return b -} - -// RemoveIndexHeaders will trim all headers and trailers from a given index. -// This is expected to save 20 bytes. -// These can be restored using RestoreIndexHeaders. -// This removes a layer of security, but is the most compact representation. -// Returns nil if headers contains errors. -// The returned slice references the provided slice. -func RemoveIndexHeaders(b []byte) []byte { - const save = 4 + len(S2IndexHeader) + len(S2IndexTrailer) + 4 - if len(b) <= save { - return nil - } - if b[0] != ChunkTypeIndex { - return nil - } - chunkLen := int(b[1]) | int(b[2])<<8 | int(b[3])<<16 - b = b[4:] - - // Validate we have enough... - if len(b) < chunkLen { - return nil - } - b = b[:chunkLen] - - if !bytes.Equal(b[:len(S2IndexHeader)], []byte(S2IndexHeader)) { - return nil - } - b = b[len(S2IndexHeader):] - if !bytes.HasSuffix(b, []byte(S2IndexTrailer)) { - return nil - } - b = bytes.TrimSuffix(b, []byte(S2IndexTrailer)) - - if len(b) < 4 { - return nil - } - return b[:len(b)-4] -} - -// RestoreIndexHeaders will index restore headers removed by RemoveIndexHeaders. -// No error checking is performed on the input. -// If a 0 length slice is sent, it is returned without modification. -func RestoreIndexHeaders(in []byte) []byte { - if len(in) == 0 { - return in - } - b := make([]byte, 0, 4+len(S2IndexHeader)+len(in)+len(S2IndexTrailer)+4) - b = append(b, ChunkTypeIndex, 0, 0, 0) - b = append(b, []byte(S2IndexHeader)...) - b = append(b, in...) - - var tmp [4]byte - binary.LittleEndian.PutUint32(tmp[:], uint32(len(b)+4+len(S2IndexTrailer))) - b = append(b, tmp[:4]...) - // Trailer - b = append(b, []byte(S2IndexTrailer)...) - - chunkLen := len(b) - skippableFrameHeader - b[1] = uint8(chunkLen >> 0) - b[2] = uint8(chunkLen >> 8) - b[3] = uint8(chunkLen >> 16) - return b -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/lz4convert.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/lz4convert.go deleted file mode 100644 index 46ed908e..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/lz4convert.go +++ /dev/null @@ -1,585 +0,0 @@ -// Copyright (c) 2022 Klaus Post. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package s2 - -import ( - "encoding/binary" - "errors" - "fmt" -) - -// LZ4Converter provides conversion from LZ4 blocks as defined here: -// https://github.com/lz4/lz4/blob/dev/doc/lz4_Block_format.md -type LZ4Converter struct { -} - -// ErrDstTooSmall is returned when provided destination is too small. -var ErrDstTooSmall = errors.New("s2: destination too small") - -// ConvertBlock will convert an LZ4 block and append it as an S2 -// block without block length to dst. -// The uncompressed size is returned as well. -// dst must have capacity to contain the entire compressed block. -func (l *LZ4Converter) ConvertBlock(dst, src []byte) ([]byte, int, error) { - if len(src) == 0 { - return dst, 0, nil - } - const debug = false - const inline = true - const lz4MinMatch = 4 - - s, d := 0, len(dst) - dst = dst[:cap(dst)] - if !debug && hasAmd64Asm { - res, sz := cvtLZ4BlockAsm(dst[d:], src) - if res < 0 { - const ( - errCorrupt = -1 - errDstTooSmall = -2 - ) - switch res { - case errCorrupt: - return nil, 0, ErrCorrupt - case errDstTooSmall: - return nil, 0, ErrDstTooSmall - default: - return nil, 0, fmt.Errorf("unexpected result: %d", res) - } - } - if d+sz > len(dst) { - return nil, 0, ErrDstTooSmall - } - return dst[:d+sz], res, nil - } - - dLimit := len(dst) - 10 - var lastOffset uint16 - var uncompressed int - if debug { - fmt.Printf("convert block start: len(src): %d, len(dst):%d \n", len(src), len(dst)) - } - - for { - if s >= len(src) { - return dst[:d], 0, ErrCorrupt - } - // Read literal info - token := src[s] - ll := int(token >> 4) - ml := int(lz4MinMatch + (token & 0xf)) - - // If upper nibble is 15, literal length is extended - if token >= 0xf0 { - for { - s++ - if s >= len(src) { - if debug { - fmt.Printf("error reading ll: s (%d) >= len(src) (%d)\n", s, len(src)) - } - return dst[:d], 0, ErrCorrupt - } - val := src[s] - ll += int(val) - if val != 255 { - break - } - } - } - // Skip past token - if s+ll >= len(src) { - if debug { - fmt.Printf("error literals: s+ll (%d+%d) >= len(src) (%d)\n", s, ll, len(src)) - } - return nil, 0, ErrCorrupt - } - s++ - if ll > 0 { - if d+ll > dLimit { - return nil, 0, ErrDstTooSmall - } - if debug { - fmt.Printf("emit %d literals\n", ll) - } - d += emitLiteralGo(dst[d:], src[s:s+ll]) - s += ll - uncompressed += ll - } - - // Check if we are done... - if s == len(src) && ml == lz4MinMatch { - break - } - // 2 byte offset - if s >= len(src)-2 { - if debug { - fmt.Printf("s (%d) >= len(src)-2 (%d)", s, len(src)-2) - } - return nil, 0, ErrCorrupt - } - offset := binary.LittleEndian.Uint16(src[s:]) - s += 2 - if offset == 0 { - if debug { - fmt.Printf("error: offset 0, ml: %d, len(src)-s: %d\n", ml, len(src)-s) - } - return nil, 0, ErrCorrupt - } - if int(offset) > uncompressed { - if debug { - fmt.Printf("error: offset (%d)> uncompressed (%d)\n", offset, uncompressed) - } - return nil, 0, ErrCorrupt - } - - if ml == lz4MinMatch+15 { - for { - if s >= len(src) { - if debug { - fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", s, len(src)) - } - return nil, 0, ErrCorrupt - } - val := src[s] - s++ - ml += int(val) - if val != 255 { - if s >= len(src) { - if debug { - fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", s, len(src)) - } - return nil, 0, ErrCorrupt - } - break - } - } - } - if offset == lastOffset { - if debug { - fmt.Printf("emit repeat, length: %d, offset: %d\n", ml, offset) - } - if !inline { - d += emitRepeat16(dst[d:], offset, ml) - } else { - length := ml - dst := dst[d:] - for len(dst) > 5 { - // Repeat offset, make length cheaper - length -= 4 - if length <= 4 { - dst[0] = uint8(length)<<2 | tagCopy1 - dst[1] = 0 - d += 2 - break - } - if length < 8 && offset < 2048 { - // Encode WITH offset - dst[1] = uint8(offset) - dst[0] = uint8(offset>>8)<<5 | uint8(length)<<2 | tagCopy1 - d += 2 - break - } - if length < (1<<8)+4 { - length -= 4 - dst[2] = uint8(length) - dst[1] = 0 - dst[0] = 5<<2 | tagCopy1 - d += 3 - break - } - if length < (1<<16)+(1<<8) { - length -= 1 << 8 - dst[3] = uint8(length >> 8) - dst[2] = uint8(length >> 0) - dst[1] = 0 - dst[0] = 6<<2 | tagCopy1 - d += 4 - break - } - const maxRepeat = (1 << 24) - 1 - length -= 1 << 16 - left := 0 - if length > maxRepeat { - left = length - maxRepeat + 4 - length = maxRepeat - 4 - } - dst[4] = uint8(length >> 16) - dst[3] = uint8(length >> 8) - dst[2] = uint8(length >> 0) - dst[1] = 0 - dst[0] = 7<<2 | tagCopy1 - if left > 0 { - d += 5 + emitRepeat16(dst[5:], offset, left) - break - } - d += 5 - break - } - } - } else { - if debug { - fmt.Printf("emit copy, length: %d, offset: %d\n", ml, offset) - } - if !inline { - d += emitCopy16(dst[d:], offset, ml) - } else { - length := ml - dst := dst[d:] - for len(dst) > 5 { - // Offset no more than 2 bytes. - if length > 64 { - off := 3 - if offset < 2048 { - // emit 8 bytes as tagCopy1, rest as repeats. - dst[1] = uint8(offset) - dst[0] = uint8(offset>>8)<<5 | uint8(8-4)<<2 | tagCopy1 - length -= 8 - off = 2 - } else { - // Emit a length 60 copy, encoded as 3 bytes. - // Emit remaining as repeat value (minimum 4 bytes). - dst[2] = uint8(offset >> 8) - dst[1] = uint8(offset) - dst[0] = 59<<2 | tagCopy2 - length -= 60 - } - // Emit remaining as repeats, at least 4 bytes remain. - d += off + emitRepeat16(dst[off:], offset, length) - break - } - if length >= 12 || offset >= 2048 { - // Emit the remaining copy, encoded as 3 bytes. - dst[2] = uint8(offset >> 8) - dst[1] = uint8(offset) - dst[0] = uint8(length-1)<<2 | tagCopy2 - d += 3 - break - } - // Emit the remaining copy, encoded as 2 bytes. - dst[1] = uint8(offset) - dst[0] = uint8(offset>>8)<<5 | uint8(length-4)<<2 | tagCopy1 - d += 2 - break - } - } - lastOffset = offset - } - uncompressed += ml - if d > dLimit { - return nil, 0, ErrDstTooSmall - } - } - - return dst[:d], uncompressed, nil -} - -// ConvertBlockSnappy will convert an LZ4 block and append it -// as a Snappy block without block length to dst. -// The uncompressed size is returned as well. -// dst must have capacity to contain the entire compressed block. -func (l *LZ4Converter) ConvertBlockSnappy(dst, src []byte) ([]byte, int, error) { - if len(src) == 0 { - return dst, 0, nil - } - const debug = false - const lz4MinMatch = 4 - - s, d := 0, len(dst) - dst = dst[:cap(dst)] - // Use assembly when possible - if !debug && hasAmd64Asm { - res, sz := cvtLZ4BlockSnappyAsm(dst[d:], src) - if res < 0 { - const ( - errCorrupt = -1 - errDstTooSmall = -2 - ) - switch res { - case errCorrupt: - return nil, 0, ErrCorrupt - case errDstTooSmall: - return nil, 0, ErrDstTooSmall - default: - return nil, 0, fmt.Errorf("unexpected result: %d", res) - } - } - if d+sz > len(dst) { - return nil, 0, ErrDstTooSmall - } - return dst[:d+sz], res, nil - } - - dLimit := len(dst) - 10 - var uncompressed int - if debug { - fmt.Printf("convert block start: len(src): %d, len(dst):%d \n", len(src), len(dst)) - } - - for { - if s >= len(src) { - return nil, 0, ErrCorrupt - } - // Read literal info - token := src[s] - ll := int(token >> 4) - ml := int(lz4MinMatch + (token & 0xf)) - - // If upper nibble is 15, literal length is extended - if token >= 0xf0 { - for { - s++ - if s >= len(src) { - if debug { - fmt.Printf("error reading ll: s (%d) >= len(src) (%d)\n", s, len(src)) - } - return nil, 0, ErrCorrupt - } - val := src[s] - ll += int(val) - if val != 255 { - break - } - } - } - // Skip past token - if s+ll >= len(src) { - if debug { - fmt.Printf("error literals: s+ll (%d+%d) >= len(src) (%d)\n", s, ll, len(src)) - } - return nil, 0, ErrCorrupt - } - s++ - if ll > 0 { - if d+ll > dLimit { - return nil, 0, ErrDstTooSmall - } - if debug { - fmt.Printf("emit %d literals\n", ll) - } - d += emitLiteralGo(dst[d:], src[s:s+ll]) - s += ll - uncompressed += ll - } - - // Check if we are done... - if s == len(src) && ml == lz4MinMatch { - break - } - // 2 byte offset - if s >= len(src)-2 { - if debug { - fmt.Printf("s (%d) >= len(src)-2 (%d)", s, len(src)-2) - } - return nil, 0, ErrCorrupt - } - offset := binary.LittleEndian.Uint16(src[s:]) - s += 2 - if offset == 0 { - if debug { - fmt.Printf("error: offset 0, ml: %d, len(src)-s: %d\n", ml, len(src)-s) - } - return nil, 0, ErrCorrupt - } - if int(offset) > uncompressed { - if debug { - fmt.Printf("error: offset (%d)> uncompressed (%d)\n", offset, uncompressed) - } - return nil, 0, ErrCorrupt - } - - if ml == lz4MinMatch+15 { - for { - if s >= len(src) { - if debug { - fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", s, len(src)) - } - return nil, 0, ErrCorrupt - } - val := src[s] - s++ - ml += int(val) - if val != 255 { - if s >= len(src) { - if debug { - fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", s, len(src)) - } - return nil, 0, ErrCorrupt - } - break - } - } - } - if debug { - fmt.Printf("emit copy, length: %d, offset: %d\n", ml, offset) - } - length := ml - // d += emitCopyNoRepeat(dst[d:], int(offset), ml) - for length > 0 { - if d >= dLimit { - return nil, 0, ErrDstTooSmall - } - - // Offset no more than 2 bytes. - if length > 64 { - // Emit a length 64 copy, encoded as 3 bytes. - dst[d+2] = uint8(offset >> 8) - dst[d+1] = uint8(offset) - dst[d+0] = 63<<2 | tagCopy2 - length -= 64 - d += 3 - continue - } - if length >= 12 || offset >= 2048 || length < 4 { - // Emit the remaining copy, encoded as 3 bytes. - dst[d+2] = uint8(offset >> 8) - dst[d+1] = uint8(offset) - dst[d+0] = uint8(length-1)<<2 | tagCopy2 - d += 3 - break - } - // Emit the remaining copy, encoded as 2 bytes. - dst[d+1] = uint8(offset) - dst[d+0] = uint8(offset>>8)<<5 | uint8(length-4)<<2 | tagCopy1 - d += 2 - break - } - uncompressed += ml - if d > dLimit { - return nil, 0, ErrDstTooSmall - } - } - - return dst[:d], uncompressed, nil -} - -// emitRepeat writes a repeat chunk and returns the number of bytes written. -// Length must be at least 4 and < 1<<24 -func emitRepeat16(dst []byte, offset uint16, length int) int { - // Repeat offset, make length cheaper - length -= 4 - if length <= 4 { - dst[0] = uint8(length)<<2 | tagCopy1 - dst[1] = 0 - return 2 - } - if length < 8 && offset < 2048 { - // Encode WITH offset - dst[1] = uint8(offset) - dst[0] = uint8(offset>>8)<<5 | uint8(length)<<2 | tagCopy1 - return 2 - } - if length < (1<<8)+4 { - length -= 4 - dst[2] = uint8(length) - dst[1] = 0 - dst[0] = 5<<2 | tagCopy1 - return 3 - } - if length < (1<<16)+(1<<8) { - length -= 1 << 8 - dst[3] = uint8(length >> 8) - dst[2] = uint8(length >> 0) - dst[1] = 0 - dst[0] = 6<<2 | tagCopy1 - return 4 - } - const maxRepeat = (1 << 24) - 1 - length -= 1 << 16 - left := 0 - if length > maxRepeat { - left = length - maxRepeat + 4 - length = maxRepeat - 4 - } - dst[4] = uint8(length >> 16) - dst[3] = uint8(length >> 8) - dst[2] = uint8(length >> 0) - dst[1] = 0 - dst[0] = 7<<2 | tagCopy1 - if left > 0 { - return 5 + emitRepeat16(dst[5:], offset, left) - } - return 5 -} - -// emitCopy writes a copy chunk and returns the number of bytes written. -// -// It assumes that: -// -// dst is long enough to hold the encoded bytes -// 1 <= offset && offset <= math.MaxUint16 -// 4 <= length && length <= math.MaxUint32 -func emitCopy16(dst []byte, offset uint16, length int) int { - // Offset no more than 2 bytes. - if length > 64 { - off := 3 - if offset < 2048 { - // emit 8 bytes as tagCopy1, rest as repeats. - dst[1] = uint8(offset) - dst[0] = uint8(offset>>8)<<5 | uint8(8-4)<<2 | tagCopy1 - length -= 8 - off = 2 - } else { - // Emit a length 60 copy, encoded as 3 bytes. - // Emit remaining as repeat value (minimum 4 bytes). - dst[2] = uint8(offset >> 8) - dst[1] = uint8(offset) - dst[0] = 59<<2 | tagCopy2 - length -= 60 - } - // Emit remaining as repeats, at least 4 bytes remain. - return off + emitRepeat16(dst[off:], offset, length) - } - if length >= 12 || offset >= 2048 { - // Emit the remaining copy, encoded as 3 bytes. - dst[2] = uint8(offset >> 8) - dst[1] = uint8(offset) - dst[0] = uint8(length-1)<<2 | tagCopy2 - return 3 - } - // Emit the remaining copy, encoded as 2 bytes. - dst[1] = uint8(offset) - dst[0] = uint8(offset>>8)<<5 | uint8(length-4)<<2 | tagCopy1 - return 2 -} - -// emitLiteral writes a literal chunk and returns the number of bytes written. -// -// It assumes that: -// -// dst is long enough to hold the encoded bytes -// 0 <= len(lit) && len(lit) <= math.MaxUint32 -func emitLiteralGo(dst, lit []byte) int { - if len(lit) == 0 { - return 0 - } - i, n := 0, uint(len(lit)-1) - switch { - case n < 60: - dst[0] = uint8(n)<<2 | tagLiteral - i = 1 - case n < 1<<8: - dst[1] = uint8(n) - dst[0] = 60<<2 | tagLiteral - i = 2 - case n < 1<<16: - dst[2] = uint8(n >> 8) - dst[1] = uint8(n) - dst[0] = 61<<2 | tagLiteral - i = 3 - case n < 1<<24: - dst[3] = uint8(n >> 16) - dst[2] = uint8(n >> 8) - dst[1] = uint8(n) - dst[0] = 62<<2 | tagLiteral - i = 4 - default: - dst[4] = uint8(n >> 24) - dst[3] = uint8(n >> 16) - dst[2] = uint8(n >> 8) - dst[1] = uint8(n) - dst[0] = 63<<2 | tagLiteral - i = 5 - } - return i + copy(dst[i:], lit) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/lz4sconvert.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/lz4sconvert.go deleted file mode 100644 index 000f3971..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/lz4sconvert.go +++ /dev/null @@ -1,467 +0,0 @@ -// Copyright (c) 2022 Klaus Post. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package s2 - -import ( - "encoding/binary" - "fmt" -) - -// LZ4sConverter provides conversion from LZ4s. -// (Intel modified LZ4 Blocks) -// https://cdrdv2-public.intel.com/743912/743912-qat-programmers-guide-v2.0.pdf -// LZ4s is a variant of LZ4 block format. LZ4s should be considered as an intermediate compressed block format. -// The LZ4s format is selected when the application sets the compType to CPA_DC_LZ4S in CpaDcSessionSetupData. -// The LZ4s block returned by the Intel® QAT hardware can be used by an external -// software post-processing to generate other compressed data formats. -// The following table lists the differences between LZ4 and LZ4s block format. LZ4s block format uses -// the same high-level formatting as LZ4 block format with the following encoding changes: -// For Min Match of 4 bytes, Copy length value 1-15 means length 4-18 with 18 bytes adding an extra byte. -// ONLY "Min match of 4 bytes" is supported. -type LZ4sConverter struct { -} - -// ConvertBlock will convert an LZ4s block and append it as an S2 -// block without block length to dst. -// The uncompressed size is returned as well. -// dst must have capacity to contain the entire compressed block. -func (l *LZ4sConverter) ConvertBlock(dst, src []byte) ([]byte, int, error) { - if len(src) == 0 { - return dst, 0, nil - } - const debug = false - const inline = true - const lz4MinMatch = 3 - - s, d := 0, len(dst) - dst = dst[:cap(dst)] - if !debug && hasAmd64Asm { - res, sz := cvtLZ4sBlockAsm(dst[d:], src) - if res < 0 { - const ( - errCorrupt = -1 - errDstTooSmall = -2 - ) - switch res { - case errCorrupt: - return nil, 0, ErrCorrupt - case errDstTooSmall: - return nil, 0, ErrDstTooSmall - default: - return nil, 0, fmt.Errorf("unexpected result: %d", res) - } - } - if d+sz > len(dst) { - return nil, 0, ErrDstTooSmall - } - return dst[:d+sz], res, nil - } - - dLimit := len(dst) - 10 - var lastOffset uint16 - var uncompressed int - if debug { - fmt.Printf("convert block start: len(src): %d, len(dst):%d \n", len(src), len(dst)) - } - - for { - if s >= len(src) { - return dst[:d], 0, ErrCorrupt - } - // Read literal info - token := src[s] - ll := int(token >> 4) - ml := int(lz4MinMatch + (token & 0xf)) - - // If upper nibble is 15, literal length is extended - if token >= 0xf0 { - for { - s++ - if s >= len(src) { - if debug { - fmt.Printf("error reading ll: s (%d) >= len(src) (%d)\n", s, len(src)) - } - return dst[:d], 0, ErrCorrupt - } - val := src[s] - ll += int(val) - if val != 255 { - break - } - } - } - // Skip past token - if s+ll >= len(src) { - if debug { - fmt.Printf("error literals: s+ll (%d+%d) >= len(src) (%d)\n", s, ll, len(src)) - } - return nil, 0, ErrCorrupt - } - s++ - if ll > 0 { - if d+ll > dLimit { - return nil, 0, ErrDstTooSmall - } - if debug { - fmt.Printf("emit %d literals\n", ll) - } - d += emitLiteralGo(dst[d:], src[s:s+ll]) - s += ll - uncompressed += ll - } - - // Check if we are done... - if ml == lz4MinMatch { - if s == len(src) { - break - } - // 0 bytes. - continue - } - // 2 byte offset - if s >= len(src)-2 { - if debug { - fmt.Printf("s (%d) >= len(src)-2 (%d)", s, len(src)-2) - } - return nil, 0, ErrCorrupt - } - offset := binary.LittleEndian.Uint16(src[s:]) - s += 2 - if offset == 0 { - if debug { - fmt.Printf("error: offset 0, ml: %d, len(src)-s: %d\n", ml, len(src)-s) - } - return nil, 0, ErrCorrupt - } - if int(offset) > uncompressed { - if debug { - fmt.Printf("error: offset (%d)> uncompressed (%d)\n", offset, uncompressed) - } - return nil, 0, ErrCorrupt - } - - if ml == lz4MinMatch+15 { - for { - if s >= len(src) { - if debug { - fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", s, len(src)) - } - return nil, 0, ErrCorrupt - } - val := src[s] - s++ - ml += int(val) - if val != 255 { - if s >= len(src) { - if debug { - fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", s, len(src)) - } - return nil, 0, ErrCorrupt - } - break - } - } - } - if offset == lastOffset { - if debug { - fmt.Printf("emit repeat, length: %d, offset: %d\n", ml, offset) - } - if !inline { - d += emitRepeat16(dst[d:], offset, ml) - } else { - length := ml - dst := dst[d:] - for len(dst) > 5 { - // Repeat offset, make length cheaper - length -= 4 - if length <= 4 { - dst[0] = uint8(length)<<2 | tagCopy1 - dst[1] = 0 - d += 2 - break - } - if length < 8 && offset < 2048 { - // Encode WITH offset - dst[1] = uint8(offset) - dst[0] = uint8(offset>>8)<<5 | uint8(length)<<2 | tagCopy1 - d += 2 - break - } - if length < (1<<8)+4 { - length -= 4 - dst[2] = uint8(length) - dst[1] = 0 - dst[0] = 5<<2 | tagCopy1 - d += 3 - break - } - if length < (1<<16)+(1<<8) { - length -= 1 << 8 - dst[3] = uint8(length >> 8) - dst[2] = uint8(length >> 0) - dst[1] = 0 - dst[0] = 6<<2 | tagCopy1 - d += 4 - break - } - const maxRepeat = (1 << 24) - 1 - length -= 1 << 16 - left := 0 - if length > maxRepeat { - left = length - maxRepeat + 4 - length = maxRepeat - 4 - } - dst[4] = uint8(length >> 16) - dst[3] = uint8(length >> 8) - dst[2] = uint8(length >> 0) - dst[1] = 0 - dst[0] = 7<<2 | tagCopy1 - if left > 0 { - d += 5 + emitRepeat16(dst[5:], offset, left) - break - } - d += 5 - break - } - } - } else { - if debug { - fmt.Printf("emit copy, length: %d, offset: %d\n", ml, offset) - } - if !inline { - d += emitCopy16(dst[d:], offset, ml) - } else { - length := ml - dst := dst[d:] - for len(dst) > 5 { - // Offset no more than 2 bytes. - if length > 64 { - off := 3 - if offset < 2048 { - // emit 8 bytes as tagCopy1, rest as repeats. - dst[1] = uint8(offset) - dst[0] = uint8(offset>>8)<<5 | uint8(8-4)<<2 | tagCopy1 - length -= 8 - off = 2 - } else { - // Emit a length 60 copy, encoded as 3 bytes. - // Emit remaining as repeat value (minimum 4 bytes). - dst[2] = uint8(offset >> 8) - dst[1] = uint8(offset) - dst[0] = 59<<2 | tagCopy2 - length -= 60 - } - // Emit remaining as repeats, at least 4 bytes remain. - d += off + emitRepeat16(dst[off:], offset, length) - break - } - if length >= 12 || offset >= 2048 { - // Emit the remaining copy, encoded as 3 bytes. - dst[2] = uint8(offset >> 8) - dst[1] = uint8(offset) - dst[0] = uint8(length-1)<<2 | tagCopy2 - d += 3 - break - } - // Emit the remaining copy, encoded as 2 bytes. - dst[1] = uint8(offset) - dst[0] = uint8(offset>>8)<<5 | uint8(length-4)<<2 | tagCopy1 - d += 2 - break - } - } - lastOffset = offset - } - uncompressed += ml - if d > dLimit { - return nil, 0, ErrDstTooSmall - } - } - - return dst[:d], uncompressed, nil -} - -// ConvertBlockSnappy will convert an LZ4s block and append it -// as a Snappy block without block length to dst. -// The uncompressed size is returned as well. -// dst must have capacity to contain the entire compressed block. -func (l *LZ4sConverter) ConvertBlockSnappy(dst, src []byte) ([]byte, int, error) { - if len(src) == 0 { - return dst, 0, nil - } - const debug = false - const lz4MinMatch = 3 - - s, d := 0, len(dst) - dst = dst[:cap(dst)] - // Use assembly when possible - if !debug && hasAmd64Asm { - res, sz := cvtLZ4sBlockSnappyAsm(dst[d:], src) - if res < 0 { - const ( - errCorrupt = -1 - errDstTooSmall = -2 - ) - switch res { - case errCorrupt: - return nil, 0, ErrCorrupt - case errDstTooSmall: - return nil, 0, ErrDstTooSmall - default: - return nil, 0, fmt.Errorf("unexpected result: %d", res) - } - } - if d+sz > len(dst) { - return nil, 0, ErrDstTooSmall - } - return dst[:d+sz], res, nil - } - - dLimit := len(dst) - 10 - var uncompressed int - if debug { - fmt.Printf("convert block start: len(src): %d, len(dst):%d \n", len(src), len(dst)) - } - - for { - if s >= len(src) { - return nil, 0, ErrCorrupt - } - // Read literal info - token := src[s] - ll := int(token >> 4) - ml := int(lz4MinMatch + (token & 0xf)) - - // If upper nibble is 15, literal length is extended - if token >= 0xf0 { - for { - s++ - if s >= len(src) { - if debug { - fmt.Printf("error reading ll: s (%d) >= len(src) (%d)\n", s, len(src)) - } - return nil, 0, ErrCorrupt - } - val := src[s] - ll += int(val) - if val != 255 { - break - } - } - } - // Skip past token - if s+ll >= len(src) { - if debug { - fmt.Printf("error literals: s+ll (%d+%d) >= len(src) (%d)\n", s, ll, len(src)) - } - return nil, 0, ErrCorrupt - } - s++ - if ll > 0 { - if d+ll > dLimit { - return nil, 0, ErrDstTooSmall - } - if debug { - fmt.Printf("emit %d literals\n", ll) - } - d += emitLiteralGo(dst[d:], src[s:s+ll]) - s += ll - uncompressed += ll - } - - // Check if we are done... - if ml == lz4MinMatch { - if s == len(src) { - break - } - // 0 bytes. - continue - } - // 2 byte offset - if s >= len(src)-2 { - if debug { - fmt.Printf("s (%d) >= len(src)-2 (%d)", s, len(src)-2) - } - return nil, 0, ErrCorrupt - } - offset := binary.LittleEndian.Uint16(src[s:]) - s += 2 - if offset == 0 { - if debug { - fmt.Printf("error: offset 0, ml: %d, len(src)-s: %d\n", ml, len(src)-s) - } - return nil, 0, ErrCorrupt - } - if int(offset) > uncompressed { - if debug { - fmt.Printf("error: offset (%d)> uncompressed (%d)\n", offset, uncompressed) - } - return nil, 0, ErrCorrupt - } - - if ml == lz4MinMatch+15 { - for { - if s >= len(src) { - if debug { - fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", s, len(src)) - } - return nil, 0, ErrCorrupt - } - val := src[s] - s++ - ml += int(val) - if val != 255 { - if s >= len(src) { - if debug { - fmt.Printf("error reading ml: s (%d) >= len(src) (%d)\n", s, len(src)) - } - return nil, 0, ErrCorrupt - } - break - } - } - } - if debug { - fmt.Printf("emit copy, length: %d, offset: %d\n", ml, offset) - } - length := ml - // d += emitCopyNoRepeat(dst[d:], int(offset), ml) - for length > 0 { - if d >= dLimit { - return nil, 0, ErrDstTooSmall - } - - // Offset no more than 2 bytes. - if length > 64 { - // Emit a length 64 copy, encoded as 3 bytes. - dst[d+2] = uint8(offset >> 8) - dst[d+1] = uint8(offset) - dst[d+0] = 63<<2 | tagCopy2 - length -= 64 - d += 3 - continue - } - if length >= 12 || offset >= 2048 || length < 4 { - // Emit the remaining copy, encoded as 3 bytes. - dst[d+2] = uint8(offset >> 8) - dst[d+1] = uint8(offset) - dst[d+0] = uint8(length-1)<<2 | tagCopy2 - d += 3 - break - } - // Emit the remaining copy, encoded as 2 bytes. - dst[d+1] = uint8(offset) - dst[d+0] = uint8(offset>>8)<<5 | uint8(length-4)<<2 | tagCopy1 - d += 2 - break - } - uncompressed += ml - if d > dLimit { - return nil, 0, ErrDstTooSmall - } - } - - return dst[:d], uncompressed, nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/reader.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/reader.go deleted file mode 100644 index 8372d752..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/reader.go +++ /dev/null @@ -1,1075 +0,0 @@ -// Copyright 2011 The Snappy-Go Authors. All rights reserved. -// Copyright (c) 2019+ Klaus Post. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package s2 - -import ( - "errors" - "fmt" - "io" - "io/ioutil" - "math" - "runtime" - "sync" -) - -// ErrCantSeek is returned if the stream cannot be seeked. -type ErrCantSeek struct { - Reason string -} - -// Error returns the error as string. -func (e ErrCantSeek) Error() string { - return fmt.Sprintf("s2: Can't seek because %s", e.Reason) -} - -// NewReader returns a new Reader that decompresses from r, using the framing -// format described at -// https://github.com/google/snappy/blob/master/framing_format.txt with S2 changes. -func NewReader(r io.Reader, opts ...ReaderOption) *Reader { - nr := Reader{ - r: r, - maxBlock: maxBlockSize, - } - for _, opt := range opts { - if err := opt(&nr); err != nil { - nr.err = err - return &nr - } - } - nr.maxBufSize = MaxEncodedLen(nr.maxBlock) + checksumSize - if nr.lazyBuf > 0 { - nr.buf = make([]byte, MaxEncodedLen(nr.lazyBuf)+checksumSize) - } else { - nr.buf = make([]byte, MaxEncodedLen(defaultBlockSize)+checksumSize) - } - nr.readHeader = nr.ignoreStreamID - nr.paramsOK = true - return &nr -} - -// ReaderOption is an option for creating a decoder. -type ReaderOption func(*Reader) error - -// ReaderMaxBlockSize allows to control allocations if the stream -// has been compressed with a smaller WriterBlockSize, or with the default 1MB. -// Blocks must be this size or smaller to decompress, -// otherwise the decoder will return ErrUnsupported. -// -// For streams compressed with Snappy this can safely be set to 64KB (64 << 10). -// -// Default is the maximum limit of 4MB. -func ReaderMaxBlockSize(blockSize int) ReaderOption { - return func(r *Reader) error { - if blockSize > maxBlockSize || blockSize <= 0 { - return errors.New("s2: block size too large. Must be <= 4MB and > 0") - } - if r.lazyBuf == 0 && blockSize < defaultBlockSize { - r.lazyBuf = blockSize - } - r.maxBlock = blockSize - return nil - } -} - -// ReaderAllocBlock allows to control upfront stream allocations -// and not allocate for frames bigger than this initially. -// If frames bigger than this is seen a bigger buffer will be allocated. -// -// Default is 1MB, which is default output size. -func ReaderAllocBlock(blockSize int) ReaderOption { - return func(r *Reader) error { - if blockSize > maxBlockSize || blockSize < 1024 { - return errors.New("s2: invalid ReaderAllocBlock. Must be <= 4MB and >= 1024") - } - r.lazyBuf = blockSize - return nil - } -} - -// ReaderIgnoreStreamIdentifier will make the reader skip the expected -// stream identifier at the beginning of the stream. -// This can be used when serving a stream that has been forwarded to a specific point. -func ReaderIgnoreStreamIdentifier() ReaderOption { - return func(r *Reader) error { - r.ignoreStreamID = true - return nil - } -} - -// ReaderSkippableCB will register a callback for chuncks with the specified ID. -// ID must be a Reserved skippable chunks ID, 0x80-0xfd (inclusive). -// For each chunk with the ID, the callback is called with the content. -// Any returned non-nil error will abort decompression. -// Only one callback per ID is supported, latest sent will be used. -// You can peek the stream, triggering the callback, by doing a Read with a 0 -// byte buffer. -func ReaderSkippableCB(id uint8, fn func(r io.Reader) error) ReaderOption { - return func(r *Reader) error { - if id < 0x80 || id > 0xfd { - return fmt.Errorf("ReaderSkippableCB: Invalid id provided, must be 0x80-0xfd (inclusive)") - } - r.skippableCB[id-0x80] = fn - return nil - } -} - -// ReaderIgnoreCRC will make the reader skip CRC calculation and checks. -func ReaderIgnoreCRC() ReaderOption { - return func(r *Reader) error { - r.ignoreCRC = true - return nil - } -} - -// Reader is an io.Reader that can read Snappy-compressed bytes. -type Reader struct { - r io.Reader - err error - decoded []byte - buf []byte - skippableCB [0xff - 0x80]func(r io.Reader) error - blockStart int64 // Uncompressed offset at start of current. - index *Index - - // decoded[i:j] contains decoded bytes that have not yet been passed on. - i, j int - // maximum block size allowed. - maxBlock int - // maximum expected buffer size. - maxBufSize int - // alloc a buffer this size if > 0. - lazyBuf int - readHeader bool - paramsOK bool - snappyFrame bool - ignoreStreamID bool - ignoreCRC bool -} - -// GetBufferCapacity returns the capacity of the internal buffer. -// This might be useful to know when reusing the same reader in combination -// with the lazy buffer option. -func (r *Reader) GetBufferCapacity() int { - return cap(r.buf) -} - -// ensureBufferSize will ensure that the buffer can take at least n bytes. -// If false is returned the buffer exceeds maximum allowed size. -func (r *Reader) ensureBufferSize(n int) bool { - if n > r.maxBufSize { - r.err = ErrCorrupt - return false - } - if cap(r.buf) >= n { - return true - } - // Realloc buffer. - r.buf = make([]byte, n) - return true -} - -// Reset discards any buffered data, resets all state, and switches the Snappy -// reader to read from r. This permits reusing a Reader rather than allocating -// a new one. -func (r *Reader) Reset(reader io.Reader) { - if !r.paramsOK { - return - } - r.index = nil - r.r = reader - r.err = nil - r.i = 0 - r.j = 0 - r.blockStart = 0 - r.readHeader = r.ignoreStreamID -} - -func (r *Reader) readFull(p []byte, allowEOF bool) (ok bool) { - if _, r.err = io.ReadFull(r.r, p); r.err != nil { - if r.err == io.ErrUnexpectedEOF || (r.err == io.EOF && !allowEOF) { - r.err = ErrCorrupt - } - return false - } - return true -} - -// skippable will skip n bytes. -// If the supplied reader supports seeking that is used. -// tmp is used as a temporary buffer for reading. -// The supplied slice does not need to be the size of the read. -func (r *Reader) skippable(tmp []byte, n int, allowEOF bool, id uint8) (ok bool) { - if id < 0x80 { - r.err = fmt.Errorf("internal error: skippable id < 0x80") - return false - } - if fn := r.skippableCB[id-0x80]; fn != nil { - rd := io.LimitReader(r.r, int64(n)) - r.err = fn(rd) - if r.err != nil { - return false - } - _, r.err = io.CopyBuffer(ioutil.Discard, rd, tmp) - return r.err == nil - } - if rs, ok := r.r.(io.ReadSeeker); ok { - _, err := rs.Seek(int64(n), io.SeekCurrent) - if err == nil { - return true - } - if err == io.ErrUnexpectedEOF || (r.err == io.EOF && !allowEOF) { - r.err = ErrCorrupt - return false - } - } - for n > 0 { - if n < len(tmp) { - tmp = tmp[:n] - } - if _, r.err = io.ReadFull(r.r, tmp); r.err != nil { - if r.err == io.ErrUnexpectedEOF || (r.err == io.EOF && !allowEOF) { - r.err = ErrCorrupt - } - return false - } - n -= len(tmp) - } - return true -} - -// Read satisfies the io.Reader interface. -func (r *Reader) Read(p []byte) (int, error) { - if r.err != nil { - return 0, r.err - } - for { - if r.i < r.j { - n := copy(p, r.decoded[r.i:r.j]) - r.i += n - return n, nil - } - if !r.readFull(r.buf[:4], true) { - return 0, r.err - } - chunkType := r.buf[0] - if !r.readHeader { - if chunkType != chunkTypeStreamIdentifier { - r.err = ErrCorrupt - return 0, r.err - } - r.readHeader = true - } - chunkLen := int(r.buf[1]) | int(r.buf[2])<<8 | int(r.buf[3])<<16 - - // The chunk types are specified at - // https://github.com/google/snappy/blob/master/framing_format.txt - switch chunkType { - case chunkTypeCompressedData: - r.blockStart += int64(r.j) - // Section 4.2. Compressed data (chunk type 0x00). - if chunkLen < checksumSize { - r.err = ErrCorrupt - return 0, r.err - } - if !r.ensureBufferSize(chunkLen) { - if r.err == nil { - r.err = ErrUnsupported - } - return 0, r.err - } - buf := r.buf[:chunkLen] - if !r.readFull(buf, false) { - return 0, r.err - } - checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24 - buf = buf[checksumSize:] - - n, err := DecodedLen(buf) - if err != nil { - r.err = err - return 0, r.err - } - if r.snappyFrame && n > maxSnappyBlockSize { - r.err = ErrCorrupt - return 0, r.err - } - - if n > len(r.decoded) { - if n > r.maxBlock { - r.err = ErrCorrupt - return 0, r.err - } - r.decoded = make([]byte, n) - } - if _, err := Decode(r.decoded, buf); err != nil { - r.err = err - return 0, r.err - } - if !r.ignoreCRC && crc(r.decoded[:n]) != checksum { - r.err = ErrCRC - return 0, r.err - } - r.i, r.j = 0, n - continue - - case chunkTypeUncompressedData: - r.blockStart += int64(r.j) - // Section 4.3. Uncompressed data (chunk type 0x01). - if chunkLen < checksumSize { - r.err = ErrCorrupt - return 0, r.err - } - if !r.ensureBufferSize(chunkLen) { - if r.err == nil { - r.err = ErrUnsupported - } - return 0, r.err - } - buf := r.buf[:checksumSize] - if !r.readFull(buf, false) { - return 0, r.err - } - checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24 - // Read directly into r.decoded instead of via r.buf. - n := chunkLen - checksumSize - if r.snappyFrame && n > maxSnappyBlockSize { - r.err = ErrCorrupt - return 0, r.err - } - if n > len(r.decoded) { - if n > r.maxBlock { - r.err = ErrCorrupt - return 0, r.err - } - r.decoded = make([]byte, n) - } - if !r.readFull(r.decoded[:n], false) { - return 0, r.err - } - if !r.ignoreCRC && crc(r.decoded[:n]) != checksum { - r.err = ErrCRC - return 0, r.err - } - r.i, r.j = 0, n - continue - - case chunkTypeStreamIdentifier: - // Section 4.1. Stream identifier (chunk type 0xff). - if chunkLen != len(magicBody) { - r.err = ErrCorrupt - return 0, r.err - } - if !r.readFull(r.buf[:len(magicBody)], false) { - return 0, r.err - } - if string(r.buf[:len(magicBody)]) != magicBody { - if string(r.buf[:len(magicBody)]) != magicBodySnappy { - r.err = ErrCorrupt - return 0, r.err - } else { - r.snappyFrame = true - } - } else { - r.snappyFrame = false - } - continue - } - - if chunkType <= 0x7f { - // Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f). - // fmt.Printf("ERR chunktype: 0x%x\n", chunkType) - r.err = ErrUnsupported - return 0, r.err - } - // Section 4.4 Padding (chunk type 0xfe). - // Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd). - if chunkLen > maxChunkSize { - // fmt.Printf("ERR chunkLen: 0x%x\n", chunkLen) - r.err = ErrUnsupported - return 0, r.err - } - - // fmt.Printf("skippable: ID: 0x%x, len: 0x%x\n", chunkType, chunkLen) - if !r.skippable(r.buf, chunkLen, false, chunkType) { - return 0, r.err - } - } -} - -// DecodeConcurrent will decode the full stream to w. -// This function should not be combined with reading, seeking or other operations. -// Up to 'concurrent' goroutines will be used. -// If <= 0, runtime.NumCPU will be used. -// On success the number of bytes decompressed nil and is returned. -// This is mainly intended for bigger streams. -func (r *Reader) DecodeConcurrent(w io.Writer, concurrent int) (written int64, err error) { - if r.i > 0 || r.j > 0 || r.blockStart > 0 { - return 0, errors.New("DecodeConcurrent called after ") - } - if concurrent <= 0 { - concurrent = runtime.NumCPU() - } - - // Write to output - var errMu sync.Mutex - var aErr error - setErr := func(e error) (ok bool) { - errMu.Lock() - defer errMu.Unlock() - if e == nil { - return aErr == nil - } - if aErr == nil { - aErr = e - } - return false - } - hasErr := func() (ok bool) { - errMu.Lock() - v := aErr != nil - errMu.Unlock() - return v - } - - var aWritten int64 - toRead := make(chan []byte, concurrent) - writtenBlocks := make(chan []byte, concurrent) - queue := make(chan chan []byte, concurrent) - reUse := make(chan chan []byte, concurrent) - for i := 0; i < concurrent; i++ { - toRead <- make([]byte, 0, r.maxBufSize) - writtenBlocks <- make([]byte, 0, r.maxBufSize) - reUse <- make(chan []byte, 1) - } - // Writer - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - for toWrite := range queue { - entry := <-toWrite - reUse <- toWrite - if hasErr() || entry == nil { - if entry != nil { - writtenBlocks <- entry - } - continue - } - if hasErr() { - writtenBlocks <- entry - continue - } - n, err := w.Write(entry) - want := len(entry) - writtenBlocks <- entry - if err != nil { - setErr(err) - continue - } - if n != want { - setErr(io.ErrShortWrite) - continue - } - aWritten += int64(n) - } - }() - - defer func() { - if r.err != nil { - setErr(r.err) - } else if err != nil { - setErr(err) - } - close(queue) - wg.Wait() - if err == nil { - err = aErr - } - written = aWritten - }() - - // Reader - for !hasErr() { - if !r.readFull(r.buf[:4], true) { - if r.err == io.EOF { - r.err = nil - } - return 0, r.err - } - chunkType := r.buf[0] - if !r.readHeader { - if chunkType != chunkTypeStreamIdentifier { - r.err = ErrCorrupt - return 0, r.err - } - r.readHeader = true - } - chunkLen := int(r.buf[1]) | int(r.buf[2])<<8 | int(r.buf[3])<<16 - - // The chunk types are specified at - // https://github.com/google/snappy/blob/master/framing_format.txt - switch chunkType { - case chunkTypeCompressedData: - r.blockStart += int64(r.j) - // Section 4.2. Compressed data (chunk type 0x00). - if chunkLen < checksumSize { - r.err = ErrCorrupt - return 0, r.err - } - if chunkLen > r.maxBufSize { - r.err = ErrCorrupt - return 0, r.err - } - orgBuf := <-toRead - buf := orgBuf[:chunkLen] - - if !r.readFull(buf, false) { - return 0, r.err - } - - checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24 - buf = buf[checksumSize:] - - n, err := DecodedLen(buf) - if err != nil { - r.err = err - return 0, r.err - } - if r.snappyFrame && n > maxSnappyBlockSize { - r.err = ErrCorrupt - return 0, r.err - } - - if n > r.maxBlock { - r.err = ErrCorrupt - return 0, r.err - } - wg.Add(1) - - decoded := <-writtenBlocks - entry := <-reUse - queue <- entry - go func() { - defer wg.Done() - decoded = decoded[:n] - _, err := Decode(decoded, buf) - toRead <- orgBuf - if err != nil { - writtenBlocks <- decoded - setErr(err) - entry <- nil - return - } - if !r.ignoreCRC && crc(decoded) != checksum { - writtenBlocks <- decoded - setErr(ErrCRC) - entry <- nil - return - } - entry <- decoded - }() - continue - - case chunkTypeUncompressedData: - - // Section 4.3. Uncompressed data (chunk type 0x01). - if chunkLen < checksumSize { - r.err = ErrCorrupt - return 0, r.err - } - if chunkLen > r.maxBufSize { - r.err = ErrCorrupt - return 0, r.err - } - // Grab write buffer - orgBuf := <-writtenBlocks - buf := orgBuf[:checksumSize] - if !r.readFull(buf, false) { - return 0, r.err - } - checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24 - // Read content. - n := chunkLen - checksumSize - - if r.snappyFrame && n > maxSnappyBlockSize { - r.err = ErrCorrupt - return 0, r.err - } - if n > r.maxBlock { - r.err = ErrCorrupt - return 0, r.err - } - // Read uncompressed - buf = orgBuf[:n] - if !r.readFull(buf, false) { - return 0, r.err - } - - if !r.ignoreCRC && crc(buf) != checksum { - r.err = ErrCRC - return 0, r.err - } - entry := <-reUse - queue <- entry - entry <- buf - continue - - case chunkTypeStreamIdentifier: - // Section 4.1. Stream identifier (chunk type 0xff). - if chunkLen != len(magicBody) { - r.err = ErrCorrupt - return 0, r.err - } - if !r.readFull(r.buf[:len(magicBody)], false) { - return 0, r.err - } - if string(r.buf[:len(magicBody)]) != magicBody { - if string(r.buf[:len(magicBody)]) != magicBodySnappy { - r.err = ErrCorrupt - return 0, r.err - } else { - r.snappyFrame = true - } - } else { - r.snappyFrame = false - } - continue - } - - if chunkType <= 0x7f { - // Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f). - // fmt.Printf("ERR chunktype: 0x%x\n", chunkType) - r.err = ErrUnsupported - return 0, r.err - } - // Section 4.4 Padding (chunk type 0xfe). - // Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd). - if chunkLen > maxChunkSize { - // fmt.Printf("ERR chunkLen: 0x%x\n", chunkLen) - r.err = ErrUnsupported - return 0, r.err - } - - // fmt.Printf("skippable: ID: 0x%x, len: 0x%x\n", chunkType, chunkLen) - if !r.skippable(r.buf, chunkLen, false, chunkType) { - return 0, r.err - } - } - return 0, r.err -} - -// Skip will skip n bytes forward in the decompressed output. -// For larger skips this consumes less CPU and is faster than reading output and discarding it. -// CRC is not checked on skipped blocks. -// io.ErrUnexpectedEOF is returned if the stream ends before all bytes have been skipped. -// If a decoding error is encountered subsequent calls to Read will also fail. -func (r *Reader) Skip(n int64) error { - if n < 0 { - return errors.New("attempted negative skip") - } - if r.err != nil { - return r.err - } - - for n > 0 { - if r.i < r.j { - // Skip in buffer. - // decoded[i:j] contains decoded bytes that have not yet been passed on. - left := int64(r.j - r.i) - if left >= n { - tmp := int64(r.i) + n - if tmp > math.MaxInt32 { - return errors.New("s2: internal overflow in skip") - } - r.i = int(tmp) - return nil - } - n -= int64(r.j - r.i) - r.i = r.j - } - - // Buffer empty; read blocks until we have content. - if !r.readFull(r.buf[:4], true) { - if r.err == io.EOF { - r.err = io.ErrUnexpectedEOF - } - return r.err - } - chunkType := r.buf[0] - if !r.readHeader { - if chunkType != chunkTypeStreamIdentifier { - r.err = ErrCorrupt - return r.err - } - r.readHeader = true - } - chunkLen := int(r.buf[1]) | int(r.buf[2])<<8 | int(r.buf[3])<<16 - - // The chunk types are specified at - // https://github.com/google/snappy/blob/master/framing_format.txt - switch chunkType { - case chunkTypeCompressedData: - r.blockStart += int64(r.j) - // Section 4.2. Compressed data (chunk type 0x00). - if chunkLen < checksumSize { - r.err = ErrCorrupt - return r.err - } - if !r.ensureBufferSize(chunkLen) { - if r.err == nil { - r.err = ErrUnsupported - } - return r.err - } - buf := r.buf[:chunkLen] - if !r.readFull(buf, false) { - return r.err - } - checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24 - buf = buf[checksumSize:] - - dLen, err := DecodedLen(buf) - if err != nil { - r.err = err - return r.err - } - if dLen > r.maxBlock { - r.err = ErrCorrupt - return r.err - } - // Check if destination is within this block - if int64(dLen) > n { - if len(r.decoded) < dLen { - r.decoded = make([]byte, dLen) - } - if _, err := Decode(r.decoded, buf); err != nil { - r.err = err - return r.err - } - if crc(r.decoded[:dLen]) != checksum { - r.err = ErrCorrupt - return r.err - } - } else { - // Skip block completely - n -= int64(dLen) - r.blockStart += int64(dLen) - dLen = 0 - } - r.i, r.j = 0, dLen - continue - case chunkTypeUncompressedData: - r.blockStart += int64(r.j) - // Section 4.3. Uncompressed data (chunk type 0x01). - if chunkLen < checksumSize { - r.err = ErrCorrupt - return r.err - } - if !r.ensureBufferSize(chunkLen) { - if r.err != nil { - r.err = ErrUnsupported - } - return r.err - } - buf := r.buf[:checksumSize] - if !r.readFull(buf, false) { - return r.err - } - checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24 - // Read directly into r.decoded instead of via r.buf. - n2 := chunkLen - checksumSize - if n2 > len(r.decoded) { - if n2 > r.maxBlock { - r.err = ErrCorrupt - return r.err - } - r.decoded = make([]byte, n2) - } - if !r.readFull(r.decoded[:n2], false) { - return r.err - } - if int64(n2) < n { - if crc(r.decoded[:n2]) != checksum { - r.err = ErrCorrupt - return r.err - } - } - r.i, r.j = 0, n2 - continue - case chunkTypeStreamIdentifier: - // Section 4.1. Stream identifier (chunk type 0xff). - if chunkLen != len(magicBody) { - r.err = ErrCorrupt - return r.err - } - if !r.readFull(r.buf[:len(magicBody)], false) { - return r.err - } - if string(r.buf[:len(magicBody)]) != magicBody { - if string(r.buf[:len(magicBody)]) != magicBodySnappy { - r.err = ErrCorrupt - return r.err - } - } - - continue - } - - if chunkType <= 0x7f { - // Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f). - r.err = ErrUnsupported - return r.err - } - if chunkLen > maxChunkSize { - r.err = ErrUnsupported - return r.err - } - // Section 4.4 Padding (chunk type 0xfe). - // Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd). - if !r.skippable(r.buf, chunkLen, false, chunkType) { - return r.err - } - } - return nil -} - -// ReadSeeker provides random or forward seeking in compressed content. -// See Reader.ReadSeeker -type ReadSeeker struct { - *Reader - readAtMu sync.Mutex -} - -// ReadSeeker will return an io.ReadSeeker and io.ReaderAt -// compatible version of the reader. -// If 'random' is specified the returned io.Seeker can be used for -// random seeking, otherwise only forward seeking is supported. -// Enabling random seeking requires the original input to support -// the io.Seeker interface. -// A custom index can be specified which will be used if supplied. -// When using a custom index, it will not be read from the input stream. -// The ReadAt position will affect regular reads and the current position of Seek. -// So using Read after ReadAt will continue from where the ReadAt stopped. -// No functions should be used concurrently. -// The returned ReadSeeker contains a shallow reference to the existing Reader, -// meaning changes performed to one is reflected in the other. -func (r *Reader) ReadSeeker(random bool, index []byte) (*ReadSeeker, error) { - // Read index if provided. - if len(index) != 0 { - if r.index == nil { - r.index = &Index{} - } - if _, err := r.index.Load(index); err != nil { - return nil, ErrCantSeek{Reason: "loading index returned: " + err.Error()} - } - } - - // Check if input is seekable - rs, ok := r.r.(io.ReadSeeker) - if !ok { - if !random { - return &ReadSeeker{Reader: r}, nil - } - return nil, ErrCantSeek{Reason: "input stream isn't seekable"} - } - - if r.index != nil { - // Seekable and index, ok... - return &ReadSeeker{Reader: r}, nil - } - - // Load from stream. - r.index = &Index{} - - // Read current position. - pos, err := rs.Seek(0, io.SeekCurrent) - if err != nil { - return nil, ErrCantSeek{Reason: "seeking input returned: " + err.Error()} - } - err = r.index.LoadStream(rs) - if err != nil { - if err == ErrUnsupported { - // If we don't require random seeking, reset input and return. - if !random { - _, err = rs.Seek(pos, io.SeekStart) - if err != nil { - return nil, ErrCantSeek{Reason: "resetting stream returned: " + err.Error()} - } - r.index = nil - return &ReadSeeker{Reader: r}, nil - } - return nil, ErrCantSeek{Reason: "input stream does not contain an index"} - } - return nil, ErrCantSeek{Reason: "reading index returned: " + err.Error()} - } - - // reset position. - _, err = rs.Seek(pos, io.SeekStart) - if err != nil { - return nil, ErrCantSeek{Reason: "seeking input returned: " + err.Error()} - } - return &ReadSeeker{Reader: r}, nil -} - -// Seek allows seeking in compressed data. -func (r *ReadSeeker) Seek(offset int64, whence int) (int64, error) { - if r.err != nil { - if !errors.Is(r.err, io.EOF) { - return 0, r.err - } - // Reset on EOF - r.err = nil - } - - // Calculate absolute offset. - absOffset := offset - - switch whence { - case io.SeekStart: - case io.SeekCurrent: - absOffset = r.blockStart + int64(r.i) + offset - case io.SeekEnd: - if r.index == nil { - return 0, ErrUnsupported - } - absOffset = r.index.TotalUncompressed + offset - default: - r.err = ErrUnsupported - return 0, r.err - } - - if absOffset < 0 { - return 0, errors.New("seek before start of file") - } - - if !r.readHeader { - // Make sure we read the header. - _, r.err = r.Read([]byte{}) - if r.err != nil { - return 0, r.err - } - } - - // If we are inside current block no need to seek. - // This includes no offset changes. - if absOffset >= r.blockStart && absOffset < r.blockStart+int64(r.j) { - r.i = int(absOffset - r.blockStart) - return r.blockStart + int64(r.i), nil - } - - rs, ok := r.r.(io.ReadSeeker) - if r.index == nil || !ok { - currOffset := r.blockStart + int64(r.i) - if absOffset >= currOffset { - err := r.Skip(absOffset - currOffset) - return r.blockStart + int64(r.i), err - } - return 0, ErrUnsupported - } - - // We can seek and we have an index. - c, u, err := r.index.Find(absOffset) - if err != nil { - return r.blockStart + int64(r.i), err - } - - // Seek to next block - _, err = rs.Seek(c, io.SeekStart) - if err != nil { - return 0, err - } - - r.i = r.j // Remove rest of current block. - r.blockStart = u - int64(r.j) // Adjust current block start for accounting. - if u < absOffset { - // Forward inside block - return absOffset, r.Skip(absOffset - u) - } - if u > absOffset { - return 0, fmt.Errorf("s2 seek: (internal error) u (%d) > absOffset (%d)", u, absOffset) - } - return absOffset, nil -} - -// ReadAt reads len(p) bytes into p starting at offset off in the -// underlying input source. It returns the number of bytes -// read (0 <= n <= len(p)) and any error encountered. -// -// When ReadAt returns n < len(p), it returns a non-nil error -// explaining why more bytes were not returned. In this respect, -// ReadAt is stricter than Read. -// -// Even if ReadAt returns n < len(p), it may use all of p as scratch -// space during the call. If some data is available but not len(p) bytes, -// ReadAt blocks until either all the data is available or an error occurs. -// In this respect ReadAt is different from Read. -// -// If the n = len(p) bytes returned by ReadAt are at the end of the -// input source, ReadAt may return either err == EOF or err == nil. -// -// If ReadAt is reading from an input source with a seek offset, -// ReadAt should not affect nor be affected by the underlying -// seek offset. -// -// Clients of ReadAt can execute parallel ReadAt calls on the -// same input source. This is however not recommended. -func (r *ReadSeeker) ReadAt(p []byte, offset int64) (int, error) { - r.readAtMu.Lock() - defer r.readAtMu.Unlock() - _, err := r.Seek(offset, io.SeekStart) - if err != nil { - return 0, err - } - n := 0 - for n < len(p) { - n2, err := r.Read(p[n:]) - if err != nil { - // This will include io.EOF - return n + n2, err - } - n += n2 - } - return n, nil -} - -// ReadByte satisfies the io.ByteReader interface. -func (r *Reader) ReadByte() (byte, error) { - if r.err != nil { - return 0, r.err - } - if r.i < r.j { - c := r.decoded[r.i] - r.i++ - return c, nil - } - var tmp [1]byte - for i := 0; i < 10; i++ { - n, err := r.Read(tmp[:]) - if err != nil { - return 0, err - } - if n == 1 { - return tmp[0], nil - } - } - return 0, io.ErrNoProgress -} - -// SkippableCB will register a callback for chunks with the specified ID. -// ID must be a Reserved skippable chunks ID, 0x80-0xfd (inclusive). -// For each chunk with the ID, the callback is called with the content. -// Any returned non-nil error will abort decompression. -// Only one callback per ID is supported, latest sent will be used. -// Sending a nil function will disable previous callbacks. -// You can peek the stream, triggering the callback, by doing a Read with a 0 -// byte buffer. -func (r *Reader) SkippableCB(id uint8, fn func(r io.Reader) error) error { - if id < 0x80 || id >= chunkTypePadding { - return fmt.Errorf("ReaderSkippableCB: Invalid id provided, must be 0x80-0xfe (inclusive)") - } - r.skippableCB[id-0x80] = fn - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/s2.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/s2.go deleted file mode 100644 index cbd1ed64..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/s2.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2011 The Snappy-Go Authors. All rights reserved. -// Copyright (c) 2019 Klaus Post. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package s2 implements the S2 compression format. -// -// S2 is an extension of Snappy. Similar to Snappy S2 is aimed for high throughput, -// which is why it features concurrent compression for bigger payloads. -// -// Decoding is compatible with Snappy compressed content, -// but content compressed with S2 cannot be decompressed by Snappy. -// -// For more information on Snappy/S2 differences see README in: https://github.com/klauspost/compress/tree/master/s2 -// -// There are actually two S2 formats: block and stream. They are related, -// but different: trying to decompress block-compressed data as a S2 stream -// will fail, and vice versa. The block format is the Decode and Encode -// functions and the stream format is the Reader and Writer types. -// -// A "better" compression option is available. This will trade some compression -// speed -// -// The block format, the more common case, is used when the complete size (the -// number of bytes) of the original data is known upfront, at the time -// compression starts. The stream format, also known as the framing format, is -// for when that isn't always true. -// -// Blocks to not offer much data protection, so it is up to you to -// add data validation of decompressed blocks. -// -// Streams perform CRC validation of the decompressed data. -// Stream compression will also be performed on multiple CPU cores concurrently -// significantly improving throughput. -package s2 - -import ( - "bytes" - "hash/crc32" - - "github.com/klauspost/compress/internal/race" -) - -/* -Each encoded block begins with the varint-encoded length of the decoded data, -followed by a sequence of chunks. Chunks begin and end on byte boundaries. The -first byte of each chunk is broken into its 2 least and 6 most significant bits -called l and m: l ranges in [0, 4) and m ranges in [0, 64). l is the chunk tag. -Zero means a literal tag. All other values mean a copy tag. - -For literal tags: - - If m < 60, the next 1 + m bytes are literal bytes. - - Otherwise, let n be the little-endian unsigned integer denoted by the next - m - 59 bytes. The next 1 + n bytes after that are literal bytes. - -For copy tags, length bytes are copied from offset bytes ago, in the style of -Lempel-Ziv compression algorithms. In particular: - - For l == 1, the offset ranges in [0, 1<<11) and the length in [4, 12). - The length is 4 + the low 3 bits of m. The high 3 bits of m form bits 8-10 - of the offset. The next byte is bits 0-7 of the offset. - - For l == 2, the offset ranges in [0, 1<<16) and the length in [1, 65). - The length is 1 + m. The offset is the little-endian unsigned integer - denoted by the next 2 bytes. - - For l == 3, the offset ranges in [0, 1<<32) and the length in - [1, 65). The length is 1 + m. The offset is the little-endian unsigned - integer denoted by the next 4 bytes. -*/ -const ( - tagLiteral = 0x00 - tagCopy1 = 0x01 - tagCopy2 = 0x02 - tagCopy4 = 0x03 -) - -const ( - checksumSize = 4 - chunkHeaderSize = 4 - magicChunk = "\xff\x06\x00\x00" + magicBody - magicChunkSnappy = "\xff\x06\x00\x00" + magicBodySnappy - magicBodySnappy = "sNaPpY" - magicBody = "S2sTwO" - - // maxBlockSize is the maximum size of the input to encodeBlock. - // - // For the framing format (Writer type instead of Encode function), - // this is the maximum uncompressed size of a block. - maxBlockSize = 4 << 20 - - // minBlockSize is the minimum size of block setting when creating a writer. - minBlockSize = 4 << 10 - - skippableFrameHeader = 4 - maxChunkSize = 1<<24 - 1 // 16777215 - - // Default block size - defaultBlockSize = 1 << 20 - - // maxSnappyBlockSize is the maximum snappy block size. - maxSnappyBlockSize = 1 << 16 - - obufHeaderLen = checksumSize + chunkHeaderSize -) - -const ( - chunkTypeCompressedData = 0x00 - chunkTypeUncompressedData = 0x01 - ChunkTypeIndex = 0x99 - chunkTypePadding = 0xfe - chunkTypeStreamIdentifier = 0xff -) - -var ( - crcTable = crc32.MakeTable(crc32.Castagnoli) - magicChunkSnappyBytes = []byte(magicChunkSnappy) // Can be passed to functions where it escapes. - magicChunkBytes = []byte(magicChunk) // Can be passed to functions where it escapes. -) - -// crc implements the checksum specified in section 3 of -// https://github.com/google/snappy/blob/master/framing_format.txt -func crc(b []byte) uint32 { - race.ReadSlice(b) - - c := crc32.Update(0, crcTable, b) - return c>>15 | c<<17 + 0xa282ead8 -} - -// literalExtraSize returns the extra size of encoding n literals. -// n should be >= 0 and <= math.MaxUint32. -func literalExtraSize(n int64) int64 { - if n == 0 { - return 0 - } - switch { - case n < 60: - return 1 - case n < 1<<8: - return 2 - case n < 1<<16: - return 3 - case n < 1<<24: - return 4 - default: - return 5 - } -} - -type byter interface { - Bytes() []byte -} - -var _ byter = &bytes.Buffer{} diff --git a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/writer.go b/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/writer.go deleted file mode 100644 index fd15078f..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/klauspost/compress/s2/writer.go +++ /dev/null @@ -1,1064 +0,0 @@ -// Copyright 2011 The Snappy-Go Authors. All rights reserved. -// Copyright (c) 2019+ Klaus Post. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package s2 - -import ( - "crypto/rand" - "encoding/binary" - "errors" - "fmt" - "io" - "runtime" - "sync" - - "github.com/klauspost/compress/internal/race" -) - -const ( - levelUncompressed = iota + 1 - levelFast - levelBetter - levelBest -) - -// NewWriter returns a new Writer that compresses to w, using the -// framing format described at -// https://github.com/google/snappy/blob/master/framing_format.txt -// -// Users must call Close to guarantee all data has been forwarded to -// the underlying io.Writer and that resources are released. -// They may also call Flush zero or more times before calling Close. -func NewWriter(w io.Writer, opts ...WriterOption) *Writer { - w2 := Writer{ - blockSize: defaultBlockSize, - concurrency: runtime.GOMAXPROCS(0), - randSrc: rand.Reader, - level: levelFast, - } - for _, opt := range opts { - if err := opt(&w2); err != nil { - w2.errState = err - return &w2 - } - } - w2.obufLen = obufHeaderLen + MaxEncodedLen(w2.blockSize) - w2.paramsOK = true - w2.ibuf = make([]byte, 0, w2.blockSize) - w2.buffers.New = func() interface{} { - return make([]byte, w2.obufLen) - } - w2.Reset(w) - return &w2 -} - -// Writer is an io.Writer that can write Snappy-compressed bytes. -type Writer struct { - errMu sync.Mutex - errState error - - // ibuf is a buffer for the incoming (uncompressed) bytes. - ibuf []byte - - blockSize int - obufLen int - concurrency int - written int64 - uncompWritten int64 // Bytes sent to compression - output chan chan result - buffers sync.Pool - pad int - - writer io.Writer - randSrc io.Reader - writerWg sync.WaitGroup - index Index - customEnc func(dst, src []byte) int - - // wroteStreamHeader is whether we have written the stream header. - wroteStreamHeader bool - paramsOK bool - snappy bool - flushOnWrite bool - appendIndex bool - bufferCB func([]byte) - level uint8 -} - -type result struct { - b []byte - // return when writing - ret []byte - // Uncompressed start offset - startOffset int64 -} - -// err returns the previously set error. -// If no error has been set it is set to err if not nil. -func (w *Writer) err(err error) error { - w.errMu.Lock() - errSet := w.errState - if errSet == nil && err != nil { - w.errState = err - errSet = err - } - w.errMu.Unlock() - return errSet -} - -// Reset discards the writer's state and switches the Snappy writer to write to w. -// This permits reusing a Writer rather than allocating a new one. -func (w *Writer) Reset(writer io.Writer) { - if !w.paramsOK { - return - } - // Close previous writer, if any. - if w.output != nil { - close(w.output) - w.writerWg.Wait() - w.output = nil - } - w.errState = nil - w.ibuf = w.ibuf[:0] - w.wroteStreamHeader = false - w.written = 0 - w.writer = writer - w.uncompWritten = 0 - w.index.reset(w.blockSize) - - // If we didn't get a writer, stop here. - if writer == nil { - return - } - // If no concurrency requested, don't spin up writer goroutine. - if w.concurrency == 1 { - return - } - - toWrite := make(chan chan result, w.concurrency) - w.output = toWrite - w.writerWg.Add(1) - - // Start a writer goroutine that will write all output in order. - go func() { - defer w.writerWg.Done() - - // Get a queued write. - for write := range toWrite { - // Wait for the data to be available. - input := <-write - if input.ret != nil && w.bufferCB != nil { - w.bufferCB(input.ret) - input.ret = nil - } - in := input.b - if len(in) > 0 { - if w.err(nil) == nil { - // Don't expose data from previous buffers. - toWrite := in[:len(in):len(in)] - // Write to output. - n, err := writer.Write(toWrite) - if err == nil && n != len(toWrite) { - err = io.ErrShortBuffer - } - _ = w.err(err) - w.err(w.index.add(w.written, input.startOffset)) - w.written += int64(n) - } - } - if cap(in) >= w.obufLen { - w.buffers.Put(in) - } - // close the incoming write request. - // This can be used for synchronizing flushes. - close(write) - } - }() -} - -// Write satisfies the io.Writer interface. -func (w *Writer) Write(p []byte) (nRet int, errRet error) { - if err := w.err(nil); err != nil { - return 0, err - } - if w.flushOnWrite { - return w.write(p) - } - // If we exceed the input buffer size, start writing - for len(p) > (cap(w.ibuf)-len(w.ibuf)) && w.err(nil) == nil { - var n int - if len(w.ibuf) == 0 { - // Large write, empty buffer. - // Write directly from p to avoid copy. - n, _ = w.write(p) - } else { - n = copy(w.ibuf[len(w.ibuf):cap(w.ibuf)], p) - w.ibuf = w.ibuf[:len(w.ibuf)+n] - w.write(w.ibuf) - w.ibuf = w.ibuf[:0] - } - nRet += n - p = p[n:] - } - if err := w.err(nil); err != nil { - return nRet, err - } - // p should always be able to fit into w.ibuf now. - n := copy(w.ibuf[len(w.ibuf):cap(w.ibuf)], p) - w.ibuf = w.ibuf[:len(w.ibuf)+n] - nRet += n - return nRet, nil -} - -// ReadFrom implements the io.ReaderFrom interface. -// Using this is typically more efficient since it avoids a memory copy. -// ReadFrom reads data from r until EOF or error. -// The return value n is the number of bytes read. -// Any error except io.EOF encountered during the read is also returned. -func (w *Writer) ReadFrom(r io.Reader) (n int64, err error) { - if err := w.err(nil); err != nil { - return 0, err - } - if len(w.ibuf) > 0 { - err := w.AsyncFlush() - if err != nil { - return 0, err - } - } - if br, ok := r.(byter); ok { - buf := br.Bytes() - if err := w.EncodeBuffer(buf); err != nil { - return 0, err - } - return int64(len(buf)), w.AsyncFlush() - } - for { - inbuf := w.buffers.Get().([]byte)[:w.blockSize+obufHeaderLen] - n2, err := io.ReadFull(r, inbuf[obufHeaderLen:]) - if err != nil { - if err == io.ErrUnexpectedEOF { - err = io.EOF - } - if err != io.EOF { - return n, w.err(err) - } - } - if n2 == 0 { - if cap(inbuf) >= w.obufLen { - w.buffers.Put(inbuf) - } - break - } - n += int64(n2) - err2 := w.writeFull(inbuf[:n2+obufHeaderLen]) - if w.err(err2) != nil { - break - } - - if err != nil { - // We got EOF and wrote everything - break - } - } - - return n, w.err(nil) -} - -// AddSkippableBlock will add a skippable block to the stream. -// The ID must be 0x80-0xfe (inclusive). -// Length of the skippable block must be <= 16777215 bytes. -func (w *Writer) AddSkippableBlock(id uint8, data []byte) (err error) { - if err := w.err(nil); err != nil { - return err - } - if len(data) == 0 { - return nil - } - if id < 0x80 || id > chunkTypePadding { - return fmt.Errorf("invalid skippable block id %x", id) - } - if len(data) > maxChunkSize { - return fmt.Errorf("skippable block excessed maximum size") - } - var header [4]byte - chunkLen := len(data) - header[0] = id - header[1] = uint8(chunkLen >> 0) - header[2] = uint8(chunkLen >> 8) - header[3] = uint8(chunkLen >> 16) - if w.concurrency == 1 { - write := func(b []byte) error { - n, err := w.writer.Write(b) - if err = w.err(err); err != nil { - return err - } - if n != len(b) { - return w.err(io.ErrShortWrite) - } - w.written += int64(n) - return w.err(nil) - } - if !w.wroteStreamHeader { - w.wroteStreamHeader = true - if w.snappy { - if err := write([]byte(magicChunkSnappy)); err != nil { - return err - } - } else { - if err := write([]byte(magicChunk)); err != nil { - return err - } - } - } - if err := write(header[:]); err != nil { - return err - } - return write(data) - } - - // Create output... - if !w.wroteStreamHeader { - w.wroteStreamHeader = true - hWriter := make(chan result) - w.output <- hWriter - if w.snappy { - hWriter <- result{startOffset: w.uncompWritten, b: magicChunkSnappyBytes} - } else { - hWriter <- result{startOffset: w.uncompWritten, b: magicChunkBytes} - } - } - - // Copy input. - inbuf := w.buffers.Get().([]byte)[:4] - copy(inbuf, header[:]) - inbuf = append(inbuf, data...) - - output := make(chan result, 1) - // Queue output. - w.output <- output - output <- result{startOffset: w.uncompWritten, b: inbuf} - - return nil -} - -// EncodeBuffer will add a buffer to the stream. -// This is the fastest way to encode a stream, -// but the input buffer cannot be written to by the caller -// until Flush or Close has been called when concurrency != 1. -// -// Use the WriterBufferDone to receive a callback when the buffer is done -// Processing. -// -// Note that input is not buffered. -// This means that each write will result in discrete blocks being created. -// For buffered writes, use the regular Write function. -func (w *Writer) EncodeBuffer(buf []byte) (err error) { - if err := w.err(nil); err != nil { - return err - } - - if w.flushOnWrite { - _, err := w.write(buf) - return err - } - // Flush queued data first. - if len(w.ibuf) > 0 { - err := w.AsyncFlush() - if err != nil { - return err - } - } - if w.concurrency == 1 { - _, err := w.writeSync(buf) - if w.bufferCB != nil { - w.bufferCB(buf) - } - return err - } - - // Spawn goroutine and write block to output channel. - if !w.wroteStreamHeader { - w.wroteStreamHeader = true - hWriter := make(chan result) - w.output <- hWriter - if w.snappy { - hWriter <- result{startOffset: w.uncompWritten, b: magicChunkSnappyBytes} - } else { - hWriter <- result{startOffset: w.uncompWritten, b: magicChunkBytes} - } - } - orgBuf := buf - for len(buf) > 0 { - // Cut input. - uncompressed := buf - if len(uncompressed) > w.blockSize { - uncompressed = uncompressed[:w.blockSize] - } - buf = buf[len(uncompressed):] - // Get an output buffer. - obuf := w.buffers.Get().([]byte)[:len(uncompressed)+obufHeaderLen] - race.WriteSlice(obuf) - - output := make(chan result) - // Queue output now, so we keep order. - w.output <- output - res := result{ - startOffset: w.uncompWritten, - } - w.uncompWritten += int64(len(uncompressed)) - if len(buf) == 0 && w.bufferCB != nil { - res.ret = orgBuf - } - go func() { - race.ReadSlice(uncompressed) - - checksum := crc(uncompressed) - - // Set to uncompressed. - chunkType := uint8(chunkTypeUncompressedData) - chunkLen := 4 + len(uncompressed) - - // Attempt compressing. - n := binary.PutUvarint(obuf[obufHeaderLen:], uint64(len(uncompressed))) - n2 := w.encodeBlock(obuf[obufHeaderLen+n:], uncompressed) - - // Check if we should use this, or store as uncompressed instead. - if n2 > 0 { - chunkType = uint8(chunkTypeCompressedData) - chunkLen = 4 + n + n2 - obuf = obuf[:obufHeaderLen+n+n2] - } else { - // copy uncompressed - copy(obuf[obufHeaderLen:], uncompressed) - } - - // Fill in the per-chunk header that comes before the body. - obuf[0] = chunkType - obuf[1] = uint8(chunkLen >> 0) - obuf[2] = uint8(chunkLen >> 8) - obuf[3] = uint8(chunkLen >> 16) - obuf[4] = uint8(checksum >> 0) - obuf[5] = uint8(checksum >> 8) - obuf[6] = uint8(checksum >> 16) - obuf[7] = uint8(checksum >> 24) - - // Queue final output. - res.b = obuf - output <- res - }() - } - return nil -} - -func (w *Writer) encodeBlock(obuf, uncompressed []byte) int { - if w.customEnc != nil { - if ret := w.customEnc(obuf, uncompressed); ret >= 0 { - return ret - } - } - if w.snappy { - switch w.level { - case levelFast: - return encodeBlockSnappy(obuf, uncompressed) - case levelBetter: - return encodeBlockBetterSnappy(obuf, uncompressed) - case levelBest: - return encodeBlockBestSnappy(obuf, uncompressed) - } - return 0 - } - switch w.level { - case levelFast: - return encodeBlock(obuf, uncompressed) - case levelBetter: - return encodeBlockBetter(obuf, uncompressed) - case levelBest: - return encodeBlockBest(obuf, uncompressed, nil) - } - return 0 -} - -func (w *Writer) write(p []byte) (nRet int, errRet error) { - if err := w.err(nil); err != nil { - return 0, err - } - if w.concurrency == 1 { - return w.writeSync(p) - } - - // Spawn goroutine and write block to output channel. - for len(p) > 0 { - if !w.wroteStreamHeader { - w.wroteStreamHeader = true - hWriter := make(chan result) - w.output <- hWriter - if w.snappy { - hWriter <- result{startOffset: w.uncompWritten, b: magicChunkSnappyBytes} - } else { - hWriter <- result{startOffset: w.uncompWritten, b: magicChunkBytes} - } - } - - var uncompressed []byte - if len(p) > w.blockSize { - uncompressed, p = p[:w.blockSize], p[w.blockSize:] - } else { - uncompressed, p = p, nil - } - - // Copy input. - // If the block is incompressible, this is used for the result. - inbuf := w.buffers.Get().([]byte)[:len(uncompressed)+obufHeaderLen] - obuf := w.buffers.Get().([]byte)[:w.obufLen] - copy(inbuf[obufHeaderLen:], uncompressed) - uncompressed = inbuf[obufHeaderLen:] - - output := make(chan result) - // Queue output now, so we keep order. - w.output <- output - res := result{ - startOffset: w.uncompWritten, - } - w.uncompWritten += int64(len(uncompressed)) - - go func() { - checksum := crc(uncompressed) - - // Set to uncompressed. - chunkType := uint8(chunkTypeUncompressedData) - chunkLen := 4 + len(uncompressed) - - // Attempt compressing. - n := binary.PutUvarint(obuf[obufHeaderLen:], uint64(len(uncompressed))) - n2 := w.encodeBlock(obuf[obufHeaderLen+n:], uncompressed) - - // Check if we should use this, or store as uncompressed instead. - if n2 > 0 { - chunkType = uint8(chunkTypeCompressedData) - chunkLen = 4 + n + n2 - obuf = obuf[:obufHeaderLen+n+n2] - } else { - // Use input as output. - obuf, inbuf = inbuf, obuf - } - - // Fill in the per-chunk header that comes before the body. - obuf[0] = chunkType - obuf[1] = uint8(chunkLen >> 0) - obuf[2] = uint8(chunkLen >> 8) - obuf[3] = uint8(chunkLen >> 16) - obuf[4] = uint8(checksum >> 0) - obuf[5] = uint8(checksum >> 8) - obuf[6] = uint8(checksum >> 16) - obuf[7] = uint8(checksum >> 24) - - // Queue final output. - res.b = obuf - output <- res - - // Put unused buffer back in pool. - w.buffers.Put(inbuf) - }() - nRet += len(uncompressed) - } - return nRet, nil -} - -// writeFull is a special version of write that will always write the full buffer. -// Data to be compressed should start at offset obufHeaderLen and fill the remainder of the buffer. -// The data will be written as a single block. -// The caller is not allowed to use inbuf after this function has been called. -func (w *Writer) writeFull(inbuf []byte) (errRet error) { - if err := w.err(nil); err != nil { - return err - } - - if w.concurrency == 1 { - _, err := w.writeSync(inbuf[obufHeaderLen:]) - if cap(inbuf) >= w.obufLen { - w.buffers.Put(inbuf) - } - return err - } - - // Spawn goroutine and write block to output channel. - if !w.wroteStreamHeader { - w.wroteStreamHeader = true - hWriter := make(chan result) - w.output <- hWriter - if w.snappy { - hWriter <- result{startOffset: w.uncompWritten, b: magicChunkSnappyBytes} - } else { - hWriter <- result{startOffset: w.uncompWritten, b: magicChunkBytes} - } - } - - // Get an output buffer. - obuf := w.buffers.Get().([]byte)[:w.obufLen] - uncompressed := inbuf[obufHeaderLen:] - - output := make(chan result) - // Queue output now, so we keep order. - w.output <- output - res := result{ - startOffset: w.uncompWritten, - } - w.uncompWritten += int64(len(uncompressed)) - - go func() { - checksum := crc(uncompressed) - - // Set to uncompressed. - chunkType := uint8(chunkTypeUncompressedData) - chunkLen := 4 + len(uncompressed) - - // Attempt compressing. - n := binary.PutUvarint(obuf[obufHeaderLen:], uint64(len(uncompressed))) - n2 := w.encodeBlock(obuf[obufHeaderLen+n:], uncompressed) - - // Check if we should use this, or store as uncompressed instead. - if n2 > 0 { - chunkType = uint8(chunkTypeCompressedData) - chunkLen = 4 + n + n2 - obuf = obuf[:obufHeaderLen+n+n2] - } else { - // Use input as output. - obuf, inbuf = inbuf, obuf - } - - // Fill in the per-chunk header that comes before the body. - obuf[0] = chunkType - obuf[1] = uint8(chunkLen >> 0) - obuf[2] = uint8(chunkLen >> 8) - obuf[3] = uint8(chunkLen >> 16) - obuf[4] = uint8(checksum >> 0) - obuf[5] = uint8(checksum >> 8) - obuf[6] = uint8(checksum >> 16) - obuf[7] = uint8(checksum >> 24) - - // Queue final output. - res.b = obuf - output <- res - - // Put unused buffer back in pool. - w.buffers.Put(inbuf) - }() - return nil -} - -func (w *Writer) writeSync(p []byte) (nRet int, errRet error) { - if err := w.err(nil); err != nil { - return 0, err - } - if !w.wroteStreamHeader { - w.wroteStreamHeader = true - var n int - var err error - if w.snappy { - n, err = w.writer.Write(magicChunkSnappyBytes) - } else { - n, err = w.writer.Write(magicChunkBytes) - } - if err != nil { - return 0, w.err(err) - } - if n != len(magicChunk) { - return 0, w.err(io.ErrShortWrite) - } - w.written += int64(n) - } - - for len(p) > 0 { - var uncompressed []byte - if len(p) > w.blockSize { - uncompressed, p = p[:w.blockSize], p[w.blockSize:] - } else { - uncompressed, p = p, nil - } - - obuf := w.buffers.Get().([]byte)[:w.obufLen] - checksum := crc(uncompressed) - - // Set to uncompressed. - chunkType := uint8(chunkTypeUncompressedData) - chunkLen := 4 + len(uncompressed) - - // Attempt compressing. - n := binary.PutUvarint(obuf[obufHeaderLen:], uint64(len(uncompressed))) - n2 := w.encodeBlock(obuf[obufHeaderLen+n:], uncompressed) - - if n2 > 0 { - chunkType = uint8(chunkTypeCompressedData) - chunkLen = 4 + n + n2 - obuf = obuf[:obufHeaderLen+n+n2] - } else { - obuf = obuf[:8] - } - - // Fill in the per-chunk header that comes before the body. - obuf[0] = chunkType - obuf[1] = uint8(chunkLen >> 0) - obuf[2] = uint8(chunkLen >> 8) - obuf[3] = uint8(chunkLen >> 16) - obuf[4] = uint8(checksum >> 0) - obuf[5] = uint8(checksum >> 8) - obuf[6] = uint8(checksum >> 16) - obuf[7] = uint8(checksum >> 24) - - n, err := w.writer.Write(obuf) - if err != nil { - return 0, w.err(err) - } - if n != len(obuf) { - return 0, w.err(io.ErrShortWrite) - } - w.err(w.index.add(w.written, w.uncompWritten)) - w.written += int64(n) - w.uncompWritten += int64(len(uncompressed)) - - if chunkType == chunkTypeUncompressedData { - // Write uncompressed data. - n, err := w.writer.Write(uncompressed) - if err != nil { - return 0, w.err(err) - } - if n != len(uncompressed) { - return 0, w.err(io.ErrShortWrite) - } - w.written += int64(n) - } - w.buffers.Put(obuf) - // Queue final output. - nRet += len(uncompressed) - } - return nRet, nil -} - -// AsyncFlush writes any buffered bytes to a block and starts compressing it. -// It does not wait for the output has been written as Flush() does. -func (w *Writer) AsyncFlush() error { - if err := w.err(nil); err != nil { - return err - } - - // Queue any data still in input buffer. - if len(w.ibuf) != 0 { - if !w.wroteStreamHeader { - _, err := w.writeSync(w.ibuf) - w.ibuf = w.ibuf[:0] - return w.err(err) - } else { - _, err := w.write(w.ibuf) - w.ibuf = w.ibuf[:0] - err = w.err(err) - if err != nil { - return err - } - } - } - return w.err(nil) -} - -// Flush flushes the Writer to its underlying io.Writer. -// This does not apply padding. -func (w *Writer) Flush() error { - if err := w.AsyncFlush(); err != nil { - return err - } - if w.output == nil { - return w.err(nil) - } - - // Send empty buffer - res := make(chan result) - w.output <- res - // Block until this has been picked up. - res <- result{b: nil, startOffset: w.uncompWritten} - // When it is closed, we have flushed. - <-res - return w.err(nil) -} - -// Close calls Flush and then closes the Writer. -// Calling Close multiple times is ok, -// but calling CloseIndex after this will make it not return the index. -func (w *Writer) Close() error { - _, err := w.closeIndex(w.appendIndex) - return err -} - -// CloseIndex calls Close and returns an index on first call. -// This is not required if you are only adding index to a stream. -func (w *Writer) CloseIndex() ([]byte, error) { - return w.closeIndex(true) -} - -func (w *Writer) closeIndex(idx bool) ([]byte, error) { - err := w.Flush() - if w.output != nil { - close(w.output) - w.writerWg.Wait() - w.output = nil - } - - var index []byte - if w.err(err) == nil && w.writer != nil { - // Create index. - if idx { - compSize := int64(-1) - if w.pad <= 1 { - compSize = w.written - } - index = w.index.appendTo(w.ibuf[:0], w.uncompWritten, compSize) - // Count as written for padding. - if w.appendIndex { - w.written += int64(len(index)) - } - } - - if w.pad > 1 { - tmp := w.ibuf[:0] - if len(index) > 0 { - // Allocate another buffer. - tmp = w.buffers.Get().([]byte)[:0] - defer w.buffers.Put(tmp) - } - add := calcSkippableFrame(w.written, int64(w.pad)) - frame, err := skippableFrame(tmp, add, w.randSrc) - if err = w.err(err); err != nil { - return nil, err - } - n, err2 := w.writer.Write(frame) - if err2 == nil && n != len(frame) { - err2 = io.ErrShortWrite - } - _ = w.err(err2) - } - if len(index) > 0 && w.appendIndex { - n, err2 := w.writer.Write(index) - if err2 == nil && n != len(index) { - err2 = io.ErrShortWrite - } - _ = w.err(err2) - } - } - err = w.err(errClosed) - if err == errClosed { - return index, nil - } - return nil, err -} - -// calcSkippableFrame will return a total size to be added for written -// to be divisible by multiple. -// The value will always be > skippableFrameHeader. -// The function will panic if written < 0 or wantMultiple <= 0. -func calcSkippableFrame(written, wantMultiple int64) int { - if wantMultiple <= 0 { - panic("wantMultiple <= 0") - } - if written < 0 { - panic("written < 0") - } - leftOver := written % wantMultiple - if leftOver == 0 { - return 0 - } - toAdd := wantMultiple - leftOver - for toAdd < skippableFrameHeader { - toAdd += wantMultiple - } - return int(toAdd) -} - -// skippableFrame will add a skippable frame with a total size of bytes. -// total should be >= skippableFrameHeader and < maxBlockSize + skippableFrameHeader -func skippableFrame(dst []byte, total int, r io.Reader) ([]byte, error) { - if total == 0 { - return dst, nil - } - if total < skippableFrameHeader { - return dst, fmt.Errorf("s2: requested skippable frame (%d) < 4", total) - } - if int64(total) >= maxBlockSize+skippableFrameHeader { - return dst, fmt.Errorf("s2: requested skippable frame (%d) >= max 1<<24", total) - } - // Chunk type 0xfe "Section 4.4 Padding (chunk type 0xfe)" - dst = append(dst, chunkTypePadding) - f := uint32(total - skippableFrameHeader) - // Add chunk length. - dst = append(dst, uint8(f), uint8(f>>8), uint8(f>>16)) - // Add data - start := len(dst) - dst = append(dst, make([]byte, f)...) - _, err := io.ReadFull(r, dst[start:]) - return dst, err -} - -var errClosed = errors.New("s2: Writer is closed") - -// WriterOption is an option for creating a encoder. -type WriterOption func(*Writer) error - -// WriterConcurrency will set the concurrency, -// meaning the maximum number of decoders to run concurrently. -// The value supplied must be at least 1. -// By default this will be set to GOMAXPROCS. -func WriterConcurrency(n int) WriterOption { - return func(w *Writer) error { - if n <= 0 { - return errors.New("concurrency must be at least 1") - } - w.concurrency = n - return nil - } -} - -// WriterAddIndex will append an index to the end of a stream -// when it is closed. -func WriterAddIndex() WriterOption { - return func(w *Writer) error { - w.appendIndex = true - return nil - } -} - -// WriterBetterCompression will enable better compression. -// EncodeBetter compresses better than Encode but typically with a -// 10-40% speed decrease on both compression and decompression. -func WriterBetterCompression() WriterOption { - return func(w *Writer) error { - w.level = levelBetter - return nil - } -} - -// WriterBestCompression will enable better compression. -// EncodeBest compresses better than Encode but typically with a -// big speed decrease on compression. -func WriterBestCompression() WriterOption { - return func(w *Writer) error { - w.level = levelBest - return nil - } -} - -// WriterUncompressed will bypass compression. -// The stream will be written as uncompressed blocks only. -// If concurrency is > 1 CRC and output will still be done async. -func WriterUncompressed() WriterOption { - return func(w *Writer) error { - w.level = levelUncompressed - return nil - } -} - -// WriterBufferDone will perform a callback when EncodeBuffer has finished -// writing a buffer to the output and the buffer can safely be reused. -// If the buffer was split into several blocks, it will be sent after the last block. -// Callbacks will not be done concurrently. -func WriterBufferDone(fn func(b []byte)) WriterOption { - return func(w *Writer) error { - w.bufferCB = fn - return nil - } -} - -// WriterBlockSize allows to override the default block size. -// Blocks will be this size or smaller. -// Minimum size is 4KB and maximum size is 4MB. -// -// Bigger blocks may give bigger throughput on systems with many cores, -// and will increase compression slightly, but it will limit the possible -// concurrency for smaller payloads for both encoding and decoding. -// Default block size is 1MB. -// -// When writing Snappy compatible output using WriterSnappyCompat, -// the maximum block size is 64KB. -func WriterBlockSize(n int) WriterOption { - return func(w *Writer) error { - if w.snappy && n > maxSnappyBlockSize || n < minBlockSize { - return errors.New("s2: block size too large. Must be <= 64K and >=4KB on for snappy compatible output") - } - if n > maxBlockSize || n < minBlockSize { - return errors.New("s2: block size too large. Must be <= 4MB and >=4KB") - } - w.blockSize = n - return nil - } -} - -// WriterPadding will add padding to all output so the size will be a multiple of n. -// This can be used to obfuscate the exact output size or make blocks of a certain size. -// The contents will be a skippable frame, so it will be invisible by the decoder. -// n must be > 0 and <= 4MB. -// The padded area will be filled with data from crypto/rand.Reader. -// The padding will be applied whenever Close is called on the writer. -func WriterPadding(n int) WriterOption { - return func(w *Writer) error { - if n <= 0 { - return fmt.Errorf("s2: padding must be at least 1") - } - // No need to waste our time. - if n == 1 { - w.pad = 0 - } - if n > maxBlockSize { - return fmt.Errorf("s2: padding must less than 4MB") - } - w.pad = n - return nil - } -} - -// WriterPaddingSrc will get random data for padding from the supplied source. -// By default crypto/rand is used. -func WriterPaddingSrc(reader io.Reader) WriterOption { - return func(w *Writer) error { - w.randSrc = reader - return nil - } -} - -// WriterSnappyCompat will write snappy compatible output. -// The output can be decompressed using either snappy or s2. -// If block size is more than 64KB it is set to that. -func WriterSnappyCompat() WriterOption { - return func(w *Writer) error { - w.snappy = true - if w.blockSize > 64<<10 { - // We choose 8 bytes less than 64K, since that will make literal emits slightly more effective. - // And allows us to skip some size checks. - w.blockSize = (64 << 10) - 8 - } - return nil - } -} - -// WriterFlushOnWrite will compress blocks on each call to the Write function. -// -// This is quite inefficient as blocks size will depend on the write size. -// -// Use WriterConcurrency(1) to also make sure that output is flushed. -// When Write calls return, otherwise they will be written when compression is done. -func WriterFlushOnWrite() WriterOption { - return func(w *Writer) error { - w.flushOnWrite = true - return nil - } -} - -// WriterCustomEncoder allows to override the encoder for blocks on the stream. -// The function must compress 'src' into 'dst' and return the bytes used in dst as an integer. -// Block size (initial varint) should not be added by the encoder. -// Returning value 0 indicates the block could not be compressed. -// Returning a negative value indicates that compression should be attempted. -// The function should expect to be called concurrently. -func WriterCustomEncoder(fn func(dst, src []byte) int) WriterOption { - return func(w *Writer) error { - w.customEnc = fn - return nil - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/.gitignore b/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/.gitignore deleted file mode 100644 index c56069fe..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.test \ No newline at end of file diff --git a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/.golangci.yml b/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/.golangci.yml deleted file mode 100644 index 39310d0d..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/.golangci.yml +++ /dev/null @@ -1,28 +0,0 @@ -linters-settings: - golint: - min-confidence: 0 - - misspell: - locale: US - -linters: - disable-all: true - enable: - - typecheck - - goimports - - misspell - - govet - - revive - - ineffassign - - gosimple - - unparam - - unused - -issues: - exclude-use-default: false - exclude: - - should have a package comment - - error strings should not be capitalized or end with punctuation or a newline - - should have comment # TODO(aead): Remove once all exported ident. have comments! -service: - golangci-lint-version: 1.51.2 # use the fixed version to not introduce new linters unexpectedly diff --git a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/LICENSE b/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/LICENSE deleted file mode 100644 index d6456956..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/README.md b/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/README.md deleted file mode 100644 index 0504822c..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/README.md +++ /dev/null @@ -1,99 +0,0 @@ -[![Godoc Reference](https://godoc.org/github.com/minio/highwayhash?status.svg)](https://godoc.org/github.com/minio/highwayhash) -[![Build Status](https://travis-ci.org/minio/highwayhash.svg?branch=master)](https://travis-ci.org/minio/highwayhash) - -## HighwayHash - -[HighwayHash](https://github.com/google/highwayhash) is a pseudo-random-function (PRF) developed by Jyrki Alakuijala, Bill Cox and Jan Wassenberg (Google research). HighwayHash takes a 256 bit key and computes 64, 128 or 256 bit hash values of given messages. - -It can be used to prevent hash-flooding attacks or authenticate short-lived messages. Additionally it can be used as a fingerprinting function. HighwayHash is not a general purpose cryptographic hash function (such as Blake2b, SHA-3 or SHA-2) and should not be used if strong collision resistance is required. - -This repository contains a native Go version and optimized assembly implementations for Intel, ARM and ppc64le architectures. - -### High performance - -HighwayHash is an approximately 5x faster SIMD hash function as compared to [SipHash](https://www.131002.net/siphash/siphash.pdf) which in itself is a fast and 'cryptographically strong' pseudo-random function designed by Aumasson and Bernstein. - -HighwayHash uses a new way of mixing inputs with AVX2 multiply and permute instructions. The multiplications are 32x32 bit giving 64 bits-wide results and are therefore infeasible to reverse. Additionally permuting equalizes the distribution of the resulting bytes. The algorithm outputs digests ranging from 64 bits up to 256 bits at no extra cost. - -### Stable - -All three output sizes of HighwayHash have been declared [stable](https://github.com/google/highwayhash/#versioning-and-stability) as of January 2018. This means that the hash results for any given input message are guaranteed not to change. - -### Installation - -Install: `go get -u github.com/minio/highwayhash` - -### Intel Performance - -Below are the single core results on an Intel Core i7 (3.1 GHz) for 256 bit outputs: - -``` -BenchmarkSum256_16 204.17 MB/s -BenchmarkSum256_64 1040.63 MB/s -BenchmarkSum256_1K 8653.30 MB/s -BenchmarkSum256_8K 13476.07 MB/s -BenchmarkSum256_1M 14928.71 MB/s -BenchmarkSum256_5M 14180.04 MB/s -BenchmarkSum256_10M 12458.65 MB/s -BenchmarkSum256_25M 11927.25 MB/s -``` - -So for moderately sized messages it tops out at about 15 GB/sec. Also for small messages (1K) the performance is already at approximately 60% of the maximum throughput. - -### ARM Performance - -Below are the single core results on an EC2 c7g.4xlarge (Graviton3) instance for 256 bit outputs: - -``` -BenchmarkSum256_16 143.66 MB/s -BenchmarkSum256_64 628.75 MB/s -BenchmarkSum256_1K 3621.71 MB/s -BenchmarkSum256_8K 5039.64 MB/s -BenchmarkSum256_1M 5279.79 MB/s -BenchmarkSum256_5M 5474.60 MB/s -BenchmarkSum256_10M 5621.73 MB/s -BenchmarkSum256_25M 5250.47 MB/s -``` - -### ppc64le Performance - -The ppc64le accelerated version is roughly 10x faster compared to the non-optimized version: - -``` -benchmark old MB/s new MB/s speedup -BenchmarkWrite_8K 531.19 5566.41 10.48x -BenchmarkSum64_8K 518.86 4971.88 9.58x -BenchmarkSum256_8K 502.45 4474.20 8.90x -``` - -### Performance compared to other hashing techniques - -On a Skylake CPU (3.0 GHz Xeon Platinum 8124M) the table below shows how HighwayHash compares to other hashing techniques for 5 MB messages (single core performance, all Golang implementations, see [benchmark](https://github.com/fwessels/HashCompare/blob/master/benchmarks_test.go)). - -``` -BenchmarkHighwayHash 11986.98 MB/s -BenchmarkSHA256_AVX512 3552.74 MB/s -BenchmarkBlake2b 972.38 MB/s -BenchmarkSHA1 950.64 MB/s -BenchmarkMD5 684.18 MB/s -BenchmarkSHA512 562.04 MB/s -BenchmarkSHA256 383.07 MB/s -``` - -*Note: the AVX512 version of SHA256 uses the [multi-buffer crypto library](https://github.com/intel/intel-ipsec-mb) technique as developed by Intel, more details can be found in [sha256-simd](https://github.com/minio/sha256-simd/).* - -### Qualitative assessment - -We have performed a 'qualitative' assessment of how HighwayHash compares to Blake2b in terms of the distribution of the checksums for varying numbers of messages. It shows that HighwayHash behaves similarly according to the following graph: - -![Hash Comparison Overview](https://s3.amazonaws.com/s3git-assets/hash-comparison-final.png) - -More information can be found in [HashCompare](https://github.com/fwessels/HashCompare). - -### Requirements - -All Go versions >= 1.11 are supported (needed for required assembly support for the different platforms). - -### Contributing - -Contributions are welcome, please send PRs for any enhancements. \ No newline at end of file diff --git a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash.go b/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash.go deleted file mode 100644 index 629fdf2e..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash.go +++ /dev/null @@ -1,225 +0,0 @@ -// Copyright (c) 2017 Minio Inc. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -// Package highwayhash implements the pseudo-random-function (PRF) HighwayHash. -// HighwayHash is a fast hash function designed to defend hash-flooding attacks -// or to authenticate short-lived messages. -// -// HighwayHash is not a general purpose cryptographic hash function and does not -// provide (strong) collision resistance. -package highwayhash - -import ( - "encoding/binary" - "errors" - "hash" -) - -const ( - // Size is the size of HighwayHash-256 checksum in bytes. - Size = 32 - // Size128 is the size of HighwayHash-128 checksum in bytes. - Size128 = 16 - // Size64 is the size of HighwayHash-64 checksum in bytes. - Size64 = 8 -) - -var errKeySize = errors.New("highwayhash: invalid key size") - -// New returns a hash.Hash computing the HighwayHash-256 checksum. -// It returns a non-nil error if the key is not 32 bytes long. -func New(key []byte) (hash.Hash, error) { - if len(key) != Size { - return nil, errKeySize - } - h := &digest{size: Size} - copy(h.key[:], key) - h.Reset() - return h, nil -} - -// New128 returns a hash.Hash computing the HighwayHash-128 checksum. -// It returns a non-nil error if the key is not 32 bytes long. -func New128(key []byte) (hash.Hash, error) { - if len(key) != Size { - return nil, errKeySize - } - h := &digest{size: Size128} - copy(h.key[:], key) - h.Reset() - return h, nil -} - -// New64 returns a hash.Hash computing the HighwayHash-64 checksum. -// It returns a non-nil error if the key is not 32 bytes long. -func New64(key []byte) (hash.Hash64, error) { - if len(key) != Size { - return nil, errKeySize - } - h := new(digest64) - h.size = Size64 - copy(h.key[:], key) - h.Reset() - return h, nil -} - -// Sum computes the HighwayHash-256 checksum of data. -// It panics if the key is not 32 bytes long. -func Sum(data, key []byte) [Size]byte { - if len(key) != Size { - panic(errKeySize) - } - var state [16]uint64 - initialize(&state, key) - if n := len(data) & (^(Size - 1)); n > 0 { - update(&state, data[:n]) - data = data[n:] - } - if len(data) > 0 { - var block [Size]byte - offset := copy(block[:], data) - hashBuffer(&state, &block, offset) - } - var hash [Size]byte - finalize(hash[:], &state) - return hash -} - -// Sum128 computes the HighwayHash-128 checksum of data. -// It panics if the key is not 32 bytes long. -func Sum128(data, key []byte) [Size128]byte { - if len(key) != Size { - panic(errKeySize) - } - var state [16]uint64 - initialize(&state, key) - if n := len(data) & (^(Size - 1)); n > 0 { - update(&state, data[:n]) - data = data[n:] - } - if len(data) > 0 { - var block [Size]byte - offset := copy(block[:], data) - hashBuffer(&state, &block, offset) - } - var hash [Size128]byte - finalize(hash[:], &state) - return hash -} - -// Sum64 computes the HighwayHash-64 checksum of data. -// It panics if the key is not 32 bytes long. -func Sum64(data, key []byte) uint64 { - if len(key) != Size { - panic(errKeySize) - } - var state [16]uint64 - initialize(&state, key) - if n := len(data) & (^(Size - 1)); n > 0 { - update(&state, data[:n]) - data = data[n:] - } - if len(data) > 0 { - var block [Size]byte - offset := copy(block[:], data) - hashBuffer(&state, &block, offset) - } - var hash [Size64]byte - finalize(hash[:], &state) - return binary.LittleEndian.Uint64(hash[:]) -} - -type digest64 struct{ digest } - -func (d *digest64) Sum64() uint64 { - state := d.state - if d.offset > 0 { - hashBuffer(&state, &d.buffer, d.offset) - } - var hash [8]byte - finalize(hash[:], &state) - return binary.LittleEndian.Uint64(hash[:]) -} - -type digest struct { - state [16]uint64 // v0 | v1 | mul0 | mul1 - - key, buffer [Size]byte - offset int - - size int -} - -func (d *digest) Size() int { return d.size } - -func (d *digest) BlockSize() int { return Size } - -func (d *digest) Reset() { - initialize(&d.state, d.key[:]) - d.offset = 0 -} - -func (d *digest) Write(p []byte) (n int, err error) { - n = len(p) - if d.offset > 0 { - remaining := Size - d.offset - if n < remaining { - d.offset += copy(d.buffer[d.offset:], p) - return - } - copy(d.buffer[d.offset:], p[:remaining]) - update(&d.state, d.buffer[:]) - p = p[remaining:] - d.offset = 0 - } - if nn := len(p) & (^(Size - 1)); nn > 0 { - update(&d.state, p[:nn]) - p = p[nn:] - } - if len(p) > 0 { - d.offset = copy(d.buffer[d.offset:], p) - } - return -} - -func (d *digest) Sum(b []byte) []byte { - state := d.state - if d.offset > 0 { - hashBuffer(&state, &d.buffer, d.offset) - } - var hash [Size]byte - finalize(hash[:d.size], &state) - return append(b, hash[:d.size]...) -} - -func hashBuffer(state *[16]uint64, buffer *[32]byte, offset int) { - var block [Size]byte - mod32 := (uint64(offset) << 32) + uint64(offset) - for i := range state[:4] { - state[i] += mod32 - } - for i := range state[4:8] { - t0 := uint32(state[i+4]) - t0 = (t0 << uint(offset)) | (t0 >> uint(32-offset)) - - t1 := uint32(state[i+4] >> 32) - t1 = (t1 << uint(offset)) | (t1 >> uint(32-offset)) - - state[i+4] = (uint64(t1) << 32) | uint64(t0) - } - - mod4 := offset & 3 - remain := offset - mod4 - - copy(block[:], buffer[:remain]) - if offset >= 16 { - copy(block[28:], buffer[offset-4:]) - } else if mod4 != 0 { - last := uint32(buffer[remain]) - last += uint32(buffer[remain+mod4>>1]) << 8 - last += uint32(buffer[offset-1]) << 16 - binary.LittleEndian.PutUint32(block[16:], last) - } - update(state, block[:]) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhashAVX2_amd64.s b/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhashAVX2_amd64.s deleted file mode 100644 index e6e4263b..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhashAVX2_amd64.s +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright (c) 2017 Minio Inc. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -// +build amd64,!gccgo,!appengine,!nacl,!noasm - -#include "textflag.h" - -DATA ·consAVX2<>+0x00(SB)/8, $0xdbe6d5d5fe4cce2f -DATA ·consAVX2<>+0x08(SB)/8, $0xa4093822299f31d0 -DATA ·consAVX2<>+0x10(SB)/8, $0x13198a2e03707344 -DATA ·consAVX2<>+0x18(SB)/8, $0x243f6a8885a308d3 -DATA ·consAVX2<>+0x20(SB)/8, $0x3bd39e10cb0ef593 -DATA ·consAVX2<>+0x28(SB)/8, $0xc0acf169b5f18a8c -DATA ·consAVX2<>+0x30(SB)/8, $0xbe5466cf34e90c6c -DATA ·consAVX2<>+0x38(SB)/8, $0x452821e638d01377 -GLOBL ·consAVX2<>(SB), (NOPTR+RODATA), $64 - -DATA ·zipperMergeAVX2<>+0x00(SB)/8, $0xf010e05020c03 -DATA ·zipperMergeAVX2<>+0x08(SB)/8, $0x70806090d0a040b -DATA ·zipperMergeAVX2<>+0x10(SB)/8, $0xf010e05020c03 -DATA ·zipperMergeAVX2<>+0x18(SB)/8, $0x70806090d0a040b -GLOBL ·zipperMergeAVX2<>(SB), (NOPTR+RODATA), $32 - -#define REDUCE_MOD(x0, x1, x2, x3, tmp0, tmp1, y0, y1) \ - MOVQ $0x3FFFFFFFFFFFFFFF, tmp0 \ - ANDQ tmp0, x3 \ - MOVQ x2, y0 \ - MOVQ x3, y1 \ - \ - MOVQ x2, tmp0 \ - MOVQ x3, tmp1 \ - SHLQ $1, tmp1 \ - SHRQ $63, tmp0 \ - MOVQ tmp1, x3 \ - ORQ tmp0, x3 \ - \ - SHLQ $1, x2 \ - \ - MOVQ y0, tmp0 \ - MOVQ y1, tmp1 \ - SHLQ $2, tmp1 \ - SHRQ $62, tmp0 \ - MOVQ tmp1, y1 \ - ORQ tmp0, y1 \ - \ - SHLQ $2, y0 \ - \ - XORQ x0, y0 \ - XORQ x2, y0 \ - XORQ x1, y1 \ - XORQ x3, y1 - -#define UPDATE(msg) \ - VPADDQ msg, Y2, Y2 \ - VPADDQ Y3, Y2, Y2 \ - \ - VPSRLQ $32, Y1, Y0 \ - BYTE $0xC5; BYTE $0xFD; BYTE $0xF4; BYTE $0xC2 \ // VPMULUDQ Y2, Y0, Y0 - VPXOR Y0, Y3, Y3 \ - \ - VPADDQ Y4, Y1, Y1 \ - \ - VPSRLQ $32, Y2, Y0 \ - BYTE $0xC5; BYTE $0xFD; BYTE $0xF4; BYTE $0xC1 \ // VPMULUDQ Y1, Y0, Y0 - VPXOR Y0, Y4, Y4 \ - \ - VPSHUFB Y5, Y2, Y0 \ - VPADDQ Y0, Y1, Y1 \ - \ - VPSHUFB Y5, Y1, Y0 \ - VPADDQ Y0, Y2, Y2 - -// func initializeAVX2(state *[16]uint64, key []byte) -TEXT ·initializeAVX2(SB), 4, $0-32 - MOVQ state+0(FP), AX - MOVQ key_base+8(FP), BX - MOVQ $·consAVX2<>(SB), CX - - VMOVDQU 0(BX), Y1 - VPSHUFD $177, Y1, Y2 - - VMOVDQU 0(CX), Y3 - VMOVDQU 32(CX), Y4 - - VPXOR Y3, Y1, Y1 - VPXOR Y4, Y2, Y2 - - VMOVDQU Y1, 0(AX) - VMOVDQU Y2, 32(AX) - VMOVDQU Y3, 64(AX) - VMOVDQU Y4, 96(AX) - VZEROUPPER - RET - -// func updateAVX2(state *[16]uint64, msg []byte) -TEXT ·updateAVX2(SB), 4, $0-32 - MOVQ state+0(FP), AX - MOVQ msg_base+8(FP), BX - MOVQ msg_len+16(FP), CX - - CMPQ CX, $32 - JB DONE - - VMOVDQU 0(AX), Y1 - VMOVDQU 32(AX), Y2 - VMOVDQU 64(AX), Y3 - VMOVDQU 96(AX), Y4 - - VMOVDQU ·zipperMergeAVX2<>(SB), Y5 - -LOOP: - VMOVDQU 0(BX), Y0 - UPDATE(Y0) - - ADDQ $32, BX - SUBQ $32, CX - JA LOOP - - VMOVDQU Y1, 0(AX) - VMOVDQU Y2, 32(AX) - VMOVDQU Y3, 64(AX) - VMOVDQU Y4, 96(AX) - VZEROUPPER - -DONE: - RET - -// func finalizeAVX2(out []byte, state *[16]uint64) -TEXT ·finalizeAVX2(SB), 4, $0-32 - MOVQ state+24(FP), AX - MOVQ out_base+0(FP), BX - MOVQ out_len+8(FP), CX - - VMOVDQU 0(AX), Y1 - VMOVDQU 32(AX), Y2 - VMOVDQU 64(AX), Y3 - VMOVDQU 96(AX), Y4 - - VMOVDQU ·zipperMergeAVX2<>(SB), Y5 - - VPERM2I128 $1, Y1, Y1, Y0 - VPSHUFD $177, Y0, Y0 - UPDATE(Y0) - - VPERM2I128 $1, Y1, Y1, Y0 - VPSHUFD $177, Y0, Y0 - UPDATE(Y0) - - VPERM2I128 $1, Y1, Y1, Y0 - VPSHUFD $177, Y0, Y0 - UPDATE(Y0) - - VPERM2I128 $1, Y1, Y1, Y0 - VPSHUFD $177, Y0, Y0 - UPDATE(Y0) - - CMPQ CX, $8 - JE skipUpdate // Just 4 rounds for 64-bit checksum - - VPERM2I128 $1, Y1, Y1, Y0 - VPSHUFD $177, Y0, Y0 - UPDATE(Y0) - - VPERM2I128 $1, Y1, Y1, Y0 - VPSHUFD $177, Y0, Y0 - UPDATE(Y0) - - CMPQ CX, $16 - JE skipUpdate // 6 rounds for 128-bit checksum - - VPERM2I128 $1, Y1, Y1, Y0 - VPSHUFD $177, Y0, Y0 - UPDATE(Y0) - - VPERM2I128 $1, Y1, Y1, Y0 - VPSHUFD $177, Y0, Y0 - UPDATE(Y0) - - VPERM2I128 $1, Y1, Y1, Y0 - VPSHUFD $177, Y0, Y0 - UPDATE(Y0) - - VPERM2I128 $1, Y1, Y1, Y0 - VPSHUFD $177, Y0, Y0 - UPDATE(Y0) - -skipUpdate: - VMOVDQU Y1, 0(AX) - VMOVDQU Y2, 32(AX) - VMOVDQU Y3, 64(AX) - VMOVDQU Y4, 96(AX) - VZEROUPPER - - CMPQ CX, $8 - JE hash64 - CMPQ CX, $16 - JE hash128 - - // 256-bit checksum - MOVQ 0*8(AX), R8 - MOVQ 1*8(AX), R9 - MOVQ 4*8(AX), R10 - MOVQ 5*8(AX), R11 - ADDQ 8*8(AX), R8 - ADDQ 9*8(AX), R9 - ADDQ 12*8(AX), R10 - ADDQ 13*8(AX), R11 - - REDUCE_MOD(R8, R9, R10, R11, R12, R13, R14, R15) - MOVQ R14, 0(BX) - MOVQ R15, 8(BX) - - MOVQ 2*8(AX), R8 - MOVQ 3*8(AX), R9 - MOVQ 6*8(AX), R10 - MOVQ 7*8(AX), R11 - ADDQ 10*8(AX), R8 - ADDQ 11*8(AX), R9 - ADDQ 14*8(AX), R10 - ADDQ 15*8(AX), R11 - - REDUCE_MOD(R8, R9, R10, R11, R12, R13, R14, R15) - MOVQ R14, 16(BX) - MOVQ R15, 24(BX) - RET - -hash128: - MOVQ 0*8(AX), R8 - MOVQ 1*8(AX), R9 - ADDQ 6*8(AX), R8 - ADDQ 7*8(AX), R9 - ADDQ 8*8(AX), R8 - ADDQ 9*8(AX), R9 - ADDQ 14*8(AX), R8 - ADDQ 15*8(AX), R9 - MOVQ R8, 0(BX) - MOVQ R9, 8(BX) - RET - -hash64: - MOVQ 0*8(AX), DX - ADDQ 4*8(AX), DX - ADDQ 8*8(AX), DX - ADDQ 12*8(AX), DX - MOVQ DX, 0(BX) - RET - diff --git a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhashSVE_arm64.s b/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhashSVE_arm64.s deleted file mode 100644 index e9b6eb06..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhashSVE_arm64.s +++ /dev/null @@ -1,132 +0,0 @@ -// -// Copyright (c) 2024 Minio Inc. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -//+build !noasm,!appengine - -#include "textflag.h" - -TEXT ·getVectorLength(SB), NOSPLIT, $0 - WORD $0xd2800002 // mov x2, #0 - WORD $0x04225022 // addvl x2, x2, #1 - WORD $0xd37df042 // lsl x2, x2, #3 - WORD $0xd2800003 // mov x3, #0 - WORD $0x04635023 // addpl x3, x3, #1 - WORD $0xd37df063 // lsl x3, x3, #3 - MOVD R2, vl+0(FP) - MOVD R3, pl+8(FP) - RET - -TEXT ·updateArm64Sve(SB), NOSPLIT, $0 - MOVD state+0(FP), R0 - MOVD msg_base+8(FP), R1 - MOVD msg_len+16(FP), R2 // length of message - SUBS $32, R2 - BMI completeSve - - WORD $0x2518e3e1 // ptrue p1.b - WORD $0xa5e0a401 // ld1d z1.d, p1/z, [x0] - WORD $0xa5e1a402 // ld1d z2.d, p1/z, [x0, #1, MUL VL] - WORD $0xa5e2a403 // ld1d z3.d, p1/z, [x0, #2, MUL VL] - WORD $0xa5e3a404 // ld1d z4.d, p1/z, [x0, #3, MUL VL] - - // Load zipper merge constants table pointer - MOVD $·zipperMergeSve(SB), R3 - WORD $0xa5e0a465 // ld1d z5.d, p1/z, [x3] - WORD $0x25b8c006 // mov z6.s, #0 - WORD $0x25d8e3e2 // ptrue p2.d /* set every other lane for "s" type */ - -loopSve: - WORD $0xa5e0a420 // ld1d z0.d, p1/z, [x1] - ADD $32, R1 - - WORD $0x04e00042 // add z2.d, z2.d, z0.d - WORD $0x04e30042 // add z2.d, z2.d, z3.d - WORD $0x04e09420 // lsr z0.d, z1.d, #32 - WORD $0x05a6c847 // sel z7.s, p2, z2.s, z6.s - WORD $0x04d004e0 // mul z0.d, p1/m, z0.d, z7.d - WORD $0x04a33003 // eor z3.d, z0.d, z3.d - WORD $0x04e10081 // add z1.d, z4.d, z1.d - WORD $0x04e09440 // lsr z0.d, z2.d, #32 - WORD $0x05a6c827 // sel z7.s, p2, z1.s, z6.s - WORD $0x04d004e0 // mul z0.d, p1/m, z0.d, z7.d - WORD $0x04a43004 // eor z4.d, z0.d, z4.d - WORD $0x05253040 // tbl z0.b, z2.b, z5.b - WORD $0x04e00021 // add z1.d, z1.d, z0.d - WORD $0x05253020 // tbl z0.b, z1.b, z5.b - WORD $0x04e00042 // add z2.d, z2.d, z0.d - - SUBS $32, R2 - BPL loopSve - - WORD $0xe5e0e401 // st1d z1.d, p1, [x0] - WORD $0xe5e1e402 // st1d z2.d, p1, [x0, #1, MUL VL] - WORD $0xe5e2e403 // st1d z3.d, p1, [x0, #2, MUL VL] - WORD $0xe5e3e404 // st1d z4.d, p1, [x0, #3, MUL VL] - -completeSve: - RET - -TEXT ·updateArm64Sve2(SB), NOSPLIT, $0 - MOVD state+0(FP), R0 - MOVD msg_base+8(FP), R1 - MOVD msg_len+16(FP), R2 // length of message - SUBS $32, R2 - BMI completeSve2 - - WORD $0x2518e3e1 // ptrue p1.b - WORD $0xa5e0a401 // ld1d z1.d, p1/z, [x0] - WORD $0xa5e1a402 // ld1d z2.d, p1/z, [x0, #1, MUL VL] - WORD $0xa5e2a403 // ld1d z3.d, p1/z, [x0, #2, MUL VL] - WORD $0xa5e3a404 // ld1d z4.d, p1/z, [x0, #3, MUL VL] - - // Load zipper merge constants table pointer - MOVD $·zipperMergeSve(SB), R3 - WORD $0xa5e0a465 // ld1d z5.d, p1/z, [x3] - -loopSve2: - WORD $0xa5e0a420 // ld1d z0.d, p1/z, [x1] - ADD $32, R1 - - WORD $0x04e00042 // add z2.d, z2.d, z0.d - WORD $0x04e30042 // add z2.d, z2.d, z3.d - WORD $0x04e09420 // lsr z0.d, z1.d, #32 - WORD $0x45c27800 // umullb z0.d, z0.s, z2.s - WORD $0x04a33003 // eor z3.d, z0.d, z3.d - WORD $0x04e10081 // add z1.d, z4.d, z1.d - WORD $0x04e09440 // lsr z0.d, z2.d, #32 - WORD $0x45c17800 // umullb z0.d, z0.s, z1.s - WORD $0x04a43004 // eor z4.d, z0.d, z4.d - WORD $0x05253040 // tbl z0.b, z2.b, z5.b - WORD $0x04e00021 // add z1.d, z1.d, z0.d - WORD $0x05253020 // tbl z0.b, z1.b, z5.b - WORD $0x04e00042 // add z2.d, z2.d, z0.d - - SUBS $32, R2 - BPL loopSve2 - - WORD $0xe5e0e401 // st1d z1.d, p1, [x0] - WORD $0xe5e1e402 // st1d z2.d, p1, [x0, #1, MUL VL] - WORD $0xe5e2e403 // st1d z3.d, p1, [x0, #2, MUL VL] - WORD $0xe5e3e404 // st1d z4.d, p1, [x0, #3, MUL VL] - -completeSve2: - RET - -DATA ·zipperMergeSve+0x00(SB)/8, $0x000f010e05020c03 -DATA ·zipperMergeSve+0x08(SB)/8, $0x070806090d0a040b -DATA ·zipperMergeSve+0x10(SB)/8, $0x101f111e15121c13 -DATA ·zipperMergeSve+0x18(SB)/8, $0x171816191d1a141b -GLOBL ·zipperMergeSve(SB), (NOPTR+RODATA), $32 diff --git a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_amd64.go b/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_amd64.go deleted file mode 100644 index b7717836..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_amd64.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2017 Minio Inc. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -//go:build amd64 && !gccgo && !appengine && !nacl && !noasm -// +build amd64,!gccgo,!appengine,!nacl,!noasm - -package highwayhash - -import "golang.org/x/sys/cpu" - -var ( - useSSE4 = cpu.X86.HasSSE41 - useAVX2 = cpu.X86.HasAVX2 - useNEON = false - useSVE = false - useSVE2 = false - useVMX = false -) - -//go:noescape -func initializeSSE4(state *[16]uint64, key []byte) - -//go:noescape -func initializeAVX2(state *[16]uint64, key []byte) - -//go:noescape -func updateSSE4(state *[16]uint64, msg []byte) - -//go:noescape -func updateAVX2(state *[16]uint64, msg []byte) - -//go:noescape -func finalizeSSE4(out []byte, state *[16]uint64) - -//go:noescape -func finalizeAVX2(out []byte, state *[16]uint64) - -func initialize(state *[16]uint64, key []byte) { - switch { - case useAVX2: - initializeAVX2(state, key) - case useSSE4: - initializeSSE4(state, key) - default: - initializeGeneric(state, key) - } -} - -func update(state *[16]uint64, msg []byte) { - switch { - case useAVX2: - updateAVX2(state, msg) - case useSSE4: - updateSSE4(state, msg) - default: - updateGeneric(state, msg) - } -} - -func finalize(out []byte, state *[16]uint64) { - switch { - case useAVX2: - finalizeAVX2(out, state) - case useSSE4: - finalizeSSE4(out, state) - default: - finalizeGeneric(out, state) - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_amd64.s b/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_amd64.s deleted file mode 100644 index 43ecf3bc..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_amd64.s +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright (c) 2017 Minio Inc. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -// +build amd64 !gccgo !appengine !nacl - -#include "textflag.h" - -DATA ·asmConstants<>+0x00(SB)/8, $0xdbe6d5d5fe4cce2f -DATA ·asmConstants<>+0x08(SB)/8, $0xa4093822299f31d0 -DATA ·asmConstants<>+0x10(SB)/8, $0x13198a2e03707344 -DATA ·asmConstants<>+0x18(SB)/8, $0x243f6a8885a308d3 -DATA ·asmConstants<>+0x20(SB)/8, $0x3bd39e10cb0ef593 -DATA ·asmConstants<>+0x28(SB)/8, $0xc0acf169b5f18a8c -DATA ·asmConstants<>+0x30(SB)/8, $0xbe5466cf34e90c6c -DATA ·asmConstants<>+0x38(SB)/8, $0x452821e638d01377 -GLOBL ·asmConstants<>(SB), (NOPTR+RODATA), $64 - -DATA ·asmZipperMerge<>+0x00(SB)/8, $0xf010e05020c03 -DATA ·asmZipperMerge<>+0x08(SB)/8, $0x70806090d0a040b -GLOBL ·asmZipperMerge<>(SB), (NOPTR+RODATA), $16 - -#define v00 X0 -#define v01 X1 -#define v10 X2 -#define v11 X3 -#define m00 X4 -#define m01 X5 -#define m10 X6 -#define m11 X7 - -#define t0 X8 -#define t1 X9 -#define t2 X10 - -#define REDUCE_MOD(x0, x1, x2, x3, tmp0, tmp1, y0, y1) \ - MOVQ $0x3FFFFFFFFFFFFFFF, tmp0 \ - ANDQ tmp0, x3 \ - MOVQ x2, y0 \ - MOVQ x3, y1 \ - \ - MOVQ x2, tmp0 \ - MOVQ x3, tmp1 \ - SHLQ $1, tmp1 \ - SHRQ $63, tmp0 \ - MOVQ tmp1, x3 \ - ORQ tmp0, x3 \ - \ - SHLQ $1, x2 \ - \ - MOVQ y0, tmp0 \ - MOVQ y1, tmp1 \ - SHLQ $2, tmp1 \ - SHRQ $62, tmp0 \ - MOVQ tmp1, y1 \ - ORQ tmp0, y1 \ - \ - SHLQ $2, y0 \ - \ - XORQ x0, y0 \ - XORQ x2, y0 \ - XORQ x1, y1 \ - XORQ x3, y1 - -#define UPDATE(msg0, msg1) \ - PADDQ msg0, v10 \ - PADDQ m00, v10 \ - PADDQ msg1, v11 \ - PADDQ m01, v11 \ - \ - MOVO v00, t0 \ - MOVO v01, t1 \ - PSRLQ $32, t0 \ - PSRLQ $32, t1 \ - PMULULQ v10, t0 \ - PMULULQ v11, t1 \ - PXOR t0, m00 \ - PXOR t1, m01 \ - \ - PADDQ m10, v00 \ - PADDQ m11, v01 \ - \ - MOVO v10, t0 \ - MOVO v11, t1 \ - PSRLQ $32, t0 \ - PSRLQ $32, t1 \ - PMULULQ v00, t0 \ - PMULULQ v01, t1 \ - PXOR t0, m10 \ - PXOR t1, m11 \ - \ - MOVO v10, t0 \ - PSHUFB t2, t0 \ - MOVO v11, t1 \ - PSHUFB t2, t1 \ - PADDQ t0, v00 \ - PADDQ t1, v01 \ - \ - MOVO v00, t0 \ - PSHUFB t2, t0 \ - MOVO v01, t1 \ - PSHUFB t2, t1 \ - PADDQ t0, v10 \ - PADDQ t1, v11 - -// func initializeSSE4(state *[16]uint64, key []byte) -TEXT ·initializeSSE4(SB), NOSPLIT, $0-32 - MOVQ state+0(FP), AX - MOVQ key_base+8(FP), BX - MOVQ $·asmConstants<>(SB), CX - - MOVOU 0(BX), v00 - MOVOU 16(BX), v01 - - PSHUFD $177, v00, v10 - PSHUFD $177, v01, v11 - - MOVOU 0(CX), m00 - MOVOU 16(CX), m01 - MOVOU 32(CX), m10 - MOVOU 48(CX), m11 - - PXOR m00, v00 - PXOR m01, v01 - PXOR m10, v10 - PXOR m11, v11 - - MOVOU v00, 0(AX) - MOVOU v01, 16(AX) - MOVOU v10, 32(AX) - MOVOU v11, 48(AX) - MOVOU m00, 64(AX) - MOVOU m01, 80(AX) - MOVOU m10, 96(AX) - MOVOU m11, 112(AX) - RET - -// func updateSSE4(state *[16]uint64, msg []byte) -TEXT ·updateSSE4(SB), NOSPLIT, $0-32 - MOVQ state+0(FP), AX - MOVQ msg_base+8(FP), BX - MOVQ msg_len+16(FP), CX - - CMPQ CX, $32 - JB DONE - - MOVOU 0(AX), v00 - MOVOU 16(AX), v01 - MOVOU 32(AX), v10 - MOVOU 48(AX), v11 - MOVOU 64(AX), m00 - MOVOU 80(AX), m01 - MOVOU 96(AX), m10 - MOVOU 112(AX), m11 - - MOVOU ·asmZipperMerge<>(SB), t2 - -LOOP: - MOVOU 0(BX), t0 - MOVOU 16(BX), t1 - - UPDATE(t0, t1) - - ADDQ $32, BX - SUBQ $32, CX - JA LOOP - - MOVOU v00, 0(AX) - MOVOU v01, 16(AX) - MOVOU v10, 32(AX) - MOVOU v11, 48(AX) - MOVOU m00, 64(AX) - MOVOU m01, 80(AX) - MOVOU m10, 96(AX) - MOVOU m11, 112(AX) - -DONE: - RET - -// func finalizeSSE4(out []byte, state *[16]uint64) -TEXT ·finalizeSSE4(SB), NOSPLIT, $0-32 - MOVQ state+24(FP), AX - MOVQ out_base+0(FP), BX - MOVQ out_len+8(FP), CX - - MOVOU 0(AX), v00 - MOVOU 16(AX), v01 - MOVOU 32(AX), v10 - MOVOU 48(AX), v11 - MOVOU 64(AX), m00 - MOVOU 80(AX), m01 - MOVOU 96(AX), m10 - MOVOU 112(AX), m11 - - MOVOU ·asmZipperMerge<>(SB), t2 - - PSHUFD $177, v01, t0 - PSHUFD $177, v00, t1 - UPDATE(t0, t1) - - PSHUFD $177, v01, t0 - PSHUFD $177, v00, t1 - UPDATE(t0, t1) - - PSHUFD $177, v01, t0 - PSHUFD $177, v00, t1 - UPDATE(t0, t1) - - PSHUFD $177, v01, t0 - PSHUFD $177, v00, t1 - UPDATE(t0, t1) - - CMPQ CX, $8 - JE skipUpdate // Just 4 rounds for 64-bit checksum - - PSHUFD $177, v01, t0 - PSHUFD $177, v00, t1 - UPDATE(t0, t1) - - PSHUFD $177, v01, t0 - PSHUFD $177, v00, t1 - UPDATE(t0, t1) - - CMPQ CX, $16 - JE skipUpdate // 6 rounds for 128-bit checksum - - PSHUFD $177, v01, t0 - PSHUFD $177, v00, t1 - UPDATE(t0, t1) - - PSHUFD $177, v01, t0 - PSHUFD $177, v00, t1 - UPDATE(t0, t1) - - PSHUFD $177, v01, t0 - PSHUFD $177, v00, t1 - UPDATE(t0, t1) - - PSHUFD $177, v01, t0 - PSHUFD $177, v00, t1 - UPDATE(t0, t1) - -skipUpdate: - MOVOU v00, 0(AX) - MOVOU v01, 16(AX) - MOVOU v10, 32(AX) - MOVOU v11, 48(AX) - MOVOU m00, 64(AX) - MOVOU m01, 80(AX) - MOVOU m10, 96(AX) - MOVOU m11, 112(AX) - - CMPQ CX, $8 - JE hash64 - CMPQ CX, $16 - JE hash128 - - // 256-bit checksum - PADDQ v00, m00 - PADDQ v10, m10 - PADDQ v01, m01 - PADDQ v11, m11 - - MOVQ m00, R8 - PEXTRQ $1, m00, R9 - MOVQ m10, R10 - PEXTRQ $1, m10, R11 - REDUCE_MOD(R8, R9, R10, R11, R12, R13, R14, R15) - MOVQ R14, 0(BX) - MOVQ R15, 8(BX) - - MOVQ m01, R8 - PEXTRQ $1, m01, R9 - MOVQ m11, R10 - PEXTRQ $1, m11, R11 - REDUCE_MOD(R8, R9, R10, R11, R12, R13, R14, R15) - MOVQ R14, 16(BX) - MOVQ R15, 24(BX) - RET - -hash128: - PADDQ v00, v11 - PADDQ m00, m11 - PADDQ v11, m11 - MOVOU m11, 0(BX) - RET - -hash64: - PADDQ v00, v10 - PADDQ m00, m10 - PADDQ v10, m10 - MOVQ m10, DX - MOVQ DX, 0(BX) - RET diff --git a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_arm64.go b/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_arm64.go deleted file mode 100644 index d94e482d..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_arm64.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (c) 2017-2024 Minio Inc. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -//go:build !noasm && !appengine -// +build !noasm,!appengine - -package highwayhash - -import ( - "golang.org/x/sys/cpu" -) - -var ( - useSSE4 = false - useAVX2 = false - useNEON = cpu.ARM64.HasASIMD - useSVE = cpu.ARM64.HasSVE - useSVE2 = false // cpu.ARM64.HasSVE2 -- disable until tested on real hardware - useVMX = false -) - -func init() { - if useSVE { - if vl, _ := getVectorLength(); vl != 256 { - // - // Since HighwahHash is designed for AVX2, - // SVE/SVE2 instructions only run correctly - // for vector length of 256 - // - useSVE2 = false - useSVE = false - } - } -} - -//go:noescape -func initializeArm64(state *[16]uint64, key []byte) - -//go:noescape -func updateArm64(state *[16]uint64, msg []byte) - -//go:noescape -func getVectorLength() (vl, pl uint64) - -//go:noescape -func updateArm64Sve(state *[16]uint64, msg []byte) - -//go:noescape -func updateArm64Sve2(state *[16]uint64, msg []byte) - -//go:noescape -func finalizeArm64(out []byte, state *[16]uint64) - -func initialize(state *[16]uint64, key []byte) { - if useNEON { - initializeArm64(state, key) - } else { - initializeGeneric(state, key) - } -} - -func update(state *[16]uint64, msg []byte) { - if useSVE2 { - updateArm64Sve2(state, msg) - } else if useSVE { - updateArm64Sve(state, msg) - } else if useNEON { - updateArm64(state, msg) - } else { - updateGeneric(state, msg) - } -} - -func finalize(out []byte, state *[16]uint64) { - if useNEON { - finalizeArm64(out, state) - } else { - finalizeGeneric(out, state) - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_arm64.s b/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_arm64.s deleted file mode 100644 index 5e97a28e..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_arm64.s +++ /dev/null @@ -1,324 +0,0 @@ -// -// Minio Cloud Storage, (C) 2017 Minio, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -//+build !noasm,!appengine - -// Use github.com/minio/asm2plan9s on this file to assemble ARM instructions to -// the opcodes of their Plan9 equivalents - -#include "textflag.h" - -#define REDUCE_MOD(x0, x1, x2, x3, tmp0, tmp1, y0, y1) \ - MOVD $0x3FFFFFFFFFFFFFFF, tmp0 \ - AND tmp0, x3 \ - MOVD x2, y0 \ - MOVD x3, y1 \ - \ - MOVD x2, tmp0 \ - MOVD x3, tmp1 \ - LSL $1, tmp1 \ - LSR $63, tmp0 \ - MOVD tmp1, x3 \ - ORR tmp0, x3 \ - \ - LSL $1, x2 \ - \ - MOVD y0, tmp0 \ - MOVD y1, tmp1 \ - LSL $2, tmp1 \ - LSR $62, tmp0 \ - MOVD tmp1, y1 \ - ORR tmp0, y1 \ - \ - LSL $2, y0 \ - \ - EOR x0, y0 \ - EOR x2, y0 \ - EOR x1, y1 \ - EOR x3, y1 - -#define UPDATE(MSG1, MSG2) \ - \ // Add message - VADD MSG1.D2, V2.D2, V2.D2 \ - VADD MSG2.D2, V3.D2, V3.D2 \ - \ - \ // v1 += mul0 - VADD V4.D2, V2.D2, V2.D2 \ - VADD V5.D2, V3.D2, V3.D2 \ - \ - \ // First pair of multiplies - VTBL V29.B16, [V0.B16, V1.B16], V10.B16 \ - VTBL V30.B16, [V2.B16, V3.B16], V11.B16 \ - \ - \ // VUMULL V10.S2, V11.S2, V12.D2 /* assembler support missing */ - \ // VUMULL2 V10.S4, V11.S4, V13.D2 /* assembler support missing */ - WORD $0x2eaac16c \ // umull v12.2d, v11.2s, v10.2s - WORD $0x6eaac16d \ // umull2 v13.2d, v11.4s, v10.4s - \ - \ // v0 += mul1 - VADD V6.D2, V0.D2, V0.D2 \ - VADD V7.D2, V1.D2, V1.D2 \ - \ - \ // Second pair of multiplies - VTBL V29.B16, [V2.B16, V3.B16], V15.B16 \ - VTBL V30.B16, [V0.B16, V1.B16], V14.B16 \ - \ - \ // EOR multiplication result in - VEOR V12.B16, V4.B16, V4.B16 \ - VEOR V13.B16, V5.B16, V5.B16 \ - \ - \ // VUMULL V14.S2, V15.S2, V16.D2 /* assembler support missing */ - \ // VUMULL2 V14.S4, V15.S4, V17.D2 /* assembler support missing */ - WORD $0x2eaec1f0 \ // umull v16.2d, v15.2s, v14.2s - WORD $0x6eaec1f1 \ // umull2 v17.2d, v15.4s, v14.4s - \ - \ // First pair of zipper-merges - VTBL V28.B16, [V2.B16], V18.B16 \ - VADD V18.D2, V0.D2, V0.D2 \ - VTBL V28.B16, [V3.B16], V19.B16 \ - VADD V19.D2, V1.D2, V1.D2 \ - \ - \ // Second pair of zipper-merges - VTBL V28.B16, [V0.B16], V20.B16 \ - VADD V20.D2, V2.D2, V2.D2 \ - VTBL V28.B16, [V1.B16], V21.B16 \ - VADD V21.D2, V3.D2, V3.D2 \ - \ - \ // EOR multiplication result in - VEOR V16.B16, V6.B16, V6.B16 \ - VEOR V17.B16, V7.B16, V7.B16 - -// func initializeArm64(state *[16]uint64, key []byte) -TEXT ·initializeArm64(SB), NOSPLIT, $0 - MOVD state+0(FP), R0 - MOVD key_base+8(FP), R1 - - VLD1 (R1), [V1.S4, V2.S4] - - VREV64 V1.S4, V3.S4 - VREV64 V2.S4, V4.S4 - - MOVD $·asmConstants(SB), R3 - VLD1 (R3), [V5.S4, V6.S4, V7.S4, V8.S4] - VEOR V5.B16, V1.B16, V1.B16 - VEOR V6.B16, V2.B16, V2.B16 - VEOR V7.B16, V3.B16, V3.B16 - VEOR V8.B16, V4.B16, V4.B16 - - VST1.P [V1.D2, V2.D2, V3.D2, V4.D2], 64(R0) - VST1 [V5.D2, V6.D2, V7.D2, V8.D2], (R0) - RET - -TEXT ·updateArm64(SB), NOSPLIT, $0 - MOVD state+0(FP), R0 - MOVD msg_base+8(FP), R1 - MOVD msg_len+16(FP), R2 // length of message - SUBS $32, R2 - BMI complete - - // Definition of registers - // v0 = v0.lo - // v1 = v0.hi - // v2 = v1.lo - // v3 = v1.hi - // v4 = mul0.lo - // v5 = mul0.hi - // v6 = mul1.lo - // v7 = mul1.hi - - // Load zipper merge constants table pointer - MOVD $·asmZipperMerge(SB), R3 - - // and load zipper merge constants into v28, v29, and v30 - VLD1 (R3), [V28.B16, V29.B16, V30.B16] - - VLD1.P 64(R0), [V0.D2, V1.D2, V2.D2, V3.D2] - VLD1 (R0), [V4.D2, V5.D2, V6.D2, V7.D2] - SUBS $64, R0 - -loop: - // Main loop - VLD1.P 32(R1), [V26.S4, V27.S4] - - UPDATE(V26, V27) - - SUBS $32, R2 - BPL loop - - // Store result - VST1.P [V0.D2, V1.D2, V2.D2, V3.D2], 64(R0) - VST1 [V4.D2, V5.D2, V6.D2, V7.D2], (R0) - -complete: - RET - -// func finalizeArm64(out []byte, state *[16]uint64) -TEXT ·finalizeArm64(SB), NOSPLIT, $0-32 - MOVD state+24(FP), R0 - MOVD out_base+0(FP), R1 - MOVD out_len+8(FP), R2 - - // Load zipper merge constants table pointer - MOVD $·asmZipperMerge(SB), R3 - - // and load zipper merge constants into v28, v29, and v30 - VLD1 (R3), [V28.B16, V29.B16, V30.B16] - - VLD1.P 64(R0), [V0.D2, V1.D2, V2.D2, V3.D2] - VLD1 (R0), [V4.D2, V5.D2, V6.D2, V7.D2] - SUB $64, R0 - - VREV64 V1.S4, V26.S4 - VREV64 V0.S4, V27.S4 - UPDATE(V26, V27) - - VREV64 V1.S4, V26.S4 - VREV64 V0.S4, V27.S4 - UPDATE(V26, V27) - - VREV64 V1.S4, V26.S4 - VREV64 V0.S4, V27.S4 - UPDATE(V26, V27) - - VREV64 V1.S4, V26.S4 - VREV64 V0.S4, V27.S4 - UPDATE(V26, V27) - - CMP $8, R2 - BEQ skipUpdate // Just 4 rounds for 64-bit checksum - - VREV64 V1.S4, V26.S4 - VREV64 V0.S4, V27.S4 - UPDATE(V26, V27) - - VREV64 V1.S4, V26.S4 - VREV64 V0.S4, V27.S4 - UPDATE(V26, V27) - - CMP $16, R2 - BEQ skipUpdate // 6 rounds for 128-bit checksum - - VREV64 V1.S4, V26.S4 - VREV64 V0.S4, V27.S4 - UPDATE(V26, V27) - - VREV64 V1.S4, V26.S4 - VREV64 V0.S4, V27.S4 - UPDATE(V26, V27) - - VREV64 V1.S4, V26.S4 - VREV64 V0.S4, V27.S4 - UPDATE(V26, V27) - - VREV64 V1.S4, V26.S4 - VREV64 V0.S4, V27.S4 - UPDATE(V26, V27) - -skipUpdate: - // Store result - VST1.P [V0.D2, V1.D2, V2.D2, V3.D2], 64(R0) - VST1 [V4.D2, V5.D2, V6.D2, V7.D2], (R0) - SUB $64, R0 - - CMP $8, R2 - BEQ hash64 - CMP $16, R2 - BEQ hash128 - - // 256-bit checksum - MOVD 0*8(R0), R8 - MOVD 1*8(R0), R9 - MOVD 4*8(R0), R10 - MOVD 5*8(R0), R11 - MOVD 8*8(R0), R4 - MOVD 9*8(R0), R5 - MOVD 12*8(R0), R6 - MOVD 13*8(R0), R7 - ADD R4, R8 - ADD R5, R9 - ADD R6, R10 - ADD R7, R11 - - REDUCE_MOD(R8, R9, R10, R11, R4, R5, R6, R7) - MOVD R6, 0(R1) - MOVD R7, 8(R1) - - MOVD 2*8(R0), R8 - MOVD 3*8(R0), R9 - MOVD 6*8(R0), R10 - MOVD 7*8(R0), R11 - MOVD 10*8(R0), R4 - MOVD 11*8(R0), R5 - MOVD 14*8(R0), R6 - MOVD 15*8(R0), R7 - ADD R4, R8 - ADD R5, R9 - ADD R6, R10 - ADD R7, R11 - - REDUCE_MOD(R8, R9, R10, R11, R4, R5, R6, R7) - MOVD R6, 16(R1) - MOVD R7, 24(R1) - RET - -hash128: - MOVD 0*8(R0), R8 - MOVD 1*8(R0), R9 - MOVD 6*8(R0), R10 - MOVD 7*8(R0), R11 - ADD R10, R8 - ADD R11, R9 - MOVD 8*8(R0), R10 - MOVD 9*8(R0), R11 - ADD R10, R8 - ADD R11, R9 - MOVD 14*8(R0), R10 - MOVD 15*8(R0), R11 - ADD R10, R8 - ADD R11, R9 - MOVD R8, 0(R1) - MOVD R9, 8(R1) - RET - -hash64: - MOVD 0*8(R0), R4 - MOVD 4*8(R0), R5 - MOVD 8*8(R0), R6 - MOVD 12*8(R0), R7 - ADD R5, R4 - ADD R7, R6 - ADD R6, R4 - MOVD R4, (R1) - RET - -DATA ·asmConstants+0x00(SB)/8, $0xdbe6d5d5fe4cce2f -DATA ·asmConstants+0x08(SB)/8, $0xa4093822299f31d0 -DATA ·asmConstants+0x10(SB)/8, $0x13198a2e03707344 -DATA ·asmConstants+0x18(SB)/8, $0x243f6a8885a308d3 -DATA ·asmConstants+0x20(SB)/8, $0x3bd39e10cb0ef593 -DATA ·asmConstants+0x28(SB)/8, $0xc0acf169b5f18a8c -DATA ·asmConstants+0x30(SB)/8, $0xbe5466cf34e90c6c -DATA ·asmConstants+0x38(SB)/8, $0x452821e638d01377 -GLOBL ·asmConstants(SB), 8, $64 - -// Constants for TBL instructions -DATA ·asmZipperMerge+0x0(SB)/8, $0x000f010e05020c03 // zipper merge constant -DATA ·asmZipperMerge+0x8(SB)/8, $0x070806090d0a040b -DATA ·asmZipperMerge+0x10(SB)/8, $0x0f0e0d0c07060504 // setup first register for multiply -DATA ·asmZipperMerge+0x18(SB)/8, $0x1f1e1d1c17161514 -DATA ·asmZipperMerge+0x20(SB)/8, $0x0b0a090803020100 // setup second register for multiply -DATA ·asmZipperMerge+0x28(SB)/8, $0x1b1a191813121110 -GLOBL ·asmZipperMerge(SB), 8, $48 diff --git a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_generic.go b/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_generic.go deleted file mode 100644 index 1f66e223..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_generic.go +++ /dev/null @@ -1,338 +0,0 @@ -// Copyright (c) 2017 Minio Inc. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -package highwayhash - -import ( - "encoding/binary" -) - -const ( - v0 = 0 - v1 = 4 - mul0 = 8 - mul1 = 12 -) - -var ( - init0 = [4]uint64{0xdbe6d5d5fe4cce2f, 0xa4093822299f31d0, 0x13198a2e03707344, 0x243f6a8885a308d3} - init1 = [4]uint64{0x3bd39e10cb0ef593, 0xc0acf169b5f18a8c, 0xbe5466cf34e90c6c, 0x452821e638d01377} -) - -func initializeGeneric(state *[16]uint64, k []byte) { - var key [4]uint64 - - key[0] = binary.LittleEndian.Uint64(k[0:]) - key[1] = binary.LittleEndian.Uint64(k[8:]) - key[2] = binary.LittleEndian.Uint64(k[16:]) - key[3] = binary.LittleEndian.Uint64(k[24:]) - - copy(state[mul0:], init0[:]) - copy(state[mul1:], init1[:]) - - for i, k := range key { - state[v0+i] = init0[i] ^ k - } - - key[0] = key[0]>>32 | key[0]<<32 - key[1] = key[1]>>32 | key[1]<<32 - key[2] = key[2]>>32 | key[2]<<32 - key[3] = key[3]>>32 | key[3]<<32 - - for i, k := range key { - state[v1+i] = init1[i] ^ k - } -} - -func updateGeneric(state *[16]uint64, msg []byte) { - for len(msg) >= 32 { - m := msg[:32] - - // add message + mul0 - // Interleave operations to hide multiplication - state[v1+0] += binary.LittleEndian.Uint64(m) + state[mul0+0] - state[mul0+0] ^= uint64(uint32(state[v1+0])) * (state[v0+0] >> 32) - state[v0+0] += state[mul1+0] - state[mul1+0] ^= uint64(uint32(state[v0+0])) * (state[v1+0] >> 32) - - state[v1+1] += binary.LittleEndian.Uint64(m[8:]) + state[mul0+1] - state[mul0+1] ^= uint64(uint32(state[v1+1])) * (state[v0+1] >> 32) - state[v0+1] += state[mul1+1] - state[mul1+1] ^= uint64(uint32(state[v0+1])) * (state[v1+1] >> 32) - - state[v1+2] += binary.LittleEndian.Uint64(m[16:]) + state[mul0+2] - state[mul0+2] ^= uint64(uint32(state[v1+2])) * (state[v0+2] >> 32) - state[v0+2] += state[mul1+2] - state[mul1+2] ^= uint64(uint32(state[v0+2])) * (state[v1+2] >> 32) - - state[v1+3] += binary.LittleEndian.Uint64(m[24:]) + state[mul0+3] - state[mul0+3] ^= uint64(uint32(state[v1+3])) * (state[v0+3] >> 32) - state[v0+3] += state[mul1+3] - state[mul1+3] ^= uint64(uint32(state[v0+3])) * (state[v1+3] >> 32) - - // inlined: zipperMerge(state[v1+0], state[v1+1], &state[v0+0], &state[v0+1]) - { - val0 := state[v1+0] - val1 := state[v1+1] - res := val0 & (0xff << (2 * 8)) - res2 := (val0 & (0xff << (7 * 8))) + (val1 & (0xff << (2 * 8))) - res += (val1 & (0xff << (7 * 8))) >> 8 - res2 += (val0 & (0xff << (6 * 8))) >> 8 - res += ((val0 & (0xff << (5 * 8))) + (val1 & (0xff << (6 * 8)))) >> 16 - res2 += (val1 & (0xff << (5 * 8))) >> 16 - res += ((val0 & (0xff << (3 * 8))) + (val1 & (0xff << (4 * 8)))) >> 24 - res2 += ((val1 & (0xff << (3 * 8))) + (val0 & (0xff << (4 * 8)))) >> 24 - res += (val0 & (0xff << (1 * 8))) << 32 - res2 += (val1 & 0xff) << 48 - res += val0 << 56 - res2 += (val1 & (0xff << (1 * 8))) << 24 - - state[v0+0] += res - state[v0+1] += res2 - } - // zipperMerge(state[v1+2], state[v1+3], &state[v0+2], &state[v0+3]) - { - val0 := state[v1+2] - val1 := state[v1+3] - res := val0 & (0xff << (2 * 8)) - res2 := (val0 & (0xff << (7 * 8))) + (val1 & (0xff << (2 * 8))) - res += (val1 & (0xff << (7 * 8))) >> 8 - res2 += (val0 & (0xff << (6 * 8))) >> 8 - res += ((val0 & (0xff << (5 * 8))) + (val1 & (0xff << (6 * 8)))) >> 16 - res2 += (val1 & (0xff << (5 * 8))) >> 16 - res += ((val0 & (0xff << (3 * 8))) + (val1 & (0xff << (4 * 8)))) >> 24 - res2 += ((val1 & (0xff << (3 * 8))) + (val0 & (0xff << (4 * 8)))) >> 24 - res += (val0 & (0xff << (1 * 8))) << 32 - res2 += (val1 & 0xff) << 48 - res += val0 << 56 - res2 += (val1 & (0xff << (1 * 8))) << 24 - - state[v0+2] += res - state[v0+3] += res2 - } - - // inlined: zipperMerge(state[v0+0], state[v0+1], &state[v1+0], &state[v1+1]) - { - val0 := state[v0+0] - val1 := state[v0+1] - res := val0 & (0xff << (2 * 8)) - res2 := (val0 & (0xff << (7 * 8))) + (val1 & (0xff << (2 * 8))) - res += (val1 & (0xff << (7 * 8))) >> 8 - res2 += (val0 & (0xff << (6 * 8))) >> 8 - res += ((val0 & (0xff << (5 * 8))) + (val1 & (0xff << (6 * 8)))) >> 16 - res2 += (val1 & (0xff << (5 * 8))) >> 16 - res += ((val0 & (0xff << (3 * 8))) + (val1 & (0xff << (4 * 8)))) >> 24 - res2 += ((val1 & (0xff << (3 * 8))) + (val0 & (0xff << (4 * 8)))) >> 24 - res += (val0 & (0xff << (1 * 8))) << 32 - res2 += (val1 & 0xff) << 48 - res += val0 << 56 - res2 += (val1 & (0xff << (1 * 8))) << 24 - - state[v1+0] += res - state[v1+1] += res2 - } - - //inlined: zipperMerge(state[v0+2], state[v0+3], &state[v1+2], &state[v1+3]) - { - val0 := state[v0+2] - val1 := state[v0+3] - res := val0 & (0xff << (2 * 8)) - res2 := (val0 & (0xff << (7 * 8))) + (val1 & (0xff << (2 * 8))) - res += (val1 & (0xff << (7 * 8))) >> 8 - res2 += (val0 & (0xff << (6 * 8))) >> 8 - res += ((val0 & (0xff << (5 * 8))) + (val1 & (0xff << (6 * 8)))) >> 16 - res2 += (val1 & (0xff << (5 * 8))) >> 16 - res += ((val0 & (0xff << (3 * 8))) + (val1 & (0xff << (4 * 8)))) >> 24 - res2 += ((val1 & (0xff << (3 * 8))) + (val0 & (0xff << (4 * 8)))) >> 24 - res += (val0 & (0xff << (1 * 8))) << 32 - res2 += (val1 & 0xff) << 48 - res += val0 << 56 - res2 += (val1 & (0xff << (1 * 8))) << 24 - - state[v1+2] += res - state[v1+3] += res2 - } - msg = msg[32:] - } -} - -func finalizeGeneric(out []byte, state *[16]uint64) { - var perm [4]uint64 - var tmp [32]byte - runs := 4 - if len(out) == 16 { - runs = 6 - } else if len(out) == 32 { - runs = 10 - } - for i := 0; i < runs; i++ { - perm[0] = state[v0+2]>>32 | state[v0+2]<<32 - perm[1] = state[v0+3]>>32 | state[v0+3]<<32 - perm[2] = state[v0+0]>>32 | state[v0+0]<<32 - perm[3] = state[v0+1]>>32 | state[v0+1]<<32 - - binary.LittleEndian.PutUint64(tmp[0:], perm[0]) - binary.LittleEndian.PutUint64(tmp[8:], perm[1]) - binary.LittleEndian.PutUint64(tmp[16:], perm[2]) - binary.LittleEndian.PutUint64(tmp[24:], perm[3]) - - update(state, tmp[:]) - } - - switch len(out) { - case 8: - binary.LittleEndian.PutUint64(out, state[v0+0]+state[v1+0]+state[mul0+0]+state[mul1+0]) - case 16: - binary.LittleEndian.PutUint64(out, state[v0+0]+state[v1+2]+state[mul0+0]+state[mul1+2]) - binary.LittleEndian.PutUint64(out[8:], state[v0+1]+state[v1+3]+state[mul0+1]+state[mul1+3]) - case 32: - h0, h1 := reduceMod(state[v0+0]+state[mul0+0], state[v0+1]+state[mul0+1], state[v1+0]+state[mul1+0], state[v1+1]+state[mul1+1]) - binary.LittleEndian.PutUint64(out[0:], h0) - binary.LittleEndian.PutUint64(out[8:], h1) - - h0, h1 = reduceMod(state[v0+2]+state[mul0+2], state[v0+3]+state[mul0+3], state[v1+2]+state[mul1+2], state[v1+3]+state[mul1+3]) - binary.LittleEndian.PutUint64(out[16:], h0) - binary.LittleEndian.PutUint64(out[24:], h1) - } -} - -// Experiments on variations left for future reference... -/* -func zipperMerge(v0, v1 uint64, d0, d1 *uint64) { - if true { - // fastest. original interleaved... - res := v0 & (0xff << (2 * 8)) - res2 := (v0 & (0xff << (7 * 8))) + (v1 & (0xff << (2 * 8))) - res += (v1 & (0xff << (7 * 8))) >> 8 - res2 += (v0 & (0xff << (6 * 8))) >> 8 - res += ((v0 & (0xff << (5 * 8))) + (v1 & (0xff << (6 * 8)))) >> 16 - res2 += (v1 & (0xff << (5 * 8))) >> 16 - res += ((v0 & (0xff << (3 * 8))) + (v1 & (0xff << (4 * 8)))) >> 24 - res2 += ((v1 & (0xff << (3 * 8))) + (v0 & (0xff << (4 * 8)))) >> 24 - res += (v0 & (0xff << (1 * 8))) << 32 - res2 += (v1 & 0xff) << 48 - res += v0 << 56 - res2 += (v1 & (0xff << (1 * 8))) << 24 - - *d0 += res - *d1 += res2 - } else if false { - // Reading bytes and combining into uint64 - var v0b [8]byte - binary.LittleEndian.PutUint64(v0b[:], v0) - var v1b [8]byte - binary.LittleEndian.PutUint64(v1b[:], v1) - var res, res2 uint64 - - res = uint64(v0b[0]) << (7 * 8) - res2 = uint64(v1b[0]) << (6 * 8) - res |= uint64(v0b[1]) << (5 * 8) - res2 |= uint64(v1b[1]) << (4 * 8) - res |= uint64(v0b[2]) << (2 * 8) - res2 |= uint64(v1b[2]) << (2 * 8) - res |= uint64(v0b[3]) - res2 |= uint64(v0b[4]) << (1 * 8) - res |= uint64(v0b[5]) << (3 * 8) - res2 |= uint64(v0b[6]) << (5 * 8) - res |= uint64(v1b[4]) << (1 * 8) - res2 |= uint64(v0b[7]) << (7 * 8) - res |= uint64(v1b[6]) << (4 * 8) - res2 |= uint64(v1b[3]) - res |= uint64(v1b[7]) << (6 * 8) - res2 |= uint64(v1b[5]) << (3 * 8) - - *d0 += res - *d1 += res2 - - } else if false { - // bytes to bytes shuffle - var v0b [8]byte - binary.LittleEndian.PutUint64(v0b[:], v0) - var v1b [8]byte - binary.LittleEndian.PutUint64(v1b[:], v1) - var res [8]byte - - //res += ((v0 & (0xff << (3 * 8))) + (v1 & (0xff << (4 * 8)))) >> 24 - res[0] = v0b[3] - res[1] = v1b[4] - - // res := v0 & (0xff << (2 * 8)) - res[2] = v0b[2] - - //res += ((v0 & (0xff << (5 * 8))) + (v1 & (0xff << (6 * 8)))) >> 16 - res[3] = v0b[5] - res[4] = v1b[6] - - //res += (v0 & (0xff << (1 * 8))) << 32 - res[5] = v0b[1] - - //res += (v1 & (0xff << (7 * 8))) >> 8 - res[6] += v1b[7] - - //res += v0 << 56 - res[7] = v0b[0] - v0 = binary.LittleEndian.Uint64(res[:]) - *d0 += v0 - - //res += ((v1 & (0xff << (3 * 8))) + (v0 & (0xff << (4 * 8)))) >> 24 - res[0] = v1b[3] - res[1] = v0b[4] - - res[2] = v1b[2] - - // res += (v1 & (0xff << (5 * 8))) >> 16 - res[3] = v1b[5] - - //res += (v1 & (0xff << (1 * 8))) << 24 - res[4] = v1b[1] - - // res += (v0 & (0xff << (6 * 8))) >> 8 - res[5] = v0b[6] - - //res := (v0 & (0xff << (7 * 8))) + (v1 & (0xff << (2 * 8))) - res[7] = v0b[7] - - //res += (v1 & 0xff) << 48 - res[6] = v1b[0] - - v0 = binary.LittleEndian.Uint64(res[:]) - *d1 += v0 - } else { - // original. - res := v0 & (0xff << (2 * 8)) - res += (v1 & (0xff << (7 * 8))) >> 8 - res += ((v0 & (0xff << (5 * 8))) + (v1 & (0xff << (6 * 8)))) >> 16 - res += ((v0 & (0xff << (3 * 8))) + (v1 & (0xff << (4 * 8)))) >> 24 - res += (v0 & (0xff << (1 * 8))) << 32 - res += v0 << 56 - - *d0 += res - - res = (v0 & (0xff << (7 * 8))) + (v1 & (0xff << (2 * 8))) - res += (v0 & (0xff << (6 * 8))) >> 8 - res += (v1 & (0xff << (5 * 8))) >> 16 - res += ((v1 & (0xff << (3 * 8))) + (v0 & (0xff << (4 * 8)))) >> 24 - res += (v1 & 0xff) << 48 - res += (v1 & (0xff << (1 * 8))) << 24 - - *d1 += res - } -} -*/ - -// reduce v = [v0, v1, v2, v3] mod the irreducible polynomial x^128 + x^2 + x -func reduceMod(v0, v1, v2, v3 uint64) (r0, r1 uint64) { - v3 &= 0x3FFFFFFFFFFFFFFF - - r0, r1 = v2, v3 - - v3 = (v3 << 1) | (v2 >> (64 - 1)) - v2 <<= 1 - r1 = (r1 << 2) | (r0 >> (64 - 2)) - r0 <<= 2 - - r0 ^= v0 ^ v2 - r1 ^= v1 ^ v3 - return -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_ppc64le.go b/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_ppc64le.go deleted file mode 100644 index cf9ee1a2..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_ppc64le.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2017 Minio Inc. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -//go:build !noasm && !appengine -// +build !noasm,!appengine - -package highwayhash - -var ( - useSSE4 = false - useAVX2 = false - useNEON = false - useSVE = false - useSVE2 = false - useVMX = true -) - -//go:noescape -func updatePpc64Le(state *[16]uint64, msg []byte) - -func initialize(state *[16]uint64, key []byte) { - initializeGeneric(state, key) -} - -func update(state *[16]uint64, msg []byte) { - if useVMX { - updatePpc64Le(state, msg) - } else { - updateGeneric(state, msg) - } -} - -func finalize(out []byte, state *[16]uint64) { - finalizeGeneric(out, state) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_ppc64le.s b/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_ppc64le.s deleted file mode 100644 index c70ec8d4..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_ppc64le.s +++ /dev/null @@ -1,182 +0,0 @@ -// -// Minio Cloud Storage, (C) 2018 Minio, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -//+build !noasm,!appengine - -#include "textflag.h" - -// Definition of registers -#define V0_LO VS32 -#define V0_LO_ V0 -#define V0_HI VS33 -#define V0_HI_ V1 -#define V1_LO VS34 -#define V1_LO_ V2 -#define V1_HI VS35 -#define V1_HI_ V3 -#define MUL0_LO VS36 -#define MUL0_LO_ V4 -#define MUL0_HI VS37 -#define MUL0_HI_ V5 -#define MUL1_LO VS38 -#define MUL1_LO_ V6 -#define MUL1_HI VS39 -#define MUL1_HI_ V7 - -// Message -#define MSG_LO VS40 -#define MSG_LO_ V8 -#define MSG_HI VS41 - -// Constants -#define ROTATE VS42 -#define ROTATE_ V10 -#define MASK VS43 -#define MASK_ V11 - -// Temps -#define TEMP1 VS44 -#define TEMP1_ V12 -#define TEMP2 VS45 -#define TEMP2_ V13 -#define TEMP3 VS46 -#define TEMP3_ V14 -#define TEMP4_ V15 -#define TEMP5_ V16 -#define TEMP6_ V17 -#define TEMP7_ V18 - -// Regular registers -#define STATE R3 -#define MSG_BASE R4 -#define MSG_LEN R5 -#define CONSTANTS R6 -#define P1 R7 -#define P2 R8 -#define P3 R9 -#define P4 R10 -#define P5 R11 -#define P6 R12 -#define P7 R14 // avoid using R13 - -TEXT ·updatePpc64Le(SB), NOFRAME|NOSPLIT, $0-32 - MOVD state+0(FP), STATE - MOVD msg_base+8(FP), MSG_BASE - MOVD msg_len+16(FP), MSG_LEN // length of message - - // Sanity check for length - CMPU MSG_LEN, $31 - BLE complete - - // Setup offsets - MOVD $16, P1 - MOVD $32, P2 - MOVD $48, P3 - MOVD $64, P4 - MOVD $80, P5 - MOVD $96, P6 - MOVD $112, P7 - - // Load state - LXVD2X (STATE)(R0), V0_LO - LXVD2X (STATE)(P1), V0_HI - LXVD2X (STATE)(P2), V1_LO - LXVD2X (STATE)(P3), V1_HI - LXVD2X (STATE)(P4), MUL0_LO - LXVD2X (STATE)(P5), MUL0_HI - LXVD2X (STATE)(P6), MUL1_LO - LXVD2X (STATE)(P7), MUL1_HI - XXPERMDI V0_LO, V0_LO, $2, V0_LO - XXPERMDI V0_HI, V0_HI, $2, V0_HI - XXPERMDI V1_LO, V1_LO, $2, V1_LO - XXPERMDI V1_HI, V1_HI, $2, V1_HI - XXPERMDI MUL0_LO, MUL0_LO, $2, MUL0_LO - XXPERMDI MUL0_HI, MUL0_HI, $2, MUL0_HI - XXPERMDI MUL1_LO, MUL1_LO, $2, MUL1_LO - XXPERMDI MUL1_HI, MUL1_HI, $2, MUL1_HI - - // Load asmConstants table pointer - MOVD $·asmConstants(SB), CONSTANTS - LXVD2X (CONSTANTS)(R0), ROTATE - LXVD2X (CONSTANTS)(P1), MASK - XXLNAND MASK, MASK, MASK - -loop: - // Main highwayhash update loop - LXVD2X (MSG_BASE)(R0), MSG_LO - VADDUDM V0_LO_, MUL1_LO_, TEMP1_ - VRLD V0_LO_, ROTATE_, TEMP2_ - VADDUDM MUL1_HI_, V0_HI_, TEMP3_ - LXVD2X (MSG_BASE)(P1), MSG_HI - ADD $32, MSG_BASE, MSG_BASE - XXPERMDI MSG_LO, MSG_LO, $2, MSG_LO - XXPERMDI MSG_HI, MSG_HI, $2, V0_LO - VADDUDM MSG_LO_, MUL0_LO_, MSG_LO_ - VADDUDM V0_LO_, MUL0_HI_, V0_LO_ - VADDUDM MSG_LO_, V1_LO_, V1_LO_ - VSRD V0_HI_, ROTATE_, MSG_LO_ - VADDUDM V0_LO_, V1_HI_, V1_HI_ - VPERM V1_LO_, V1_LO_, MASK_, V0_LO_ - VMULOUW V1_LO_, TEMP2_, TEMP2_ - VPERM V1_HI_, V1_HI_, MASK_, TEMP7_ - VADDUDM V0_LO_, TEMP1_, V0_LO_ - VMULOUW V1_HI_, MSG_LO_, MSG_LO_ - VADDUDM TEMP7_, TEMP3_, V0_HI_ - VPERM V0_LO_, V0_LO_, MASK_, TEMP6_ - VRLD V1_LO_, ROTATE_, TEMP4_ - VSRD V1_HI_, ROTATE_, TEMP5_ - VPERM V0_HI_, V0_HI_, MASK_, TEMP7_ - XXLXOR MUL0_LO, TEMP2, MUL0_LO - VMULOUW TEMP1_, TEMP4_, TEMP1_ - VMULOUW TEMP3_, TEMP5_, TEMP3_ - XXLXOR MUL0_HI, MSG_LO, MUL0_HI - XXLXOR MUL1_LO, TEMP1, MUL1_LO - XXLXOR MUL1_HI, TEMP3, MUL1_HI - VADDUDM TEMP6_, V1_LO_, V1_LO_ - VADDUDM TEMP7_, V1_HI_, V1_HI_ - - SUB $32, MSG_LEN, MSG_LEN - CMPU MSG_LEN, $32 - BGE loop - - // Save state - XXPERMDI V0_LO, V0_LO, $2, V0_LO - XXPERMDI V0_HI, V0_HI, $2, V0_HI - XXPERMDI V1_LO, V1_LO, $2, V1_LO - XXPERMDI V1_HI, V1_HI, $2, V1_HI - XXPERMDI MUL0_LO, MUL0_LO, $2, MUL0_LO - XXPERMDI MUL0_HI, MUL0_HI, $2, MUL0_HI - XXPERMDI MUL1_LO, MUL1_LO, $2, MUL1_LO - XXPERMDI MUL1_HI, MUL1_HI, $2, MUL1_HI - STXVD2X V0_LO, (STATE)(R0) - STXVD2X V0_HI, (STATE)(P1) - STXVD2X V1_LO, (STATE)(P2) - STXVD2X V1_HI, (STATE)(P3) - STXVD2X MUL0_LO, (STATE)(P4) - STXVD2X MUL0_HI, (STATE)(P5) - STXVD2X MUL1_LO, (STATE)(P6) - STXVD2X MUL1_HI, (STATE)(P7) - -complete: - RET - -// Constants table -DATA ·asmConstants+0x0(SB)/8, $0x0000000000000020 -DATA ·asmConstants+0x8(SB)/8, $0x0000000000000020 -DATA ·asmConstants+0x10(SB)/8, $0x070806090d0a040b // zipper merge constant -DATA ·asmConstants+0x18(SB)/8, $0x000f010e05020c03 // zipper merge constant - -GLOBL ·asmConstants(SB), 8, $32 diff --git a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_ref.go b/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_ref.go deleted file mode 100644 index 42cbbb4c..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/minio/highwayhash/highwayhash_ref.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2017 Minio Inc. All rights reserved. -// Use of this source code is governed by a license that can be -// found in the LICENSE file. - -//go:build noasm || (!amd64 && !arm64 && !ppc64le) -// +build noasm !amd64,!arm64,!ppc64le - -package highwayhash - -var ( - useSSE4 = false - useAVX2 = false - useNEON = false - useSVE = false - useSVE2 = false - useVMX = false -) - -func initialize(state *[16]uint64, k []byte) { - initializeGeneric(state, k) -} - -func update(state *[16]uint64, msg []byte) { - updateGeneric(state, msg) -} - -func finalize(out []byte, state *[16]uint64) { - finalizeGeneric(out, state) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/LICENSE b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/LICENSE deleted file mode 100644 index 261eeb9e..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/Makefile b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/Makefile deleted file mode 100644 index 108c1a45..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -.PHONY: test cover - -build: - go build - -fmt: - gofmt -w -s *.go - goimports -w *.go - go mod tidy - -test: - go vet ./... - staticcheck ./... - rm -rf ./coverage.out - go test -v -coverprofile=./coverage.out ./... - -cover: - go tool cover -html=coverage.out \ No newline at end of file diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/account_claims.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/account_claims.go deleted file mode 100644 index 9da374ae..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/account_claims.go +++ /dev/null @@ -1,482 +0,0 @@ -/* - * Copyright 2018-2024 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "errors" - "fmt" - "sort" - "time" - - "github.com/nats-io/nkeys" -) - -// NoLimit is used to indicate a limit field is unlimited in value. -const ( - NoLimit = -1 - AnyAccount = "*" -) - -type AccountLimits struct { - Imports int64 `json:"imports,omitempty"` // Max number of imports - Exports int64 `json:"exports,omitempty"` // Max number of exports - WildcardExports bool `json:"wildcards,omitempty"` // Are wildcards allowed in exports - DisallowBearer bool `json:"disallow_bearer,omitempty"` // User JWT can't be bearer token - Conn int64 `json:"conn,omitempty"` // Max number of active connections - LeafNodeConn int64 `json:"leaf,omitempty"` // Max number of active leaf node connections -} - -// IsUnlimited returns true if all limits are unlimited -func (a *AccountLimits) IsUnlimited() bool { - return *a == AccountLimits{NoLimit, NoLimit, true, false, NoLimit, NoLimit} -} - -type NatsLimits struct { - Subs int64 `json:"subs,omitempty"` // Max number of subscriptions - Data int64 `json:"data,omitempty"` // Max number of bytes - Payload int64 `json:"payload,omitempty"` // Max message payload -} - -// IsUnlimited returns true if all limits are unlimited -func (n *NatsLimits) IsUnlimited() bool { - return *n == NatsLimits{NoLimit, NoLimit, NoLimit} -} - -type JetStreamLimits struct { - MemoryStorage int64 `json:"mem_storage,omitempty"` // Max number of bytes stored in memory across all streams. (0 means disabled) - DiskStorage int64 `json:"disk_storage,omitempty"` // Max number of bytes stored on disk across all streams. (0 means disabled) - Streams int64 `json:"streams,omitempty"` // Max number of streams - Consumer int64 `json:"consumer,omitempty"` // Max number of consumers - MaxAckPending int64 `json:"max_ack_pending,omitempty"` // Max ack pending of a Stream - MemoryMaxStreamBytes int64 `json:"mem_max_stream_bytes,omitempty"` // Max bytes a memory backed stream can have. (0 means disabled/unlimited) - DiskMaxStreamBytes int64 `json:"disk_max_stream_bytes,omitempty"` // Max bytes a disk backed stream can have. (0 means disabled/unlimited) - MaxBytesRequired bool `json:"max_bytes_required,omitempty"` // Max bytes required by all Streams -} - -// IsUnlimited returns true if all limits are unlimited -func (j *JetStreamLimits) IsUnlimited() bool { - lim := *j - // workaround in case NoLimit was used instead of 0 - if lim.MemoryMaxStreamBytes < 0 { - lim.MemoryMaxStreamBytes = 0 - } - if lim.DiskMaxStreamBytes < 0 { - lim.DiskMaxStreamBytes = 0 - } - if lim.MaxAckPending < 0 { - lim.MaxAckPending = 0 - } - return lim == JetStreamLimits{NoLimit, NoLimit, NoLimit, NoLimit, 0, 0, 0, false} -} - -type JetStreamTieredLimits map[string]JetStreamLimits - -// OperatorLimits are used to limit access by an account -type OperatorLimits struct { - NatsLimits - AccountLimits - JetStreamLimits - JetStreamTieredLimits `json:"tiered_limits,omitempty"` -} - -// IsJSEnabled returns if this account claim has JS enabled either through a tier or the non tiered limits. -func (o *OperatorLimits) IsJSEnabled() bool { - if len(o.JetStreamTieredLimits) > 0 { - for _, l := range o.JetStreamTieredLimits { - if l.MemoryStorage != 0 || l.DiskStorage != 0 { - return true - } - } - return false - } - l := o.JetStreamLimits - return l.MemoryStorage != 0 || l.DiskStorage != 0 -} - -// IsEmpty returns true if all limits are 0/false/empty. -func (o *OperatorLimits) IsEmpty() bool { - return o.NatsLimits == NatsLimits{} && - o.AccountLimits == AccountLimits{} && - o.JetStreamLimits == JetStreamLimits{} && - len(o.JetStreamTieredLimits) == 0 -} - -// IsUnlimited returns true if all limits are unlimited -func (o *OperatorLimits) IsUnlimited() bool { - return o.AccountLimits.IsUnlimited() && o.NatsLimits.IsUnlimited() && - o.JetStreamLimits.IsUnlimited() && len(o.JetStreamTieredLimits) == 0 -} - -// Validate checks that the operator limits contain valid values -func (o *OperatorLimits) Validate(vr *ValidationResults) { - // negative values mean unlimited, so all numbers are valid - if len(o.JetStreamTieredLimits) > 0 { - if (o.JetStreamLimits != JetStreamLimits{}) { - vr.AddError("JetStream Limits and tiered JetStream Limits are mutually exclusive") - } - if _, ok := o.JetStreamTieredLimits[""]; ok { - vr.AddError(`Tiered JetStream Limits can not contain a blank "" tier name`) - } - } -} - -// WeightedMapping for publishes -type WeightedMapping struct { - Subject Subject `json:"subject"` - Weight uint8 `json:"weight,omitempty"` - Cluster string `json:"cluster,omitempty"` -} - -func (m *WeightedMapping) GetWeight() uint8 { - if m.Weight == 0 { - return 100 - } - return m.Weight -} - -type Mapping map[Subject][]WeightedMapping - -func (m *Mapping) Validate(vr *ValidationResults) { - for ubFrom, wm := range (map[Subject][]WeightedMapping)(*m) { - ubFrom.Validate(vr) - perCluster := make(map[string]uint8) - total := uint8(0) - for _, e := range wm { - e.Subject.Validate(vr) - if e.Cluster != "" { - t := perCluster[e.Cluster] - t += e.Weight - perCluster[e.Cluster] = t - if t > 100 { - vr.AddError("Mapping %q in cluster %q exceeds 100%% among all of it's weighted to mappings", ubFrom, e.Cluster) - } - } else { - total += e.GetWeight() - } - } - if total > 100 { - vr.AddError("Mapping %q exceeds 100%% among all of it's weighted to mappings", ubFrom) - } - } -} - -func (a *Account) AddMapping(sub Subject, to ...WeightedMapping) { - a.Mappings[sub] = to -} - -// ExternalAuthorization enables external authorization for account users. -// AuthUsers are those users specified to bypass the authorization callout and should be used for the authorization service itself. -// AllowedAccounts specifies which accounts, if any, that the authorization service can bind an authorized user to. -// The authorization response, a user JWT, will still need to be signed by the correct account. -// If optional XKey is specified, that is the public xkey (x25519) and the server will encrypt the request such that only the -// holder of the private key can decrypt. The auth service can also optionally encrypt the response back to the server using it's -// public xkey which will be in the authorization request. -type ExternalAuthorization struct { - AuthUsers StringList `json:"auth_users,omitempty"` - AllowedAccounts StringList `json:"allowed_accounts,omitempty"` - XKey string `json:"xkey,omitempty"` -} - -func (ac *ExternalAuthorization) IsEnabled() bool { - return len(ac.AuthUsers) > 0 -} - -// HasExternalAuthorization helper function to determine if external authorization is enabled. -func (a *Account) HasExternalAuthorization() bool { - return a.Authorization.IsEnabled() -} - -// EnableExternalAuthorization helper function to setup external authorization. -func (a *Account) EnableExternalAuthorization(users ...string) { - a.Authorization.AuthUsers.Add(users...) -} - -func (ac *ExternalAuthorization) Validate(vr *ValidationResults) { - if len(ac.AllowedAccounts) > 0 && len(ac.AuthUsers) == 0 { - vr.AddError("External authorization cannot have accounts without users specified") - } - // Make sure users are all valid user nkeys. - // Make sure allowed accounts are all valid account nkeys. - for _, u := range ac.AuthUsers { - if !nkeys.IsValidPublicUserKey(u) { - vr.AddError("AuthUser %q is not a valid user public key", u) - } - } - for _, a := range ac.AllowedAccounts { - if a == AnyAccount && len(ac.AllowedAccounts) > 1 { - vr.AddError("AllowedAccounts can only be a list of accounts or %q", AnyAccount) - continue - } else if a == AnyAccount { - continue - } else if !nkeys.IsValidPublicAccountKey(a) { - vr.AddError("Account %q is not a valid account public key", a) - } - } - if ac.XKey != "" && !nkeys.IsValidPublicCurveKey(ac.XKey) { - vr.AddError("XKey %q is not a valid public xkey", ac.XKey) - } -} - -const ( - ClusterTrafficSystem = "system" - ClusterTrafficOwner = "owner" -) - -type ClusterTraffic string - -func (ct ClusterTraffic) Valid() error { - if ct == "" || ct == ClusterTrafficSystem || ct == ClusterTrafficOwner { - return nil - } - return fmt.Errorf("unknown cluster traffic option: %q", ct) -} - -// Account holds account specific claims data -type Account struct { - Imports Imports `json:"imports,omitempty"` - Exports Exports `json:"exports,omitempty"` - Limits OperatorLimits `json:"limits,omitempty"` - SigningKeys SigningKeys `json:"signing_keys,omitempty"` - Revocations RevocationList `json:"revocations,omitempty"` - DefaultPermissions Permissions `json:"default_permissions,omitempty"` - Mappings Mapping `json:"mappings,omitempty"` - Authorization ExternalAuthorization `json:"authorization,omitempty"` - Trace *MsgTrace `json:"trace,omitempty"` - ClusterTraffic ClusterTraffic `json:"cluster_traffic,omitempty"` - Info - GenericFields -} - -// MsgTrace holds distributed message tracing configuration -type MsgTrace struct { - // Destination is the subject the server will send message traces to - // if the inbound message contains the "traceparent" header and has - // its sampled field indicating that the trace should be triggered. - Destination Subject `json:"dest,omitempty"` - // Sampling is used to set the probability sampling, that is, the - // server will get a random number between 1 and 100 and trigger - // the trace if the number is lower than this Sampling value. - // The valid range is [1..100]. If the value is not set Validate() - // will set the value to 100. - Sampling int `json:"sampling,omitempty"` -} - -// Validate checks if the account is valid, based on the wrapper -func (a *Account) Validate(acct *AccountClaims, vr *ValidationResults) { - a.Imports.Validate(acct.Subject, vr) - a.Exports.Validate(vr) - a.Limits.Validate(vr) - a.DefaultPermissions.Validate(vr) - a.Mappings.Validate(vr) - a.Authorization.Validate(vr) - if a.Trace != nil { - tvr := CreateValidationResults() - a.Trace.Destination.Validate(tvr) - if !tvr.IsEmpty() { - vr.AddError(fmt.Sprintf("the account Trace.Destination %s", tvr.Issues[0].Description)) - } - if a.Trace.Destination.HasWildCards() { - vr.AddError("the account Trace.Destination subject %q is not a valid publish subject", a.Trace.Destination) - } - if a.Trace.Sampling < 0 || a.Trace.Sampling > 100 { - vr.AddError("the account Trace.Sampling value '%d' is not valid, should be in the range [1..100]", a.Trace.Sampling) - } else if a.Trace.Sampling == 0 { - a.Trace.Sampling = 100 - } - } - - if !a.Limits.IsEmpty() && a.Limits.Imports >= 0 && int64(len(a.Imports)) > a.Limits.Imports { - vr.AddError("the account contains more imports than allowed by the operator") - } - - // Check Imports and Exports for limit violations. - if a.Limits.Imports != NoLimit { - if int64(len(a.Imports)) > a.Limits.Imports { - vr.AddError("the account contains more imports than allowed by the operator") - } - } - if a.Limits.Exports != NoLimit { - if int64(len(a.Exports)) > a.Limits.Exports { - vr.AddError("the account contains more exports than allowed by the operator") - } - // Check for wildcard restrictions - if !a.Limits.WildcardExports { - for _, ex := range a.Exports { - if ex.Subject.HasWildCards() { - vr.AddError("the account contains wildcard exports that are not allowed by the operator") - } - } - } - } - a.SigningKeys.Validate(vr) - a.Info.Validate(vr) - - if err := a.ClusterTraffic.Valid(); err != nil { - vr.AddError(err.Error()) - } -} - -// AccountClaims defines the body of an account JWT -type AccountClaims struct { - ClaimsData - Account `json:"nats,omitempty"` -} - -// NewAccountClaims creates a new account JWT -func NewAccountClaims(subject string) *AccountClaims { - if subject == "" { - return nil - } - c := &AccountClaims{} - c.SigningKeys = make(SigningKeys) - // Set to unlimited to start. We do it this way so we get compiler - // errors if we add to the OperatorLimits. - c.Limits = OperatorLimits{ - NatsLimits{NoLimit, NoLimit, NoLimit}, - AccountLimits{NoLimit, NoLimit, true, false, NoLimit, NoLimit}, - JetStreamLimits{0, 0, 0, 0, 0, 0, 0, false}, - JetStreamTieredLimits{}, - } - c.Subject = subject - c.Mappings = Mapping{} - return c -} - -// Encode converts account claims into a JWT string -func (a *AccountClaims) Encode(pair nkeys.KeyPair) (string, error) { - return a.EncodeWithSigner(pair, nil) -} - -func (a *AccountClaims) EncodeWithSigner(pair nkeys.KeyPair, fn SignFn) (string, error) { - if !nkeys.IsValidPublicAccountKey(a.Subject) { - return "", errors.New("expected subject to be account public key") - } - sort.Sort(a.Exports) - sort.Sort(a.Imports) - a.Type = AccountClaim - return a.ClaimsData.encode(pair, a, fn) -} - -// DecodeAccountClaims decodes account claims from a JWT string -func DecodeAccountClaims(token string) (*AccountClaims, error) { - claims, err := Decode(token) - if err != nil { - return nil, err - } - ac, ok := claims.(*AccountClaims) - if !ok { - return nil, errors.New("not account claim") - } - return ac, nil -} - -func (a *AccountClaims) String() string { - return a.ClaimsData.String(a) -} - -// Payload pulls the accounts specific payload out of the claims -func (a *AccountClaims) Payload() interface{} { - return &a.Account -} - -// Validate checks the accounts contents -func (a *AccountClaims) Validate(vr *ValidationResults) { - a.ClaimsData.Validate(vr) - a.Account.Validate(a, vr) - - if nkeys.IsValidPublicAccountKey(a.ClaimsData.Issuer) { - if !a.Limits.IsEmpty() { - vr.AddWarning("self-signed account JWTs shouldn't contain operator limits") - } - } -} - -func (a *AccountClaims) ClaimType() ClaimType { - return a.Type -} - -func (a *AccountClaims) updateVersion() { - a.GenericFields.Version = libVersion -} - -// ExpectedPrefixes defines the types that can encode an account jwt, account and operator -func (a *AccountClaims) ExpectedPrefixes() []nkeys.PrefixByte { - return []nkeys.PrefixByte{nkeys.PrefixByteAccount, nkeys.PrefixByteOperator} -} - -// Claims returns the accounts claims data -func (a *AccountClaims) Claims() *ClaimsData { - return &a.ClaimsData -} -func (a *AccountClaims) GetTags() TagList { - return a.Account.Tags -} - -// DidSign checks the claims against the account's public key and its signing keys -func (a *AccountClaims) DidSign(c Claims) bool { - if c != nil { - issuer := c.Claims().Issuer - if issuer == a.Subject { - return true - } - uc, ok := c.(*UserClaims) - if ok && uc.IssuerAccount == a.Subject { - return a.SigningKeys.Contains(issuer) - } - at, ok := c.(*ActivationClaims) - if ok && at.IssuerAccount == a.Subject { - return a.SigningKeys.Contains(issuer) - } - } - return false -} - -// Revoke enters a revocation by public key using time.Now(). -func (a *AccountClaims) Revoke(pubKey string) { - a.RevokeAt(pubKey, time.Now()) -} - -// RevokeAt enters a revocation by public key and timestamp into this account -// This will revoke all jwt issued for pubKey, prior to timestamp -// If there is already a revocation for this public key that is newer, it is kept. -// The value is expected to be a public key or "*" (means all public keys) -func (a *AccountClaims) RevokeAt(pubKey string, timestamp time.Time) { - if a.Revocations == nil { - a.Revocations = RevocationList{} - } - a.Revocations.Revoke(pubKey, timestamp) -} - -// ClearRevocation removes any revocation for the public key -func (a *AccountClaims) ClearRevocation(pubKey string) { - a.Revocations.ClearRevocation(pubKey) -} - -// isRevoked checks if the public key is in the revoked list with a timestamp later than the one passed in. -// Generally this method is called with the subject and issue time of the jwt to be tested. -// DO NOT pass time.Now(), it will not produce a stable/expected response. -func (a *AccountClaims) isRevoked(pubKey string, claimIssuedAt time.Time) bool { - return a.Revocations.IsRevoked(pubKey, claimIssuedAt) -} - -// IsClaimRevoked checks if the account revoked the claim passed in. -// Invalid claims (nil, no Subject or IssuedAt) will return true. -func (a *AccountClaims) IsClaimRevoked(claim *UserClaims) bool { - if claim == nil || claim.IssuedAt == 0 || claim.Subject == "" { - return true - } - return a.isRevoked(claim.Subject, time.Unix(claim.IssuedAt, 0)) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/activation_claims.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/activation_claims.go deleted file mode 100644 index 63fe7883..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/activation_claims.go +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2018-2024 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "crypto/sha256" - "encoding/base32" - "errors" - "fmt" - "strings" - - "github.com/nats-io/nkeys" -) - -// Activation defines the custom parts of an activation claim -type Activation struct { - ImportSubject Subject `json:"subject,omitempty"` - ImportType ExportType `json:"kind,omitempty"` - // IssuerAccount stores the public key for the account the issuer represents. - // When set, the claim was issued by a signing key. - IssuerAccount string `json:"issuer_account,omitempty"` - GenericFields -} - -// IsService returns true if an Activation is for a service -func (a *Activation) IsService() bool { - return a.ImportType == Service -} - -// IsStream returns true if an Activation is for a stream -func (a *Activation) IsStream() bool { - return a.ImportType == Stream -} - -// Validate checks the exports and limits in an activation JWT -func (a *Activation) Validate(vr *ValidationResults) { - if !a.IsService() && !a.IsStream() { - vr.AddError("invalid import type: %q", a.ImportType) - } - - a.ImportSubject.Validate(vr) -} - -// ActivationClaims holds the data specific to an activation JWT -type ActivationClaims struct { - ClaimsData - Activation `json:"nats,omitempty"` -} - -// NewActivationClaims creates a new activation claim with the provided sub -func NewActivationClaims(subject string) *ActivationClaims { - if subject == "" { - return nil - } - ac := &ActivationClaims{} - ac.Subject = subject - return ac -} - -// Encode turns an activation claim into a JWT strimg -func (a *ActivationClaims) Encode(pair nkeys.KeyPair) (string, error) { - return a.EncodeWithSigner(pair, nil) -} - -func (a *ActivationClaims) EncodeWithSigner(pair nkeys.KeyPair, fn SignFn) (string, error) { - if !nkeys.IsValidPublicAccountKey(a.ClaimsData.Subject) { - return "", errors.New("expected subject to be an account") - } - a.Type = ActivationClaim - return a.ClaimsData.encode(pair, a, fn) -} - -// DecodeActivationClaims tries to create an activation claim from a JWT string -func DecodeActivationClaims(token string) (*ActivationClaims, error) { - claims, err := Decode(token) - if err != nil { - return nil, err - } - ac, ok := claims.(*ActivationClaims) - if !ok { - return nil, errors.New("not activation claim") - } - return ac, nil -} - -// Payload returns the activation specific part of the JWT -func (a *ActivationClaims) Payload() interface{} { - return a.Activation -} - -// Validate checks the claims -func (a *ActivationClaims) Validate(vr *ValidationResults) { - a.validateWithTimeChecks(vr, true) -} - -// Validate checks the claims -func (a *ActivationClaims) validateWithTimeChecks(vr *ValidationResults, timeChecks bool) { - if timeChecks { - a.ClaimsData.Validate(vr) - } - a.Activation.Validate(vr) - if a.IssuerAccount != "" && !nkeys.IsValidPublicAccountKey(a.IssuerAccount) { - vr.AddError("account_id is not an account public key") - } -} - -func (a *ActivationClaims) ClaimType() ClaimType { - return a.Type -} - -func (a *ActivationClaims) updateVersion() { - a.GenericFields.Version = libVersion -} - -// ExpectedPrefixes defines the types that can sign an activation jwt, account and oeprator -func (a *ActivationClaims) ExpectedPrefixes() []nkeys.PrefixByte { - return []nkeys.PrefixByte{nkeys.PrefixByteAccount, nkeys.PrefixByteOperator} -} - -// Claims returns the generic part of the JWT -func (a *ActivationClaims) Claims() *ClaimsData { - return &a.ClaimsData -} - -func (a *ActivationClaims) String() string { - return a.ClaimsData.String(a) -} - -// HashID returns a hash of the claims that can be used to identify it. -// The hash is calculated by creating a string with -// issuerPubKey.subjectPubKey. and constructing the sha-256 hash and base32 encoding that. -// is the exported subject, minus any wildcards, so foo.* becomes foo. -// the one special case is that if the export start with "*" or is ">" the "_" -func (a *ActivationClaims) HashID() (string, error) { - - if a.Issuer == "" || a.Subject == "" || a.ImportSubject == "" { - return "", fmt.Errorf("not enough data in the activaion claims to create a hash") - } - - subject := cleanSubject(string(a.ImportSubject)) - base := fmt.Sprintf("%s.%s.%s", a.Issuer, a.Subject, subject) - h := sha256.New() - h.Write([]byte(base)) - sha := h.Sum(nil) - hash := base32.StdEncoding.EncodeToString(sha) - - return hash, nil -} - -func cleanSubject(subject string) string { - split := strings.Split(subject, ".") - cleaned := "" - - for i, tok := range split { - if tok == "*" || tok == ">" { - if i == 0 { - cleaned = "_" - break - } - - cleaned = strings.Join(split[:i], ".") - break - } - } - if cleaned == "" { - cleaned = subject - } - return cleaned -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/authorization_claims.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/authorization_claims.go deleted file mode 100644 index 3448f11d..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/authorization_claims.go +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright 2022-2024 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "errors" - - "github.com/nats-io/nkeys" -) - -// ServerID is basic static info for a NATS server. -type ServerID struct { - Name string `json:"name"` - Host string `json:"host"` - ID string `json:"id"` - Version string `json:"version,omitempty"` - Cluster string `json:"cluster,omitempty"` - Tags TagList `json:"tags,omitempty"` - XKey string `json:"xkey,omitempty"` -} - -// ClientInformation is information about a client that is trying to authorize. -type ClientInformation struct { - Host string `json:"host,omitempty"` - ID uint64 `json:"id,omitempty"` - User string `json:"user,omitempty"` - Name string `json:"name,omitempty"` - Tags TagList `json:"tags,omitempty"` - NameTag string `json:"name_tag,omitempty"` - Kind string `json:"kind,omitempty"` - Type string `json:"type,omitempty"` - MQTT string `json:"mqtt_id,omitempty"` - Nonce string `json:"nonce,omitempty"` -} - -// ConnectOptions represents options that were set in the CONNECT protocol from the client -// during authorization. -type ConnectOptions struct { - JWT string `json:"jwt,omitempty"` - Nkey string `json:"nkey,omitempty"` - SignedNonce string `json:"sig,omitempty"` - Token string `json:"auth_token,omitempty"` - Username string `json:"user,omitempty"` - Password string `json:"pass,omitempty"` - Name string `json:"name,omitempty"` - Lang string `json:"lang,omitempty"` - Version string `json:"version,omitempty"` - Protocol int `json:"protocol"` -} - -// ClientTLS is information about TLS state if present, including client certs. -// If the client certs were present and verified they will be under verified chains -// with the client peer cert being VerifiedChains[0]. These are complete and pem encoded. -// If they were not verified, they will be under certs. -type ClientTLS struct { - Version string `json:"version,omitempty"` - Cipher string `json:"cipher,omitempty"` - Certs StringList `json:"certs,omitempty"` - VerifiedChains []StringList `json:"verified_chains,omitempty"` -} - -// AuthorizationRequest represents all the information we know about the client that -// will be sent to an external authorization service. -type AuthorizationRequest struct { - Server ServerID `json:"server_id"` - UserNkey string `json:"user_nkey"` - ClientInformation ClientInformation `json:"client_info"` - ConnectOptions ConnectOptions `json:"connect_opts"` - TLS *ClientTLS `json:"client_tls,omitempty"` - RequestNonce string `json:"request_nonce,omitempty"` - GenericFields -} - -// AuthorizationRequestClaims defines an external auth request JWT. -// These wil be signed by a NATS server. -type AuthorizationRequestClaims struct { - ClaimsData - AuthorizationRequest `json:"nats"` -} - -// NewAuthorizationRequestClaims creates an auth request JWT with the specific subject/public key. -func NewAuthorizationRequestClaims(subject string) *AuthorizationRequestClaims { - if subject == "" { - return nil - } - var ac AuthorizationRequestClaims - ac.Subject = subject - return &ac -} - -// Validate checks the generic and specific parts of the auth request jwt. -func (ac *AuthorizationRequestClaims) Validate(vr *ValidationResults) { - if ac.UserNkey == "" { - vr.AddError("User nkey is required") - } else if !nkeys.IsValidPublicUserKey(ac.UserNkey) { - vr.AddError("User nkey %q is not a valid user public key", ac.UserNkey) - } - ac.ClaimsData.Validate(vr) -} - -// Encode tries to turn the auth request claims into a JWT string. -func (ac *AuthorizationRequestClaims) Encode(pair nkeys.KeyPair) (string, error) { - return ac.EncodeWithSigner(pair, nil) -} - -func (ac *AuthorizationRequestClaims) EncodeWithSigner(pair nkeys.KeyPair, fn SignFn) (string, error) { - ac.Type = AuthorizationRequestClaim - return ac.ClaimsData.encode(pair, ac, fn) -} - -// DecodeAuthorizationRequestClaims tries to parse an auth request claims from a JWT string -func DecodeAuthorizationRequestClaims(token string) (*AuthorizationRequestClaims, error) { - claims, err := Decode(token) - if err != nil { - return nil, err - } - ac, ok := claims.(*AuthorizationRequestClaims) - if !ok { - return nil, errors.New("not an authorization request claim") - } - return ac, nil -} - -// ExpectedPrefixes defines the types that can encode an auth request jwt, servers. -func (ac *AuthorizationRequestClaims) ExpectedPrefixes() []nkeys.PrefixByte { - return []nkeys.PrefixByte{nkeys.PrefixByteServer} -} - -func (ac *AuthorizationRequestClaims) ClaimType() ClaimType { - return ac.Type -} - -// Claims returns the request claims data. -func (ac *AuthorizationRequestClaims) Claims() *ClaimsData { - return &ac.ClaimsData -} - -// Payload pulls the request specific payload out of the claims. -func (ac *AuthorizationRequestClaims) Payload() interface{} { - return &ac.AuthorizationRequest -} - -func (ac *AuthorizationRequestClaims) String() string { - return ac.ClaimsData.String(ac) -} - -func (ac *AuthorizationRequestClaims) updateVersion() { - ac.GenericFields.Version = libVersion -} - -type AuthorizationResponse struct { - Jwt string `json:"jwt,omitempty"` - Error string `json:"error,omitempty"` - // IssuerAccount stores the public key for the account the issuer represents. - // When set, the claim was issued by a signing key. - IssuerAccount string `json:"issuer_account,omitempty"` - GenericFields -} - -type AuthorizationResponseClaims struct { - ClaimsData - AuthorizationResponse `json:"nats"` -} - -func NewAuthorizationResponseClaims(subject string) *AuthorizationResponseClaims { - if subject == "" { - return nil - } - var ac AuthorizationResponseClaims - ac.Subject = subject - return &ac -} - -// DecodeAuthorizationResponseClaims tries to parse an auth request claims from a JWT string -func DecodeAuthorizationResponseClaims(token string) (*AuthorizationResponseClaims, error) { - claims, err := Decode(token) - if err != nil { - return nil, err - } - ac, ok := claims.(*AuthorizationResponseClaims) - if !ok { - return nil, errors.New("not an authorization request claim") - } - return ac, nil -} - -// ExpectedPrefixes defines the types that can encode an auth request jwt, servers. -func (ar *AuthorizationResponseClaims) ExpectedPrefixes() []nkeys.PrefixByte { - return []nkeys.PrefixByte{nkeys.PrefixByteAccount} -} - -func (ar *AuthorizationResponseClaims) ClaimType() ClaimType { - return ar.Type -} - -// Claims returns the request claims data. -func (ar *AuthorizationResponseClaims) Claims() *ClaimsData { - return &ar.ClaimsData -} - -// Payload pulls the request specific payload out of the claims. -func (ar *AuthorizationResponseClaims) Payload() interface{} { - return &ar.AuthorizationResponse -} - -func (ar *AuthorizationResponseClaims) String() string { - return ar.ClaimsData.String(ar) -} - -func (ar *AuthorizationResponseClaims) updateVersion() { - ar.GenericFields.Version = libVersion -} - -// Validate checks the generic and specific parts of the auth request jwt. -func (ar *AuthorizationResponseClaims) Validate(vr *ValidationResults) { - if !nkeys.IsValidPublicUserKey(ar.Subject) { - vr.AddError("Subject must be a user public key") - } - if !nkeys.IsValidPublicServerKey(ar.Audience) { - vr.AddError("Audience must be a server public key") - } - if ar.Error == "" && ar.Jwt == "" { - vr.AddError("Error or Jwt is required") - } - if ar.Error != "" && ar.Jwt != "" { - vr.AddError("Only Error or Jwt can be set") - } - if ar.IssuerAccount != "" && !nkeys.IsValidPublicAccountKey(ar.IssuerAccount) { - vr.AddError("issuer_account is not an account public key") - } - ar.ClaimsData.Validate(vr) -} - -// Encode tries to turn the auth request claims into a JWT string. -func (ar *AuthorizationResponseClaims) Encode(pair nkeys.KeyPair) (string, error) { - return ar.EncodeWithSigner(pair, nil) -} - -func (ar *AuthorizationResponseClaims) EncodeWithSigner(pair nkeys.KeyPair, fn SignFn) (string, error) { - ar.Type = AuthorizationResponseClaim - return ar.ClaimsData.encode(pair, ar, fn) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/claims.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/claims.go deleted file mode 100644 index 9b816c34..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/claims.go +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright 2018-2024 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "crypto/sha512" - "encoding/base32" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "time" - - "github.com/nats-io/nkeys" -) - -// ClaimType is used to indicate the type of JWT being stored in a Claim -type ClaimType string - -const ( - // OperatorClaim is the type of an operator JWT - OperatorClaim = "operator" - // AccountClaim is the type of an Account JWT - AccountClaim = "account" - // UserClaim is the type of an user JWT - UserClaim = "user" - // ActivationClaim is the type of an activation JWT - ActivationClaim = "activation" - // AuthorizationRequestClaim is the type of an auth request claim JWT - AuthorizationRequestClaim = "authorization_request" - // AuthorizationResponseClaim is the response for an auth request - AuthorizationResponseClaim = "authorization_response" - // GenericClaim is a type that doesn't match Operator/Account/User/ActionClaim - GenericClaim = "generic" -) - -func IsGenericClaimType(s string) bool { - switch s { - case OperatorClaim: - fallthrough - case AccountClaim: - fallthrough - case UserClaim: - fallthrough - case AuthorizationRequestClaim: - fallthrough - case AuthorizationResponseClaim: - fallthrough - case ActivationClaim: - return false - case GenericClaim: - return true - default: - return true - } -} - -// SignFn is used in an external sign environment. The function should be -// able to locate the private key for the specified pub key specified and sign the -// specified data returning the signature as generated. -type SignFn func(pub string, data []byte) ([]byte, error) - -// Claims is a JWT claims -type Claims interface { - Claims() *ClaimsData - Encode(kp nkeys.KeyPair) (string, error) - EncodeWithSigner(pair nkeys.KeyPair, fn SignFn) (string, error) - ExpectedPrefixes() []nkeys.PrefixByte - Payload() interface{} - String() string - Validate(vr *ValidationResults) - ClaimType() ClaimType - - verify(payload string, sig []byte) bool - updateVersion() -} - -type GenericFields struct { - Tags TagList `json:"tags,omitempty"` - Type ClaimType `json:"type,omitempty"` - Version int `json:"version,omitempty"` -} - -// ClaimsData is the base struct for all claims -type ClaimsData struct { - Audience string `json:"aud,omitempty"` - Expires int64 `json:"exp,omitempty"` - ID string `json:"jti,omitempty"` - IssuedAt int64 `json:"iat,omitempty"` - Issuer string `json:"iss,omitempty"` - Name string `json:"name,omitempty"` - NotBefore int64 `json:"nbf,omitempty"` - Subject string `json:"sub,omitempty"` -} - -// Prefix holds the prefix byte for an NKey -type Prefix struct { - nkeys.PrefixByte -} - -func encodeToString(d []byte) string { - return base64.RawURLEncoding.EncodeToString(d) -} - -func decodeString(s string) ([]byte, error) { - return base64.RawURLEncoding.DecodeString(s) -} - -func serialize(v interface{}) (string, error) { - j, err := json.Marshal(v) - if err != nil { - return "", err - } - return encodeToString(j), nil -} - -func (c *ClaimsData) doEncode(header *Header, kp nkeys.KeyPair, claim Claims, fn SignFn) (string, error) { - if header == nil { - return "", errors.New("header is required") - } - - if kp == nil { - return "", errors.New("keypair is required") - } - - if c != claim.Claims() { - return "", errors.New("claim and claim data do not match") - } - - if c.Subject == "" { - return "", errors.New("subject is not set") - } - - h, err := serialize(header) - if err != nil { - return "", err - } - - issuerBytes, err := kp.PublicKey() - if err != nil { - return "", err - } - - prefixes := claim.ExpectedPrefixes() - if prefixes != nil { - ok := false - for _, p := range prefixes { - switch p { - case nkeys.PrefixByteAccount: - if nkeys.IsValidPublicAccountKey(issuerBytes) { - ok = true - } - case nkeys.PrefixByteOperator: - if nkeys.IsValidPublicOperatorKey(issuerBytes) { - ok = true - } - case nkeys.PrefixByteServer: - if nkeys.IsValidPublicServerKey(issuerBytes) { - ok = true - } - case nkeys.PrefixByteCluster: - if nkeys.IsValidPublicClusterKey(issuerBytes) { - ok = true - } - case nkeys.PrefixByteUser: - if nkeys.IsValidPublicUserKey(issuerBytes) { - ok = true - } - } - } - if !ok { - return "", fmt.Errorf("unable to validate expected prefixes - %v", prefixes) - } - } - - c.Issuer = issuerBytes - c.IssuedAt = time.Now().UTC().Unix() - c.ID = "" // to create a repeatable hash - c.ID, err = c.hash() - if err != nil { - return "", err - } - - claim.updateVersion() - - payload, err := serialize(claim) - if err != nil { - return "", err - } - - toSign := fmt.Sprintf("%s.%s", h, payload) - eSig := "" - if header.Algorithm == AlgorithmNkeyOld { - return "", errors.New(AlgorithmNkeyOld + " not supported to write jwtV2") - } else if header.Algorithm == AlgorithmNkey { - var sig []byte - if fn != nil { - pk, err := kp.PublicKey() - if err != nil { - return "", err - } - sig, err = fn(pk, []byte(toSign)) - if err != nil { - return "", err - } - } else { - sig, err = kp.Sign([]byte(toSign)) - if err != nil { - return "", err - } - } - eSig = encodeToString(sig) - } else { - return "", errors.New(header.Algorithm + " not supported to write jwtV2") - } - // hash need no padding - return fmt.Sprintf("%s.%s", toSign, eSig), nil -} - -func (c *ClaimsData) hash() (string, error) { - j, err := json.Marshal(c) - if err != nil { - return "", err - } - h := sha512.New512_256() - h.Write(j) - return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(h.Sum(nil)), nil -} - -// Encode encodes a claim into a JWT token. The claim is signed with the -// provided nkey's private key -func (c *ClaimsData) encode(kp nkeys.KeyPair, payload Claims, fn SignFn) (string, error) { - return c.doEncode(&Header{TokenTypeJwt, AlgorithmNkey}, kp, payload, fn) -} - -// Returns a JSON representation of the claim -func (c *ClaimsData) String(claim interface{}) string { - j, err := json.MarshalIndent(claim, "", " ") - if err != nil { - return "" - } - return string(j) -} - -func parseClaims(s string, target Claims) error { - h, err := decodeString(s) - if err != nil { - return err - } - return json.Unmarshal(h, &target) -} - -// Verify verifies that the encoded payload was signed by the -// provided public key. Verify is called automatically with -// the claims portion of the token and the public key in the claim. -// Client code need to insure that the public key in the -// claim is trusted. -func (c *ClaimsData) verify(payload string, sig []byte) bool { - // decode the public key - kp, err := nkeys.FromPublicKey(c.Issuer) - if err != nil { - return false - } - if err := kp.Verify([]byte(payload), sig); err != nil { - return false - } - return true -} - -// Validate checks a claim to make sure it is valid. Validity checks -// include expiration and not before constraints. -func (c *ClaimsData) Validate(vr *ValidationResults) { - now := time.Now().UTC().Unix() - if c.Expires > 0 && now > c.Expires { - vr.AddTimeCheck("claim is expired") - } - - if c.NotBefore > 0 && c.NotBefore > now { - vr.AddTimeCheck("claim is not yet valid") - } -} - -// IsSelfSigned returns true if the claims issuer is the subject -func (c *ClaimsData) IsSelfSigned() bool { - return c.Issuer == c.Subject -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/creds_utils.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/creds_utils.go deleted file mode 100644 index eade49d6..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/creds_utils.go +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright 2019-2020 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "bytes" - "errors" - "fmt" - "regexp" - "strings" - "time" - - "github.com/nats-io/nkeys" -) - -// DecorateJWT returns a decorated JWT that describes the kind of JWT -func DecorateJWT(jwtString string) ([]byte, error) { - gc, err := Decode(jwtString) - if err != nil { - return nil, err - } - return formatJwt(string(gc.ClaimType()), jwtString) -} - -func formatJwt(kind string, jwtString string) ([]byte, error) { - templ := `-----BEGIN NATS %s JWT----- -%s -------END NATS %s JWT------ - -` - w := bytes.NewBuffer(nil) - kind = strings.ToUpper(kind) - _, err := fmt.Fprintf(w, templ, kind, jwtString, kind) - if err != nil { - return nil, err - } - return w.Bytes(), nil -} - -// DecorateSeed takes a seed and returns a string that wraps -// the seed in the form: -// -// ************************* IMPORTANT ************************* -// NKEY Seed printed below can be used sign and prove identity. -// NKEYs are sensitive and should be treated as secrets. -// -// -----BEGIN USER NKEY SEED----- -// SUAIO3FHUX5PNV2LQIIP7TZ3N4L7TX3W53MQGEIVYFIGA635OZCKEYHFLM -// ------END USER NKEY SEED------ -func DecorateSeed(seed []byte) ([]byte, error) { - w := bytes.NewBuffer(nil) - ts := bytes.TrimSpace(seed) - pre := string(ts[0:2]) - kind := "" - switch pre { - case "SU": - kind = "USER" - case "SA": - kind = "ACCOUNT" - case "SO": - kind = "OPERATOR" - default: - return nil, errors.New("seed is not an operator, account or user seed") - } - header := `************************* IMPORTANT ************************* -NKEY Seed printed below can be used to sign and prove identity. -NKEYs are sensitive and should be treated as secrets. - ------BEGIN %s NKEY SEED----- -` - _, err := fmt.Fprintf(w, header, kind) - if err != nil { - return nil, err - } - w.Write(ts) - - footer := ` -------END %s NKEY SEED------ - -************************************************************* -` - _, err = fmt.Fprintf(w, footer, kind) - if err != nil { - return nil, err - } - return w.Bytes(), nil -} - -var userConfigRE = regexp.MustCompile(`\s*(?:(?:[-]{3,}.*[-]{3,}\r?\n)([\w\-.=]+)(?:\r?\n[-]{3,}.*[-]{3,}(\r?\n|\z)))`) - -// An user config file looks like this: -// -----BEGIN NATS USER JWT----- -// eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5... -// ------END NATS USER JWT------ -// -// ************************* IMPORTANT ************************* -// NKEY Seed printed below can be used sign and prove identity. -// NKEYs are sensitive and should be treated as secrets. -// -// -----BEGIN USER NKEY SEED----- -// SUAIO3FHUX5PNV2LQIIP7TZ3N4L7TX3W53MQGEIVYFIGA635OZCKEYHFLM -// ------END USER NKEY SEED------ - -// FormatUserConfig returns a decorated file with a decorated JWT and decorated seed -func FormatUserConfig(jwtString string, seed []byte) ([]byte, error) { - gc, err := Decode(jwtString) - if err != nil { - return nil, err - } - if gc.ClaimType() != UserClaim { - return nil, fmt.Errorf("%q cannot be serialized as a user config", string(gc.ClaimType())) - } - - w := bytes.NewBuffer(nil) - - jd, err := formatJwt(string(gc.ClaimType()), jwtString) - if err != nil { - return nil, err - } - _, err = w.Write(jd) - if err != nil { - return nil, err - } - if !bytes.HasPrefix(bytes.TrimSpace(seed), []byte("SU")) { - return nil, fmt.Errorf("nkey seed is not an user seed") - } - - d, err := DecorateSeed(seed) - if err != nil { - return nil, err - } - _, err = w.Write(d) - if err != nil { - return nil, err - } - - return w.Bytes(), nil -} - -// ParseDecoratedJWT takes a creds file and returns the JWT portion. -func ParseDecoratedJWT(contents []byte) (string, error) { - items := userConfigRE.FindAllSubmatch(contents, -1) - if len(items) == 0 { - return string(contents), nil - } - // First result should be the user JWT. - // We copy here so that if the file contained a seed file too we wipe appropriately. - raw := items[0][1] - tmp := make([]byte, len(raw)) - copy(tmp, raw) - return string(tmp), nil -} - -// ParseDecoratedNKey takes a creds file, finds the NKey portion and creates a -// key pair from it. -func ParseDecoratedNKey(contents []byte) (nkeys.KeyPair, error) { - var seed []byte - - items := userConfigRE.FindAllSubmatch(contents, -1) - if len(items) > 1 { - seed = items[1][1] - } else { - lines := bytes.Split(contents, []byte("\n")) - for _, line := range lines { - if bytes.HasPrefix(bytes.TrimSpace(line), []byte("SO")) || - bytes.HasPrefix(bytes.TrimSpace(line), []byte("SA")) || - bytes.HasPrefix(bytes.TrimSpace(line), []byte("SU")) { - seed = line - break - } - } - } - if seed == nil { - return nil, errors.New("no nkey seed found") - } - if !bytes.HasPrefix(seed, []byte("SO")) && - !bytes.HasPrefix(seed, []byte("SA")) && - !bytes.HasPrefix(seed, []byte("SU")) { - return nil, errors.New("doesn't contain a seed nkey") - } - kp, err := nkeys.FromSeed(seed) - if err != nil { - return nil, err - } - return kp, nil -} - -// ParseDecoratedUserNKey takes a creds file, finds the NKey portion and creates a -// key pair from it. Similar to ParseDecoratedNKey but fails for non-user keys. -func ParseDecoratedUserNKey(contents []byte) (nkeys.KeyPair, error) { - nk, err := ParseDecoratedNKey(contents) - if err != nil { - return nil, err - } - seed, err := nk.Seed() - if err != nil { - return nil, err - } - if !bytes.HasPrefix(seed, []byte("SU")) { - return nil, errors.New("doesn't contain an user seed nkey") - } - kp, err := nkeys.FromSeed(seed) - if err != nil { - return nil, err - } - return kp, nil -} - -// IssueUserJWT takes an account scoped signing key, account id, and use public key (and optionally a user's name, an expiration duration and tags) and returns a valid signed JWT. -// The scopedSigningKey, is a mandatory account scoped signing nkey pair to sign the generated jwt (note that it _must_ be a signing key attached to the account (and a _scoped_ signing key), not the account's private (seed) key). -// The accountId, is a mandatory public account nkey. Will return error when not set or not account nkey. -// The publicUserKey, is a mandatory public user nkey. Will return error when not set or not user nkey. -// The name, is an optional human-readable name. When absent, default to publicUserKey. -// The expirationDuration, is an optional but recommended duration, when the generated jwt needs to expire. If not set, JWT will not expire. -// The tags, is an optional list of tags to be included in the JWT. -// -// Returns: -// string, resulting jwt. -// error, when issues arose. -func IssueUserJWT(scopedSigningKey nkeys.KeyPair, accountId string, publicUserKey string, name string, expirationDuration time.Duration, tags ...string) (string, error) { - - if !nkeys.IsValidPublicAccountKey(accountId) { - return "", errors.New("issueUserJWT requires an account key for the accountId parameter, but got " + nkeys.Prefix(accountId).String()) - } - - if !nkeys.IsValidPublicUserKey(publicUserKey) { - return "", errors.New("issueUserJWT requires an account key for the publicUserKey parameter, but got " + nkeys.Prefix(publicUserKey).String()) - } - - claim := NewUserClaims(publicUserKey) - claim.SetScoped(true) - - if expirationDuration != 0 { - claim.Expires = time.Now().Add(expirationDuration).UTC().Unix() - } - - claim.IssuerAccount = accountId - if name != "" { - claim.Name = name - } else { - claim.Name = publicUserKey - } - - claim.Subject = publicUserKey - claim.Tags = tags - - encoded, err := claim.Encode(scopedSigningKey) - if err != nil { - return "", errors.New("err encoding claim " + err.Error()) - } - - return encoded, nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder.go deleted file mode 100644 index 1e043a38..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder.go +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2020-2022 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "encoding/json" - "errors" - "fmt" - "strings" - - "github.com/nats-io/nkeys" -) - -const libVersion = 2 - -type identifier struct { - Type ClaimType `json:"type,omitempty"` - GenericFields `json:"nats,omitempty"` -} - -func (i *identifier) Kind() ClaimType { - if i.Type != "" { - return i.Type - } - return i.GenericFields.Type -} - -func (i *identifier) Version() int { - if i.Type != "" { - return 1 - } - return i.GenericFields.Version -} - -type v1ClaimsDataDeletedFields struct { - Tags TagList `json:"tags,omitempty"` - Type ClaimType `json:"type,omitempty"` - IssuerAccount string `json:"issuer_account,omitempty"` -} - -// Decode takes a JWT string decodes it and validates it -// and return the embedded Claims. If the token header -// doesn't match the expected algorithm, or the claim is -// not valid or verification fails an error is returned. -func Decode(token string) (Claims, error) { - // must have 3 chunks - chunks := strings.Split(token, ".") - if len(chunks) != 3 { - return nil, errors.New("expected 3 chunks") - } - - // header - if _, err := parseHeaders(chunks[0]); err != nil { - return nil, err - } - // claim - data, err := decodeString(chunks[1]) - if err != nil { - return nil, err - } - ver, claim, err := loadClaims(data) - if err != nil { - return nil, err - } - - // sig - sig, err := decodeString(chunks[2]) - if err != nil { - return nil, err - } - - if ver <= 1 { - if !claim.verify(chunks[1], sig) { - return nil, errors.New("claim failed V1 signature verification") - } - } else { - if !claim.verify(token[:len(chunks[0])+len(chunks[1])+1], sig) { - return nil, errors.New("claim failed V2 signature verification") - } - } - - prefixes := claim.ExpectedPrefixes() - if prefixes != nil { - ok := false - issuer := claim.Claims().Issuer - for _, p := range prefixes { - switch p { - case nkeys.PrefixByteAccount: - if nkeys.IsValidPublicAccountKey(issuer) { - ok = true - } - case nkeys.PrefixByteOperator: - if nkeys.IsValidPublicOperatorKey(issuer) { - ok = true - } - case nkeys.PrefixByteUser: - if nkeys.IsValidPublicUserKey(issuer) { - ok = true - } - case nkeys.PrefixByteServer: - if nkeys.IsValidPublicServerKey(issuer) { - ok = true - } - } - } - if !ok { - return nil, fmt.Errorf("unable to validate expected prefixes - %v", prefixes) - } - } - return claim, nil -} - -func loadClaims(data []byte) (int, Claims, error) { - var id identifier - if err := json.Unmarshal(data, &id); err != nil { - return -1, nil, err - } - - if id.Version() > libVersion { - return -1, nil, errors.New("JWT was generated by a newer version ") - } - - var claim Claims - var err error - switch id.Kind() { - case OperatorClaim: - claim, err = loadOperator(data, id.Version()) - case AccountClaim: - claim, err = loadAccount(data, id.Version()) - case UserClaim: - claim, err = loadUser(data, id.Version()) - case ActivationClaim: - claim, err = loadActivation(data, id.Version()) - case AuthorizationRequestClaim: - claim, err = loadAuthorizationRequest(data, id.Version()) - case AuthorizationResponseClaim: - claim, err = loadAuthorizationResponse(data, id.Version()) - case "cluster": - return -1, nil, errors.New("ClusterClaims are not supported") - case "server": - return -1, nil, errors.New("ServerClaims are not supported") - default: - var gc GenericClaims - if err := json.Unmarshal(data, &gc); err != nil { - return -1, nil, err - } - return -1, &gc, nil - } - - return id.Version(), claim, err -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_account.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_account.go deleted file mode 100644 index 025aa0e1..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_account.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2020 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "encoding/json" - "fmt" -) - -type v1NatsAccount struct { - Imports Imports `json:"imports,omitempty"` - Exports Exports `json:"exports,omitempty"` - Limits struct { - NatsLimits - AccountLimits - } `json:"limits,omitempty"` - SigningKeys StringList `json:"signing_keys,omitempty"` - Revocations RevocationList `json:"revocations,omitempty"` -} - -func loadAccount(data []byte, version int) (*AccountClaims, error) { - switch version { - case 1: - var v1a v1AccountClaims - if err := json.Unmarshal(data, &v1a); err != nil { - return nil, err - } - return v1a.Migrate() - case 2: - var v2a AccountClaims - v2a.SigningKeys = make(SigningKeys) - if err := json.Unmarshal(data, &v2a); err != nil { - return nil, err - } - if len(v2a.Limits.JetStreamTieredLimits) > 0 { - v2a.Limits.JetStreamLimits = JetStreamLimits{} - } - return &v2a, nil - default: - return nil, fmt.Errorf("library supports version %d or less - received %d", libVersion, version) - } -} - -type v1AccountClaims struct { - ClaimsData - v1ClaimsDataDeletedFields - v1NatsAccount `json:"nats,omitempty"` -} - -func (oa v1AccountClaims) Migrate() (*AccountClaims, error) { - return oa.migrateV1() -} - -func (oa v1AccountClaims) migrateV1() (*AccountClaims, error) { - var a AccountClaims - // copy the base claim - a.ClaimsData = oa.ClaimsData - // move the moved fields - a.Account.Type = oa.v1ClaimsDataDeletedFields.Type - a.Account.Tags = oa.v1ClaimsDataDeletedFields.Tags - // copy the account data - a.Account.Imports = oa.v1NatsAccount.Imports - a.Account.Exports = oa.v1NatsAccount.Exports - a.Account.Limits.AccountLimits = oa.v1NatsAccount.Limits.AccountLimits - a.Account.Limits.NatsLimits = oa.v1NatsAccount.Limits.NatsLimits - a.Account.Limits.JetStreamLimits = JetStreamLimits{} - a.Account.SigningKeys = make(SigningKeys) - for _, v := range oa.SigningKeys { - a.Account.SigningKeys.Add(v) - } - a.Account.Revocations = oa.v1NatsAccount.Revocations - a.Version = 1 - return &a, nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_activation.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_activation.go deleted file mode 100644 index d10dbf57..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_activation.go +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2020 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package jwt - -import ( - "encoding/json" - "fmt" -) - -// Migration adds GenericFields -type v1NatsActivation struct { - ImportSubject Subject `json:"subject,omitempty"` - ImportType ExportType `json:"type,omitempty"` - // Limit values deprecated inv v2 - Max int64 `json:"max,omitempty"` - Payload int64 `json:"payload,omitempty"` - Src string `json:"src,omitempty"` - Times []TimeRange `json:"times,omitempty"` -} - -type v1ActivationClaims struct { - ClaimsData - v1ClaimsDataDeletedFields - v1NatsActivation `json:"nats,omitempty"` -} - -func loadActivation(data []byte, version int) (*ActivationClaims, error) { - switch version { - case 1: - var v1a v1ActivationClaims - v1a.Max = NoLimit - v1a.Payload = NoLimit - if err := json.Unmarshal(data, &v1a); err != nil { - return nil, err - } - return v1a.Migrate() - case 2: - var v2a ActivationClaims - if err := json.Unmarshal(data, &v2a); err != nil { - return nil, err - } - return &v2a, nil - default: - return nil, fmt.Errorf("library supports version %d or less - received %d", libVersion, version) - } -} - -func (oa v1ActivationClaims) Migrate() (*ActivationClaims, error) { - return oa.migrateV1() -} - -func (oa v1ActivationClaims) migrateV1() (*ActivationClaims, error) { - var a ActivationClaims - // copy the base claim - a.ClaimsData = oa.ClaimsData - // move the moved fields - a.Activation.Type = oa.v1ClaimsDataDeletedFields.Type - a.Activation.Tags = oa.v1ClaimsDataDeletedFields.Tags - a.Activation.IssuerAccount = oa.v1ClaimsDataDeletedFields.IssuerAccount - // copy the activation data - a.ImportSubject = oa.ImportSubject - a.ImportType = oa.ImportType - a.Version = 1 - return &a, nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_authorization.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_authorization.go deleted file mode 100644 index aebd9552..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_authorization.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2022 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "encoding/json" -) - -func loadAuthorizationRequest(data []byte, version int) (*AuthorizationRequestClaims, error) { - var ac AuthorizationRequestClaims - if err := json.Unmarshal(data, &ac); err != nil { - return nil, err - } - return &ac, nil -} - -func loadAuthorizationResponse(data []byte, version int) (*AuthorizationResponseClaims, error) { - var ac AuthorizationResponseClaims - if err := json.Unmarshal(data, &ac); err != nil { - return nil, err - } - return &ac, nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_operator.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_operator.go deleted file mode 100644 index 511b204c..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_operator.go +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2020 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "encoding/json" - "fmt" -) - -type v1NatsOperator struct { - SigningKeys StringList `json:"signing_keys,omitempty"` - AccountServerURL string `json:"account_server_url,omitempty"` - OperatorServiceURLs StringList `json:"operator_service_urls,omitempty"` - SystemAccount string `json:"system_account,omitempty"` -} - -func loadOperator(data []byte, version int) (*OperatorClaims, error) { - switch version { - case 1: - var v1a v1OperatorClaims - if err := json.Unmarshal(data, &v1a); err != nil { - return nil, err - } - return v1a.Migrate() - case 2: - var v2a OperatorClaims - if err := json.Unmarshal(data, &v2a); err != nil { - return nil, err - } - return &v2a, nil - default: - return nil, fmt.Errorf("library supports version %d or less - received %d", libVersion, version) - } -} - -type v1OperatorClaims struct { - ClaimsData - v1ClaimsDataDeletedFields - v1NatsOperator `json:"nats,omitempty"` -} - -func (oa v1OperatorClaims) Migrate() (*OperatorClaims, error) { - return oa.migrateV1() -} - -func (oa v1OperatorClaims) migrateV1() (*OperatorClaims, error) { - var a OperatorClaims - // copy the base claim - a.ClaimsData = oa.ClaimsData - // move the moved fields - a.Operator.Type = oa.v1ClaimsDataDeletedFields.Type - a.Operator.Tags = oa.v1ClaimsDataDeletedFields.Tags - // copy the account data - a.Operator.SigningKeys = oa.v1NatsOperator.SigningKeys - a.Operator.AccountServerURL = oa.v1NatsOperator.AccountServerURL - a.Operator.OperatorServiceURLs = oa.v1NatsOperator.OperatorServiceURLs - a.Operator.SystemAccount = oa.v1NatsOperator.SystemAccount - a.Version = 1 - return &a, nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_user.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_user.go deleted file mode 100644 index e3e53c41..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/decoder_user.go +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2020 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "encoding/json" - "fmt" -) - -type v1User struct { - Permissions - Limits - BearerToken bool `json:"bearer_token,omitempty"` - // Limit values deprecated inv v2 - Max int64 `json:"max,omitempty"` -} - -type v1UserClaimsDataDeletedFields struct { - v1ClaimsDataDeletedFields - IssuerAccount string `json:"issuer_account,omitempty"` -} - -type v1UserClaims struct { - ClaimsData - v1UserClaimsDataDeletedFields - v1User `json:"nats,omitempty"` -} - -func loadUser(data []byte, version int) (*UserClaims, error) { - switch version { - case 1: - var v1a v1UserClaims - v1a.Limits = Limits{NatsLimits: NatsLimits{NoLimit, NoLimit, NoLimit}} - v1a.Max = NoLimit - if err := json.Unmarshal(data, &v1a); err != nil { - return nil, err - } - return v1a.Migrate() - case 2: - var v2a UserClaims - if err := json.Unmarshal(data, &v2a); err != nil { - return nil, err - } - return &v2a, nil - default: - return nil, fmt.Errorf("library supports version %d or less - received %d", libVersion, version) - } -} - -func (oa v1UserClaims) Migrate() (*UserClaims, error) { - return oa.migrateV1() -} - -func (oa v1UserClaims) migrateV1() (*UserClaims, error) { - var u UserClaims - // copy the base claim - u.ClaimsData = oa.ClaimsData - // move the moved fields - u.User.Type = oa.v1ClaimsDataDeletedFields.Type - u.User.Tags = oa.v1ClaimsDataDeletedFields.Tags - u.User.IssuerAccount = oa.IssuerAccount - // copy the user data - u.User.Permissions = oa.v1User.Permissions - u.User.Limits = oa.v1User.Limits - u.User.BearerToken = oa.v1User.BearerToken - u.Version = 1 - return &u, nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/exports.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/exports.go deleted file mode 100644 index 0f26e84a..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/exports.go +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright 2018-2019 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "encoding/json" - "fmt" - "strings" - "time" -) - -// ResponseType is used to store an export response type -type ResponseType string - -const ( - // ResponseTypeSingleton is used for a service that sends a single response only - ResponseTypeSingleton = "Singleton" - - // ResponseTypeStream is used for a service that will send multiple responses - ResponseTypeStream = "Stream" - - // ResponseTypeChunked is used for a service that sends a single response in chunks (so not quite a stream) - ResponseTypeChunked = "Chunked" -) - -// ServiceLatency is used when observing and exported service for -// latency measurements. -// Sampling 1-100, represents sampling rate, defaults to 100. -// Results is the subject where the latency metrics are published. -// A metric will be defined by the nats-server's ServiceLatency. Time durations -// are in nanoseconds. -// see https://github.com/nats-io/nats-server/blob/main/server/accounts.go#L524 -// e.g. -// -// { -// "app": "dlc22", -// "start": "2019-09-16T21:46:23.636869585-07:00", -// "svc": 219732, -// "nats": { -// "req": 320415, -// "resp": 228268, -// "sys": 0 -// }, -// "total": 768415 -// } -type ServiceLatency struct { - Sampling SamplingRate `json:"sampling"` - Results Subject `json:"results"` -} - -type SamplingRate int - -const Headers = SamplingRate(0) - -// MarshalJSON marshals the field as "headers" or percentages -func (r *SamplingRate) MarshalJSON() ([]byte, error) { - sr := *r - if sr == 0 { - return []byte(`"headers"`), nil - } - if sr >= 1 && sr <= 100 { - return []byte(fmt.Sprintf("%d", sr)), nil - } - return nil, fmt.Errorf("unknown sampling rate") -} - -// UnmarshalJSON unmashals numbers as percentages or "headers" -func (t *SamplingRate) UnmarshalJSON(b []byte) error { - if len(b) == 0 { - return fmt.Errorf("empty sampling rate") - } - if strings.ToLower(string(b)) == `"headers"` { - *t = Headers - return nil - } - var j int - err := json.Unmarshal(b, &j) - if err != nil { - return err - } - *t = SamplingRate(j) - return nil -} - -func (sl *ServiceLatency) Validate(vr *ValidationResults) { - if sl.Sampling != 0 { - if sl.Sampling < 1 || sl.Sampling > 100 { - vr.AddError("sampling percentage needs to be between 1-100") - } - } - sl.Results.Validate(vr) - if sl.Results.HasWildCards() { - vr.AddError("results subject can not contain wildcards") - } -} - -// Export represents a single export -type Export struct { - Name string `json:"name,omitempty"` - Subject Subject `json:"subject,omitempty"` - Type ExportType `json:"type,omitempty"` - TokenReq bool `json:"token_req,omitempty"` - Revocations RevocationList `json:"revocations,omitempty"` - ResponseType ResponseType `json:"response_type,omitempty"` - ResponseThreshold time.Duration `json:"response_threshold,omitempty"` - Latency *ServiceLatency `json:"service_latency,omitempty"` - AccountTokenPosition uint `json:"account_token_position,omitempty"` - Advertise bool `json:"advertise,omitempty"` - AllowTrace bool `json:"allow_trace,omitempty"` - Info -} - -// IsService returns true if an export is for a service -func (e *Export) IsService() bool { - return e.Type == Service -} - -// IsStream returns true if an export is for a stream -func (e *Export) IsStream() bool { - return e.Type == Stream -} - -// IsSingleResponse returns true if an export has a single response -// or no response type is set, also checks that the type is service -func (e *Export) IsSingleResponse() bool { - return e.Type == Service && (e.ResponseType == ResponseTypeSingleton || e.ResponseType == "") -} - -// IsChunkedResponse returns true if an export has a chunked response -func (e *Export) IsChunkedResponse() bool { - return e.Type == Service && e.ResponseType == ResponseTypeChunked -} - -// IsStreamResponse returns true if an export has a chunked response -func (e *Export) IsStreamResponse() bool { - return e.Type == Service && e.ResponseType == ResponseTypeStream -} - -// Validate appends validation issues to the passed in results list -func (e *Export) Validate(vr *ValidationResults) { - if e == nil { - vr.AddError("null export is not allowed") - return - } - if !e.IsService() && !e.IsStream() { - vr.AddError("invalid export type: %q", e.Type) - } - if e.IsService() && !e.IsSingleResponse() && !e.IsChunkedResponse() && !e.IsStreamResponse() { - vr.AddError("invalid response type for service: %q", e.ResponseType) - } - if e.IsStream() { - if e.ResponseType != "" { - vr.AddError("invalid response type for stream: %q", e.ResponseType) - } - if e.AllowTrace { - vr.AddError("AllowTrace only valid for service export") - } - } - if e.Latency != nil { - if !e.IsService() { - vr.AddError("latency tracking only permitted for services") - } - e.Latency.Validate(vr) - } - if e.ResponseThreshold.Nanoseconds() < 0 { - vr.AddError("negative response threshold is invalid") - } - if e.ResponseThreshold.Nanoseconds() > 0 && !e.IsService() { - vr.AddError("response threshold only valid for services") - } - e.Subject.Validate(vr) - if e.AccountTokenPosition > 0 { - if !e.Subject.HasWildCards() { - vr.AddError("Account Token Position can only be used with wildcard subjects: %s", e.Subject) - } else { - subj := string(e.Subject) - token := strings.Split(subj, ".") - tkCnt := uint(len(token)) - if e.AccountTokenPosition > tkCnt { - vr.AddError("Account Token Position %d exceeds length of subject '%s'", - e.AccountTokenPosition, e.Subject) - } else if tk := token[e.AccountTokenPosition-1]; tk != "*" { - vr.AddError("Account Token Position %d matches '%s' but must match a * in: %s", - e.AccountTokenPosition, tk, e.Subject) - } - } - } - e.Info.Validate(vr) -} - -// Revoke enters a revocation by publickey using time.Now(). -func (e *Export) Revoke(pubKey string) { - e.RevokeAt(pubKey, time.Now()) -} - -// RevokeAt enters a revocation by publickey and timestamp into this export -// If there is already a revocation for this public key that is newer, it is kept. -func (e *Export) RevokeAt(pubKey string, timestamp time.Time) { - if e.Revocations == nil { - e.Revocations = RevocationList{} - } - - e.Revocations.Revoke(pubKey, timestamp) -} - -// ClearRevocation removes any revocation for the public key -func (e *Export) ClearRevocation(pubKey string) { - e.Revocations.ClearRevocation(pubKey) -} - -// isRevoked checks if the public key is in the revoked list with a timestamp later than the one passed in. -// Generally this method is called with the subject and issue time of the jwt to be tested. -// DO NOT pass time.Now(), it will not produce a stable/expected response. -func (e *Export) isRevoked(pubKey string, claimIssuedAt time.Time) bool { - return e.Revocations.IsRevoked(pubKey, claimIssuedAt) -} - -// IsClaimRevoked checks if the activation revoked the claim passed in. -// Invalid claims (nil, no Subject or IssuedAt) will return true. -func (e *Export) IsClaimRevoked(claim *ActivationClaims) bool { - if claim == nil || claim.IssuedAt == 0 || claim.Subject == "" { - return true - } - return e.isRevoked(claim.Subject, time.Unix(claim.IssuedAt, 0)) -} - -// Exports is a slice of exports -type Exports []*Export - -// Add appends exports to the list -func (e *Exports) Add(i ...*Export) { - *e = append(*e, i...) -} - -func isContainedIn(kind ExportType, subjects []Subject, vr *ValidationResults) { - m := make(map[string]string) - for i, ns := range subjects { - for j, s := range subjects { - if i == j { - continue - } - if ns.IsContainedIn(s) { - str := string(s) - _, ok := m[str] - if !ok { - m[str] = string(ns) - } - } - } - } - - if len(m) != 0 { - for k, v := range m { - var vi ValidationIssue - vi.Blocking = true - vi.Description = fmt.Sprintf("%s export subject %q already exports %q", kind, k, v) - vr.Add(&vi) - } - } -} - -// Validate calls validate on all of the exports -func (e *Exports) Validate(vr *ValidationResults) { - var serviceSubjects []Subject - var streamSubjects []Subject - - for _, v := range *e { - if v == nil { - vr.AddError("null export is not allowed") - continue - } - if v.IsService() { - serviceSubjects = append(serviceSubjects, v.Subject) - } else { - streamSubjects = append(streamSubjects, v.Subject) - } - v.Validate(vr) - } - - isContainedIn(Service, serviceSubjects, vr) - isContainedIn(Stream, streamSubjects, vr) -} - -// HasExportContainingSubject checks if the export list has an export with the provided subject -func (e *Exports) HasExportContainingSubject(subject Subject) bool { - for _, s := range *e { - if subject.IsContainedIn(s.Subject) { - return true - } - } - return false -} - -func (e Exports) Len() int { - return len(e) -} - -func (e Exports) Swap(i, j int) { - e[i], e[j] = e[j], e[i] -} - -func (e Exports) Less(i, j int) bool { - return e[i].Subject < e[j].Subject -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/genericlaims.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/genericlaims.go deleted file mode 100644 index e680866f..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/genericlaims.go +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2018-2024 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "encoding/json" - "errors" - "strings" - - "github.com/nats-io/nkeys" -) - -// GenericClaims can be used to read a JWT as a map for any non-generic fields -type GenericClaims struct { - ClaimsData - Data map[string]interface{} `json:"nats,omitempty"` -} - -// NewGenericClaims creates a map-based Claims -func NewGenericClaims(subject string) *GenericClaims { - if subject == "" { - return nil - } - c := GenericClaims{} - c.Subject = subject - c.Data = make(map[string]interface{}) - return &c -} - -// DecodeGeneric takes a JWT string and decodes it into a ClaimsData and map -func DecodeGeneric(token string) (*GenericClaims, error) { - // must have 3 chunks - chunks := strings.Split(token, ".") - if len(chunks) != 3 { - return nil, errors.New("expected 3 chunks") - } - - // header - header, err := parseHeaders(chunks[0]) - if err != nil { - return nil, err - } - // claim - data, err := decodeString(chunks[1]) - if err != nil { - return nil, err - } - - gc := struct { - GenericClaims - GenericFields - }{} - if err := json.Unmarshal(data, &gc); err != nil { - return nil, err - } - - // sig - sig, err := decodeString(chunks[2]) - if err != nil { - return nil, err - } - - if header.Algorithm == AlgorithmNkeyOld { - if !gc.verify(chunks[1], sig) { - return nil, errors.New("claim failed V1 signature verification") - } - if tp := gc.GenericFields.Type; tp != "" { - // the conversion needs to be from a string because - // on custom types the type is not going to be one of - // the constants - gc.GenericClaims.Data["type"] = string(tp) - } - if tp := gc.GenericFields.Tags; len(tp) != 0 { - gc.GenericClaims.Data["tags"] = tp - } - - } else { - if !gc.verify(token[:len(chunks[0])+len(chunks[1])+1], sig) { - return nil, errors.New("claim failed V2 signature verification") - } - } - return &gc.GenericClaims, nil -} - -// Claims returns the standard part of the generic claim -func (gc *GenericClaims) Claims() *ClaimsData { - return &gc.ClaimsData -} - -// Payload returns the custom part of the claims data -func (gc *GenericClaims) Payload() interface{} { - return &gc.Data -} - -// Encode takes a generic claims and creates a JWT string -func (gc *GenericClaims) Encode(pair nkeys.KeyPair) (string, error) { - return gc.ClaimsData.encode(pair, gc, nil) -} - -func (gc *GenericClaims) EncodeWithSigner(pair nkeys.KeyPair, fn SignFn) (string, error) { - return gc.ClaimsData.encode(pair, gc, fn) -} - -// Validate checks the generic part of the claims data -func (gc *GenericClaims) Validate(vr *ValidationResults) { - gc.ClaimsData.Validate(vr) -} - -func (gc *GenericClaims) String() string { - return gc.ClaimsData.String(gc) -} - -// ExpectedPrefixes returns the types allowed to encode a generic JWT, which is nil for all -func (gc *GenericClaims) ExpectedPrefixes() []nkeys.PrefixByte { - return nil -} - -func (gc *GenericClaims) ClaimType() ClaimType { - v, ok := gc.Data["type"] - if !ok { - v, ok = gc.Data["nats"] - if ok { - m, ok := v.(map[string]interface{}) - if ok { - v = m["type"] - } - } - } - - switch ct := v.(type) { - case string: - if IsGenericClaimType(ct) { - return GenericClaim - } - return ClaimType(ct) - case ClaimType: - return ct - default: - return "" - } -} - -func (gc *GenericClaims) updateVersion() { - if gc.Data != nil { - // store as float as that is what decoding with json does too - gc.Data["version"] = float64(libVersion) - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/header.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/header.go deleted file mode 100644 index 32f8a508..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/header.go +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2018-2019 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "encoding/json" - "fmt" - "strings" -) - -const ( - // Version is semantic version. - Version = "2.4.0" - - // TokenTypeJwt is the JWT token type supported JWT tokens - // encoded and decoded by this library - // from RFC7519 5.1 "typ": - // it is RECOMMENDED that "JWT" always be spelled using uppercase characters for compatibility - TokenTypeJwt = "JWT" - - // AlgorithmNkey is the algorithm supported by JWT tokens - // encoded and decoded by this library - AlgorithmNkeyOld = "ed25519" - AlgorithmNkey = AlgorithmNkeyOld + "-nkey" -) - -// Header is a JWT Jose Header -type Header struct { - Type string `json:"typ"` - Algorithm string `json:"alg"` -} - -// Parses a header JWT token -func parseHeaders(s string) (*Header, error) { - h, err := decodeString(s) - if err != nil { - return nil, err - } - header := Header{} - if err := json.Unmarshal(h, &header); err != nil { - return nil, err - } - - if err := header.Valid(); err != nil { - return nil, err - } - return &header, nil -} - -// Valid validates the Header. It returns nil if the Header is -// a JWT header, and the algorithm used is the NKEY algorithm. -func (h *Header) Valid() error { - if TokenTypeJwt != strings.ToUpper(h.Type) { - return fmt.Errorf("not supported type %q", h.Type) - } - - alg := strings.ToLower(h.Algorithm) - if !strings.HasPrefix(alg, AlgorithmNkeyOld) { - return fmt.Errorf("unexpected %q algorithm", h.Algorithm) - } - if AlgorithmNkeyOld != alg && AlgorithmNkey != alg { - return fmt.Errorf("unexpected %q algorithm", h.Algorithm) - } - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/imports.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/imports.go deleted file mode 100644 index c8524d07..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/imports.go +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2018-2020 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -// Import describes a mapping from another account into this one -type Import struct { - Name string `json:"name,omitempty"` - // Subject field in an import is always from the perspective of the - // initial publisher - in the case of a stream it is the account owning - // the stream (the exporter), and in the case of a service it is the - // account making the request (the importer). - Subject Subject `json:"subject,omitempty"` - Account string `json:"account,omitempty"` - Token string `json:"token,omitempty"` - // Deprecated: use LocalSubject instead - // To field in an import is always from the perspective of the subscriber - // in the case of a stream it is the client of the stream (the importer), - // from the perspective of a service, it is the subscription waiting for - // requests (the exporter). If the field is empty, it will default to the - // value in the Subject field. - To Subject `json:"to,omitempty"` - // Local subject used to subscribe (for streams) and publish (for services) to. - // This value only needs setting if you want to change the value of Subject. - // If the value of Subject ends in > then LocalSubject needs to end in > as well. - // LocalSubject can contain $ wildcard references where number references the nth wildcard in Subject. - // The sum of wildcard reference and * tokens needs to match the number of * token in Subject. - LocalSubject RenamingSubject `json:"local_subject,omitempty"` - Type ExportType `json:"type,omitempty"` - Share bool `json:"share,omitempty"` - AllowTrace bool `json:"allow_trace,omitempty"` -} - -// IsService returns true if the import is of type service -func (i *Import) IsService() bool { - return i.Type == Service -} - -// IsStream returns true if the import is of type stream -func (i *Import) IsStream() bool { - return i.Type == Stream -} - -// Returns the value of To without triggering the deprecation warning for a read -func (i *Import) GetTo() string { - return string(i.To) -} - -// Validate checks if an import is valid for the wrapping account -func (i *Import) Validate(actPubKey string, vr *ValidationResults) { - if i == nil { - vr.AddError("null import is not allowed") - return - } - if !i.IsService() && !i.IsStream() { - vr.AddError("invalid import type: %q", i.Type) - } - if i.IsService() && i.AllowTrace { - vr.AddError("AllowTrace only valid for stream import") - } - - if i.Account == "" { - vr.AddError("account to import from is not specified") - } - - if i.GetTo() != "" { - vr.AddWarning("the field to has been deprecated (use LocalSubject instead)") - } - - i.Subject.Validate(vr) - if i.LocalSubject != "" { - i.LocalSubject.Validate(i.Subject, vr) - if i.To != "" { - vr.AddError("Local Subject replaces To") - } - } - - if i.Share && !i.IsService() { - vr.AddError("sharing information (for latency tracking) is only valid for services: %q", i.Subject) - } - var act *ActivationClaims - - if i.Token != "" { - var err error - act, err = DecodeActivationClaims(i.Token) - if err != nil { - vr.AddError("import %q contains an invalid activation token", i.Subject) - } - } - - if act != nil { - if !(act.Issuer == i.Account || act.IssuerAccount == i.Account) { - vr.AddError("activation token doesn't match account for import %q", i.Subject) - } - if act.ClaimsData.Subject != actPubKey { - vr.AddError("activation token doesn't match account it is being included in, %q", i.Subject) - } - if act.ImportType != i.Type { - vr.AddError("mismatch between token import type %s and type of import %s", act.ImportType, i.Type) - } - act.validateWithTimeChecks(vr, false) - subj := i.Subject - if i.IsService() && i.To != "" { - subj = i.To - } - if !subj.IsContainedIn(act.ImportSubject) { - vr.AddError("activation token import subject %q doesn't match import %q", act.ImportSubject, i.Subject) - } - } -} - -// Imports is a list of import structs -type Imports []*Import - -// Validate checks if an import is valid for the wrapping account -func (i *Imports) Validate(acctPubKey string, vr *ValidationResults) { - toSet := make(map[Subject]struct{}, len(*i)) - for _, v := range *i { - if v == nil { - vr.AddError("null import is not allowed") - continue - } - if v.Type == Service { - sub := v.To - if sub == "" { - sub = v.LocalSubject.ToSubject() - } - if sub == "" { - sub = v.Subject - } - for k := range toSet { - if sub.IsContainedIn(k) || k.IsContainedIn(sub) { - vr.AddError("overlapping subject namespace for %q and %q", sub, k) - } - } - if _, ok := toSet[sub]; ok { - vr.AddError("overlapping subject namespace for %q", v.To) - } - toSet[sub] = struct{}{} - } - v.Validate(acctPubKey, vr) - } -} - -// Add is a simple way to add imports -func (i *Imports) Add(a ...*Import) { - *i = append(*i, a...) -} - -func (i Imports) Len() int { - return len(i) -} - -func (i Imports) Swap(j, k int) { - i[j], i[k] = i[k], i[j] -} - -func (i Imports) Less(j, k int) bool { - return i[j].Subject < i[k].Subject -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/operator_claims.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/operator_claims.go deleted file mode 100644 index b5c9c94c..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/operator_claims.go +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright 2018-2024 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "errors" - "fmt" - "net/url" - "strconv" - "strings" - - "github.com/nats-io/nkeys" -) - -// Operator specific claims -type Operator struct { - // Slice of other operator NKeys that can be used to sign on behalf of the main - // operator identity. - SigningKeys StringList `json:"signing_keys,omitempty"` - // AccountServerURL is a partial URL like "https://host.domain.org:/jwt/v1" - // tools will use the prefix and build queries by appending /accounts/ - // or /operator to the path provided. Note this assumes that the account server - // can handle requests in a nats-account-server compatible way. See - // https://github.com/nats-io/nats-account-server. - AccountServerURL string `json:"account_server_url,omitempty"` - // A list of NATS urls (tls://host:port) where tools can connect to the server - // using proper credentials. - OperatorServiceURLs StringList `json:"operator_service_urls,omitempty"` - // Identity of the system account - SystemAccount string `json:"system_account,omitempty"` - // Min Server version - AssertServerVersion string `json:"assert_server_version,omitempty"` - // Signing of subordinate objects will require signing keys - StrictSigningKeyUsage bool `json:"strict_signing_key_usage,omitempty"` - GenericFields -} - -func ParseServerVersion(version string) (int, int, int, error) { - if version == "" { - return 0, 0, 0, nil - } - split := strings.Split(version, ".") - if len(split) != 3 { - return 0, 0, 0, fmt.Errorf("asserted server version must be of the form ..") - } else if major, err := strconv.Atoi(split[0]); err != nil { - return 0, 0, 0, fmt.Errorf("asserted server version cant parse %s to int", split[0]) - } else if minor, err := strconv.Atoi(split[1]); err != nil { - return 0, 0, 0, fmt.Errorf("asserted server version cant parse %s to int", split[1]) - } else if update, err := strconv.Atoi(split[2]); err != nil { - return 0, 0, 0, fmt.Errorf("asserted server version cant parse %s to int", split[2]) - } else if major < 0 || minor < 0 || update < 0 { - return 0, 0, 0, fmt.Errorf("asserted server version can'b contain negative values: %s", version) - } else { - return major, minor, update, nil - } -} - -// Validate checks the validity of the operators contents -func (o *Operator) Validate(vr *ValidationResults) { - if err := o.validateAccountServerURL(); err != nil { - vr.AddError(err.Error()) - } - - for _, v := range o.validateOperatorServiceURLs() { - if v != nil { - vr.AddError(v.Error()) - } - } - - for _, k := range o.SigningKeys { - if !nkeys.IsValidPublicOperatorKey(k) { - vr.AddError("%s is not an operator public key", k) - } - } - if o.SystemAccount != "" { - if !nkeys.IsValidPublicAccountKey(o.SystemAccount) { - vr.AddError("%s is not an account public key", o.SystemAccount) - } - } - if _, _, _, err := ParseServerVersion(o.AssertServerVersion); err != nil { - vr.AddError("assert server version error: %s", err) - } -} - -func (o *Operator) validateAccountServerURL() error { - if o.AccountServerURL != "" { - // We don't care what kind of URL it is so long as it parses - // and has a protocol. The account server may impose additional - // constraints on the type of URLs that it is able to notify to - u, err := url.Parse(o.AccountServerURL) - if err != nil { - return fmt.Errorf("error parsing account server url: %v", err) - } - if u.Scheme == "" { - return fmt.Errorf("account server url %q requires a protocol", o.AccountServerURL) - } - } - return nil -} - -// ValidateOperatorServiceURL returns an error if the URL is not a valid NATS or TLS url. -func ValidateOperatorServiceURL(v string) error { - // should be possible for the service url to not be expressed - if v == "" { - return nil - } - u, err := url.Parse(v) - if err != nil { - return fmt.Errorf("error parsing operator service url %q: %v", v, err) - } - - if u.User != nil { - return fmt.Errorf("operator service url %q - credentials are not supported", v) - } - - if u.Path != "" { - return fmt.Errorf("operator service url %q - paths are not supported", v) - } - - lcs := strings.ToLower(u.Scheme) - switch lcs { - case "nats": - return nil - case "tls": - return nil - case "ws": - return nil - case "wss": - return nil - default: - return fmt.Errorf("operator service url %q - protocol not supported (only 'nats', 'tls', 'ws', 'wss' only)", v) - } -} - -func (o *Operator) validateOperatorServiceURLs() []error { - var errs []error - for _, v := range o.OperatorServiceURLs { - if v != "" { - if err := ValidateOperatorServiceURL(v); err != nil { - errs = append(errs, err) - } - } - } - return errs -} - -// OperatorClaims define the data for an operator JWT -type OperatorClaims struct { - ClaimsData - Operator `json:"nats,omitempty"` -} - -// NewOperatorClaims creates a new operator claim with the specified subject, which should be an operator public key -func NewOperatorClaims(subject string) *OperatorClaims { - if subject == "" { - return nil - } - c := &OperatorClaims{} - c.Subject = subject - c.Issuer = subject - return c -} - -// DidSign checks the claims against the operator's public key and its signing keys -func (oc *OperatorClaims) DidSign(op Claims) bool { - if op == nil { - return false - } - issuer := op.Claims().Issuer - if issuer == oc.Subject { - if !oc.StrictSigningKeyUsage { - return true - } - return op.Claims().Subject == oc.Subject - } - return oc.SigningKeys.Contains(issuer) -} - -// Encode the claims into a JWT string -func (oc *OperatorClaims) Encode(pair nkeys.KeyPair) (string, error) { - return oc.EncodeWithSigner(pair, nil) -} - -func (oc *OperatorClaims) EncodeWithSigner(pair nkeys.KeyPair, fn SignFn) (string, error) { - if !nkeys.IsValidPublicOperatorKey(oc.Subject) { - return "", errors.New("expected subject to be an operator public key") - } - err := oc.validateAccountServerURL() - if err != nil { - return "", err - } - oc.Type = OperatorClaim - return oc.ClaimsData.encode(pair, oc, fn) -} - -func (oc *OperatorClaims) ClaimType() ClaimType { - return oc.Type -} - -// DecodeOperatorClaims tries to create an operator claims from a JWt string -func DecodeOperatorClaims(token string) (*OperatorClaims, error) { - claims, err := Decode(token) - if err != nil { - return nil, err - } - oc, ok := claims.(*OperatorClaims) - if !ok { - return nil, errors.New("not operator claim") - } - return oc, nil -} - -func (oc *OperatorClaims) String() string { - return oc.ClaimsData.String(oc) -} - -// Payload returns the operator specific data for an operator JWT -func (oc *OperatorClaims) Payload() interface{} { - return &oc.Operator -} - -// Validate the contents of the claims -func (oc *OperatorClaims) Validate(vr *ValidationResults) { - oc.ClaimsData.Validate(vr) - oc.Operator.Validate(vr) -} - -// ExpectedPrefixes defines the nkey types that can sign operator claims, operator -func (oc *OperatorClaims) ExpectedPrefixes() []nkeys.PrefixByte { - return []nkeys.PrefixByte{nkeys.PrefixByteOperator} -} - -// Claims returns the generic claims data -func (oc *OperatorClaims) Claims() *ClaimsData { - return &oc.ClaimsData -} - -func (oc *OperatorClaims) updateVersion() { - oc.GenericFields.Version = libVersion -} - -func (oc *OperatorClaims) GetTags() TagList { - return oc.Operator.Tags -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/revocation_list.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/revocation_list.go deleted file mode 100644 index c582896b..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/revocation_list.go +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2020 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "time" -) - -const All = "*" - -// RevocationList is used to store a mapping of public keys to unix timestamps -type RevocationList map[string]int64 -type RevocationEntry struct { - PublicKey string - TimeStamp int64 -} - -// Revoke enters a revocation by publickey and timestamp into this export -// If there is already a revocation for this public key that is newer, it is kept. -func (r RevocationList) Revoke(pubKey string, timestamp time.Time) { - newTS := timestamp.Unix() - // cannot move a revocation into the future - only into the past - if ts, ok := r[pubKey]; ok && ts > newTS { - return - } - r[pubKey] = newTS -} - -// MaybeCompact will compact the revocation list if jwt.All is found. Any -// revocation that is covered by a jwt.All revocation will be deleted, thus -// reducing the size of the JWT. Returns a slice of entries that were removed -// during the process. -func (r RevocationList) MaybeCompact() []RevocationEntry { - var deleted []RevocationEntry - ats, ok := r[All] - if ok { - for k, ts := range r { - if k != All && ats >= ts { - deleted = append(deleted, RevocationEntry{ - PublicKey: k, - TimeStamp: ts, - }) - delete(r, k) - } - } - } - return deleted -} - -// ClearRevocation removes any revocation for the public key -func (r RevocationList) ClearRevocation(pubKey string) { - delete(r, pubKey) -} - -// IsRevoked checks if the public key is in the revoked list with a timestamp later than -// the one passed in. Generally this method is called with an issue time but other time's can -// be used for testing. -func (r RevocationList) IsRevoked(pubKey string, timestamp time.Time) bool { - if r.allRevoked(timestamp) { - return true - } - ts, ok := r[pubKey] - return ok && ts >= timestamp.Unix() -} - -// allRevoked returns true if All is set and the timestamp is later or same as the -// one passed. This is called by IsRevoked. -func (r RevocationList) allRevoked(timestamp time.Time) bool { - ts, ok := r[All] - return ok && ts >= timestamp.Unix() -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/signingkeys.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/signingkeys.go deleted file mode 100644 index a997cbb1..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/signingkeys.go +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright 2020-2024 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "encoding/json" - "errors" - "fmt" - "sort" - - "github.com/nats-io/nkeys" -) - -type Scope interface { - SigningKey() string - ValidateScopedSigner(claim Claims) error - Validate(vr *ValidationResults) -} - -type ScopeType int - -const ( - UserScopeType ScopeType = iota + 1 -) - -func (t ScopeType) String() string { - switch t { - case UserScopeType: - return "user_scope" - } - return "unknown" -} - -func (t *ScopeType) MarshalJSON() ([]byte, error) { - switch *t { - case UserScopeType: - return []byte("\"user_scope\""), nil - } - return nil, fmt.Errorf("unknown scope type %q", t) -} - -func (t *ScopeType) UnmarshalJSON(b []byte) error { - var s string - err := json.Unmarshal(b, &s) - if err != nil { - return err - } - switch s { - case "user_scope": - *t = UserScopeType - return nil - } - return fmt.Errorf("unknown scope type %q", t) -} - -type UserScope struct { - Kind ScopeType `json:"kind"` - Key string `json:"key"` - Role string `json:"role"` - Template UserPermissionLimits `json:"template"` - Description string `json:"description"` -} - -func NewUserScope() *UserScope { - var s UserScope - s.Kind = UserScopeType - s.Template.NatsLimits = NatsLimits{NoLimit, NoLimit, NoLimit} - return &s -} - -func (us UserScope) SigningKey() string { - return us.Key -} - -func (us UserScope) Validate(vr *ValidationResults) { - if !nkeys.IsValidPublicAccountKey(us.Key) { - vr.AddError("%s is not an account public key", us.Key) - } -} - -func (us UserScope) ValidateScopedSigner(c Claims) error { - uc, ok := c.(*UserClaims) - if !ok { - return fmt.Errorf("not an user claim - scoped signing key requires user claim") - } - if uc.Claims().Issuer != us.Key { - return errors.New("issuer not the scoped signer") - } - if !uc.HasEmptyPermissions() { - return errors.New("scoped users require no permissions or limits set") - } - return nil -} - -// SigningKeys is a map keyed by a public account key -type SigningKeys map[string]Scope - -func (sk SigningKeys) Validate(vr *ValidationResults) { - for k, v := range sk { - // regular signing keys won't have a scope - if v != nil { - v.Validate(vr) - } else { - if !nkeys.IsValidPublicAccountKey(k) { - vr.AddError("%q is not a valid account signing key", k) - } - } - } -} - -// MarshalJSON serializes the scoped signing keys as an array -func (sk *SigningKeys) MarshalJSON() ([]byte, error) { - if sk == nil { - return nil, nil - } - - keys := sk.Keys() - sort.Strings(keys) - - var a []interface{} - for _, k := range keys { - if (*sk)[k] != nil { - a = append(a, (*sk)[k]) - } else { - a = append(a, k) - } - } - return json.Marshal(a) -} - -func (sk *SigningKeys) UnmarshalJSON(data []byte) error { - if *sk == nil { - *sk = make(SigningKeys) - } - // read an array - we can have a string or an map - var a []interface{} - if err := json.Unmarshal(data, &a); err != nil { - return err - } - for _, i := range a { - switch v := i.(type) { - case string: - (*sk)[v] = nil - case map[string]interface{}: - d, err := json.Marshal(v) - if err != nil { - return err - } - switch v["kind"] { - case UserScopeType.String(): - us := NewUserScope() - if err := json.Unmarshal(d, &us); err != nil { - return err - } - (*sk)[us.Key] = us - default: - return fmt.Errorf("unknown signing key scope %q", v["type"]) - } - } - } - return nil -} - -func (sk SigningKeys) Keys() []string { - var keys []string - for k := range sk { - keys = append(keys, k) - } - return keys -} - -// GetScope returns nil if the key is not associated -func (sk SigningKeys) GetScope(k string) (Scope, bool) { - v, ok := sk[k] - if !ok { - return nil, false - } - return v, true -} - -func (sk SigningKeys) Contains(k string) bool { - _, ok := sk[k] - return ok -} - -func (sk SigningKeys) Add(keys ...string) { - for _, k := range keys { - sk[k] = nil - } -} - -func (sk SigningKeys) AddScopedSigner(s Scope) { - sk[s.SigningKey()] = s -} - -func (sk SigningKeys) Remove(keys ...string) { - for _, k := range keys { - delete(sk, k) - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/types.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/types.go deleted file mode 100644 index d5814db3..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/types.go +++ /dev/null @@ -1,495 +0,0 @@ -/* - * Copyright 2018-2019 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "encoding/json" - "fmt" - "net" - "net/url" - "reflect" - "strconv" - "strings" - "time" -) - -const MaxInfoLength = 8 * 1024 - -type Info struct { - Description string `json:"description,omitempty"` - InfoURL string `json:"info_url,omitempty"` -} - -func (s Info) Validate(vr *ValidationResults) { - if len(s.Description) > MaxInfoLength { - vr.AddError("Description is too long") - } - if s.InfoURL != "" { - if len(s.InfoURL) > MaxInfoLength { - vr.AddError("Info URL is too long") - } - u, err := url.Parse(s.InfoURL) - if err == nil && (u.Hostname() == "" || u.Scheme == "") { - err = fmt.Errorf("no hostname or scheme") - } - if err != nil { - vr.AddError("error parsing info url: %v", err) - } - } -} - -// ExportType defines the type of import/export. -type ExportType int - -const ( - // Unknown is used if we don't know the type - Unknown ExportType = iota - // Stream defines the type field value for a stream "stream" - Stream - // Service defines the type field value for a service "service" - Service -) - -func (t ExportType) String() string { - switch t { - case Stream: - return "stream" - case Service: - return "service" - } - return "unknown" -} - -// MarshalJSON marshals the enum as a quoted json string -func (t *ExportType) MarshalJSON() ([]byte, error) { - switch *t { - case Stream: - return []byte("\"stream\""), nil - case Service: - return []byte("\"service\""), nil - } - return nil, fmt.Errorf("unknown export type") -} - -// UnmarshalJSON unmashals a quoted json string to the enum value -func (t *ExportType) UnmarshalJSON(b []byte) error { - var j string - err := json.Unmarshal(b, &j) - if err != nil { - return err - } - switch j { - case "stream": - *t = Stream - return nil - case "service": - *t = Service - return nil - } - return fmt.Errorf("unknown export type %q", j) -} - -type RenamingSubject Subject - -func (s RenamingSubject) Validate(from Subject, vr *ValidationResults) { - v := Subject(s) - v.Validate(vr) - if from == "" { - vr.AddError("subject cannot be empty") - } - if strings.Contains(string(s), " ") { - vr.AddError("subject %q cannot have spaces", v) - } - matchesSuffix := func(s Subject) bool { - return s == ">" || strings.HasSuffix(string(s), ".>") - } - if matchesSuffix(v) != matchesSuffix(from) { - vr.AddError("both, renaming subject and subject, need to end or not end in >") - } - fromCnt := from.countTokenWildcards() - refCnt := 0 - for _, tk := range strings.Split(string(v), ".") { - if tk == "*" { - refCnt++ - } - if len(tk) < 2 { - continue - } - if tk[0] == '$' { - if idx, err := strconv.Atoi(tk[1:]); err == nil { - if idx > fromCnt { - vr.AddError("Reference $%d in %q reference * in %q that do not exist", idx, s, from) - } else { - refCnt++ - } - } - } - } - if refCnt != fromCnt { - vr.AddError("subject does not contain enough * or reference wildcards $[0-9]") - } -} - -// Replaces reference tokens with * -func (s RenamingSubject) ToSubject() Subject { - if !strings.Contains(string(s), "$") { - return Subject(s) - } - bldr := strings.Builder{} - tokens := strings.Split(string(s), ".") - for i, tk := range tokens { - convert := false - if len(tk) > 1 && tk[0] == '$' { - if _, err := strconv.Atoi(tk[1:]); err == nil { - convert = true - } - } - if convert { - bldr.WriteString("*") - } else { - bldr.WriteString(tk) - } - if i != len(tokens)-1 { - bldr.WriteString(".") - } - } - return Subject(bldr.String()) -} - -// Subject is a string that represents a NATS subject -type Subject string - -// Validate checks that a subject string is valid, ie not empty and without spaces -func (s Subject) Validate(vr *ValidationResults) { - v := string(s) - if v == "" { - vr.AddError("subject cannot be empty") - // No other checks after that make sense - return - } - if strings.Contains(v, " ") { - vr.AddError("subject %q cannot have spaces", v) - } - if v[0] == '.' || v[len(v)-1] == '.' { - vr.AddError("subject %q cannot start or end with a `.`", v) - } - if strings.Contains(v, "..") { - vr.AddError("subject %q cannot contain consecutive `.`", v) - } -} - -func (s Subject) countTokenWildcards() int { - v := string(s) - if v == "*" { - return 1 - } - cnt := 0 - for _, t := range strings.Split(v, ".") { - if t == "*" { - cnt++ - } - } - return cnt -} - -// HasWildCards is used to check if a subject contains a > or * -func (s Subject) HasWildCards() bool { - v := string(s) - return strings.HasSuffix(v, ".>") || - strings.Contains(v, ".*.") || - strings.HasSuffix(v, ".*") || - strings.HasPrefix(v, "*.") || - v == "*" || - v == ">" -} - -// IsContainedIn does a simple test to see if the subject is contained in another subject -func (s Subject) IsContainedIn(other Subject) bool { - otherArray := strings.Split(string(other), ".") - myArray := strings.Split(string(s), ".") - - if len(myArray) > len(otherArray) && otherArray[len(otherArray)-1] != ">" { - return false - } - - if len(myArray) < len(otherArray) { - return false - } - - for ind, tok := range otherArray { - myTok := myArray[ind] - - if ind == len(otherArray)-1 && tok == ">" { - return true - } - - if tok != myTok && tok != "*" { - return false - } - } - - return true -} - -// TimeRange is used to represent a start and end time -type TimeRange struct { - Start string `json:"start,omitempty"` - End string `json:"end,omitempty"` -} - -// Validate checks the values in a time range struct -func (tr *TimeRange) Validate(vr *ValidationResults) { - format := "15:04:05" - - if tr.Start == "" { - vr.AddError("time ranges start must contain a start") - } else { - _, err := time.Parse(format, tr.Start) - if err != nil { - vr.AddError("start in time range is invalid %q", tr.Start) - } - } - - if tr.End == "" { - vr.AddError("time ranges end must contain an end") - } else { - _, err := time.Parse(format, tr.End) - if err != nil { - vr.AddError("end in time range is invalid %q", tr.End) - } - } -} - -// Src is a comma separated list of CIDR specifications -type UserLimits struct { - Src CIDRList `json:"src,omitempty"` - Times []TimeRange `json:"times,omitempty"` - Locale string `json:"times_location,omitempty"` -} - -func (u *UserLimits) Empty() bool { - return reflect.DeepEqual(*u, UserLimits{}) -} - -func (u *UserLimits) IsUnlimited() bool { - return len(u.Src) == 0 && len(u.Times) == 0 -} - -// Limits are used to control acccess for users and importing accounts -type Limits struct { - UserLimits - NatsLimits -} - -func (l *Limits) IsUnlimited() bool { - return l.UserLimits.IsUnlimited() && l.NatsLimits.IsUnlimited() -} - -// Validate checks the values in a limit struct -func (l *Limits) Validate(vr *ValidationResults) { - if len(l.Src) != 0 { - for _, cidr := range l.Src { - _, ipNet, err := net.ParseCIDR(cidr) - if err != nil || ipNet == nil { - vr.AddError("invalid cidr %q in user src limits", cidr) - } - } - } - - if len(l.Times) > 0 { - for _, t := range l.Times { - t.Validate(vr) - } - } - - if l.Locale != "" { - if _, err := time.LoadLocation(l.Locale); err != nil { - vr.AddError("could not parse iana time zone by name: %v", err) - } - } -} - -// Permission defines allow/deny subjects -type Permission struct { - Allow StringList `json:"allow,omitempty"` - Deny StringList `json:"deny,omitempty"` -} - -func (p *Permission) Empty() bool { - return len(p.Allow) == 0 && len(p.Deny) == 0 -} - -func checkPermission(vr *ValidationResults, subj string, permitQueue bool) { - tk := strings.Split(subj, " ") - switch len(tk) { - case 1: - Subject(tk[0]).Validate(vr) - case 2: - Subject(tk[0]).Validate(vr) - Subject(tk[1]).Validate(vr) - if !permitQueue { - vr.AddError(`Permission Subject "%s" is not allowed to contain queue`, subj) - } - default: - vr.AddError(`Permission Subject "%s" contains too many spaces`, subj) - } -} - -// Validate the allow, deny elements of a permission -func (p *Permission) Validate(vr *ValidationResults, permitQueue bool) { - for _, subj := range p.Allow { - checkPermission(vr, subj, permitQueue) - } - for _, subj := range p.Deny { - checkPermission(vr, subj, permitQueue) - } -} - -// ResponsePermission can be used to allow responses to any reply subject -// that is received on a valid subscription. -type ResponsePermission struct { - MaxMsgs int `json:"max"` - Expires time.Duration `json:"ttl"` -} - -// Validate the response permission. -func (p *ResponsePermission) Validate(_ *ValidationResults) { - // Any values can be valid for now. -} - -// Permissions are used to restrict subject access, either on a user or for everyone on a server by default -type Permissions struct { - Pub Permission `json:"pub,omitempty"` - Sub Permission `json:"sub,omitempty"` - Resp *ResponsePermission `json:"resp,omitempty"` -} - -// Validate the pub and sub fields in the permissions list -func (p *Permissions) Validate(vr *ValidationResults) { - if p.Resp != nil { - p.Resp.Validate(vr) - } - p.Sub.Validate(vr, true) - p.Pub.Validate(vr, false) -} - -// StringList is a wrapper for an array of strings -type StringList []string - -// Contains returns true if the list contains the string -func (u *StringList) Contains(p string) bool { - for _, t := range *u { - if t == p { - return true - } - } - return false -} - -// Add appends 1 or more strings to a list -func (u *StringList) Add(p ...string) { - for _, v := range p { - if !u.Contains(v) && v != "" { - *u = append(*u, v) - } - } -} - -// Remove removes 1 or more strings from a list -func (u *StringList) Remove(p ...string) { - for _, v := range p { - for i, t := range *u { - if t == v { - a := *u - *u = append(a[:i], a[i+1:]...) - break - } - } - } -} - -// TagList is a unique array of lower case strings -// All tag list methods lower case the strings in the arguments -type TagList []string - -// Contains returns true if the list contains the tags -func (u *TagList) Contains(p string) bool { - p = strings.ToLower(strings.TrimSpace(p)) - for _, t := range *u { - if t == p { - return true - } - } - return false -} - -// Add appends 1 or more tags to a list -func (u *TagList) Add(p ...string) { - for _, v := range p { - v = strings.ToLower(strings.TrimSpace(v)) - if !u.Contains(v) && v != "" { - *u = append(*u, v) - } - } -} - -// Remove removes 1 or more tags from a list -func (u *TagList) Remove(p ...string) { - for _, v := range p { - v = strings.ToLower(strings.TrimSpace(v)) - for i, t := range *u { - if t == v { - a := *u - *u = append(a[:i], a[i+1:]...) - break - } - } - } -} - -type CIDRList TagList - -func (c *CIDRList) Contains(p string) bool { - return (*TagList)(c).Contains(p) -} - -func (c *CIDRList) Add(p ...string) { - (*TagList)(c).Add(p...) -} - -func (c *CIDRList) Remove(p ...string) { - (*TagList)(c).Remove(p...) -} - -func (c *CIDRList) Set(values string) { - *c = CIDRList{} - c.Add(strings.Split(strings.ToLower(values), ",")...) -} - -func (c *CIDRList) UnmarshalJSON(body []byte) (err error) { - // parse either as array of strings or comma separate list - var request []string - var list string - if err := json.Unmarshal(body, &request); err == nil { - *c = request - return nil - } else if err := json.Unmarshal(body, &list); err == nil { - c.Set(list) - return nil - } else { - return err - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/user_claims.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/user_claims.go deleted file mode 100644 index 294cc4b7..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/user_claims.go +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2018-2024 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "errors" - "reflect" - - "github.com/nats-io/nkeys" -) - -const ( - ConnectionTypeStandard = "STANDARD" - ConnectionTypeWebsocket = "WEBSOCKET" - ConnectionTypeLeafnode = "LEAFNODE" - ConnectionTypeLeafnodeWS = "LEAFNODE_WS" - ConnectionTypeMqtt = "MQTT" - ConnectionTypeMqttWS = "MQTT_WS" - ConnectionTypeInProcess = "IN_PROCESS" -) - -type UserPermissionLimits struct { - Permissions - Limits - BearerToken bool `json:"bearer_token,omitempty"` - AllowedConnectionTypes StringList `json:"allowed_connection_types,omitempty"` -} - -// User defines the user specific data in a user JWT -type User struct { - UserPermissionLimits - // IssuerAccount stores the public key for the account the issuer represents. - // When set, the claim was issued by a signing key. - IssuerAccount string `json:"issuer_account,omitempty"` - GenericFields -} - -// Validate checks the permissions and limits in a User jwt -func (u *User) Validate(vr *ValidationResults) { - u.Permissions.Validate(vr) - u.Limits.Validate(vr) - // When BearerToken is true server will ignore any nonce-signing verification -} - -// UserClaims defines a user JWT -type UserClaims struct { - ClaimsData - User `json:"nats,omitempty"` -} - -// NewUserClaims creates a user JWT with the specific subject/public key -func NewUserClaims(subject string) *UserClaims { - if subject == "" { - return nil - } - c := &UserClaims{} - c.Subject = subject - c.Limits = Limits{ - UserLimits{CIDRList{}, nil, ""}, - NatsLimits{NoLimit, NoLimit, NoLimit}, - } - return c -} - -func (u *UserClaims) SetScoped(t bool) { - if t { - u.UserPermissionLimits = UserPermissionLimits{} - } else { - u.Limits = Limits{ - UserLimits{CIDRList{}, nil, ""}, - NatsLimits{NoLimit, NoLimit, NoLimit}, - } - } -} - -func (u *UserClaims) HasEmptyPermissions() bool { - return reflect.DeepEqual(u.UserPermissionLimits, UserPermissionLimits{}) -} - -// Encode tries to turn the user claims into a JWT string -func (u *UserClaims) Encode(pair nkeys.KeyPair) (string, error) { - return u.EncodeWithSigner(pair, nil) -} - -func (u *UserClaims) EncodeWithSigner(pair nkeys.KeyPair, fn SignFn) (string, error) { - if !nkeys.IsValidPublicUserKey(u.Subject) { - return "", errors.New("expected subject to be user public key") - } - u.Type = UserClaim - return u.ClaimsData.encode(pair, u, fn) -} - -// DecodeUserClaims tries to parse a user claims from a JWT string -func DecodeUserClaims(token string) (*UserClaims, error) { - claims, err := Decode(token) - if err != nil { - return nil, err - } - ac, ok := claims.(*UserClaims) - if !ok { - return nil, errors.New("not user claim") - } - return ac, nil -} - -func (u *UserClaims) ClaimType() ClaimType { - return u.Type -} - -// Validate checks the generic and specific parts of the user jwt -func (u *UserClaims) Validate(vr *ValidationResults) { - u.ClaimsData.Validate(vr) - u.User.Validate(vr) - if u.IssuerAccount != "" && !nkeys.IsValidPublicAccountKey(u.IssuerAccount) { - vr.AddError("account_id is not an account public key") - } -} - -// ExpectedPrefixes defines the types that can encode a user JWT, account -func (u *UserClaims) ExpectedPrefixes() []nkeys.PrefixByte { - return []nkeys.PrefixByte{nkeys.PrefixByteAccount} -} - -// Claims returns the generic data from a user jwt -func (u *UserClaims) Claims() *ClaimsData { - return &u.ClaimsData -} - -// Payload returns the user specific data from a user JWT -func (u *UserClaims) Payload() interface{} { - return &u.User -} - -func (u *UserClaims) String() string { - return u.ClaimsData.String(u) -} - -func (u *UserClaims) updateVersion() { - u.GenericFields.Version = libVersion -} - -// IsBearerToken returns true if nonce-signing requirements should be skipped -func (u *UserClaims) IsBearerToken() bool { - return u.BearerToken -} - -func (u *UserClaims) GetTags() TagList { - return u.User.Tags -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/validation.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/validation.go deleted file mode 100644 index e76a03ac..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/jwt/v2/validation.go +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2018 The NATS Authors - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package jwt - -import ( - "errors" - "fmt" -) - -// ValidationIssue represents an issue during JWT validation, it may or may not be a blocking error -type ValidationIssue struct { - Description string - Blocking bool - TimeCheck bool -} - -func (ve *ValidationIssue) Error() string { - return ve.Description -} - -// ValidationResults is a list of ValidationIssue pointers -type ValidationResults struct { - Issues []*ValidationIssue -} - -// CreateValidationResults creates an empty list of validation issues -func CreateValidationResults() *ValidationResults { - var issues []*ValidationIssue - return &ValidationResults{ - Issues: issues, - } -} - -// Add appends an issue to the list -func (v *ValidationResults) Add(vi *ValidationIssue) { - v.Issues = append(v.Issues, vi) -} - -// AddError creates a new validation error and adds it to the list -func (v *ValidationResults) AddError(format string, args ...interface{}) { - v.Add(&ValidationIssue{ - Description: fmt.Sprintf(format, args...), - Blocking: true, - TimeCheck: false, - }) -} - -// AddTimeCheck creates a new validation issue related to a time check and adds it to the list -func (v *ValidationResults) AddTimeCheck(format string, args ...interface{}) { - v.Add(&ValidationIssue{ - Description: fmt.Sprintf(format, args...), - Blocking: false, - TimeCheck: true, - }) -} - -// AddWarning creates a new validation warning and adds it to the list -func (v *ValidationResults) AddWarning(format string, args ...interface{}) { - v.Add(&ValidationIssue{ - Description: fmt.Sprintf(format, args...), - Blocking: false, - TimeCheck: false, - }) -} - -// IsBlocking returns true if the list contains a blocking error -func (v *ValidationResults) IsBlocking(includeTimeChecks bool) bool { - for _, i := range v.Issues { - if i.Blocking { - return true - } - - if includeTimeChecks && i.TimeCheck { - return true - } - } - return false -} - -// IsEmpty returns true if the list is empty -func (v *ValidationResults) IsEmpty() bool { - return len(v.Issues) == 0 -} - -// Errors returns only blocking issues as errors -func (v *ValidationResults) Errors() []error { - var errs []error - for _, v := range v.Issues { - if v.Blocking { - errs = append(errs, errors.New(v.Description)) - } - } - return errs -} - -// Warnings returns only non blocking issues as strings -func (v *ValidationResults) Warnings() []string { - var errs []string - for _, v := range v.Issues { - if !v.Blocking { - errs = append(errs, v.Description) - } - } - return errs -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.coveralls.yml b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.coveralls.yml deleted file mode 100644 index cf27a370..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.coveralls.yml +++ /dev/null @@ -1 +0,0 @@ -service_name: travis-pro diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.gitignore b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.gitignore deleted file mode 100644 index dd7aaf4f..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.gitignore +++ /dev/null @@ -1,55 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test -dist - -# Configuration Files -*.conf -*.cfg - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe - -# Eclipse -.project - -# IntelliJ -.idea/ - -# Emacs -*~ -\#*\# -.\#* - -# Visual Studio Code -.vscode - -# Mac -.DS_Store - -# bin -nats-server -gnatsd -check - -# coverage -coverage.out - -# Cross compiled binaries -pkg diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.golangci.yml b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.golangci.yml deleted file mode 100644 index ea8097fc..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.golangci.yml +++ /dev/null @@ -1,71 +0,0 @@ -# Config file for golangci-lint -run: - concurrency: 4 - issues-exit-code: 1 - tests: true - modules-download-mode: readonly - -output: - formats: - - format: colored-line-number - path: stdout - print-issued-lines: true - print-linter-name: true - -linters: - disable-all: true - enable: - # - errcheck - - forbidigo - - goimports - - gosimple - - govet - - ineffassign - # - maligned - - misspell - # - prealloc - - staticcheck - # - unparam - - unused - -linters-settings: - errcheck: - check-type-assertions: false - check-blank: false - forbidigo: - forbid: - - ^fmt\.Print(f|ln)?$ - govet: - settings: - printf: - funcs: - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf - misspell: - locale: US - unparam: - check-exported: false - prealloc: - simple: true - range-loops: true - for-loops: true - -issues: - exclude-dirs: - - .github - - doc - - docker - - logos - - scripts - - util - exclude-rules: - - path: "main.go" # Excludes main usage - linters: [forbidigo] - - source: "nats-server: v%s" # Excludes PrintServerAndExit - linters: [forbidigo] - - path: "server/opts.go" # Excludes TLS usage options - linters: [forbidigo] - - path: "_test.go" # Excludes unit tests - linters: [forbidigo] diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.goreleaser-nightly.yml b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.goreleaser-nightly.yml deleted file mode 100644 index ead4d5e2..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.goreleaser-nightly.yml +++ /dev/null @@ -1,40 +0,0 @@ -project_name: nats-server -version: 2 - -builds: - - main: . - id: nats-server - binary: nats-server - ldflags: - - -w -X github.com/nats-io/nats-server/v2/server.gitCommit={{.ShortCommit}} - env: - - GO111MODULE=on - - CGO_ENABLED=0 - goos: - - linux - goarch: - - amd64 - mod_timestamp: "{{ .CommitTimestamp }}" - -release: - disable: true - -dockers: - - goos: linux - goarch: amd64 - dockerfile: docker/Dockerfile.nightly - skip_push: false - build_flag_templates: - - '--build-arg=VERSION={{ if ne .Branch "main" }}{{ replace .Branch "/" "-" }}{{ else }}nightly{{ end }}-{{ time "20060102" }}' - image_templates: - - synadia/nats-server:{{ if ne .Branch "main" }}{{ replace .Branch "/" "-" }}{{ else }}nightly{{ end }} - - synadia/nats-server:{{ if ne .Branch "main" }}{{ replace .Branch "/" "-" }}{{ else }}nightly{{ end }}-{{ time "20060102" }} - extra_files: - - docker/nats-server.conf - -checksum: - name_template: "SHA256SUMS" - algorithm: sha256 - -snapshot: - version_template: '{{ if ne .Branch "main" }}{{ replace .Branch "/" "-" }}{{ else }}nightly{{ end }}-{{ time "20060102" }}' diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.goreleaser.yml b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.goreleaser.yml deleted file mode 100644 index d7aab77b..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.goreleaser.yml +++ /dev/null @@ -1,95 +0,0 @@ -project_name: nats-server -version: 2 - -release: - github: - owner: '{{ envOrDefault "GITHUB_REPOSITORY_OWNER" "nats-io" }}' - name: nats-server - name_template: "Release {{.Tag}}" - draft: true - -changelog: - disable: true - -builds: - - main: . - binary: nats-server - flags: - - -trimpath - ldflags: - - -w -X 'github.com/nats-io/nats-server/v2/server.gitCommit={{.ShortCommit}}' -X 'github.com/nats-io/nats-server/v2/server.serverVersion={{.Tag}}' - env: - # This is the toolchain version we use for releases. To override, set the env var, e.g.: - # GORELEASER_TOOLCHAIN="go1.22.8" TARGET='linux_amd64' goreleaser build --snapshot --clean --single-target - - GOTOOLCHAIN={{ envOrDefault "GORELEASER_TOOLCHAIN" "go1.24.1" }} - - GO111MODULE=on - - CGO_ENABLED=0 - goos: - - darwin - - linux - - windows - - freebsd - goarch: - - amd64 - - arm - - arm64 - - 386 - - mips64le - - s390x - - ppc64le - goarm: - - 6 - - 7 - ignore: - - goos: darwin - goarch: 386 - - goos: freebsd - goarch: arm - - goos: freebsd - goarch: arm64 - - goos: freebsd - goarch: 386 - mod_timestamp: "{{ .CommitTimestamp }}" - -nfpms: - - file_name_template: "{{.ProjectName}}-{{.Tag}}-{{.Arch}}{{if .Arm}}{{.Arm}}{{end}}" - homepage: https://nats.io - description: High-Performance server for NATS, the cloud native messaging system. - maintainer: Ivan Kozlovic - license: Apache 2.0 - vendor: Synadia Inc. - formats: - - deb - - rpm - mtime: "{{ .CommitDate }}" - contents: - - src: /usr/bin/nats-server - dst: /usr/local/bin/nats-server - type: "symlink" - -archives: - - name_template: "{{.ProjectName}}-{{.Tag}}-{{.Os}}-{{.Arch}}{{if .Arm}}{{.Arm}}{{end}}" - id: targz-archives - wrap_in_directory: true - formats: ["tar.gz"] - format_overrides: - - goos: windows - formats: ["zip"] - files: - - src: README.md - info: - mtime: "{{ .CommitDate }}" - - src: LICENSE - info: - mtime: "{{ .CommitDate }}" - -checksum: - name_template: "SHA256SUMS" - algorithm: sha256 - -sboms: - - artifacts: binary - documents: - [ - "{{.ProjectName}}-{{.Tag}}-{{.Os}}-{{.Arch}}{{if .Arm}}{{.Arm}}{{end}}.sbom.spdx.json", - ] diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.travis.yml b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.travis.yml deleted file mode 100644 index bd8672a5..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/.travis.yml +++ /dev/null @@ -1,55 +0,0 @@ -os: linux -dist: focal - -vm: - size: x-large - -language: go -go: - # This should be quoted or use .x, but should not be unquoted. - # Remember that a YAML bare float drops trailing zeroes. - # These toolchain versions define the test environment. - # When updating, also update the GOTOOLCHAIN value in .goreleaser.yml. - - "1.24.1" - - "1.23.7" - -go_import_path: github.com/nats-io/nats-server - -addons: - apt: - packages: - - rpm - -jobs: - include: - - name: "Compile and various other checks" - env: TEST_SUITE=compile - - name: "Run TestNoRace tests" - env: TEST_SUITE=no_race_tests - - name: "Run Store tests" - env: TEST_SUITE=store_tests - - name: "Run JetStream tests" - env: TEST_SUITE=js_tests - - name: "Run JetStream cluster tests (1)" - env: TEST_SUITE=js_cluster_tests_1 - - name: "Run JetStream cluster tests (2)" - env: TEST_SUITE=js_cluster_tests_2 - - name: "Run JetStream cluster tests (3)" - env: TEST_SUITE=js_cluster_tests_3 - - name: "Run JetStream cluster tests (4)" - env: TEST_SUITE=js_cluster_tests_4 - - name: "Run JetStream super cluster tests" - env: TEST_SUITE=js_super_cluster_tests - - name: "Run MQTT tests" - env: TEST_SUITE=mqtt_tests - - name: "Run Message Tracing tests" - env: TEST_SUITE=msgtrace_tests - - name: "Run all other tests from the server package" - env: TEST_SUITE=srv_pkg_non_js_tests - - name: "Run all tests from all other packages" - env: TEST_SUITE=non_srv_pkg_tests - - name: "Compile with older Go release" - go: "1.23.x" - env: TEST_SUITE=build_only - -script: ./scripts/runTestsOnTravis.sh $TEST_SUITE diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/AMBASSADORS.md b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/AMBASSADORS.md deleted file mode 100644 index 82f38c32..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/AMBASSADORS.md +++ /dev/null @@ -1,5 +0,0 @@ -# Ambassadors - -The NATS ambassador program recognizes community members that go above and beyond in their contributions to the community and the ecosystem. Learn more [here](https://nats.io/community#nats-ambassador-program). - -- [Maurice van Veen](https://nats.io/community#maurice-van-veen) diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/CODE-OF-CONDUCT.md b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/CODE-OF-CONDUCT.md deleted file mode 100644 index b850d49e..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/CODE-OF-CONDUCT.md +++ /dev/null @@ -1,3 +0,0 @@ -## Community Code of Conduct - -NATS follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/CONTRIBUTING.md b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/CONTRIBUTING.md deleted file mode 100644 index f0543e9b..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/CONTRIBUTING.md +++ /dev/null @@ -1,45 +0,0 @@ -# Contributing - -Thanks for your interest in contributing! This document contains `nats-io/nats-server` specific contributing details. If you are a first-time contributor, please refer to the general [NATS Contributor Guide](https://nats.io/contributing/) to get a comprehensive overview of contributing to the NATS project. - -## Getting started - -There are three general ways you can contribute to this repo: - -- Proposing an enhancement or new feature -- Reporting a bug or regression -- Contributing changes to the source code - -For the first two, refer to the [GitHub Issues](https://github.com/nats-io/nats-server/issues/new/choose) which guides you through the available options along with the needed information to collect. - -## Contributing changes - -_Prior to opening a pull request, it is recommended to open an issue first to ensure the maintainers can review intended changes. Exceptions to this rule include fixing non-functional source such as code comments, documentation or other supporting files._ - -Proposing source code changes is done through GitHub's standard pull request workflow. - -If your branch is a work-in-progress then please start by creating your pull requests as draft, by clicking the down-arrow next to the `Create pull request` button and instead selecting `Create draft pull request`. - -This will defer the automatic process of requesting a review from the NATS team and significantly reduces noise until you are ready. Once you are happy, you can click the `Ready for review` button. - -### Guidelines - -A good pull request includes: - -- A high-level description of the changes, including links to any issues that are related by adding comments like `Resolves #NNN` to your description. See [Linking a Pull Request to an Issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) for more information. -- An up-to-date parent commit. Please make sure you are pulling in the latest `main` branch and rebasing your work on top of it, i.e. `git rebase main`. -- Unit tests where appropriate. Bug fixes will benefit from the addition of regression tests. New features will not be accepted without suitable test coverage! -- No more commits than necessary. Sometimes having multiple commits is useful for telling a story or isolating changes from one another, but please squash down any unnecessary commits that may just be for clean-up, comments or small changes. -- No additional external dependencies that aren't absolutely essential. Please do everything you can to avoid pulling in additional libraries/dependencies into `go.mod` as we will be very critical of these. - -### Sign-off - -In order to accept a contribution, you will first need to certify that the contribution is your original work and that you license the work to the project under the [Apache-2.0 license](https://github.com/nats-io/nats-server/blob/main/LICENSE). - -This is done by using `Signed-off-by` statements, which should appear in **both** your commit messages and your PR description. Please note that we can only accept sign-offs under a legal name. Nicknames and aliases are not permitted. - -To perform a sign-off with `git`, use `git commit -s` (or `--signoff`). - -## Get help - -If you have questions about the contribution process, please start a [GitHub discussion](https://github.com/nats-io/nats-server/discussions), join the [NATS Slack](https://slack.nats.io/), or send your question to the [NATS Google Group](https://groups.google.com/forum/#!forum/natsio). diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/DEPENDENCIES.md b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/DEPENDENCIES.md deleted file mode 100644 index 66ee9f67..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/DEPENDENCIES.md +++ /dev/null @@ -1,19 +0,0 @@ -# External Dependencies - -This file lists the dependencies used in this repository. - -| Dependency | License | -|-|-| -| Go | BSD 3-Clause "New" or "Revised" License | -| github.com/nats-io/nats-server/v2 | Apache License 2.0 | -| github.com/google/go-tpm | Apache License 2.0 | -| github.com/klauspost/compress | BSD 3-Clause "New" or "Revised" License | -| github.com/minio/highwayhash | Apache License 2.0 | -| github.com/nats-io/jwt/v2 | Apache License 2.0 | -| github.com/nats-io/nats.go | Apache License 2.0 | -| github.com/nats-io/nkeys | Apache License 2.0 | -| github.com/nats-io/nuid | Apache License 2.0 | -| go.uber.org/automaxprocs | MIT License | -| golang.org/x/crypto | BSD 3-Clause "New" or "Revised" License | -| golang.org/x/sys | BSD 3-Clause "New" or "Revised" License | -| golang.org/x/time | BSD 3-Clause "New" or "Revised" License | diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/GOVERNANCE.md b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/GOVERNANCE.md deleted file mode 100644 index 5b2bc435..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/GOVERNANCE.md +++ /dev/null @@ -1,3 +0,0 @@ -# NATS Server Governance - -NATS Server is part of the NATS project and is subject to the [NATS Governance](https://github.com/nats-io/nats-general/blob/main/GOVERNANCE.md). diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/LICENSE b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/LICENSE deleted file mode 100644 index 261eeb9e..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/MAINTAINERS.md b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/MAINTAINERS.md deleted file mode 100644 index c89f1c2b..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/MAINTAINERS.md +++ /dev/null @@ -1,10 +0,0 @@ -# Maintainers - -Maintainership is on a per project basis. Reference [NATS Governance Model](https://github.com/nats-io/nats-general/blob/main/GOVERNANCE.md). - -### Maintainers - - Derek Collison [@derekcollison](https://github.com/derekcollison) - - Ivan Kozlovic [@kozlovic](https://github.com/kozlovic) - - Waldemar Quevedo [@wallyqs](https://github.com/wallyqs) - - Oleg Shaldybin [@olegshaldybin](https://github.com/olegshaldybin) - - R.I. Pienaar [@ripienaar](https://github.com/ripienaar) diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/README.md b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/README.md deleted file mode 100644 index 1cb1591f..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/README.md +++ /dev/null @@ -1,76 +0,0 @@ -

- NATS Logo -

- -[NATS](https://nats.io) is a simple, secure and performant communications system for digital systems, services and devices. NATS is part of the Cloud Native Computing Foundation ([CNCF](https://cncf.io)). NATS has over [40 client language implementations](https://nats.io/download/), and its server can run on-premise, in the cloud, at the edge, and even on a Raspberry Pi. NATS can secure and simplify design and operation of modern distributed systems. - -[![License][License-Image]][License-Url] [![Build][Build-Status-Image]][Build-Status-Url] [![Release][Release-Image]][Release-Url] [![Slack][Slack-Image]][Slack-Url] [![Coverage][Coverage-Image]][Coverage-Url] [![Docker Downloads][Docker-Image]][Docker-Url] [![GitHub Downloads][GitHub-Image]][Somsubhra-URL] [![CII Best Practices][CIIBestPractices-Image]][CIIBestPractices-Url] [![Artifact Hub][ArtifactHub-Image]][ArtifactHub-Url] - -## Documentation - -- [Official Website](https://nats.io) -- [Official Documentation](https://docs.nats.io) -- [FAQ](https://docs.nats.io/reference/faq) -- Watch [a video overview](https://rethink.synadia.com/episodes/1/) of NATS. -- Watch [this video from SCALE 13x](https://www.youtube.com/watch?v=sm63oAVPqAM) to learn more about its origin story and design philosophy. - -## Contact - -- [Twitter](https://twitter.com/nats_io): Follow us on Twitter! -- [Google Groups](https://groups.google.com/forum/#!forum/natsio): Where you can ask questions -- [Slack](https://natsio.slack.com): Click [here](https://slack.nats.io) to join. You can ask questions to our maintainers and to the rich and active community. - -## Contributing - -If you are interested in contributing to NATS, read about our... - -- [Contributing guide](./CONTRIBUTING.md) -- [Report issues or propose Pull Requests](https://github.com/nats-io) - -[License-Url]: https://www.apache.org/licenses/LICENSE-2.0 -[License-Image]: https://img.shields.io/badge/License-Apache2-blue.svg -[Docker-Image]: https://img.shields.io/docker/pulls/_/nats.svg -[Docker-Url]: https://hub.docker.com/_/nats -[Slack-Image]: https://img.shields.io/badge/chat-on%20slack-green -[Slack-Url]: https://slack.nats.io -[Fossa-Url]: https://app.fossa.io/projects/git%2Bgithub.com%2Fnats-io%2Fnats-server?ref=badge_shield -[Fossa-Image]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fnats-io%2Fnats-server.svg?type=shield -[Build-Status-Url]: https://travis-ci.com/github/nats-io/nats-server -[Build-Status-Image]: https://travis-ci.org/nats-io/nats-server.svg?branch=main -[Release-Url]: https://github.com/nats-io/nats-server/releases/latest -[Release-Image]: https://img.shields.io/github/v/release/nats-io/nats-server -[Coverage-Url]: https://coveralls.io/r/nats-io/nats-server?branch=main -[Coverage-image]: https://coveralls.io/repos/github/nats-io/nats-server/badge.svg?branch=main -[ReportCard-Url]: https://goreportcard.com/report/nats-io/nats-server -[ReportCard-Image]: https://goreportcard.com/badge/github.com/nats-io/nats-server -[CIIBestPractices-Url]: https://bestpractices.coreinfrastructure.org/projects/1895 -[CIIBestPractices-Image]: https://bestpractices.coreinfrastructure.org/projects/1895/badge -[ArtifactHub-Url]: https://artifacthub.io/packages/helm/nats/nats -[ArtifactHub-Image]: https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/nats -[GitHub-Release]: https://github.com/nats-io/nats-server/releases/ -[GitHub-Image]: https://img.shields.io/github/downloads/nats-io/nats-server/total.svg?logo=github -[Somsubhra-url]: https://somsubhra.github.io/github-release-stats/?username=nats-io&repository=nats-server - -## Roadmap - -The NATS product roadmap can be found [here](https://nats.io/about/#roadmap). - -## Adopters - -Who uses NATS? See our [list of users](https://nats.io/#who-uses-nats) on [https://nats.io](https://nats.io). - -## Security - -### Security Audit - -A third party security audit was performed by Cure53, you can see the full report [here](https://github.com/nats-io/nats-general/blob/main/reports/Cure53_NATS_Audit.pdf). - -### Reporting Security Vulnerabilities - -If you've found a vulnerability or a potential vulnerability in the NATS server, please let us know at -[nats-security](mailto:security@nats.io). - -## License - -Unless otherwise noted, the NATS source files are distributed -under the Apache Version 2.0 license found in the LICENSE file. diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/TODO.md b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/TODO.md deleted file mode 100644 index aa6aef46..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/TODO.md +++ /dev/null @@ -1,54 +0,0 @@ - -# General - -- [ ] Auth for queue groups? -- [ ] Blacklist or ERR escalation to close connection for auth/permissions -- [ ] Protocol updates, MAP, MPUB, etc -- [ ] Multiple listen endpoints -- [ ] Websocket / HTTP2 strategy -- [ ] T series reservations -- [ ] _SYS. server events? -- [ ] No downtime restart -- [ ] Signal based reload of configuration -- [ ] brew, apt-get, rpm, chocately (windows) -- [ ] IOVec pools and writev for high fanout? -- [ ] Modify cluster support for single message across routes between pub/sub and d-queue -- [ ] Memory limits/warnings? -- [ ] Limit number of subscriptions a client can have, total memory usage etc. -- [ ] Multi-tenant accounts with isolation of subject space -- [ ] Pedantic state -- [X] _SYS.> reserved for server events? -- [X] Listen configure key vs addr and port -- [X] Add ENV and variable support to dconf? ucl? -- [X] Buffer pools/sync pools? -- [X] Multiple Authorization / Access -- [X] Write dynamic socket buffer sizes -- [X] Read dynamic socket buffer sizes -- [X] Info updates contain other implicit route servers -- [X] Sublist better at high concurrency, cache uses writelock always currently -- [X] Switch to 1.4/1.5 and use maps vs hashmaps in sublist -- [X] NewSource on Rand to lower lock contention on QueueSubs, or redesign! -- [X] Default sort by cid on connz -- [X] Track last activity time per connection? -- [X] Add total connections to varz so we won't miss spikes, etc. -- [X] Add starttime and uptime to connz list. -- [X] Gossip Protocol for discovery for clustering -- [X] Add in HTTP requests to varz? -- [X] Add favico and help link for monitoring? -- [X] Better user/pass support using bcrypt etc. -- [X] SSL/TLS support -- [X] Add support for / to point to varz, connz, etc.. -- [X] Support sort options for /connz via nats-top -- [X] Dropped message statistics (slow consumers) -- [X] Add current time to each monitoring endpoint -- [X] varz uptime do days and only integer secs -- [X] Place version in varz (same info sent to clients) -- [X] Place server ID/UUID in varz -- [X] nats-top equivalent, utils -- [X] Connz report routes (/routez) -- [X] Docker -- [X] Remove reliance on `ps` -- [X] Syslog support -- [X] Client support for language and version -- [X] Fix benchmarks on linux -- [X] Daemon mode? Won't fix diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/fuzz.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/fuzz.go deleted file mode 100644 index 2db114ce..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/fuzz.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2020-2021 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build gofuzz - -package conf - -func Fuzz(data []byte) int { - _, err := Parse(string(data)) - if err != nil { - return 0 - } - return 1 -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/lex.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/lex.go deleted file mode 100644 index 80a9a114..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/lex.go +++ /dev/null @@ -1,1319 +0,0 @@ -// Copyright 2013-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Customized heavily from -// https://github.com/BurntSushi/toml/blob/master/lex.go, which is based on -// Rob Pike's talk: http://cuddle.googlecode.com/hg/talk/lex.html - -// The format supported is less restrictive than today's formats. -// Supports mixed Arrays [], nested Maps {}, multiple comment types (# and //) -// Also supports key value assignments using '=' or ':' or whiteSpace() -// e.g. foo = 2, foo : 2, foo 2 -// maps can be assigned with no key separator as well -// semicolons as value terminators in key/value assignments are optional -// -// see lex_test.go for more examples. - -package conf - -import ( - "encoding/hex" - "fmt" - "strings" - "unicode" - "unicode/utf8" -) - -type itemType int - -const ( - itemError itemType = iota - itemNIL // used in the parser to indicate no type - itemEOF - itemKey - itemText - itemString - itemBool - itemInteger - itemFloat - itemDatetime - itemArrayStart - itemArrayEnd - itemMapStart - itemMapEnd - itemCommentStart - itemVariable - itemInclude -) - -const ( - eof = 0 - mapStart = '{' - mapEnd = '}' - keySepEqual = '=' - keySepColon = ':' - arrayStart = '[' - arrayEnd = ']' - arrayValTerm = ',' - mapValTerm = ',' - commentHashStart = '#' - commentSlashStart = '/' - dqStringStart = '"' - dqStringEnd = '"' - sqStringStart = '\'' - sqStringEnd = '\'' - optValTerm = ';' - topOptStart = '{' - topOptValTerm = ',' - topOptTerm = '}' - blockStart = '(' - blockEnd = ')' - mapEndString = string(mapEnd) -) - -type stateFn func(lx *lexer) stateFn - -type lexer struct { - input string - start int - pos int - width int - line int - state stateFn - items chan item - - // A stack of state functions used to maintain context. - // The idea is to reuse parts of the state machine in various places. - // For example, values can appear at the top level or within arbitrarily - // nested arrays. The last state on the stack is used after a value has - // been lexed. Similarly for comments. - stack []stateFn - - // Used for processing escapable substrings in double-quoted and raw strings - stringParts []string - stringStateFn stateFn - - // lstart is the start position of the current line. - lstart int - - // ilstart is the start position of the line from the current item. - ilstart int -} - -type item struct { - typ itemType - val string - line int - pos int -} - -func (lx *lexer) nextItem() item { - for { - select { - case item := <-lx.items: - return item - default: - lx.state = lx.state(lx) - } - } -} - -func lex(input string) *lexer { - lx := &lexer{ - input: input, - state: lexTop, - line: 1, - items: make(chan item, 10), - stack: make([]stateFn, 0, 10), - stringParts: []string{}, - } - return lx -} - -func (lx *lexer) push(state stateFn) { - lx.stack = append(lx.stack, state) -} - -func (lx *lexer) pop() stateFn { - if len(lx.stack) == 0 { - return lx.errorf("BUG in lexer: no states to pop.") - } - li := len(lx.stack) - 1 - last := lx.stack[li] - lx.stack = lx.stack[0:li] - return last -} - -func (lx *lexer) emit(typ itemType) { - val := strings.Join(lx.stringParts, "") + lx.input[lx.start:lx.pos] - // Position of item in line where it started. - pos := lx.pos - lx.ilstart - len(val) - lx.items <- item{typ, val, lx.line, pos} - lx.start = lx.pos - lx.ilstart = lx.lstart -} - -func (lx *lexer) emitString() { - var finalString string - if len(lx.stringParts) > 0 { - finalString = strings.Join(lx.stringParts, "") + lx.input[lx.start:lx.pos] - lx.stringParts = []string{} - } else { - finalString = lx.input[lx.start:lx.pos] - } - // Position of string in line where it started. - pos := lx.pos - lx.ilstart - len(finalString) - lx.items <- item{itemString, finalString, lx.line, pos} - lx.start = lx.pos - lx.ilstart = lx.lstart -} - -func (lx *lexer) addCurrentStringPart(offset int) { - lx.stringParts = append(lx.stringParts, lx.input[lx.start:lx.pos-offset]) - lx.start = lx.pos -} - -func (lx *lexer) addStringPart(s string) stateFn { - lx.stringParts = append(lx.stringParts, s) - lx.start = lx.pos - return lx.stringStateFn -} - -func (lx *lexer) hasEscapedParts() bool { - return len(lx.stringParts) > 0 -} - -func (lx *lexer) next() (r rune) { - if lx.pos >= len(lx.input) { - lx.width = 0 - return eof - } - - if lx.input[lx.pos] == '\n' { - lx.line++ - - // Mark start position of current line. - lx.lstart = lx.pos - } - r, lx.width = utf8.DecodeRuneInString(lx.input[lx.pos:]) - lx.pos += lx.width - - return r -} - -// ignore skips over the pending input before this point. -func (lx *lexer) ignore() { - lx.start = lx.pos - lx.ilstart = lx.lstart -} - -// backup steps back one rune. Can be called only once per call of next. -func (lx *lexer) backup() { - lx.pos -= lx.width - if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' { - lx.line-- - } -} - -// peek returns but does not consume the next rune in the input. -func (lx *lexer) peek() rune { - r := lx.next() - lx.backup() - return r -} - -// errorf stops all lexing by emitting an error and returning `nil`. -// Note that any value that is a character is escaped if it's a special -// character (new lines, tabs, etc.). -func (lx *lexer) errorf(format string, values ...any) stateFn { - for i, value := range values { - if v, ok := value.(rune); ok { - values[i] = escapeSpecial(v) - } - } - - // Position of error in current line. - pos := lx.pos - lx.lstart - lx.items <- item{ - itemError, - fmt.Sprintf(format, values...), - lx.line, - pos, - } - return nil -} - -// lexTop consumes elements at the top level of data structure. -func lexTop(lx *lexer) stateFn { - r := lx.next() - if unicode.IsSpace(r) { - return lexSkip(lx, lexTop) - } - - switch r { - case topOptStart: - lx.push(lexTop) - return lexSkip(lx, lexBlockStart) - case commentHashStart: - lx.push(lexTop) - return lexCommentStart - case commentSlashStart: - rn := lx.next() - if rn == commentSlashStart { - lx.push(lexTop) - return lexCommentStart - } - lx.backup() - fallthrough - case eof: - if lx.pos > lx.start { - return lx.errorf("Unexpected EOF.") - } - lx.emit(itemEOF) - return nil - } - - // At this point, the only valid item can be a key, so we back up - // and let the key lexer do the rest. - lx.backup() - lx.push(lexTopValueEnd) - return lexKeyStart -} - -// lexTopValueEnd is entered whenever a top-level value has been consumed. -// It must see only whitespace, and will turn back to lexTop upon a new line. -// If it sees EOF, it will quit the lexer successfully. -func lexTopValueEnd(lx *lexer) stateFn { - r := lx.next() - switch { - case r == commentHashStart: - // a comment will read to a new line for us. - lx.push(lexTop) - return lexCommentStart - case r == commentSlashStart: - rn := lx.next() - if rn == commentSlashStart { - lx.push(lexTop) - return lexCommentStart - } - lx.backup() - fallthrough - case isWhitespace(r): - return lexTopValueEnd - case isNL(r) || r == eof || r == optValTerm || r == topOptValTerm || r == topOptTerm: - lx.ignore() - return lexTop - } - return lx.errorf("Expected a top-level value to end with a new line, "+ - "comment or EOF, but got '%v' instead.", r) -} - -func lexBlockStart(lx *lexer) stateFn { - r := lx.next() - if unicode.IsSpace(r) { - return lexSkip(lx, lexBlockStart) - } - - switch r { - case topOptStart: - lx.push(lexBlockEnd) - return lexSkip(lx, lexBlockStart) - case topOptTerm: - lx.ignore() - return lx.pop() - case commentHashStart: - lx.push(lexBlockStart) - return lexCommentStart - case commentSlashStart: - rn := lx.next() - if rn == commentSlashStart { - lx.push(lexBlockStart) - return lexCommentStart - } - lx.backup() - fallthrough - case eof: - if lx.pos > lx.start { - return lx.errorf("Unexpected EOF.") - } - lx.emit(itemEOF) - return nil - } - - // At this point, the only valid item can be a key, so we back up - // and let the key lexer do the rest. - lx.backup() - lx.push(lexBlockValueEnd) - return lexKeyStart -} - -// lexBlockValueEnd is entered whenever a block-level value has been consumed. -// It must see only whitespace, and will turn back to lexBlockStart upon a new line. -// If it sees EOF, it will quit the lexer successfully. -func lexBlockValueEnd(lx *lexer) stateFn { - r := lx.next() - switch { - case r == commentHashStart: - // a comment will read to a new line for us. - lx.push(lexBlockValueEnd) - return lexCommentStart - case r == commentSlashStart: - rn := lx.next() - if rn == commentSlashStart { - lx.push(lexBlockValueEnd) - return lexCommentStart - } - lx.backup() - fallthrough - case isWhitespace(r): - return lexBlockValueEnd - case isNL(r) || r == optValTerm || r == topOptValTerm: - lx.ignore() - return lexBlockStart - case r == topOptTerm: - lx.backup() - return lexBlockEnd - } - return lx.errorf("Expected a block-level value to end with a new line, "+ - "comment or EOF, but got '%v' instead.", r) -} - -// lexBlockEnd is entered whenever a block-level value has been consumed. -// It must see only whitespace, and will turn back to lexTop upon a "}". -func lexBlockEnd(lx *lexer) stateFn { - r := lx.next() - switch { - case r == commentHashStart: - // a comment will read to a new line for us. - lx.push(lexBlockStart) - return lexCommentStart - case r == commentSlashStart: - rn := lx.next() - if rn == commentSlashStart { - lx.push(lexBlockStart) - return lexCommentStart - } - lx.backup() - fallthrough - case isNL(r) || isWhitespace(r): - return lexBlockEnd - case r == optValTerm || r == topOptValTerm: - lx.ignore() - return lexBlockStart - case r == topOptTerm: - lx.ignore() - return lx.pop() - } - return lx.errorf("Expected a block-level to end with a '}', but got '%v' instead.", r) -} - -// lexKeyStart consumes a key name up until the first non-whitespace character. -// lexKeyStart will ignore whitespace. It will also eat enclosing quotes. -func lexKeyStart(lx *lexer) stateFn { - r := lx.peek() - switch { - case isKeySeparator(r): - return lx.errorf("Unexpected key separator '%v'", r) - case unicode.IsSpace(r): - lx.next() - return lexSkip(lx, lexKeyStart) - case r == dqStringStart: - lx.next() - return lexSkip(lx, lexDubQuotedKey) - case r == sqStringStart: - lx.next() - return lexSkip(lx, lexQuotedKey) - } - lx.ignore() - lx.next() - return lexKey -} - -// lexDubQuotedKey consumes the text of a key between quotes. -func lexDubQuotedKey(lx *lexer) stateFn { - r := lx.peek() - if r == dqStringEnd { - lx.emit(itemKey) - lx.next() - return lexSkip(lx, lexKeyEnd) - } else if r == eof { - if lx.pos > lx.start { - return lx.errorf("Unexpected EOF.") - } - lx.emit(itemEOF) - return nil - } - lx.next() - return lexDubQuotedKey -} - -// lexQuotedKey consumes the text of a key between quotes. -func lexQuotedKey(lx *lexer) stateFn { - r := lx.peek() - if r == sqStringEnd { - lx.emit(itemKey) - lx.next() - return lexSkip(lx, lexKeyEnd) - } else if r == eof { - if lx.pos > lx.start { - return lx.errorf("Unexpected EOF.") - } - lx.emit(itemEOF) - return nil - } - lx.next() - return lexQuotedKey -} - -// keyCheckKeyword will check for reserved keywords as the key value when the key is -// separated with a space. -func (lx *lexer) keyCheckKeyword(fallThrough, push stateFn) stateFn { - key := strings.ToLower(lx.input[lx.start:lx.pos]) - switch key { - case "include": - lx.ignore() - if push != nil { - lx.push(push) - } - return lexIncludeStart - } - lx.emit(itemKey) - return fallThrough -} - -// lexIncludeStart will consume the whitespace til the start of the value. -func lexIncludeStart(lx *lexer) stateFn { - r := lx.next() - if isWhitespace(r) { - return lexSkip(lx, lexIncludeStart) - } - lx.backup() - return lexInclude -} - -// lexIncludeQuotedString consumes the inner contents of a string. It assumes that the -// beginning '"' has already been consumed and ignored. It will not interpret any -// internal contents. -func lexIncludeQuotedString(lx *lexer) stateFn { - r := lx.next() - switch { - case r == sqStringEnd: - lx.backup() - lx.emit(itemInclude) - lx.next() - lx.ignore() - return lx.pop() - case r == eof: - return lx.errorf("Unexpected EOF in quoted include") - } - return lexIncludeQuotedString -} - -// lexIncludeDubQuotedString consumes the inner contents of a string. It assumes that the -// beginning '"' has already been consumed and ignored. It will not interpret any -// internal contents. -func lexIncludeDubQuotedString(lx *lexer) stateFn { - r := lx.next() - switch { - case r == dqStringEnd: - lx.backup() - lx.emit(itemInclude) - lx.next() - lx.ignore() - return lx.pop() - case r == eof: - return lx.errorf("Unexpected EOF in double quoted include") - } - return lexIncludeDubQuotedString -} - -// lexIncludeString consumes the inner contents of a raw string. -func lexIncludeString(lx *lexer) stateFn { - r := lx.next() - switch { - case isNL(r) || r == eof || r == optValTerm || r == mapEnd || isWhitespace(r): - lx.backup() - lx.emit(itemInclude) - return lx.pop() - case r == sqStringEnd: - lx.backup() - lx.emit(itemInclude) - lx.next() - lx.ignore() - return lx.pop() - } - return lexIncludeString -} - -// lexInclude will consume the include value. -func lexInclude(lx *lexer) stateFn { - r := lx.next() - switch { - case r == sqStringStart: - lx.ignore() // ignore the " or ' - return lexIncludeQuotedString - case r == dqStringStart: - lx.ignore() // ignore the " or ' - return lexIncludeDubQuotedString - case r == arrayStart: - return lx.errorf("Expected include value but found start of an array") - case r == mapStart: - return lx.errorf("Expected include value but found start of a map") - case r == blockStart: - return lx.errorf("Expected include value but found start of a block") - case unicode.IsDigit(r), r == '-': - return lx.errorf("Expected include value but found start of a number") - case r == '\\': - return lx.errorf("Expected include value but found escape sequence") - case isNL(r): - return lx.errorf("Expected include value but found new line") - } - lx.backup() - return lexIncludeString -} - -// lexKey consumes the text of a key. Assumes that the first character (which -// is not whitespace) has already been consumed. -func lexKey(lx *lexer) stateFn { - r := lx.peek() - if unicode.IsSpace(r) { - // Spaces signal we could be looking at a keyword, e.g. include. - // Keywords will eat the keyword and set the appropriate return stateFn. - return lx.keyCheckKeyword(lexKeyEnd, nil) - } else if isKeySeparator(r) || r == eof { - lx.emit(itemKey) - return lexKeyEnd - } - lx.next() - return lexKey -} - -// lexKeyEnd consumes the end of a key (up to the key separator). -// Assumes that the first whitespace character after a key (or the '=' or ':' -// separator) has NOT been consumed. -func lexKeyEnd(lx *lexer) stateFn { - r := lx.next() - switch { - case unicode.IsSpace(r): - return lexSkip(lx, lexKeyEnd) - case isKeySeparator(r): - return lexSkip(lx, lexValue) - case r == eof: - lx.emit(itemEOF) - return nil - } - // We start the value here - lx.backup() - return lexValue -} - -// lexValue starts the consumption of a value anywhere a value is expected. -// lexValue will ignore whitespace. -// After a value is lexed, the last state on the next is popped and returned. -func lexValue(lx *lexer) stateFn { - // We allow whitespace to precede a value, but NOT new lines. - // In array syntax, the array states are responsible for ignoring new lines. - r := lx.next() - if isWhitespace(r) { - return lexSkip(lx, lexValue) - } - - switch { - case r == arrayStart: - lx.ignore() - lx.emit(itemArrayStart) - return lexArrayValue - case r == mapStart: - lx.ignore() - lx.emit(itemMapStart) - return lexMapKeyStart - case r == sqStringStart: - lx.ignore() // ignore the " or ' - return lexQuotedString - case r == dqStringStart: - lx.ignore() // ignore the " or ' - lx.stringStateFn = lexDubQuotedString - return lexDubQuotedString - case r == '-': - return lexNegNumberStart - case r == blockStart: - lx.ignore() - return lexBlock - case unicode.IsDigit(r): - lx.backup() // avoid an extra state and use the same as above - return lexNumberOrDateOrStringOrIPStart - case r == '.': // special error case, be kind to users - return lx.errorf("Floats must start with a digit") - case isNL(r): - return lx.errorf("Expected value but found new line") - } - lx.backup() - lx.stringStateFn = lexString - return lexString -} - -// lexArrayValue consumes one value in an array. It assumes that '[' or ',' -// have already been consumed. All whitespace and new lines are ignored. -func lexArrayValue(lx *lexer) stateFn { - r := lx.next() - switch { - case unicode.IsSpace(r): - return lexSkip(lx, lexArrayValue) - case r == commentHashStart: - lx.push(lexArrayValue) - return lexCommentStart - case r == commentSlashStart: - rn := lx.next() - if rn == commentSlashStart { - lx.push(lexArrayValue) - return lexCommentStart - } - lx.backup() - fallthrough - case r == arrayValTerm: - return lx.errorf("Unexpected array value terminator '%v'.", arrayValTerm) - case r == arrayEnd: - return lexArrayEnd - } - - lx.backup() - lx.push(lexArrayValueEnd) - return lexValue -} - -// lexArrayValueEnd consumes the cruft between values of an array. Namely, -// it ignores whitespace and expects either a ',' or a ']'. -func lexArrayValueEnd(lx *lexer) stateFn { - r := lx.next() - switch { - case isWhitespace(r): - return lexSkip(lx, lexArrayValueEnd) - case r == commentHashStart: - lx.push(lexArrayValueEnd) - return lexCommentStart - case r == commentSlashStart: - rn := lx.next() - if rn == commentSlashStart { - lx.push(lexArrayValueEnd) - return lexCommentStart - } - lx.backup() - fallthrough - case r == arrayValTerm || isNL(r): - return lexSkip(lx, lexArrayValue) // Move onto next - case r == arrayEnd: - return lexArrayEnd - } - return lx.errorf("Expected an array value terminator %q or an array "+ - "terminator %q, but got '%v' instead.", arrayValTerm, arrayEnd, r) -} - -// lexArrayEnd finishes the lexing of an array. It assumes that a ']' has -// just been consumed. -func lexArrayEnd(lx *lexer) stateFn { - lx.ignore() - lx.emit(itemArrayEnd) - return lx.pop() -} - -// lexMapKeyStart consumes a key name up until the first non-whitespace -// character. -// lexMapKeyStart will ignore whitespace. -func lexMapKeyStart(lx *lexer) stateFn { - r := lx.peek() - switch { - case isKeySeparator(r): - return lx.errorf("Unexpected key separator '%v'.", r) - case r == arrayEnd: - return lx.errorf("Unexpected array end '%v' processing map.", r) - case unicode.IsSpace(r): - lx.next() - return lexSkip(lx, lexMapKeyStart) - case r == mapEnd: - lx.next() - return lexSkip(lx, lexMapEnd) - case r == commentHashStart: - lx.next() - lx.push(lexMapKeyStart) - return lexCommentStart - case r == commentSlashStart: - lx.next() - rn := lx.next() - if rn == commentSlashStart { - lx.push(lexMapKeyStart) - return lexCommentStart - } - lx.backup() - case r == sqStringStart: - lx.next() - return lexSkip(lx, lexMapQuotedKey) - case r == dqStringStart: - lx.next() - return lexSkip(lx, lexMapDubQuotedKey) - case r == eof: - return lx.errorf("Unexpected EOF processing map.") - } - lx.ignore() - lx.next() - return lexMapKey -} - -// lexMapQuotedKey consumes the text of a key between quotes. -func lexMapQuotedKey(lx *lexer) stateFn { - if r := lx.peek(); r == eof { - return lx.errorf("Unexpected EOF processing quoted map key.") - } else if r == sqStringEnd { - lx.emit(itemKey) - lx.next() - return lexSkip(lx, lexMapKeyEnd) - } - lx.next() - return lexMapQuotedKey -} - -// lexMapDubQuotedKey consumes the text of a key between quotes. -func lexMapDubQuotedKey(lx *lexer) stateFn { - if r := lx.peek(); r == eof { - return lx.errorf("Unexpected EOF processing double quoted map key.") - } else if r == dqStringEnd { - lx.emit(itemKey) - lx.next() - return lexSkip(lx, lexMapKeyEnd) - } - lx.next() - return lexMapDubQuotedKey -} - -// lexMapKey consumes the text of a key. Assumes that the first character (which -// is not whitespace) has already been consumed. -func lexMapKey(lx *lexer) stateFn { - if r := lx.peek(); r == eof { - return lx.errorf("Unexpected EOF processing map key.") - } else if unicode.IsSpace(r) { - // Spaces signal we could be looking at a keyword, e.g. include. - // Keywords will eat the keyword and set the appropriate return stateFn. - return lx.keyCheckKeyword(lexMapKeyEnd, lexMapValueEnd) - } else if isKeySeparator(r) { - lx.emit(itemKey) - return lexMapKeyEnd - } - lx.next() - return lexMapKey -} - -// lexMapKeyEnd consumes the end of a key (up to the key separator). -// Assumes that the first whitespace character after a key (or the '=' -// separator) has NOT been consumed. -func lexMapKeyEnd(lx *lexer) stateFn { - r := lx.next() - switch { - case unicode.IsSpace(r): - return lexSkip(lx, lexMapKeyEnd) - case isKeySeparator(r): - return lexSkip(lx, lexMapValue) - } - // We start the value here - lx.backup() - return lexMapValue -} - -// lexMapValue consumes one value in a map. It assumes that '{' or ',' -// have already been consumed. All whitespace and new lines are ignored. -// Map values can be separated by ',' or simple NLs. -func lexMapValue(lx *lexer) stateFn { - r := lx.next() - switch { - case unicode.IsSpace(r): - return lexSkip(lx, lexMapValue) - case r == mapValTerm: - return lx.errorf("Unexpected map value terminator %q.", mapValTerm) - case r == mapEnd: - return lexSkip(lx, lexMapEnd) - } - lx.backup() - lx.push(lexMapValueEnd) - return lexValue -} - -// lexMapValueEnd consumes the cruft between values of a map. Namely, -// it ignores whitespace and expects either a ',' or a '}'. -func lexMapValueEnd(lx *lexer) stateFn { - r := lx.next() - switch { - case isWhitespace(r): - return lexSkip(lx, lexMapValueEnd) - case r == commentHashStart: - lx.push(lexMapValueEnd) - return lexCommentStart - case r == commentSlashStart: - rn := lx.next() - if rn == commentSlashStart { - lx.push(lexMapValueEnd) - return lexCommentStart - } - lx.backup() - fallthrough - case r == optValTerm || r == mapValTerm || isNL(r): - return lexSkip(lx, lexMapKeyStart) // Move onto next - case r == mapEnd: - return lexSkip(lx, lexMapEnd) - } - return lx.errorf("Expected a map value terminator %q or a map "+ - "terminator %q, but got '%v' instead.", mapValTerm, mapEnd, r) -} - -// lexMapEnd finishes the lexing of a map. It assumes that a '}' has -// just been consumed. -func lexMapEnd(lx *lexer) stateFn { - lx.ignore() - lx.emit(itemMapEnd) - return lx.pop() -} - -// Checks if the unquoted string was actually a boolean -func (lx *lexer) isBool() bool { - str := strings.ToLower(lx.input[lx.start:lx.pos]) - return str == "true" || str == "false" || - str == "on" || str == "off" || - str == "yes" || str == "no" -} - -// Check if the unquoted string is a variable reference, starting with $. -func (lx *lexer) isVariable() bool { - if lx.start >= len(lx.input) { - return false - } - if lx.input[lx.start] == '$' { - lx.start += 1 - return true - } - return false -} - -// lexQuotedString consumes the inner contents of a string. It assumes that the -// beginning '"' has already been consumed and ignored. It will not interpret any -// internal contents. -func lexQuotedString(lx *lexer) stateFn { - r := lx.next() - switch { - case r == sqStringEnd: - lx.backup() - lx.emit(itemString) - lx.next() - lx.ignore() - return lx.pop() - case r == eof: - if lx.pos > lx.start { - return lx.errorf("Unexpected EOF.") - } - lx.emit(itemEOF) - return nil - } - return lexQuotedString -} - -// lexDubQuotedString consumes the inner contents of a string. It assumes that the -// beginning '"' has already been consumed and ignored. It will not interpret any -// internal contents. -func lexDubQuotedString(lx *lexer) stateFn { - r := lx.next() - switch { - case r == '\\': - lx.addCurrentStringPart(1) - return lexStringEscape - case r == dqStringEnd: - lx.backup() - lx.emitString() - lx.next() - lx.ignore() - return lx.pop() - case r == eof: - if lx.pos > lx.start { - return lx.errorf("Unexpected EOF.") - } - lx.emit(itemEOF) - return nil - } - return lexDubQuotedString -} - -// lexString consumes the inner contents of a raw string. -func lexString(lx *lexer) stateFn { - r := lx.next() - switch { - case r == '\\': - lx.addCurrentStringPart(1) - return lexStringEscape - // Termination of non-quoted strings - case isNL(r) || r == eof || r == optValTerm || - r == arrayValTerm || r == arrayEnd || r == mapEnd || - isWhitespace(r): - - lx.backup() - if lx.hasEscapedParts() { - lx.emitString() - } else if lx.isBool() { - lx.emit(itemBool) - } else if lx.isVariable() { - lx.emit(itemVariable) - } else { - lx.emitString() - } - return lx.pop() - case r == sqStringEnd: - lx.backup() - lx.emitString() - lx.next() - lx.ignore() - return lx.pop() - } - return lexString -} - -// lexBlock consumes the inner contents as a string. It assumes that the -// beginning '(' has already been consumed and ignored. It will continue -// processing until it finds a ')' on a new line by itself. -func lexBlock(lx *lexer) stateFn { - r := lx.next() - switch { - case r == blockEnd: - lx.backup() - lx.backup() - - // Looking for a ')' character on a line by itself, if the previous - // character isn't a new line, then break so we keep processing the block. - if lx.next() != '\n' { - lx.next() - break - } - lx.next() - - // Make sure the next character is a new line or an eof. We want a ')' on a - // bare line by itself. - switch lx.next() { - case '\n', eof: - lx.backup() - lx.backup() - lx.emit(itemString) - lx.next() - lx.ignore() - return lx.pop() - } - lx.backup() - case r == eof: - return lx.errorf("Unexpected EOF processing block.") - } - return lexBlock -} - -// lexStringEscape consumes an escaped character. It assumes that the preceding -// '\\' has already been consumed. -func lexStringEscape(lx *lexer) stateFn { - r := lx.next() - switch r { - case 'x': - return lexStringBinary - case 't': - return lx.addStringPart("\t") - case 'n': - return lx.addStringPart("\n") - case 'r': - return lx.addStringPart("\r") - case '"': - return lx.addStringPart("\"") - case '\\': - return lx.addStringPart("\\") - } - return lx.errorf("Invalid escape character '%v'. Only the following "+ - "escape characters are allowed: \\xXX, \\t, \\n, \\r, \\\", \\\\.", r) -} - -// lexStringBinary consumes two hexadecimal digits following '\x'. It assumes -// that the '\x' has already been consumed. -func lexStringBinary(lx *lexer) stateFn { - r := lx.next() - if isNL(r) { - return lx.errorf("Expected two hexadecimal digits after '\\x', but hit end of line") - } - r = lx.next() - if isNL(r) { - return lx.errorf("Expected two hexadecimal digits after '\\x', but hit end of line") - } - offset := lx.pos - 2 - byteString, err := hex.DecodeString(lx.input[offset:lx.pos]) - if err != nil { - return lx.errorf("Expected two hexadecimal digits after '\\x', but got '%s'", lx.input[offset:lx.pos]) - } - lx.addStringPart(string(byteString)) - return lx.stringStateFn -} - -// lexNumberOrDateOrStringOrIPStart consumes either a (positive) -// integer, a float, a datetime, or IP, or String that started with a -// number. It assumes that NO negative sign has been consumed, that -// is triggered above. -func lexNumberOrDateOrStringOrIPStart(lx *lexer) stateFn { - r := lx.next() - if !unicode.IsDigit(r) { - if r == '.' { - return lx.errorf("Floats must start with a digit, not '.'.") - } - return lx.errorf("Expected a digit but got '%v'.", r) - } - return lexNumberOrDateOrStringOrIP -} - -// lexNumberOrDateOrStringOrIP consumes either a (positive) integer, -// float, datetime, IP or string without quotes that starts with a -// number. -func lexNumberOrDateOrStringOrIP(lx *lexer) stateFn { - r := lx.next() - switch { - case r == '-': - if lx.pos-lx.start != 5 { - return lx.errorf("All ISO8601 dates must be in full Zulu form.") - } - return lexDateAfterYear - case unicode.IsDigit(r): - return lexNumberOrDateOrStringOrIP - case r == '.': - // Assume float at first, but could be IP - return lexFloatStart - case isNumberSuffix(r): - return lexConvenientNumber - case !(isNL(r) || r == eof || r == mapEnd || r == optValTerm || r == mapValTerm || isWhitespace(r) || unicode.IsDigit(r)): - // Treat it as a string value once we get a rune that - // is not a number. - lx.stringStateFn = lexString - return lexString - } - lx.backup() - lx.emit(itemInteger) - return lx.pop() -} - -// lexConvenientNumber is when we have a suffix, e.g. 1k or 1Mb -func lexConvenientNumber(lx *lexer) stateFn { - r := lx.next() - switch { - case r == 'b' || r == 'B' || r == 'i' || r == 'I': - return lexConvenientNumber - } - lx.backup() - if isNL(r) || r == eof || r == mapEnd || r == optValTerm || r == mapValTerm || isWhitespace(r) || unicode.IsDigit(r) { - lx.emit(itemInteger) - return lx.pop() - } - // This is not a number, so treat it as a string. - lx.stringStateFn = lexString - return lexString -} - -// lexDateAfterYear consumes a full Zulu Datetime in ISO8601 format. -// It assumes that "YYYY-" has already been consumed. -func lexDateAfterYear(lx *lexer) stateFn { - formats := []rune{ - // digits are '0'. - // everything else is direct equality. - '0', '0', '-', '0', '0', - 'T', - '0', '0', ':', '0', '0', ':', '0', '0', - 'Z', - } - for _, f := range formats { - r := lx.next() - if f == '0' { - if !unicode.IsDigit(r) { - return lx.errorf("Expected digit in ISO8601 datetime, "+ - "but found '%v' instead.", r) - } - } else if f != r { - return lx.errorf("Expected '%v' in ISO8601 datetime, "+ - "but found '%v' instead.", f, r) - } - } - lx.emit(itemDatetime) - return lx.pop() -} - -// lexNegNumberStart consumes either an integer or a float. It assumes that a -// negative sign has already been read, but that *no* digits have been consumed. -// lexNegNumberStart will move to the appropriate integer or float states. -func lexNegNumberStart(lx *lexer) stateFn { - // we MUST see a digit. Even floats have to start with a digit. - r := lx.next() - if !unicode.IsDigit(r) { - if r == '.' { - return lx.errorf("Floats must start with a digit, not '.'.") - } - return lx.errorf("Expected a digit but got '%v'.", r) - } - return lexNegNumber -} - -// lexNegNumber consumes a negative integer or a float after seeing the first digit. -func lexNegNumber(lx *lexer) stateFn { - r := lx.next() - switch { - case unicode.IsDigit(r): - return lexNegNumber - case r == '.': - return lexFloatStart - case isNumberSuffix(r): - return lexConvenientNumber - } - lx.backup() - lx.emit(itemInteger) - return lx.pop() -} - -// lexFloatStart starts the consumption of digits of a float after a '.'. -// Namely, at least one digit is required. -func lexFloatStart(lx *lexer) stateFn { - r := lx.next() - if !unicode.IsDigit(r) { - return lx.errorf("Floats must have a digit after the '.', but got "+ - "'%v' instead.", r) - } - return lexFloat -} - -// lexFloat consumes the digits of a float after a '.'. -// Assumes that one digit has been consumed after a '.' already. -func lexFloat(lx *lexer) stateFn { - r := lx.next() - if unicode.IsDigit(r) { - return lexFloat - } - - // Not a digit, if its another '.', need to see if we falsely assumed a float. - if r == '.' { - return lexIPAddr - } - - lx.backup() - lx.emit(itemFloat) - return lx.pop() -} - -// lexIPAddr consumes IP addrs, like 127.0.0.1:4222 -func lexIPAddr(lx *lexer) stateFn { - r := lx.next() - if unicode.IsDigit(r) || r == '.' || r == ':' || r == '-' { - return lexIPAddr - } - lx.backup() - lx.emit(itemString) - return lx.pop() -} - -// lexCommentStart begins the lexing of a comment. It will emit -// itemCommentStart and consume no characters, passing control to lexComment. -func lexCommentStart(lx *lexer) stateFn { - lx.ignore() - lx.emit(itemCommentStart) - return lexComment -} - -// lexComment lexes an entire comment. It assumes that '#' has been consumed. -// It will consume *up to* the first new line character, and pass control -// back to the last state on the stack. -func lexComment(lx *lexer) stateFn { - r := lx.peek() - if isNL(r) || r == eof { - lx.emit(itemText) - return lx.pop() - } - lx.next() - return lexComment -} - -// lexSkip ignores all slurped input and moves on to the next state. -func lexSkip(lx *lexer, nextState stateFn) stateFn { - return func(lx *lexer) stateFn { - lx.ignore() - return nextState - } -} - -// Tests to see if we have a number suffix -func isNumberSuffix(r rune) bool { - return r == 'k' || r == 'K' || r == 'm' || r == 'M' || r == 'g' || r == 'G' || r == 't' || r == 'T' || r == 'p' || r == 'P' || r == 'e' || r == 'E' -} - -// Tests for both key separators -func isKeySeparator(r rune) bool { - return r == keySepEqual || r == keySepColon -} - -// isWhitespace returns true if `r` is a whitespace character according -// to the spec. -func isWhitespace(r rune) bool { - return r == '\t' || r == ' ' -} - -func isNL(r rune) bool { - return r == '\n' || r == '\r' -} - -func (itype itemType) String() string { - switch itype { - case itemError: - return "Error" - case itemNIL: - return "NIL" - case itemEOF: - return "EOF" - case itemText: - return "Text" - case itemString: - return "String" - case itemBool: - return "Bool" - case itemInteger: - return "Integer" - case itemFloat: - return "Float" - case itemDatetime: - return "DateTime" - case itemKey: - return "Key" - case itemArrayStart: - return "ArrayStart" - case itemArrayEnd: - return "ArrayEnd" - case itemMapStart: - return "MapStart" - case itemMapEnd: - return "MapEnd" - case itemCommentStart: - return "CommentStart" - case itemVariable: - return "Variable" - case itemInclude: - return "Include" - } - panic(fmt.Sprintf("BUG: Unknown type '%s'.", itype.String())) -} - -func (item item) String() string { - return fmt.Sprintf("(%s, '%s', %d, %d)", item.typ.String(), item.val, item.line, item.pos) -} - -func escapeSpecial(c rune) string { - switch c { - case '\n': - return "\\n" - } - return string(c) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/parse.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/parse.go deleted file mode 100644 index c1f064ae..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/parse.go +++ /dev/null @@ -1,497 +0,0 @@ -// Copyright 2013-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package conf supports a configuration file format used by gnatsd. It is -// a flexible format that combines the best of traditional -// configuration formats and newer styles such as JSON and YAML. -package conf - -// The format supported is less restrictive than today's formats. -// Supports mixed Arrays [], nested Maps {}, multiple comment types (# and //) -// Also supports key value assignments using '=' or ':' or whiteSpace() -// e.g. foo = 2, foo : 2, foo 2 -// maps can be assigned with no key separator as well -// semicolons as value terminators in key/value assignments are optional -// -// see parse_test.go for more examples. - -import ( - "crypto/sha256" - "encoding/json" - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - "time" - "unicode" -) - -const _EMPTY_ = "" - -type parser struct { - mapping map[string]any - lx *lexer - - // The current scoped context, can be array or map - ctx any - - // stack of contexts, either map or array/slice stack - ctxs []any - - // Keys stack - keys []string - - // Keys stack as items - ikeys []item - - // The config file path, empty by default. - fp string - - // pedantic reports error when configuration is not correct. - pedantic bool -} - -// Parse will return a map of keys to any, although concrete types -// underly them. The values supported are string, bool, int64, float64, DateTime. -// Arrays and nested Maps are also supported. -func Parse(data string) (map[string]any, error) { - p, err := parse(data, "", false) - if err != nil { - return nil, err - } - return p.mapping, nil -} - -// ParseWithChecks is equivalent to Parse but runs in pedantic mode. -func ParseWithChecks(data string) (map[string]any, error) { - p, err := parse(data, "", true) - if err != nil { - return nil, err - } - return p.mapping, nil -} - -// ParseFile is a helper to open file, etc. and parse the contents. -func ParseFile(fp string) (map[string]any, error) { - data, err := os.ReadFile(fp) - if err != nil { - return nil, fmt.Errorf("error opening config file: %v", err) - } - - p, err := parse(string(data), fp, false) - if err != nil { - return nil, err - } - return p.mapping, nil -} - -// ParseFileWithChecks is equivalent to ParseFile but runs in pedantic mode. -func ParseFileWithChecks(fp string) (map[string]any, error) { - data, err := os.ReadFile(fp) - if err != nil { - return nil, err - } - - p, err := parse(string(data), fp, true) - if err != nil { - return nil, err - } - - return p.mapping, nil -} - -// cleanupUsedEnvVars will recursively remove all already used -// environment variables which might be in the parsed tree. -func cleanupUsedEnvVars(m map[string]any) { - for k, v := range m { - t := v.(*token) - if t.usedVariable { - delete(m, k) - continue - } - // Cleanup any other env var that is still in the map. - if tm, ok := t.value.(map[string]any); ok { - cleanupUsedEnvVars(tm) - } - } -} - -// ParseFileWithChecksDigest returns the processed config and a digest -// that represents the configuration. -func ParseFileWithChecksDigest(fp string) (map[string]any, string, error) { - data, err := os.ReadFile(fp) - if err != nil { - return nil, _EMPTY_, err - } - p, err := parse(string(data), fp, true) - if err != nil { - return nil, _EMPTY_, err - } - // Filter out any environment variables before taking the digest. - cleanupUsedEnvVars(p.mapping) - digest := sha256.New() - e := json.NewEncoder(digest) - err = e.Encode(p.mapping) - if err != nil { - return nil, _EMPTY_, err - } - return p.mapping, fmt.Sprintf("sha256:%x", digest.Sum(nil)), nil -} - -type token struct { - item item - value any - usedVariable bool - sourceFile string -} - -func (t *token) MarshalJSON() ([]byte, error) { - return json.Marshal(t.value) -} - -func (t *token) Value() any { - return t.value -} - -func (t *token) Line() int { - return t.item.line -} - -func (t *token) IsUsedVariable() bool { - return t.usedVariable -} - -func (t *token) SourceFile() string { - return t.sourceFile -} - -func (t *token) Position() int { - return t.item.pos -} - -func parse(data, fp string, pedantic bool) (p *parser, err error) { - p = &parser{ - mapping: make(map[string]any), - lx: lex(data), - ctxs: make([]any, 0, 4), - keys: make([]string, 0, 4), - ikeys: make([]item, 0, 4), - fp: filepath.Dir(fp), - pedantic: pedantic, - } - p.pushContext(p.mapping) - - var prevItem item - for { - it := p.next() - if it.typ == itemEOF { - // Here we allow the final character to be a bracket '}' - // in order to support JSON like configurations. - if prevItem.typ == itemKey && prevItem.val != mapEndString { - return nil, fmt.Errorf("config is invalid (%s:%d:%d)", fp, it.line, it.pos) - } - break - } - prevItem = it - if err := p.processItem(it, fp); err != nil { - return nil, err - } - } - return p, nil -} - -func (p *parser) next() item { - return p.lx.nextItem() -} - -func (p *parser) pushContext(ctx any) { - p.ctxs = append(p.ctxs, ctx) - p.ctx = ctx -} - -func (p *parser) popContext() any { - if len(p.ctxs) == 0 { - panic("BUG in parser, context stack empty") - } - li := len(p.ctxs) - 1 - last := p.ctxs[li] - p.ctxs = p.ctxs[0:li] - p.ctx = p.ctxs[len(p.ctxs)-1] - return last -} - -func (p *parser) pushKey(key string) { - p.keys = append(p.keys, key) -} - -func (p *parser) popKey() string { - if len(p.keys) == 0 { - panic("BUG in parser, keys stack empty") - } - li := len(p.keys) - 1 - last := p.keys[li] - p.keys = p.keys[0:li] - return last -} - -func (p *parser) pushItemKey(key item) { - p.ikeys = append(p.ikeys, key) -} - -func (p *parser) popItemKey() item { - if len(p.ikeys) == 0 { - panic("BUG in parser, item keys stack empty") - } - li := len(p.ikeys) - 1 - last := p.ikeys[li] - p.ikeys = p.ikeys[0:li] - return last -} - -func (p *parser) processItem(it item, fp string) error { - setValue := func(it item, v any) { - if p.pedantic { - p.setValue(&token{it, v, false, fp}) - } else { - p.setValue(v) - } - } - - switch it.typ { - case itemError: - return fmt.Errorf("Parse error on line %d: '%s'", it.line, it.val) - case itemKey: - // Keep track of the keys as items and strings, - // we do this in order to be able to still support - // includes without many breaking changes. - p.pushKey(it.val) - - if p.pedantic { - p.pushItemKey(it) - } - case itemMapStart: - newCtx := make(map[string]any) - p.pushContext(newCtx) - case itemMapEnd: - setValue(it, p.popContext()) - case itemString: - // FIXME(dlc) sanitize string? - setValue(it, it.val) - case itemInteger: - lastDigit := 0 - for _, r := range it.val { - if !unicode.IsDigit(r) && r != '-' { - break - } - lastDigit++ - } - numStr := it.val[:lastDigit] - num, err := strconv.ParseInt(numStr, 10, 64) - if err != nil { - if e, ok := err.(*strconv.NumError); ok && - e.Err == strconv.ErrRange { - return fmt.Errorf("integer '%s' is out of the range", it.val) - } - return fmt.Errorf("expected integer, but got '%s'", it.val) - } - // Process a suffix - suffix := strings.ToLower(strings.TrimSpace(it.val[lastDigit:])) - - switch suffix { - case "": - setValue(it, num) - case "k": - setValue(it, num*1000) - case "kb", "ki", "kib": - setValue(it, num*1024) - case "m": - setValue(it, num*1000*1000) - case "mb", "mi", "mib": - setValue(it, num*1024*1024) - case "g": - setValue(it, num*1000*1000*1000) - case "gb", "gi", "gib": - setValue(it, num*1024*1024*1024) - case "t": - setValue(it, num*1000*1000*1000*1000) - case "tb", "ti", "tib": - setValue(it, num*1024*1024*1024*1024) - case "p": - setValue(it, num*1000*1000*1000*1000*1000) - case "pb", "pi", "pib": - setValue(it, num*1024*1024*1024*1024*1024) - case "e": - setValue(it, num*1000*1000*1000*1000*1000*1000) - case "eb", "ei", "eib": - setValue(it, num*1024*1024*1024*1024*1024*1024) - } - case itemFloat: - num, err := strconv.ParseFloat(it.val, 64) - if err != nil { - if e, ok := err.(*strconv.NumError); ok && - e.Err == strconv.ErrRange { - return fmt.Errorf("float '%s' is out of the range", it.val) - } - return fmt.Errorf("expected float, but got '%s'", it.val) - } - setValue(it, num) - case itemBool: - switch strings.ToLower(it.val) { - case "true", "yes", "on": - setValue(it, true) - case "false", "no", "off": - setValue(it, false) - default: - return fmt.Errorf("expected boolean value, but got '%s'", it.val) - } - - case itemDatetime: - dt, err := time.Parse("2006-01-02T15:04:05Z", it.val) - if err != nil { - return fmt.Errorf( - "expected Zulu formatted DateTime, but got '%s'", it.val) - } - setValue(it, dt) - case itemArrayStart: - var array = make([]any, 0) - p.pushContext(array) - case itemArrayEnd: - array := p.ctx - p.popContext() - setValue(it, array) - case itemVariable: - value, found, err := p.lookupVariable(it.val) - if err != nil { - return fmt.Errorf("variable reference for '%s' on line %d could not be parsed: %s", - it.val, it.line, err) - } - if !found { - return fmt.Errorf("variable reference for '%s' on line %d can not be found", - it.val, it.line) - } - - if p.pedantic { - switch tk := value.(type) { - case *token: - // Mark the looked up variable as used, and make - // the variable reference become handled as a token. - tk.usedVariable = true - p.setValue(&token{it, tk.Value(), false, fp}) - default: - // Special case to add position context to bcrypt references. - p.setValue(&token{it, value, false, fp}) - } - } else { - p.setValue(value) - } - case itemInclude: - var ( - m map[string]any - err error - ) - if p.pedantic { - m, err = ParseFileWithChecks(filepath.Join(p.fp, it.val)) - } else { - m, err = ParseFile(filepath.Join(p.fp, it.val)) - } - if err != nil { - return fmt.Errorf("error parsing include file '%s', %v", it.val, err) - } - for k, v := range m { - p.pushKey(k) - - if p.pedantic { - switch tk := v.(type) { - case *token: - p.pushItemKey(tk.item) - } - } - p.setValue(v) - } - } - - return nil -} - -// Used to map an environment value into a temporary map to pass to secondary Parse call. -const pkey = "pk" - -// We special case raw strings here that are bcrypt'd. This allows us not to force quoting the strings -const bcryptPrefix = "2a$" - -// lookupVariable will lookup a variable reference. It will use block scoping on keys -// it has seen before, with the top level scoping being the environment variables. We -// ignore array contexts and only process the map contexts.. -// -// Returns true for ok if it finds something, similar to map. -func (p *parser) lookupVariable(varReference string) (any, bool, error) { - // Do special check to see if it is a raw bcrypt string. - if strings.HasPrefix(varReference, bcryptPrefix) { - return "$" + varReference, true, nil - } - - // Loop through contexts currently on the stack. - for i := len(p.ctxs) - 1; i >= 0; i-- { - ctx := p.ctxs[i] - // Process if it is a map context - if m, ok := ctx.(map[string]any); ok { - if v, ok := m[varReference]; ok { - return v, ok, nil - } - } - } - - // If we are here, we have exhausted our context maps and still not found anything. - // Parse from the environment. - if vStr, ok := os.LookupEnv(varReference); ok { - // Everything we get here will be a string value, so we need to process as a parser would. - if vmap, err := Parse(fmt.Sprintf("%s=%s", pkey, vStr)); err == nil { - v, ok := vmap[pkey] - return v, ok, nil - } else { - return nil, false, err - } - } - return nil, false, nil -} - -func (p *parser) setValue(val any) { - // Test to see if we are on an array or a map - - // Array processing - if ctx, ok := p.ctx.([]any); ok { - p.ctx = append(ctx, val) - p.ctxs[len(p.ctxs)-1] = p.ctx - } - - // Map processing - if ctx, ok := p.ctx.(map[string]any); ok { - key := p.popKey() - - if p.pedantic { - // Change the position to the beginning of the key - // since more useful when reporting errors. - switch v := val.(type) { - case *token: - it := p.popItemKey() - v.item.pos = it.pos - v.item.line = it.line - ctx[key] = v - } - } else { - // FIXME(dlc), make sure to error if redefining same key? - ctx[key] = val - } - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/simple.conf b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/simple.conf deleted file mode 100644 index 8f75d73a..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/conf/simple.conf +++ /dev/null @@ -1,6 +0,0 @@ -listen: 127.0.0.1:4222 - -authorization { - include 'includes/users.conf' # Pull in from file - timeout: 0.5 -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/internal/fastrand/LICENSE b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/internal/fastrand/LICENSE deleted file mode 100644 index c12aa07b..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/internal/fastrand/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2011 The LevelDB-Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/internal/fastrand/fastrand.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/internal/fastrand/fastrand.go deleted file mode 100644 index fbc58292..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/internal/fastrand/fastrand.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2020-2023 The LevelDB-Go, Pebble and NATS Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be found in -// the LICENSE file. - -package fastrand - -import _ "unsafe" // required by go:linkname - -// Uint32 returns a lock free uint32 value. -// -//go:linkname Uint32 runtime.fastrand -func Uint32() uint32 - -// Uint32n returns a lock free uint32 value in the interval [0, n). -// -//go:linkname Uint32n runtime.fastrandn -func Uint32n(n uint32) uint32 - -// Uint64 returns a lock free uint64 value. -func Uint64() uint64 { - v := uint64(Uint32()) - return v<<32 | uint64(Uint32()) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/internal/ldap/dn.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/internal/ldap/dn.go deleted file mode 100644 index 878ffbca..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/internal/ldap/dn.go +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com) -// Portions copyright (c) 2015-2016 go-ldap Authors -package ldap - -import ( - "bytes" - "crypto/x509/pkix" - "encoding/asn1" - enchex "encoding/hex" - "errors" - "fmt" - "strings" -) - -var attributeTypeNames = map[string]string{ - "2.5.4.3": "CN", - "2.5.4.5": "SERIALNUMBER", - "2.5.4.6": "C", - "2.5.4.7": "L", - "2.5.4.8": "ST", - "2.5.4.9": "STREET", - "2.5.4.10": "O", - "2.5.4.11": "OU", - "2.5.4.17": "POSTALCODE", - // FIXME: Add others. - "0.9.2342.19200300.100.1.25": "DC", -} - -// AttributeTypeAndValue represents an attributeTypeAndValue from https://tools.ietf.org/html/rfc4514 -type AttributeTypeAndValue struct { - // Type is the attribute type - Type string - // Value is the attribute value - Value string -} - -// RelativeDN represents a relativeDistinguishedName from https://tools.ietf.org/html/rfc4514 -type RelativeDN struct { - Attributes []*AttributeTypeAndValue -} - -// DN represents a distinguishedName from https://tools.ietf.org/html/rfc4514 -type DN struct { - RDNs []*RelativeDN -} - -// FromCertSubject takes a pkix.Name from a cert and returns a DN -// that uses the same set. Does not support multi value RDNs. -func FromCertSubject(subject pkix.Name) (*DN, error) { - dn := &DN{ - RDNs: make([]*RelativeDN, 0), - } - for i := len(subject.Names) - 1; i >= 0; i-- { - name := subject.Names[i] - oidString := name.Type.String() - typeName, ok := attributeTypeNames[oidString] - if !ok { - return nil, fmt.Errorf("invalid type name: %+v", name) - } - v, ok := name.Value.(string) - if !ok { - return nil, fmt.Errorf("invalid type value: %+v", v) - } - rdn := &RelativeDN{ - Attributes: []*AttributeTypeAndValue{ - { - Type: typeName, - Value: v, - }, - }, - } - dn.RDNs = append(dn.RDNs, rdn) - } - return dn, nil -} - -// FromRawCertSubject takes a raw subject from a certificate -// and uses asn1.Unmarshal to get the individual RDNs in the -// original order, including multi-value RDNs. -func FromRawCertSubject(rawSubject []byte) (*DN, error) { - dn := &DN{ - RDNs: make([]*RelativeDN, 0), - } - var rdns pkix.RDNSequence - _, err := asn1.Unmarshal(rawSubject, &rdns) - if err != nil { - return nil, err - } - - for i := len(rdns) - 1; i >= 0; i-- { - rdn := rdns[i] - if len(rdn) == 0 { - continue - } - - r := &RelativeDN{} - attrs := make([]*AttributeTypeAndValue, 0) - for j := len(rdn) - 1; j >= 0; j-- { - atv := rdn[j] - - typeName := "" - name := atv.Type.String() - typeName, ok := attributeTypeNames[name] - if !ok { - return nil, fmt.Errorf("invalid type name: %+v", name) - } - value, ok := atv.Value.(string) - if !ok { - return nil, fmt.Errorf("invalid type value: %+v", atv.Value) - } - attr := &AttributeTypeAndValue{ - Type: typeName, - Value: value, - } - attrs = append(attrs, attr) - } - r.Attributes = attrs - dn.RDNs = append(dn.RDNs, r) - } - - return dn, nil -} - -// ParseDN returns a distinguishedName or an error. -// The function respects https://tools.ietf.org/html/rfc4514 -func ParseDN(str string) (*DN, error) { - dn := new(DN) - dn.RDNs = make([]*RelativeDN, 0) - rdn := new(RelativeDN) - rdn.Attributes = make([]*AttributeTypeAndValue, 0) - buffer := bytes.Buffer{} - attribute := new(AttributeTypeAndValue) - escaping := false - - unescapedTrailingSpaces := 0 - stringFromBuffer := func() string { - s := buffer.String() - s = s[0 : len(s)-unescapedTrailingSpaces] - buffer.Reset() - unescapedTrailingSpaces = 0 - return s - } - - for i := 0; i < len(str); i++ { - char := str[i] - switch { - case escaping: - unescapedTrailingSpaces = 0 - escaping = false - switch char { - case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\': - buffer.WriteByte(char) - continue - } - // Not a special character, assume hex encoded octet - if len(str) == i+1 { - return nil, errors.New("got corrupted escaped character") - } - - dst := []byte{0} - n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2])) - if err != nil { - return nil, fmt.Errorf("failed to decode escaped character: %s", err) - } else if n != 1 { - return nil, fmt.Errorf("expected 1 byte when un-escaping, got %d", n) - } - buffer.WriteByte(dst[0]) - i++ - case char == '\\': - unescapedTrailingSpaces = 0 - escaping = true - case char == '=': - attribute.Type = stringFromBuffer() - // Special case: If the first character in the value is # the following data - // is BER encoded. Throw an error since not supported right now. - if len(str) > i+1 && str[i+1] == '#' { - return nil, errors.New("unsupported BER encoding") - } - case char == ',' || char == '+': - // We're done with this RDN or value, push it - if len(attribute.Type) == 0 { - return nil, errors.New("incomplete type, value pair") - } - attribute.Value = stringFromBuffer() - rdn.Attributes = append(rdn.Attributes, attribute) - attribute = new(AttributeTypeAndValue) - if char == ',' { - dn.RDNs = append(dn.RDNs, rdn) - rdn = new(RelativeDN) - rdn.Attributes = make([]*AttributeTypeAndValue, 0) - } - case char == ' ' && buffer.Len() == 0: - // ignore unescaped leading spaces - continue - default: - if char == ' ' { - // Track unescaped spaces in case they are trailing and we need to remove them - unescapedTrailingSpaces++ - } else { - // Reset if we see a non-space char - unescapedTrailingSpaces = 0 - } - buffer.WriteByte(char) - } - } - if buffer.Len() > 0 { - if len(attribute.Type) == 0 { - return nil, errors.New("DN ended with incomplete type, value pair") - } - attribute.Value = stringFromBuffer() - rdn.Attributes = append(rdn.Attributes, attribute) - dn.RDNs = append(dn.RDNs, rdn) - } - return dn, nil -} - -// Equal returns true if the DNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch). -// Returns true if they have the same number of relative distinguished names -// and corresponding relative distinguished names (by position) are the same. -func (d *DN) Equal(other *DN) bool { - if len(d.RDNs) != len(other.RDNs) { - return false - } - for i := range d.RDNs { - if !d.RDNs[i].Equal(other.RDNs[i]) { - return false - } - } - return true -} - -// RDNsMatch returns true if the individual RDNs of the DNs -// are the same regardless of ordering. -func (d *DN) RDNsMatch(other *DN) bool { - if len(d.RDNs) != len(other.RDNs) { - return false - } - -CheckNextRDN: - for _, irdn := range d.RDNs { - for _, ordn := range other.RDNs { - if (len(irdn.Attributes) == len(ordn.Attributes)) && - (irdn.hasAllAttributes(ordn.Attributes) && ordn.hasAllAttributes(irdn.Attributes)) { - // Found the RDN, check if next one matches. - continue CheckNextRDN - } - } - - // Could not find a matching individual RDN, auth fails. - return false - } - return true -} - -// AncestorOf returns true if the other DN consists of at least one RDN followed by all the RDNs of the current DN. -// "ou=widgets,o=acme.com" is an ancestor of "ou=sprockets,ou=widgets,o=acme.com" -// "ou=widgets,o=acme.com" is not an ancestor of "ou=sprockets,ou=widgets,o=foo.com" -// "ou=widgets,o=acme.com" is not an ancestor of "ou=widgets,o=acme.com" -func (d *DN) AncestorOf(other *DN) bool { - if len(d.RDNs) >= len(other.RDNs) { - return false - } - // Take the last `len(d.RDNs)` RDNs from the other DN to compare against - otherRDNs := other.RDNs[len(other.RDNs)-len(d.RDNs):] - for i := range d.RDNs { - if !d.RDNs[i].Equal(otherRDNs[i]) { - return false - } - } - return true -} - -// Equal returns true if the RelativeDNs are equal as defined by rfc4517 4.2.15 (distinguishedNameMatch). -// Relative distinguished names are the same if and only if they have the same number of AttributeTypeAndValues -// and each attribute of the first RDN is the same as the attribute of the second RDN with the same attribute type. -// The order of attributes is not significant. -// Case of attribute types is not significant. -func (r *RelativeDN) Equal(other *RelativeDN) bool { - if len(r.Attributes) != len(other.Attributes) { - return false - } - return r.hasAllAttributes(other.Attributes) && other.hasAllAttributes(r.Attributes) -} - -func (r *RelativeDN) hasAllAttributes(attrs []*AttributeTypeAndValue) bool { - for _, attr := range attrs { - found := false - for _, myattr := range r.Attributes { - if myattr.Equal(attr) { - found = true - break - } - } - if !found { - return false - } - } - return true -} - -// Equal returns true if the AttributeTypeAndValue is equivalent to the specified AttributeTypeAndValue -// Case of the attribute type is not significant -func (a *AttributeTypeAndValue) Equal(other *AttributeTypeAndValue) bool { - return strings.EqualFold(a.Type, other.Type) && a.Value == other.Value -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/locksordering.txt b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/locksordering.txt deleted file mode 100644 index 4a2c9e71..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/locksordering.txt +++ /dev/null @@ -1,32 +0,0 @@ -Here is the list of some established lock ordering. - -In this list, A -> B means that you can have A.Lock() then B.Lock(), not the opposite. - -jetStream -> jsAccount -> Server -> client -> Account - -jetStream -> jsAccount -> stream -> consumer - -A lock to protect jetstream account's usage has been introduced: jsAccount.usageMu. -This lock is independent and can be invoked under any other lock: jsAccount -> jsa.usageMu, stream -> jsa.usageMu, etc... - -A lock to protect the account's leafnodes list was also introduced to -allow that lock to be held and the acquire a client lock which is not -possible with the normal account lock. - -accountLeafList -> client - -AccountResolver interface has various implementations, but assume: AccountResolver -> Server - -A reloadMu lock was added to prevent newly connecting clients racing with the configuration reload. -This must be taken out as soon as a reload is about to happen before any other locks: - - reloadMu -> Server - reloadMu -> optsMu - -The "jscmMu" lock in the Account is used to serialise calls to checkJetStreamMigrate and -clearObserverState so that they cannot interleave which would leave Raft nodes in -inconsistent observer states. - - jscmMu -> Account -> jsAccount - jscmMu -> stream.clsMu - jscmMu -> RaftNode \ No newline at end of file diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/logger/log.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/logger/log.go deleted file mode 100644 index 38473fd0..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/logger/log.go +++ /dev/null @@ -1,405 +0,0 @@ -// Copyright 2012-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package logger provides logging facilities for the NATS server -package logger - -import ( - "fmt" - "log" - "os" - "path/filepath" - "strings" - "sync" - "sync/atomic" - "time" -) - -// Default file permissions for log files. -const defaultLogPerms = os.FileMode(0640) - -// Logger is the server logger -type Logger struct { - sync.Mutex - logger *log.Logger - debug bool - trace bool - infoLabel string - warnLabel string - errorLabel string - fatalLabel string - debugLabel string - traceLabel string - fl *fileLogger -} - -type LogOption interface { - isLoggerOption() -} - -// LogUTC controls whether timestamps in the log output should be UTC or local time. -type LogUTC bool - -func (l LogUTC) isLoggerOption() {} - -func logFlags(time bool, opts ...LogOption) int { - flags := 0 - if time { - flags = log.LstdFlags | log.Lmicroseconds - } - - for _, opt := range opts { - switch v := opt.(type) { - case LogUTC: - if time && bool(v) { - flags |= log.LUTC - } - } - } - - return flags -} - -// NewStdLogger creates a logger with output directed to Stderr -func NewStdLogger(time, debug, trace, colors, pid bool, opts ...LogOption) *Logger { - flags := logFlags(time, opts...) - - pre := "" - if pid { - pre = pidPrefix() - } - - l := &Logger{ - logger: log.New(os.Stderr, pre, flags), - debug: debug, - trace: trace, - } - - if colors { - setColoredLabelFormats(l) - } else { - setPlainLabelFormats(l) - } - - return l -} - -// NewFileLogger creates a logger with output directed to a file -func NewFileLogger(filename string, time, debug, trace, pid bool, opts ...LogOption) *Logger { - flags := logFlags(time, opts...) - - pre := "" - if pid { - pre = pidPrefix() - } - - fl, err := newFileLogger(filename, pre, time) - if err != nil { - log.Fatalf("error opening file: %v", err) - return nil - } - - l := &Logger{ - logger: log.New(fl, pre, flags), - debug: debug, - trace: trace, - fl: fl, - } - fl.Lock() - fl.l = l - fl.Unlock() - - setPlainLabelFormats(l) - return l -} - -type writerAndCloser interface { - Write(b []byte) (int, error) - Close() error - Name() string -} - -type fileLogger struct { - out int64 - canRotate int32 - sync.Mutex - l *Logger - f writerAndCloser - limit int64 - olimit int64 - pid string - time bool - closed bool - maxNumFiles int -} - -func newFileLogger(filename, pidPrefix string, time bool) (*fileLogger, error) { - fileflags := os.O_WRONLY | os.O_APPEND | os.O_CREATE - f, err := os.OpenFile(filename, fileflags, defaultLogPerms) - if err != nil { - return nil, err - } - stats, err := f.Stat() - if err != nil { - f.Close() - return nil, err - } - fl := &fileLogger{ - canRotate: 0, - f: f, - out: stats.Size(), - pid: pidPrefix, - time: time, - } - return fl, nil -} - -func (l *fileLogger) setLimit(limit int64) { - l.Lock() - l.olimit, l.limit = limit, limit - atomic.StoreInt32(&l.canRotate, 1) - rotateNow := l.out > l.limit - l.Unlock() - if rotateNow { - l.l.Noticef("Rotating logfile...") - } -} - -func (l *fileLogger) setMaxNumFiles(max int) { - l.Lock() - l.maxNumFiles = max - l.Unlock() -} - -func (l *fileLogger) logDirect(label, format string, v ...any) int { - var entrya = [256]byte{} - var entry = entrya[:0] - if l.pid != "" { - entry = append(entry, l.pid...) - } - if l.time { - now := time.Now() - year, month, day := now.Date() - hour, min, sec := now.Clock() - microsec := now.Nanosecond() / 1000 - entry = append(entry, fmt.Sprintf("%04d/%02d/%02d %02d:%02d:%02d.%06d ", - year, month, day, hour, min, sec, microsec)...) - } - entry = append(entry, label...) - entry = append(entry, fmt.Sprintf(format, v...)...) - entry = append(entry, '\r', '\n') - l.f.Write(entry) - return len(entry) -} - -func (l *fileLogger) logPurge(fname string) { - var backups []string - lDir := filepath.Dir(fname) - lBase := filepath.Base(fname) - entries, err := os.ReadDir(lDir) - if err != nil { - l.logDirect(l.l.errorLabel, "Unable to read directory %q for log purge (%v), will attempt next rotation", lDir, err) - return - } - for _, entry := range entries { - if entry.IsDir() || entry.Name() == lBase || !strings.HasPrefix(entry.Name(), lBase) { - continue - } - if stamp, found := strings.CutPrefix(entry.Name(), fmt.Sprintf("%s%s", lBase, ".")); found { - _, err := time.Parse("2006:01:02:15:04:05.999999999", strings.Replace(stamp, ".", ":", 5)) - if err == nil { - backups = append(backups, entry.Name()) - } - } - } - currBackups := len(backups) - maxBackups := l.maxNumFiles - 1 - if currBackups > maxBackups { - // backups sorted oldest to latest based on timestamped lexical filename (ReadDir) - for i := 0; i < currBackups-maxBackups; i++ { - if err := os.Remove(filepath.Join(lDir, string(os.PathSeparator), backups[i])); err != nil { - l.logDirect(l.l.errorLabel, "Unable to remove backup log file %q (%v), will attempt next rotation", backups[i], err) - // Bail fast, we'll try again next rotation - return - } - l.logDirect(l.l.infoLabel, "Purged log file %q", backups[i]) - } - } -} - -func (l *fileLogger) Write(b []byte) (int, error) { - if atomic.LoadInt32(&l.canRotate) == 0 { - n, err := l.f.Write(b) - if err == nil { - atomic.AddInt64(&l.out, int64(n)) - } - return n, err - } - l.Lock() - n, err := l.f.Write(b) - if err == nil { - l.out += int64(n) - if l.out > l.limit { - if err := l.f.Close(); err != nil { - l.limit *= 2 - l.logDirect(l.l.errorLabel, "Unable to close logfile for rotation (%v), will attempt next rotation at size %v", err, l.limit) - l.Unlock() - return n, err - } - fname := l.f.Name() - now := time.Now() - bak := fmt.Sprintf("%s.%04d.%02d.%02d.%02d.%02d.%02d.%09d", fname, - now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), - now.Second(), now.Nanosecond()) - os.Rename(fname, bak) - fileflags := os.O_WRONLY | os.O_APPEND | os.O_CREATE - f, err := os.OpenFile(fname, fileflags, defaultLogPerms) - if err != nil { - l.Unlock() - panic(fmt.Sprintf("Unable to re-open the logfile %q after rotation: %v", fname, err)) - } - l.f = f - n := l.logDirect(l.l.infoLabel, "Rotated log, backup saved as %q", bak) - l.out = int64(n) - l.limit = l.olimit - if l.maxNumFiles > 0 { - l.logPurge(fname) - } - } - } - l.Unlock() - return n, err -} - -func (l *fileLogger) close() error { - l.Lock() - if l.closed { - l.Unlock() - return nil - } - l.closed = true - l.Unlock() - return l.f.Close() -} - -// SetSizeLimit sets the size of a logfile after which a backup -// is created with the file name + "year.month.day.hour.min.sec.nanosec" -// and the current log is truncated. -func (l *Logger) SetSizeLimit(limit int64) error { - l.Lock() - if l.fl == nil { - l.Unlock() - return fmt.Errorf("can set log size limit only for file logger") - } - fl := l.fl - l.Unlock() - fl.setLimit(limit) - return nil -} - -// SetMaxNumFiles sets the number of archived log files that will be retained -func (l *Logger) SetMaxNumFiles(max int) error { - l.Lock() - if l.fl == nil { - l.Unlock() - return fmt.Errorf("can set log max number of files only for file logger") - } - fl := l.fl - l.Unlock() - fl.setMaxNumFiles(max) - return nil -} - -// NewTestLogger creates a logger with output directed to Stderr with a prefix. -// Useful for tracing in tests when multiple servers are in the same pid -func NewTestLogger(prefix string, time bool) *Logger { - flags := 0 - if time { - flags = log.LstdFlags | log.Lmicroseconds - } - l := &Logger{ - logger: log.New(os.Stderr, prefix, flags), - debug: true, - trace: true, - } - setColoredLabelFormats(l) - return l -} - -// Close implements the io.Closer interface to clean up -// resources in the server's logger implementation. -// Caller must ensure threadsafety. -func (l *Logger) Close() error { - if l.fl != nil { - return l.fl.close() - } - return nil -} - -// Generate the pid prefix string -func pidPrefix() string { - return fmt.Sprintf("[%d] ", os.Getpid()) -} - -func setPlainLabelFormats(l *Logger) { - l.infoLabel = "[INF] " - l.debugLabel = "[DBG] " - l.warnLabel = "[WRN] " - l.errorLabel = "[ERR] " - l.fatalLabel = "[FTL] " - l.traceLabel = "[TRC] " -} - -func setColoredLabelFormats(l *Logger) { - colorFormat := "[\x1b[%sm%s\x1b[0m] " - l.infoLabel = fmt.Sprintf(colorFormat, "32", "INF") - l.debugLabel = fmt.Sprintf(colorFormat, "36", "DBG") - l.warnLabel = fmt.Sprintf(colorFormat, "0;93", "WRN") - l.errorLabel = fmt.Sprintf(colorFormat, "31", "ERR") - l.fatalLabel = fmt.Sprintf(colorFormat, "31", "FTL") - l.traceLabel = fmt.Sprintf(colorFormat, "33", "TRC") -} - -// Noticef logs a notice statement -func (l *Logger) Noticef(format string, v ...any) { - l.logger.Printf(l.infoLabel+format, v...) -} - -// Warnf logs a notice statement -func (l *Logger) Warnf(format string, v ...any) { - l.logger.Printf(l.warnLabel+format, v...) -} - -// Errorf logs an error statement -func (l *Logger) Errorf(format string, v ...any) { - l.logger.Printf(l.errorLabel+format, v...) -} - -// Fatalf logs a fatal error -func (l *Logger) Fatalf(format string, v ...any) { - l.logger.Fatalf(l.fatalLabel+format, v...) -} - -// Debugf logs a debug statement -func (l *Logger) Debugf(format string, v ...any) { - if l.debug { - l.logger.Printf(l.debugLabel+format, v...) - } -} - -// Tracef logs a trace statement -func (l *Logger) Tracef(format string, v ...any) { - if l.trace { - l.logger.Printf(l.traceLabel+format, v...) - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/logger/syslog.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/logger/syslog.go deleted file mode 100644 index 211dd97c..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/logger/syslog.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2012-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build !windows - -package logger - -import ( - "fmt" - "log" - "log/syslog" - "net/url" - "os" - "strings" -) - -// SysLogger provides a system logger facility -type SysLogger struct { - writer *syslog.Writer - debug bool - trace bool -} - -// SetSyslogName sets the name to use for the syslog. -// Currently used only on Windows. -func SetSyslogName(name string) {} - -// GetSysLoggerTag generates the tag name for use in syslog statements. If -// the executable is linked, the name of the link will be used as the tag, -// otherwise, the name of the executable is used. "nats-server" is the default -// for the NATS server. -func GetSysLoggerTag() string { - procName := os.Args[0] - if strings.ContainsRune(procName, os.PathSeparator) { - parts := strings.FieldsFunc(procName, func(c rune) bool { - return c == os.PathSeparator - }) - procName = parts[len(parts)-1] - } - return procName -} - -// NewSysLogger creates a new system logger -func NewSysLogger(debug, trace bool) *SysLogger { - w, err := syslog.New(syslog.LOG_DAEMON|syslog.LOG_NOTICE, GetSysLoggerTag()) - if err != nil { - log.Fatalf("error connecting to syslog: %q", err.Error()) - } - - return &SysLogger{ - writer: w, - debug: debug, - trace: trace, - } -} - -// NewRemoteSysLogger creates a new remote system logger -func NewRemoteSysLogger(fqn string, debug, trace bool) *SysLogger { - network, addr := getNetworkAndAddr(fqn) - w, err := syslog.Dial(network, addr, syslog.LOG_DEBUG, GetSysLoggerTag()) - if err != nil { - log.Fatalf("error connecting to syslog: %q", err.Error()) - } - - return &SysLogger{ - writer: w, - debug: debug, - trace: trace, - } -} - -func getNetworkAndAddr(fqn string) (network, addr string) { - u, err := url.Parse(fqn) - if err != nil { - log.Fatal(err) - } - - network = u.Scheme - if network == "udp" || network == "tcp" { - addr = u.Host - } else if network == "unix" { - addr = u.Path - } else { - log.Fatalf("error invalid network type: %q", u.Scheme) - } - - return -} - -// Noticef logs a notice statement -func (l *SysLogger) Noticef(format string, v ...any) { - l.writer.Notice(fmt.Sprintf(format, v...)) -} - -// Warnf logs a warning statement -func (l *SysLogger) Warnf(format string, v ...any) { - l.writer.Warning(fmt.Sprintf(format, v...)) -} - -// Fatalf logs a fatal error -func (l *SysLogger) Fatalf(format string, v ...any) { - l.writer.Crit(fmt.Sprintf(format, v...)) -} - -// Errorf logs an error statement -func (l *SysLogger) Errorf(format string, v ...any) { - l.writer.Err(fmt.Sprintf(format, v...)) -} - -// Debugf logs a debug statement -func (l *SysLogger) Debugf(format string, v ...any) { - if l.debug { - l.writer.Debug(fmt.Sprintf(format, v...)) - } -} - -// Tracef logs a trace statement -func (l *SysLogger) Tracef(format string, v ...any) { - if l.trace { - l.writer.Notice(fmt.Sprintf(format, v...)) - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/logger/syslog_windows.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/logger/syslog_windows.go deleted file mode 100644 index c341a5d9..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/logger/syslog_windows.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2012-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package logger logs to the windows event log -package logger - -import ( - "fmt" - "os" - "strings" - - "golang.org/x/sys/windows/svc/eventlog" -) - -var natsEventSource = "NATS-Server" - -// SetSyslogName sets the name to use for the system log event source -func SetSyslogName(name string) { - natsEventSource = name -} - -// SysLogger logs to the windows event logger -type SysLogger struct { - writer *eventlog.Log - debug bool - trace bool -} - -// NewSysLogger creates a log using the windows event logger -func NewSysLogger(debug, trace bool) *SysLogger { - if err := eventlog.InstallAsEventCreate(natsEventSource, eventlog.Info|eventlog.Error|eventlog.Warning); err != nil { - if !strings.Contains(err.Error(), "registry key already exists") { - panic(fmt.Sprintf("could not access event log: %v", err)) - } - } - - w, err := eventlog.Open(natsEventSource) - if err != nil { - panic(fmt.Sprintf("could not open event log: %v", err)) - } - - return &SysLogger{ - writer: w, - debug: debug, - trace: trace, - } -} - -// NewRemoteSysLogger creates a remote event logger -func NewRemoteSysLogger(fqn string, debug, trace bool) *SysLogger { - w, err := eventlog.OpenRemote(fqn, natsEventSource) - if err != nil { - panic(fmt.Sprintf("could not open event log: %v", err)) - } - - return &SysLogger{ - writer: w, - debug: debug, - trace: trace, - } -} - -func formatMsg(tag, format string, v ...any) string { - orig := fmt.Sprintf(format, v...) - return fmt.Sprintf("pid[%d][%s]: %s", os.Getpid(), tag, orig) -} - -// Noticef logs a notice statement -func (l *SysLogger) Noticef(format string, v ...any) { - l.writer.Info(1, formatMsg("NOTICE", format, v...)) -} - -// Warnf logs a warning statement -func (l *SysLogger) Warnf(format string, v ...any) { - l.writer.Info(1, formatMsg("WARN", format, v...)) -} - -// Fatalf logs a fatal error -func (l *SysLogger) Fatalf(format string, v ...any) { - msg := formatMsg("FATAL", format, v...) - l.writer.Error(5, msg) - panic(msg) -} - -// Errorf logs an error statement -func (l *SysLogger) Errorf(format string, v ...any) { - l.writer.Error(2, formatMsg("ERROR", format, v...)) -} - -// Debugf logs a debug statement -func (l *SysLogger) Debugf(format string, v ...any) { - if l.debug { - l.writer.Info(3, formatMsg("DEBUG", format, v...)) - } -} - -// Tracef logs a trace statement -func (l *SysLogger) Tracef(format string, v ...any) { - if l.trace { - l.writer.Info(4, formatMsg("TRACE", format, v...)) - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/main.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/main.go deleted file mode 100644 index a85eab97..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/main.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2012-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -//go:generate go run server/errors_gen.go - -import ( - "flag" - "fmt" - "os" - - "github.com/nats-io/nats-server/v2/server" - "go.uber.org/automaxprocs/maxprocs" -) - -var usageStr = ` -Usage: nats-server [options] - -Server Options: - -a, --addr, --net Bind to host address (default: 0.0.0.0) - -p, --port Use port for clients (default: 4222) - -n, --name - --server_name Server name (default: auto) - -P, --pid File to store PID - -m, --http_port Use port for http monitoring - -ms,--https_port Use port for https monitoring - -c, --config Configuration file - -t Test configuration and exit - -sl,--signal [=] Send signal to nats-server process (ldm, stop, quit, term, reopen, reload) - can be either a PID (e.g. 1) or the path to a PID file (e.g. /var/run/nats-server.pid) - --client_advertise Client URL to advertise to other servers - --ports_file_dir Creates a ports file in the specified directory (_.ports). - -Logging Options: - -l, --log File to redirect log output - -T, --logtime Timestamp log entries (default: true) - -s, --syslog Log to syslog or windows event log - -r, --remote_syslog Syslog server addr (udp://localhost:514) - -D, --debug Enable debugging output - -V, --trace Trace the raw protocol - -VV Verbose trace (traces system account as well) - -DV Debug and trace - -DVV Debug and verbose trace (traces system account as well) - --log_size_limit Logfile size limit (default: auto) - --max_traced_msg_len Maximum printable length for traced messages (default: unlimited) - -JetStream Options: - -js, --jetstream Enable JetStream functionality - -sd, --store_dir Set the storage directory - -Authorization Options: - --user User required for connections - --pass Password required for connections - --auth Authorization token required for connections - -TLS Options: - --tls Enable TLS, do not verify clients (default: false) - --tlscert Server certificate file - --tlskey Private key for server certificate - --tlsverify Enable TLS, verify client certificates - --tlscacert Client certificate CA for verification - -Cluster Options: - --routes Routes to solicit and connect - --cluster Cluster URL for solicited routes - --cluster_name Cluster Name, if not set one will be dynamically generated - --no_advertise Do not advertise known cluster information to clients - --cluster_advertise Cluster URL to advertise to other servers - --connect_retries For implicit routes, number of connect retries - --cluster_listen Cluster url from which members can solicit routes - -Profiling Options: - --profile Profiling HTTP port - -Common Options: - -h, --help Show this message - -v, --version Show version - --help_tls TLS help -` - -// usage will print out the flag options for the server. -func usage() { - fmt.Printf("%s\n", usageStr) - os.Exit(0) -} - -func main() { - exe := "nats-server" - - // Create a FlagSet and sets the usage - fs := flag.NewFlagSet(exe, flag.ExitOnError) - fs.Usage = usage - - // Configure the options from the flags/config file - opts, err := server.ConfigureOptions(fs, os.Args[1:], - server.PrintServerAndExit, - fs.Usage, - server.PrintTLSHelpAndDie) - if err != nil { - server.PrintAndDie(fmt.Sprintf("%s: %s", exe, err)) - } else if opts.CheckConfig { - fmt.Fprintf(os.Stderr, "%s: configuration file %s is valid (%s)\n", exe, opts.ConfigFile, opts.ConfigDigest()) - os.Exit(0) - } - - // Create the server with appropriate options. - s, err := server.NewServer(opts) - if err != nil { - server.PrintAndDie(fmt.Sprintf("%s: %s", exe, err)) - } - - // Configure the logger based on the flags. - s.ConfigureLogger() - - // Start things up. Block here until done. - if err := server.Run(s); err != nil { - server.PrintAndDie(err.Error()) - } - - // Adjust MAXPROCS if running under linux/cgroups quotas. - undo, err := maxprocs.Set(maxprocs.Logger(s.Debugf)) - if err != nil { - s.Warnf("Failed to set GOMAXPROCS: %v", err) - } else { - defer undo() - } - - s.WaitForShutdown() -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/README-MQTT.md b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/README-MQTT.md deleted file mode 100644 index ddab83a1..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/README-MQTT.md +++ /dev/null @@ -1,595 +0,0 @@ -**MQTT Implementation Overview** - -Revision 1.1 - -Authors: Ivan Kozlovic, Lev Brouk - -NATS Server currently supports most of MQTT 3.1.1. This document describes how -it is implementated. - -It is strongly recommended to review the [MQTT v3.1.1 -specifications](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html) -and get a detailed understanding before proceeding with this document. - -# Contents - -1. [Concepts](#1-concepts) - - [Server, client](#server-client) - - [Connection, client ID, session](#connection-client-id-session) - - [Packets, messages, and subscriptions](#packets-messages-and-subscriptions) - - [Quality of Service (QoS), publish identifier (PI)](#quality-of-service-qos-publish-identifier-pi) - - [Retained message](#retained-message) - - [Will message](#will-message) -2. [Use of JetStream](#2-use-of-jetstream) - - [JetStream API](#jetstream-api) - - [Streams](#streams) - - [Consumers and Internal NATS Subscriptions](#consumers-and-internal-nats-subscriptions) -3. [Lifecycles](#3-lifecycles) - - [Connection, Session](#connection-session) - - [Subscription](#subscription) - - [Message](#message) - - [Retained messages](#retained-messages) -4. [Implementation Notes](#4-implementation-notes) - - [Hooking into NATS I/O](#hooking-into-nats-io) - - [Session Management](#session-management) - - [Processing QoS acks: PUBACK, PUBREC, PUBCOMP](#processing-qos-acks-puback-pubrec-pubcomp) - - [Subject Wildcards](#subject-wildcards) -5. [Known issues](#5-known-issues) - -# 1. Concepts - -## Server, client - -In the MQTT specification there are concepts of **Client** and **Server**, used -somewhat interchangeably with those of **Sender** and **Receiver**. A **Server** -acts as a **Receiver** when it gets `PUBLISH` messages from a **Sender** -**Client**, and acts as a **Sender** when it delivers them to subscribed -**Clients**. - -In the NATS server implementation there are also concepts (types) `server` and -`client`. `client` is an internal representation of a (connected) client and -runs its own read and write loops. Both of these have an `mqtt` field that if -set makes them behave as MQTT-compliant. - -The code and comments may sometimes be confusing as they refer to `server` and -`client` sometimes ambiguously between MQTT and NATS. - -## Connection, client ID, session - -When an MQTT client connects to a server, it must send a `CONNECT` packet to -create an **MQTT Connection**. The packet must include a **Client Identifier**. -The server will then create or load a previously saved **Session** for the (hash -of) the client ID. - -## Packets, messages, and subscriptions - -The low level unit of transmission in MQTT is a **Packet**. Examples of packets -are: `CONNECT`, `SUBSCRIBE`, `SUBACK`, `PUBLISH`, `PUBCOMP`, etc. - -An **MQTT Message** starts with a `PUBLISH` packet that a client sends to the -server. It is then matched against the current **MQTT Subscriptions** and is -delivered to them as appropriate. During the message delivery the server acts as -an MQTT client, and the receiver acts as an MQTT server. - -Internally we use **NATS Messages** and **NATS Subscriptions** to facilitate -message delivery. This may be somewhat confusing as the code refers to `msg` and -`sub`. What may be even more confusing is that some MQTT packets (specifically, -`PUBREL`) are represented as NATS messages, and that the original MQTT packet -"metadata" may be encoded as NATS message headers. - -## Quality of Service (QoS), publish identifier (PI) - -MQTT specifies 3 levels of quality of service (**QoS**): - -- `0` for at most once. A single delivery attempt. -- `1` for at least once. Will try to redeliver until acknowledged by the - receiver. -- `2` for exactly once. See the [SPEC REF] for the acknowledgement flow. - -QoS 1 and 2 messages need to be identified with publish identifiers (**PI**s). A -PI is a 16-bit integer that must uniquely identify a message for the duration of -the required exchange of acknowledgment packets. - -Note that the QoS applies separately to the transmission of a message from a -sender client to the server, and from the server to the receiving client. There -is no protocol-level acknowledgements between the receiver and the original -sender. The sender passes the ownership of messages to the server, and the -server then delivers them at maximum possible QoS to the receivers -(subscribers). The PIs for in-flight outgoing messages are issued and stored per -session. - -## Retained message - -A **Retained Message** is not part of any MQTT session and is not removed when the -session that produced it goes away. Instead, the server needs to persist a -_single_ retained message per topic. When a subscription is started, the server -needs to send the “matching” retained messages, that is, messages that would -have been delivered to the new subscription should that subscription had been -running prior to the publication of this message. - -Retained messages are removed when the server receives a retained message with -an empty body. Still, this retained message that serves as a “delete” of a -retained message will be processed as a normal published message. - -Retained messages can have QoS. - -## Will message - -The `CONNECT` packet can contain information about a **Will Message** that needs to -be sent to any client subscribing on the Will topic/subject in the event that -the client is disconnected implicitly, that is, not as a result as the client -sending the `DISCONNECT` packet. - -Will messages can have the retain flag and QoS. - -# 2. Use of JetStream - -The MQTT implementation relies heavily on JetStream. We use it to: - -- Persist (and restore) the [Session](#connection-client-id-session) state. -- Store and retrieve [Retained messages](#retained-message). -- Persist incoming [QoS 1 and - 2](#quality-of-service-qos-publish-identifier-pi) messages, and - re-deliver if needed. -- Store and de-duplicate incoming [QoS - 2](#quality-of-service-qos-publish-identifier-pi) messages. -- Persist and re-deliver outgoing [QoS - 2](#quality-of-service-qos-publish-identifier-pi) `PUBREL` packets. - -Here is the overview of how we set up and use JetStream **streams**, -**consumers**, and **internal NATS subscriptions**. - -## JetStream API - -All interactions with JetStream are performed via `mqttJSA` that sends NATS -requests to JetStream. Most are processed synchronously and await a response, -some (e.g. `jsa.sendAck()`) are sent asynchronously. JetStream API is usually -referred to as `jsa` in the code. No special locking is required to use `jsa`, -however the asynchronous use of JetStream may create race conditions with -delivery callbacks. - -## Streams - -We create the following streams unless they already exist. Failing to ensure the -streams would prevent the client from connecting. - -Each stream is created with a replica value that is determined by the size of -the cluster but limited to 3. It can also be overwritten by the stream_replicas -option in the MQTT configuration block. - -The streams are created the first time an Account Session Manager is initialized -and are used by all sessions in it. Note that to avoid race conditions, some -subscriptions are created first. The streams are never deleted. See -`mqttCreateAccountSessionManager()` for details. - -1. `$MQTT_sess` stores persisted **Session** records. It filters on - `"$MQTT.sess.>` subject and has a “limits” policy with `MaxMsgsPer` setting - of 1. -2. `$MQTT_msgs` is used for **QoS 1 and 2 message delivery**. - It filters on `$MQTT.msgs.>` subject and has an “interest” policy. -3. `$MQTT_rmsgs` stores **Retained Messages**. They are all - stored (and filtered) on a single subject `$MQTT.rmsg`. This stream has a - limits policy. -4. `$MQTT_qos2in` stores and deduplicates **Incoming QoS 2 Messages**. It - filters on `$MQTT.qos2.in.>` and has a "limits" policy with `MaxMsgsPer` of - 1. -5. `$MQTT_out` stores **Outgoing QoS 2** `PUBREL` packets. It filters on - `$MQTT.out.>` and has a "interest" retention policy. - -## Consumers and Internal NATS Subscriptions - -### Account Scope - -- A durable consumer for [Retained Messages](#retained-message) - - `$MQTT_rmsgs_` -- A subscription to handle all [jsa](#jetstream-api) replies for the account. -- A subscription to replies to "session persist" requests, so that we can detect - the use of a session with the same client ID anywhere in the cluster. -- 2 subscriptions to support [retained messages](#retained-message): - `$MQTT.sub.` for the messages themselves, and one to receive replies to - "delete retained message" JS API (on the JS reply subject var). - -### Session Scope - -When a new QoS 2 MQTT subscription is detected in a session, we ensure that -there is a durable consumer for [QoS -2](#quality-of-service-qos-publish-identifier-pi) `PUBREL`s out for delivery - -`$MQTT_PUBREL_` - -### Subscription Scope - -For all MQTT subscriptions, regardless of their QoS, we create internal NATS subscriptions to - -- `subject` (directly encoded from `topic`). This subscription is used to - deliver QoS 0 messages, and messages originating from NATS. -- if needed, `subject fwc` complements `subject` for topics like `topic.#` to - include `topic` itself, see [top-level wildcards](#subject-wildcards) - -For QoS 1 or 2 MQTT subscriptions we ensure: - -- A durable consumer for messages out for delivery - `_` -- An internal subscription to `$MQTT.sub.` to deliver the messages to the - receiving client. - -### (Old) Notes - -As indicated before, for a QoS1 or QoS2 subscription, the server will create a -JetStream consumer with the appropriate subject filter. If the subscription -already existed, then only the NATS subscription is created for the JetStream -consumer’s delivery subject. - -Note that JS consumers can be created with an “Replicas” override, which from -recent discussion is problematic with “Interest” policy streams, which -“$MQTT_msgs” is. - -We do handle situations where a subscription on the same subject filter is sent -with a different QoS as per MQTT specifications. If the existing was on QoS 1 or -2, and the “new” is for QoS 0, then we delete the existing JS consumer. - -Subscriptions that are QoS 0 have a NATS subscription with the callback function -being `mqttDeliverMsgCbQos0()`; while QoS 1 and 2 have a NATS subscription with -callback `mqttDeliverMsgCbQos12()`. Both those functions have comments that -describe the reason for their existence and what they are doing. For instance -the `mqttDeliverMsgCbQos0()` callback will reject any producing client that is -of type JETSTREAM, so that it handles only non JetStream (QoS 1 and 2) messages. - -Both these functions end-up calling mqttDeliver() which will first enqueue the -possible retained messages buffer before delivering any new message. The message -itself being delivered is serialized in MQTT format and enqueued to the client’s -outbound buffer and call to addToPCD is made so that it is flushed out of the -readloop. - -# 3. Lifecycles - -## Connection, Session - -An MQTT connection is created when a listening MQTT server receives a `CONNECT` -packet. See `mqttProcessConnect()`. A connection is associated with a session. -Steps: - -1. Ensure that we have an `AccountSessionManager` so we can have an - `mqttSession`. Lazily initialize JetStream streams, and internal consumers - and subscriptions. See `getOrCreateMQTTAccountSessionManager()`. -2. Find and disconnect any previous session/client for the same ID. See - `mqttProcessConnect()`. -3. Ensure we have an `mqttSession` - create a new or load a previously persisted - one. If the clean flag is set in `CONNECT`, clean the session. see - `mqttSession.clear()` -4. Initialize session's subscriptions, if any. -5. Always send back a `CONNACK` packet. If there were errors in previous steps, - include the error. - -An MQTT connection can be closed for a number of reasons, including receiving a -`DISCONNECT` from the client, explicit internal errors processing MQTT packets, -or the server receiving another `CONNECT` packet with the same client ID. See -`mqttHandleClosedClient()` and `mqttHandleWill()`. Steps: - -1. Send out the Will Message if applicable (if not caused by a `DISCONNECT` packet) -2. Delete the JetStream consumers for to QoS 1 and 2 packet delivery through - JS API calls (if "clean" session flag is set) -3. Delete the session record from the “$MQTT_sess” stream, based on recorded - stream sequence. (if "clean" session flag is set) -4. Close the client connection. - -On an explicit disconnect, that is, the client sends the DISCONNECT packet, the -server will NOT send the Will, as per specifications. - -For sessions that had the “clean” flag, the JS consumers corresponding to QoS 1 -subscriptions are deleted through JS API calls, the session record is then -deleted (based on recorded stream sequence) from the “$MQTT_sess” stream. - -Finally, the client connection is closed - -Sessions are persisted on disconnect, and on subscriptions changes. - -## Subscription - -Receiving an MQTT `SUBSCRIBE` packet creates new subscriptions, or updates -existing subscriptions in a session. Each `SUBSCRIBE` packet may contain several -specific subscriptions (`topic` + QoS in each). We always respond with a -`SUBACK`, which may indicate which subscriptions errored out. - -For each subscription in the packet, we: - -1. Ignore it if `topic` starts with `$MQTT.sub.`. -2. Set up QoS 0 message delivery - an internal NATS subscription on `topic`. -3. Replay any retained messages for `topic`, once as QoS 0. -4. If we already have a subscription on `topic`, update its QoS -5. If this is a QoS 2 subscription in the session, ensure we have the [PUBREL - consumer](#session-scope) for the session. -6. If this is a QoS 1 or 2 subscription, ensure we have the [Message - consumer](#subscription-scope) for this subscription (or delete one if it - exists and this is now a QoS 0 sub). -7. Add an extra subscription for the [top-level wildcard](#subject-wildcards) case. -8. Update the session, persist it if changed. - -When a session is restored (no clean flag), we go through the same steps to -re-subscribe to its stored subscription, except step #8 which would have been -redundant. - -When we get an `UNSUBSCRIBE` packet, it can contain multiple subscriptions to -unsubscribe. The parsing will generate a slice of mqttFilter objects that -contain the “filter” (the topic with possibly wildcard of the subscription) and -the QoS value. The server goes through the list and deletes the JS consumer (if -QoS 1 or 2) and unsubscribes the NATS subscription for the delivery subject (if -it was a QoS 1 or 2) or on the actual topic/subject. In case of the “#” -wildcard, the server will handle the “level up” subscriptions that NATS had to -create. - -Again, we update the session and persist it as needed in the `$MQTT_sess` -stream. - -## Message - -1. Detect an incoming PUBLISH packet, parse and check the message QoS. Fill out - the session's `mqttPublish` struct that contains information about the - published message. (see `mqttParse()`, `mqttParsePub()`) -2. Process the message according to its QoS (see `mqttProcessPub()`) - - - QoS 0: - - Initiate message delivery - - QoS 1: - - Initiate message delivery - - Send back a `PUBACK` - - QoS 2: - - Store the message in `$MQTT_qos2in` stream, using a PI-specific subject. - Since `MaxMsgsPer` is set to 1, we will ignore duplicates on the PI. - - Send back a `PUBREC` - - "Wait" for a `PUBREL`, then initiate message delivery - - Remove the previously stored QoS2 message - - Send back a `PUBCOMP` - -3. Initiate message delivery (see `mqttInitiateMsgDelivery()`) - - - Convert the MQTT `topic` into a NATS `subject` using - `mqttTopicToNATSPubSubject()` function. If there is a known subject - mapping, then we select the new subject using `selectMappedSubject()` - function and then convert back this subject into an MQTT topic using - `natsSubjectToMQTTTopic()` function. - - Re-serialize the `PUBLISH` packet received as a NATS message. Use NATS - headers for the metadata, and the deliverable MQTT `PUBLISH` packet as the - contents. - - Publish the messages as `subject` (and `subject fwc` if applicable, see - [subject wildcards](#subject-wildcards)). Use the "standard" NATS - `c.processInboundClientMsg()` to do that. `processInboundClientMsg()` will - distribute the message to any NATS subscriptions (including routes, - gateways, leafnodes) and the relevant MQTT subscriptions. - - Check for retained messages, process as needed. See - `c.processInboundClientMsg()` calling `c.mqttHandlePubRetain()` For MQTT - clients. - - If the message QoS is 1 or 2, store it in `$MQTT_msgs` stream as - `$MQTT.msgs.` for "at least once" delivery with retries. - -4. Let NATS and JetStream deliver to the internal subscriptions, and to the - receiving clients. See `mqttDeliverMsgCb...()` - - - The NATS message posted to `subject` (and `subject fwc`) will be delivered - to each relevant internal subscription by calling `mqttDeliverMsgCbQoS0()`. - The function has access to both the publishing and the receiving clients. - - - Ignore all irrelevant invocations. Specifically, do nothing if the - message needs to be delivered with a higher QoS - that will be handled by - the other, `...QoS12` callback. Note that if the original message was - publuished with a QoS 1 or 2, but the subscription has its maximum QoS - set to 0, the message will be delivered by this callback. - - Ignore "reserved" subscriptions, as per MQTT spec. - - Decode delivery `topic` from the NATS `subject`. - - Write (enqueue) outgoing `PUBLISH` packet. - - **DONE for QoS 0** - - - The NATS message posted to JetStream as `$MQTT.msgs.subject` will be - consumed by subscription-specific consumers. Note that MQTT subscriptions - with max QoS 0 do not have JetStream consumers. They are handled by the - QoS0 callback. - - The consumers will deliver it to the `$MQTT.sub.` - subject for their respective NATS subscriptions by calling - `mqttDeliverMsgCbQoS12()`. This callback too has access to both the - publishing and the receiving clients. - - - Ignore "reserved" subscriptions, as per MQTT spec. - - See if this is a re-delivery from JetStream by checking `sess.cpending` - for the JS reply subject. If so, use the existing PI and treat this as a - duplicate redelivery. - - Otherwise, assign the message a new PI (see `trackPublish()` and - `bumpPI()`) and store it in `sess.cpending` and `sess.pendingPublish`, - along with the JS reply subject that can be used to remove this pending - message from the consumer once it's delivered to the receipient. - - Decode delivery `topic` from the NATS `subject`. - - Write (enqueue) outgoing `PUBLISH` packet. - -5. QoS 1: "Wait" for a `PUBACK`. See `mqttProcessPubAck()`. - - - When received, remove the PI from the tracking maps, send an ACK to - consumer to remove the message. - - **DONE for QoS 1** - -6. QoS 2: "Wait" for a `PUBREC`. When received, we need to do all the same - things as in the QoS 1 `PUBACK` case, but we need to send out a `PUBREL`, and - continue using the same PI until the delivery flow is complete and we get - back a `PUBCOMP`. For that, we add the PI to `sess.pendingPubRel`, and to - `sess.cpending` with the PubRel consumer durable name. - - We also compose and store a headers-only NATS message signifying a `PUBREL` - out for delivery, and store it in the `$MQTT_qos2out` stream, as - `$MQTT.qos2.out.`. - -7. QoS 2: Deliver `PUBREL`. The PubRel session-specific consumer will publish to - internal subscription on `$MQTT.qos2.delivery`, calling - `mqttDeliverPubRelCb()`. We store the ACK reply subject in `cpending` to - remove the JS message on `PUBCOMP`, compose and send out a `PUBREL` packet. - -8. QoS 2: "Wait" for a `PUBCOMP`. See `mqttProcessPubComp()`. - - When received, remove the PI from the tracking maps, send an ACK to - consumer to remove the `PUBREL` message. - - **DONE for QoS 2** - -## Retained messages - -When we process an inbound `PUBLISH` and submit it to -`processInboundClientMsg()` function, for MQTT clients it will invoke -`mqttHandlePubRetain()` which checks if the published message is “retained” or -not. - -If it is, then we construct a record representing the retained message and store -it in the `$MQTT_rmsg` stream, under the single `$MQTT.rmsg` subject. The stored -record (in JSON) contains information about the subject, topic, MQTT flags, user -that produced this message and the message content itself. It is stored and the -stream sequence is remembered in the memory structure that contains retained -messages. - -Note that when creating an account session manager, the retained messages stream -is read from scratch to load all the messages through the use of a JS consumer. -The associated subscription will process the recovered retained messages or any -new that comes from the network. - -A retained message is added to a map and a subscription is created and inserted -into a sublist that will be used to perform a ReverseMatch() when a subscription -is started and we want to find all retained messages that the subscription would -have received if it had been running prior to the message being published. - -If a retained message on topic “foo” already exists, then the server has to -delete the old message at the stream sequence we saved when storing it. - -This could have been done with having retained messages stored under -`$MQTT.rmsg.` as opposed to all under a single subject, and make use of -the `MaxMsgsPer` field set to 1. The `MaxMsgsPer` option was introduced well into -the availability of MQTT and changes to the sessions was made in [PR -#2501](https://github.com/nats-io/nats-server/pull/2501), with a conversion of -existing streams such as `$MQTT*sess*` into a single stream with unique -subjects, but the changes were not made to the retained messages stream. - -There are also subscriptions for the handling of retained messages which are -messages that are asked by the publisher to be retained by the MQTT server to be -delivered to matching subscriptions when they start. There is a single message -per topic. Retained messages are deleted when the user sends a retained message -(there is a flag in the PUBLISH protocol) on a given topic with an empty body. -The difficulty with retained messages is to handle them in a cluster since all -servers need to be aware of their presence so that they can deliver them to -subscriptions that those servers may become the leader for. - -- `$MQTT_rmsgs` which has a “limits” policy and holds retained messages, all - under `$MQTT.rmsg` single subject. Not sure why I did not use MaxMsgsPer for - this stream and not filter `$MQTT.rmsg.>`. - -The first step when processing a new subscription is to gather the retained -messages that would be a match for this subscription. To do so, the server will -serialize into a buffer all messages for the account session manager’s sublist’s -ReverseMatch result. We use the returned subscriptions’ subject to find from a -map appropriate retained message (see `serializeRetainedMsgsForSub()` for -details). - -# 4. Implementation Notes - -## Hooking into NATS I/O - -### Starting the accept loop - -The MQTT accept loop is started when the server detects that an MQTT port has -been defined in the configuration file. It works similarly to all other accept -loops. Note that for MQTT over websocket, the websocket port has to be defined -and MQTT clients will connect to that port instead of the MQTT port and need to -provide `/mqtt` as part of the URL to redirect the creation of the client to an -MQTT client (with websocket support) instead of a regular NATS with websocket. -See the branching done in `startWebsocketServer()`. See `startMQTT()`. - -### Starting the read/write loops - -When a TCP connection is accepted, the internal go routine will invoke -`createMQTTClient()`. This function will set a `c.mqtt` object that will make it -become an MQTT client (through the `isMqtt()` helper function). The `readLoop()` -and `writeLoop()` are started similarly to other clients. However, the read loop -will branch out to `mqttParse()` instead when detecting that this is an MQTT -client. - -## Session Management - -### Account Session Manager - -`mqttAccountSessionManager` is an object that holds the state of all sessions in -an account. It also manages the lifecycle of JetStream streams and internal -subscriptions for processing JS API replies, session updates, etc. See -`mqttCreateAccountSessionManager()`. It is lazily initialized upon the first -MQTT `CONNECT` packet received. Account session manager is referred to as `asm` -in the code. - -Note that creating the account session manager (and attempting to create the -streams) is done only once per account on a given server, since once created the -account session manager for a given account would be found in the sessions map -of the mqttSessionManager object. - -### Find and disconnect previous session/client - -Once all that is done, we now go to the creation of the session object itself. -For that, we first need to make sure that it does not already exist, meaning -that it is registered on the server - or anywhere in the cluster. Note that MQTT -dictates that if a session with the same ID connects, the OLD session needs to -be closed, not the new one being created. NATS Server complies with this -requirement. - -Once a session is detected to already exists, the old one (as described above) -is closed and the new one accepted, however, the session ID is maintained in a -flappers map so that we detect situations where sessions with the same ID are -started multiple times causing the previous one to be closed. When that -detection occurs, the newly created session is put in “jail” for a second to -avoid a very rapid succession of connect/disconnect. This has already been seen -by users since there was some issue there where we would schedule the connection -closed instead of waiting in place which was causing a panic. - -We also protect from multiple clients on a given server trying to connect with -the same ID at the “same time” while the processing of a CONNECT of a session is -not yet finished. This is done with the use of a sessLocked map, keyed by the -session ID. - -### Create or restore the session - -If everything is good up to that point, the server will either create or restore -a session from the stream. This is done in the `createOrRestoreSession()` -function. The client/session ID is hashed and added to the session’s stream -subject along with the JS domain to prevent clients connecting from different -domains to “pollute” the session stream of a given domain. - -Since each session constitutes a subject and the stream has a maximum of 1 -message per subject, we attempt to load the last message on the formed subject. -If we don’t find it, then the session object is created “empty”, while if we -find a record, we create the session object based on the record persisted on the -stream. - -If the session was restored from the JS stream, we keep track of the stream -sequence where the record was located. When we save the session (even if it -already exists) we will use this sequence number to set the -`JSExpectedLastSubjSeq` header so that we handle possibly different servers in a -(super)cluster to detect the race of clients trying to use the same session ID, -since only one of the write should succeed. On success, the session’s new -sequence is remembered by the server that did the write. - -When created or restored, the CONNACK can now be sent back to the client, and if -there were any recovered subscriptions, they are now processed. - -## Processing QoS acks: PUBACK, PUBREC, PUBCOMP - -When the server delivers a message with QoS 1 or 2 (also a `PUBREL` for QoS 2) to a subscribed client, the client will send back an acknowledgement. See `mqttProcessPubAck()`, `mqttProcessPubRec()`, and `mqttProcessPubComp()` - -While the specific logic for each packet differs, these handlers all update the -session's PI mappings (`cpending`, `pendingPublish`, `pendingPubRel`), and if -needed send an ACK to JetStream to remove the message from its consumer and stop -the re-delivery attempts. - -## Subject Wildcards - -Note that MQTT subscriptions have wildcards too, the `“+”` wildcard is equivalent -to NATS’s `“*”` wildcard, however, MQTT’s wildcard `“#”` is similar to `“>”`, except -that it also includes the level above. That is, a subscription on `“foo/#”` would -receive messages on `“foo/bar/baz”`, but also on `“foo”`. - -So, for MQTT subscriptions enging with a `'#'` we are forced to create 2 -internal NATS subscriptions, one on `“foo”` and one on `“foo.>”`. - -# 5. Known issues -- "active" redelivery for QoS from JetStream (compliant, just a note) -- JetStream QoS redelivery happens out of (original) order -- finish delivery of in-flight messages after UNSUB -- finish delivery of in-flight messages after a reconnect -- consider replacing `$MQTT_msgs` with `$MQTT_out`. -- consider using unique `$MQTT.rmsg.>` and `MaxMsgsPer` for retained messages. -- add a cli command to list/clean old sessions diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/README.md b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/README.md deleted file mode 100644 index 38e8621e..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Tests - -Tests that run on Travis have been split into jobs that run in their own VM in parallel. This reduces the overall running time but also is allowing recycling of a job when we get a flapper as opposed to have to recycle the whole test suite. - -## JetStream Tests - -For JetStream tests, we need to observe a naming convention so that no tests are omitted when running on Travis. - -The script `runTestsOnTravis.sh` will run a given job based on the definition found in "`.travis.yml`". - -As for the naming convention: - -- All JetStream test name should start with `TestJetStream` -- Cluster tests should go into `jetstream_cluster_test.go` and start with `TestJetStreamCluster` -- Super-cluster tests should go into `jetstream_super_cluster_test.go` and start with `TestJetStreamSuperCluster` - -Not following this convention means that some tests may not be executed on Travis. diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/accounts.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/accounts.go deleted file mode 100644 index 8146bb02..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/accounts.go +++ /dev/null @@ -1,4655 +0,0 @@ -// Copyright 2018-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "cmp" - "encoding/hex" - "errors" - "fmt" - "io" - "io/fs" - "math" - "math/rand" - "net/http" - "net/textproto" - "reflect" - "slices" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/nats-io/jwt/v2" - "github.com/nats-io/nats-server/v2/internal/fastrand" - "github.com/nats-io/nkeys" - "github.com/nats-io/nuid" -) - -// For backwards compatibility with NATS < 2.0, users who are not explicitly defined into an -// account will be grouped in the default global account. -const globalAccountName = DEFAULT_GLOBAL_ACCOUNT - -const defaultMaxSubLimitReportThreshold = int64(2 * time.Second) - -var maxSubLimitReportThreshold = defaultMaxSubLimitReportThreshold - -// Account are subject namespace definitions. By default no messages are shared between accounts. -// You can share via Exports and Imports of Streams and Services. -type Account struct { - stats - gwReplyMapping - Name string - Nkey string - Issuer string - claimJWT string - updated time.Time - mu sync.RWMutex - sqmu sync.Mutex - sl *Sublist - ic *client - sq *sendq - isid uint64 - etmr *time.Timer - ctmr *time.Timer - strack map[string]sconns - nrclients int32 - sysclients int32 - nleafs int32 - nrleafs int32 - clients map[*client]struct{} - rm map[string]int32 - lqws map[string]int32 - usersRevoked map[string]int64 - mappings []*mapping - hasMapped atomic.Bool - lmu sync.RWMutex - lleafs []*client - leafClusters map[string]uint64 - imports importMap - exports exportMap - js *jsAccount - jsLimits map[string]JetStreamAccountLimits - limits - expired atomic.Bool - incomplete bool - signingKeys map[string]jwt.Scope - extAuth *jwt.ExternalAuthorization - srv *Server // server this account is registered with (possibly nil) - lds string // loop detection subject for leaf nodes - siReply []byte // service reply prefix, will form wildcard subscription. - eventIds *nuid.NUID - eventIdsMu sync.Mutex - defaultPerms *Permissions - tags jwt.TagList - nameTag string - lastLimErr int64 - routePoolIdx int - // If the trace destination is specified and a message with a traceParentHdr - // is received, and has the least significant bit of the last token set to 1, - // then if traceDestSampling is > 0 and < 100, a random value will be selected - // and if it falls between 0 and that value, message tracing will be triggered. - traceDest string - traceDestSampling int - // Guarantee that only one goroutine can be running either checkJetStreamMigrate - // or clearObserverState at a given time for this account to prevent interleaving. - jscmMu sync.Mutex -} - -const ( - accDedicatedRoute = -1 - accTransitioningToDedicatedRoute = -2 -) - -// Account based limits. -type limits struct { - mpay int32 - msubs int32 - mconns int32 - mleafs int32 - disallowBearer bool -} - -// Used to track remote clients and leafnodes per remote server. -type sconns struct { - conns int32 - leafs int32 -} - -// Import stream mapping struct -type streamImport struct { - acc *Account - from string - to string - tr *subjectTransform - rtr *subjectTransform - claim *jwt.Import - usePub bool - invalid bool - // This is `allow_trace` and when true and message tracing is happening, - // we will trace egresses past the account boundary, if `false`, we stop - // at the account boundary. - atrc bool -} - -const ClientInfoHdr = "Nats-Request-Info" - -// Import service mapping struct -type serviceImport struct { - acc *Account - claim *jwt.Import - se *serviceExport - sid []byte - from string - to string - tr *subjectTransform - ts int64 - rt ServiceRespType - latency *serviceLatency - m1 *ServiceLatency - rc *client - usePub bool - response bool - invalid bool - share bool - tracking bool - didDeliver bool - atrc bool // allow trace (got from service export) - trackingHdr http.Header // header from request -} - -// This is used to record when we create a mapping for implicit service -// imports. We use this to clean up entries that are not singletons when -// we detect that interest is no longer present. The key to the map will -// be the actual interest. We record the mapped subject and the account. -type serviceRespEntry struct { - acc *Account - msub string -} - -// ServiceRespType represents the types of service request response types. -type ServiceRespType uint8 - -// Service response types. Defaults to a singleton. -const ( - Singleton ServiceRespType = iota - Streamed - Chunked -) - -// String helper. -func (rt ServiceRespType) String() string { - switch rt { - case Singleton: - return "Singleton" - case Streamed: - return "Streamed" - case Chunked: - return "Chunked" - } - return "Unknown ServiceResType" -} - -// exportAuth holds configured approvals or boolean indicating an -// auth token is required for import. -type exportAuth struct { - tokenReq bool - accountPos uint - approved map[string]*Account - actsRevoked map[string]int64 -} - -// streamExport -type streamExport struct { - exportAuth -} - -// serviceExport holds additional information for exported services. -type serviceExport struct { - exportAuth - acc *Account - respType ServiceRespType - latency *serviceLatency - rtmr *time.Timer - respThresh time.Duration - // This is `allow_trace` and when true and message tracing is happening, - // when processing a service import we will go through account boundary - // and trace egresses on that other account. If `false`, we stop at the - // account boundary. - atrc bool -} - -// Used to track service latency. -type serviceLatency struct { - sampling int8 // percentage from 1-100 or 0 to indicate triggered by header - subject string -} - -// exportMap tracks the exported streams and services. -type exportMap struct { - streams map[string]*streamExport - services map[string]*serviceExport - responses map[string]*serviceImport -} - -// importMap tracks the imported streams and services. -// For services we will also track the response mappings as well. -type importMap struct { - streams []*streamImport - services map[string]*serviceImport - rrMap map[string][]*serviceRespEntry -} - -// NewAccount creates a new unlimited account with the given name. -func NewAccount(name string) *Account { - a := &Account{ - Name: name, - limits: limits{-1, -1, -1, -1, false}, - eventIds: nuid.New(), - } - return a -} - -func (a *Account) String() string { - return a.Name -} - -func (a *Account) setTraceDest(dest string) { - a.mu.Lock() - a.traceDest = dest - a.mu.Unlock() -} - -func (a *Account) getTraceDestAndSampling() (string, int) { - a.mu.RLock() - dest := a.traceDest - sampling := a.traceDestSampling - a.mu.RUnlock() - return dest, sampling -} - -// Used to create shallow copies of accounts for transfer -// from opts to real accounts in server struct. -// Account `na` write lock is expected to be held on entry -// while account `a` is the one from the Options struct -// being loaded/reloaded and do not need locking. -func (a *Account) shallowCopy(na *Account) { - na.Nkey = a.Nkey - na.Issuer = a.Issuer - na.traceDest, na.traceDestSampling = a.traceDest, a.traceDestSampling - - if a.imports.streams != nil { - na.imports.streams = make([]*streamImport, 0, len(a.imports.streams)) - for _, v := range a.imports.streams { - si := *v - na.imports.streams = append(na.imports.streams, &si) - } - } - if a.imports.services != nil { - na.imports.services = make(map[string]*serviceImport) - for k, v := range a.imports.services { - si := *v - na.imports.services[k] = &si - } - } - if a.exports.streams != nil { - na.exports.streams = make(map[string]*streamExport) - for k, v := range a.exports.streams { - if v != nil { - se := *v - na.exports.streams[k] = &se - } else { - na.exports.streams[k] = nil - } - } - } - if a.exports.services != nil { - na.exports.services = make(map[string]*serviceExport) - for k, v := range a.exports.services { - if v != nil { - se := *v - na.exports.services[k] = &se - } else { - na.exports.services[k] = nil - } - } - } - na.mappings = a.mappings - na.hasMapped.Store(len(na.mappings) > 0) - - // JetStream - na.jsLimits = a.jsLimits - // Server config account limits. - na.limits = a.limits -} - -// nextEventID uses its own lock for better concurrency. -func (a *Account) nextEventID() string { - a.eventIdsMu.Lock() - id := a.eventIds.Next() - a.eventIdsMu.Unlock() - return id -} - -// Returns a slice of clients stored in the account, or nil if none is present. -// Lock is held on entry. -func (a *Account) getClientsLocked() []*client { - if len(a.clients) == 0 { - return nil - } - clients := make([]*client, 0, len(a.clients)) - for c := range a.clients { - clients = append(clients, c) - } - return clients -} - -// Returns a slice of clients stored in the account, or nil if none is present. -func (a *Account) getClients() []*client { - a.mu.RLock() - clients := a.getClientsLocked() - a.mu.RUnlock() - return clients -} - -// Called to track a remote server and connections and leafnodes it -// has for this account. -func (a *Account) updateRemoteServer(m *AccountNumConns) []*client { - a.mu.Lock() - if a.strack == nil { - a.strack = make(map[string]sconns) - } - // This does not depend on receiving all updates since each one is idempotent. - // FIXME(dlc) - We should cleanup when these both go to zero. - prev := a.strack[m.Server.ID] - a.strack[m.Server.ID] = sconns{conns: int32(m.Conns), leafs: int32(m.LeafNodes)} - a.nrclients += int32(m.Conns) - prev.conns - a.nrleafs += int32(m.LeafNodes) - prev.leafs - - mtce := a.mconns != jwt.NoLimit && (len(a.clients)-int(a.sysclients)+int(a.nrclients) > int(a.mconns)) - // If we are over here some have snuck in and we need to rebalance. - // All others will probably be doing the same thing but better to be - // conservative and bit harsh here. Clients will reconnect if we over compensate. - var clients []*client - if mtce { - clients = a.getClientsLocked() - slices.SortFunc(clients, func(i, j *client) int { return -i.start.Compare(j.start) }) // reserve - over := (len(a.clients) - int(a.sysclients) + int(a.nrclients)) - int(a.mconns) - if over < len(clients) { - clients = clients[:over] - } - } - // Now check leafnodes. - mtlce := a.mleafs != jwt.NoLimit && (a.nleafs+a.nrleafs > a.mleafs) - if mtlce { - // Take ones from the end. - a.lmu.RLock() - leafs := a.lleafs - over := int(a.nleafs + a.nrleafs - a.mleafs) - if over < len(leafs) { - leafs = leafs[len(leafs)-over:] - } - clients = append(clients, leafs...) - a.lmu.RUnlock() - } - a.mu.Unlock() - - // If we have exceeded our max clients this will be populated. - return clients -} - -// Removes tracking for a remote server that has shutdown. -func (a *Account) removeRemoteServer(sid string) { - a.mu.Lock() - if a.strack != nil { - prev := a.strack[sid] - delete(a.strack, sid) - a.nrclients -= prev.conns - a.nrleafs -= prev.leafs - } - a.mu.Unlock() -} - -// When querying for subject interest this is the number of -// expected responses. We need to actually check that the entry -// has active connections. -func (a *Account) expectedRemoteResponses() (expected int32) { - a.mu.RLock() - for _, sc := range a.strack { - if sc.conns > 0 || sc.leafs > 0 { - expected++ - } - } - a.mu.RUnlock() - return -} - -// Clears eventing and tracking for this account. -func (a *Account) clearEventing() { - a.mu.Lock() - a.nrclients = 0 - // Now clear state - clearTimer(&a.etmr) - clearTimer(&a.ctmr) - a.clients = nil - a.strack = nil - a.mu.Unlock() -} - -// GetName will return the accounts name. -func (a *Account) GetName() string { - if a == nil { - return "n/a" - } - a.mu.RLock() - name := a.Name - a.mu.RUnlock() - return name -} - -// getNameTag will return the name tag or the account name if not set. -func (a *Account) getNameTag() string { - if a == nil { - return _EMPTY_ - } - a.mu.RLock() - defer a.mu.RUnlock() - return a.getNameTagLocked() -} - -// getNameTagLocked will return the name tag or the account name if not set. -// Lock should be held. -func (a *Account) getNameTagLocked() string { - if a == nil { - return _EMPTY_ - } - nameTag := a.nameTag - if nameTag == _EMPTY_ { - nameTag = a.Name - } - return nameTag -} - -// NumConnections returns active number of clients for this account for -// all known servers. -func (a *Account) NumConnections() int { - a.mu.RLock() - nc := len(a.clients) - int(a.sysclients) + int(a.nrclients) - a.mu.RUnlock() - return nc -} - -// NumRemoteConnections returns the number of client or leaf connections that -// are not on this server. -func (a *Account) NumRemoteConnections() int { - a.mu.RLock() - nc := int(a.nrclients + a.nrleafs) - a.mu.RUnlock() - return nc -} - -// NumLocalConnections returns active number of clients for this account -// on this server. -func (a *Account) NumLocalConnections() int { - a.mu.RLock() - nlc := a.numLocalConnections() - a.mu.RUnlock() - return nlc -} - -// Do not account for the system accounts. -func (a *Account) numLocalConnections() int { - return len(a.clients) - int(a.sysclients) - int(a.nleafs) -} - -// This is for extended local interest. -// Lock should not be held. -func (a *Account) numLocalAndLeafConnections() int { - a.mu.RLock() - nlc := len(a.clients) - int(a.sysclients) - a.mu.RUnlock() - return nlc -} - -func (a *Account) numLocalLeafNodes() int { - return int(a.nleafs) -} - -// MaxTotalConnectionsReached returns if we have reached our limit for number of connections. -func (a *Account) MaxTotalConnectionsReached() bool { - var mtce bool - a.mu.RLock() - if a.mconns != jwt.NoLimit { - mtce = len(a.clients)-int(a.sysclients)+int(a.nrclients) >= int(a.mconns) - } - a.mu.RUnlock() - return mtce -} - -// MaxActiveConnections return the set limit for the account system -// wide for total number of active connections. -func (a *Account) MaxActiveConnections() int { - a.mu.RLock() - mconns := int(a.mconns) - a.mu.RUnlock() - return mconns -} - -// MaxTotalLeafNodesReached returns if we have reached our limit for number of leafnodes. -func (a *Account) MaxTotalLeafNodesReached() bool { - a.mu.RLock() - mtc := a.maxTotalLeafNodesReached() - a.mu.RUnlock() - return mtc -} - -func (a *Account) maxTotalLeafNodesReached() bool { - if a.mleafs != jwt.NoLimit { - return a.nleafs+a.nrleafs >= a.mleafs - } - return false -} - -// NumLeafNodes returns the active number of local and remote -// leaf node connections. -func (a *Account) NumLeafNodes() int { - a.mu.RLock() - nln := int(a.nleafs + a.nrleafs) - a.mu.RUnlock() - return nln -} - -// NumRemoteLeafNodes returns the active number of remote -// leaf node connections. -func (a *Account) NumRemoteLeafNodes() int { - a.mu.RLock() - nrn := int(a.nrleafs) - a.mu.RUnlock() - return nrn -} - -// MaxActiveLeafNodes return the set limit for the account system -// wide for total number of leavenode connections. -// NOTE: these are tracked separately. -func (a *Account) MaxActiveLeafNodes() int { - a.mu.RLock() - mleafs := int(a.mleafs) - a.mu.RUnlock() - return mleafs -} - -// RoutedSubs returns how many subjects we would send across a route when first -// connected or expressing interest. Local client subs. -func (a *Account) RoutedSubs() int { - a.mu.RLock() - defer a.mu.RUnlock() - return len(a.rm) -} - -// TotalSubs returns total number of Subscriptions for this account. -func (a *Account) TotalSubs() int { - a.mu.RLock() - defer a.mu.RUnlock() - if a.sl == nil { - return 0 - } - return int(a.sl.Count()) -} - -func (a *Account) shouldLogMaxSubErr() bool { - if a == nil { - return true - } - a.mu.RLock() - last := a.lastLimErr - a.mu.RUnlock() - if now := time.Now().UnixNano(); now-last >= maxSubLimitReportThreshold { - a.mu.Lock() - a.lastLimErr = now - a.mu.Unlock() - return true - } - return false -} - -// MapDest is for mapping published subjects for clients. -type MapDest struct { - Subject string `json:"subject"` - Weight uint8 `json:"weight"` - Cluster string `json:"cluster,omitempty"` -} - -func NewMapDest(subject string, weight uint8) *MapDest { - return &MapDest{subject, weight, _EMPTY_} -} - -// destination is for internal representation for a weighted mapped destination. -type destination struct { - tr *subjectTransform - weight uint8 -} - -// mapping is an internal entry for mapping subjects. -type mapping struct { - src string - wc bool - dests []*destination - cdests map[string][]*destination -} - -// AddMapping adds in a simple route mapping from src subject to dest subject -// for inbound client messages. -func (a *Account) AddMapping(src, dest string) error { - return a.AddWeightedMappings(src, NewMapDest(dest, 100)) -} - -// AddWeightedMappings will add in a weighted mappings for the destinations. -func (a *Account) AddWeightedMappings(src string, dests ...*MapDest) error { - a.mu.Lock() - defer a.mu.Unlock() - - if !IsValidSubject(src) { - return ErrBadSubject - } - - m := &mapping{src: src, wc: subjectHasWildcard(src), dests: make([]*destination, 0, len(dests)+1)} - seen := make(map[string]struct{}) - - var tw = make(map[string]uint8) - for _, d := range dests { - if _, ok := seen[d.Subject]; ok { - return fmt.Errorf("duplicate entry for %q", d.Subject) - } - seen[d.Subject] = struct{}{} - if d.Weight > 100 { - return fmt.Errorf("individual weights need to be <= 100") - } - tw[d.Cluster] += d.Weight - if tw[d.Cluster] > 100 { - return fmt.Errorf("total weight needs to be <= 100") - } - err := ValidateMapping(src, d.Subject) - if err != nil { - return err - } - tr, err := NewSubjectTransform(src, d.Subject) - if err != nil { - return err - } - if d.Cluster == _EMPTY_ { - m.dests = append(m.dests, &destination{tr, d.Weight}) - } else { - // We have a cluster scoped filter. - if m.cdests == nil { - m.cdests = make(map[string][]*destination) - } - ad := m.cdests[d.Cluster] - ad = append(ad, &destination{tr, d.Weight}) - m.cdests[d.Cluster] = ad - } - } - - processDestinations := func(dests []*destination) ([]*destination, error) { - var ltw uint8 - for _, d := range dests { - ltw += d.weight - } - // Auto add in original at weight difference if all entries weight does not total to 100. - // Iff the src was not already added in explicitly, meaning they want loss. - _, haveSrc := seen[src] - if ltw != 100 && !haveSrc { - dest := src - if m.wc { - // We need to make the appropriate markers for the wildcards etc. - dest = transformTokenize(dest) - } - tr, err := NewSubjectTransform(src, dest) - if err != nil { - return nil, err - } - aw := 100 - ltw - if len(dests) == 0 { - aw = 100 - } - dests = append(dests, &destination{tr, aw}) - } - slices.SortFunc(dests, func(i, j *destination) int { return cmp.Compare(i.weight, j.weight) }) - - var lw uint8 - for _, d := range dests { - d.weight += lw - lw = d.weight - } - return dests, nil - } - - var err error - if m.dests, err = processDestinations(m.dests); err != nil { - return err - } - - // Option cluster scoped destinations - for cluster, dests := range m.cdests { - if dests, err = processDestinations(dests); err != nil { - return err - } - m.cdests[cluster] = dests - } - - // Replace an old one if it exists. - for i, em := range a.mappings { - if em.src == src { - a.mappings[i] = m - return nil - } - } - // If we did not replace add to the end. - a.mappings = append(a.mappings, m) - a.hasMapped.Store(len(a.mappings) > 0) - - // If we have connected leafnodes make sure to update. - if a.nleafs > 0 { - // Need to release because lock ordering is client -> account - a.mu.Unlock() - // Now grab the leaf list lock. We can hold client lock under this one. - a.lmu.RLock() - for _, lc := range a.lleafs { - lc.forceAddToSmap(src) - } - a.lmu.RUnlock() - a.mu.Lock() - } - return nil -} - -// RemoveMapping will remove an existing mapping. -func (a *Account) RemoveMapping(src string) bool { - a.mu.Lock() - defer a.mu.Unlock() - for i, m := range a.mappings { - if m.src == src { - // Swap last one into this spot. Its ok to change order. - a.mappings[i] = a.mappings[len(a.mappings)-1] - a.mappings[len(a.mappings)-1] = nil // gc - a.mappings = a.mappings[:len(a.mappings)-1] - a.hasMapped.Store(len(a.mappings) > 0) - // If we have connected leafnodes make sure to update. - if a.nleafs > 0 { - // Need to release because lock ordering is client -> account - a.mu.Unlock() - // Now grab the leaf list lock. We can hold client lock under this one. - a.lmu.RLock() - for _, lc := range a.lleafs { - lc.forceRemoveFromSmap(src) - } - a.lmu.RUnlock() - a.mu.Lock() - } - return true - } - } - return false -} - -// Indicates we have mapping entries. -func (a *Account) hasMappings() bool { - if a == nil { - return false - } - return a.hasMapped.Load() -} - -// This performs the logic to map to a new dest subject based on mappings. -// Should only be called from processInboundClientMsg or service import processing. -func (a *Account) selectMappedSubject(dest string) (string, bool) { - if !a.hasMappings() { - return dest, false - } - - a.mu.RLock() - // In case we have to tokenize for subset matching. - tsa := [32]string{} - tts := tsa[:0] - - var m *mapping - for _, rm := range a.mappings { - if !rm.wc && rm.src == dest { - m = rm - break - } else { - // tokenize and reuse for subset matching. - if len(tts) == 0 { - start := 0 - subject := dest - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - tts = append(tts, subject[start:i]) - start = i + 1 - } - } - tts = append(tts, subject[start:]) - } - if isSubsetMatch(tts, rm.src) { - m = rm - break - } - } - } - - if m == nil { - a.mu.RUnlock() - return dest, false - } - - // The selected destination for the mapping. - var d *destination - var ndest string - - dests := m.dests - if len(m.cdests) > 0 { - cn := a.srv.cachedClusterName() - dests = m.cdests[cn] - if dests == nil { - // Fallback to main if we do not match the cluster. - dests = m.dests - } - } - - // Optimize for single entry case. - if len(dests) == 1 && dests[0].weight == 100 { - d = dests[0] - } else { - w := uint8(fastrand.Uint32n(100)) - for _, rm := range dests { - if w < rm.weight { - d = rm - break - } - } - } - - if d != nil { - if len(d.tr.dtokmftokindexesargs) == 0 { - ndest = d.tr.dest - } else { - ndest = d.tr.TransformTokenizedSubject(tts) - } - } - - a.mu.RUnlock() - return ndest, true -} - -// SubscriptionInterest returns true if this account has a matching subscription -// for the given `subject`. -func (a *Account) SubscriptionInterest(subject string) bool { - return a.Interest(subject) > 0 -} - -// Interest returns the number of subscriptions for a given subject that match. -func (a *Account) Interest(subject string) int { - var nms int - a.mu.RLock() - if a.sl != nil { - np, nq := a.sl.NumInterest(subject) - nms = np + nq - } - a.mu.RUnlock() - return nms -} - -// addClient keeps our accounting of local active clients or leafnodes updated. -// Returns previous total. -func (a *Account) addClient(c *client) int { - a.mu.Lock() - n := len(a.clients) - - // Could come here earlier than the account is registered with the server. - // Make sure we can still track clients. - if a.clients == nil { - a.clients = make(map[*client]struct{}) - } - a.clients[c] = struct{}{} - - // If we did not add it, we are done - if n == len(a.clients) { - a.mu.Unlock() - return n - } - if c.kind != CLIENT && c.kind != LEAF { - a.sysclients++ - } else if c.kind == LEAF { - a.nleafs++ - } - a.mu.Unlock() - - // If we added a new leaf use the list lock and add it to the list. - if c.kind == LEAF { - a.lmu.Lock() - a.lleafs = append(a.lleafs, c) - a.lmu.Unlock() - } - - if c != nil && c.srv != nil { - c.srv.accConnsUpdate(a) - } - - return n -} - -// For registering clusters for remote leafnodes. -// We only register as the hub. -func (a *Account) registerLeafNodeCluster(cluster string) { - a.mu.Lock() - defer a.mu.Unlock() - if a.leafClusters == nil { - a.leafClusters = make(map[string]uint64) - } - a.leafClusters[cluster]++ -} - -// Check to see if we already have this cluster registered. -func (a *Account) hasLeafNodeCluster(cluster string) bool { - a.mu.RLock() - defer a.mu.RUnlock() - return a.leafClusters[cluster] > 0 -} - -// Check to see if this cluster is isolated, meaning the only one. -// Read Lock should be held. -func (a *Account) isLeafNodeClusterIsolated(cluster string) bool { - if cluster == _EMPTY_ { - return false - } - if len(a.leafClusters) > 1 { - return false - } - return a.leafClusters[cluster] == uint64(a.nleafs) -} - -// Helper function to remove leaf nodes. If number of leafnodes gets large -// this may need to be optimized out of linear search but believe number -// of active leafnodes per account scope to be small and therefore cache friendly. -// Lock should not be held on general account lock. -func (a *Account) removeLeafNode(c *client) { - // Make sure we hold the list lock as well. - a.lmu.Lock() - defer a.lmu.Unlock() - - ll := len(a.lleafs) - for i, l := range a.lleafs { - if l == c { - a.lleafs[i] = a.lleafs[ll-1] - if ll == 1 { - a.lleafs = nil - } else { - a.lleafs = a.lleafs[:ll-1] - } - return - } - } -} - -// removeClient keeps our accounting of local active clients updated. -func (a *Account) removeClient(c *client) int { - a.mu.Lock() - n := len(a.clients) - delete(a.clients, c) - // If we did not actually remove it, we are done. - if n == len(a.clients) { - a.mu.Unlock() - return n - } - if c.kind != CLIENT && c.kind != LEAF { - a.sysclients-- - } else if c.kind == LEAF { - a.nleafs-- - // Need to do cluster accounting here. - // Do cluster accounting if we are a hub. - if c.isHubLeafNode() { - cluster := c.remoteCluster() - if count := a.leafClusters[cluster]; count > 1 { - a.leafClusters[cluster]-- - } else if count == 1 { - delete(a.leafClusters, cluster) - } - } - } - a.mu.Unlock() - - if c.kind == LEAF { - a.removeLeafNode(c) - } - - if c != nil && c.srv != nil { - c.srv.accConnsUpdate(a) - } - - return n -} - -func setExportAuth(ea *exportAuth, subject string, accounts []*Account, accountPos uint) error { - if accountPos > 0 { - token := strings.Split(subject, tsep) - if len(token) < int(accountPos) || token[accountPos-1] != "*" { - return ErrInvalidSubject - } - } - ea.accountPos = accountPos - // empty means auth required but will be import token. - if accounts == nil { - return nil - } - if len(accounts) == 0 { - ea.tokenReq = true - return nil - } - if ea.approved == nil { - ea.approved = make(map[string]*Account, len(accounts)) - } - for _, acc := range accounts { - ea.approved[acc.Name] = acc - } - return nil -} - -// AddServiceExport will configure the account with the defined export. -func (a *Account) AddServiceExport(subject string, accounts []*Account) error { - return a.addServiceExportWithResponseAndAccountPos(subject, Singleton, accounts, 0) -} - -// AddServiceExport will configure the account with the defined export. -func (a *Account) addServiceExportWithAccountPos(subject string, accounts []*Account, accountPos uint) error { - return a.addServiceExportWithResponseAndAccountPos(subject, Singleton, accounts, accountPos) -} - -// AddServiceExportWithResponse will configure the account with the defined export and response type. -func (a *Account) AddServiceExportWithResponse(subject string, respType ServiceRespType, accounts []*Account) error { - return a.addServiceExportWithResponseAndAccountPos(subject, respType, accounts, 0) -} - -// AddServiceExportWithresponse will configure the account with the defined export and response type. -func (a *Account) addServiceExportWithResponseAndAccountPos( - subject string, respType ServiceRespType, accounts []*Account, accountPos uint) error { - if a == nil { - return ErrMissingAccount - } - - a.mu.Lock() - if a.exports.services == nil { - a.exports.services = make(map[string]*serviceExport) - } - - se := a.exports.services[subject] - // Always create a service export - if se == nil { - se = &serviceExport{} - } - - if respType != Singleton { - se.respType = respType - } - - if accounts != nil || accountPos > 0 { - if err := setExportAuth(&se.exportAuth, subject, accounts, accountPos); err != nil { - a.mu.Unlock() - return err - } - } - lrt := a.lowestServiceExportResponseTime() - se.acc = a - se.respThresh = DEFAULT_SERVICE_EXPORT_RESPONSE_THRESHOLD - a.exports.services[subject] = se - - var clients []*client - nlrt := a.lowestServiceExportResponseTime() - if nlrt != lrt && len(a.clients) > 0 { - clients = a.getClientsLocked() - } - // Need to release because lock ordering is client -> Account - a.mu.Unlock() - if len(clients) > 0 { - updateAllClientsServiceExportResponseTime(clients, nlrt) - } - return nil -} - -// TrackServiceExport will enable latency tracking of the named service. -// Results will be published in this account to the given results subject. -func (a *Account) TrackServiceExport(service, results string) error { - return a.TrackServiceExportWithSampling(service, results, DEFAULT_SERVICE_LATENCY_SAMPLING) -} - -// TrackServiceExportWithSampling will enable latency tracking of the named service for the given -// sampling rate (1-100). Results will be published in this account to the given results subject. -func (a *Account) TrackServiceExportWithSampling(service, results string, sampling int) error { - if a == nil { - return ErrMissingAccount - } - - if sampling != 0 { // 0 means triggered by header - if sampling < 1 || sampling > 100 { - return ErrBadSampling - } - } - if !IsValidPublishSubject(results) { - return ErrBadPublishSubject - } - // Don't loop back on outselves. - if a.IsExportService(results) { - return ErrBadPublishSubject - } - - if a.srv != nil && !a.srv.EventsEnabled() { - return ErrNoSysAccount - } - - a.mu.Lock() - if a.exports.services == nil { - a.mu.Unlock() - return ErrMissingService - } - ea, ok := a.exports.services[service] - if !ok { - a.mu.Unlock() - return ErrMissingService - } - if ea == nil { - ea = &serviceExport{} - a.exports.services[service] = ea - } else if ea.respType != Singleton { - a.mu.Unlock() - return ErrBadServiceType - } - ea.latency = &serviceLatency{ - sampling: int8(sampling), - subject: results, - } - s := a.srv - a.mu.Unlock() - - if s == nil { - return nil - } - - // Now track down the imports and add in latency as needed to enable. - s.accounts.Range(func(k, v any) bool { - acc := v.(*Account) - acc.mu.Lock() - for _, im := range acc.imports.services { - if im != nil && im.acc.Name == a.Name && subjectIsSubsetMatch(im.to, service) { - im.latency = ea.latency - } - } - acc.mu.Unlock() - return true - }) - - return nil -} - -// UnTrackServiceExport will disable latency tracking of the named service. -func (a *Account) UnTrackServiceExport(service string) { - if a == nil || (a.srv != nil && !a.srv.EventsEnabled()) { - return - } - - a.mu.Lock() - if a.exports.services == nil { - a.mu.Unlock() - return - } - ea, ok := a.exports.services[service] - if !ok || ea == nil || ea.latency == nil { - a.mu.Unlock() - return - } - // We have latency here. - ea.latency = nil - s := a.srv - a.mu.Unlock() - - if s == nil { - return - } - - // Now track down the imports and clean them up. - s.accounts.Range(func(k, v any) bool { - acc := v.(*Account) - acc.mu.Lock() - for _, im := range acc.imports.services { - if im != nil && im.acc.Name == a.Name { - if subjectIsSubsetMatch(im.to, service) { - im.latency, im.m1 = nil, nil - } - } - } - acc.mu.Unlock() - return true - }) -} - -// IsExportService will indicate if this service exists. Will check wildcard scenarios. -func (a *Account) IsExportService(service string) bool { - a.mu.RLock() - defer a.mu.RUnlock() - _, ok := a.exports.services[service] - if ok { - return true - } - tokens := strings.Split(service, tsep) - for subj := range a.exports.services { - if isSubsetMatch(tokens, subj) { - return true - } - } - return false -} - -// IsExportServiceTracking will indicate if given publish subject is an export service with tracking enabled. -func (a *Account) IsExportServiceTracking(service string) bool { - a.mu.RLock() - ea, ok := a.exports.services[service] - if ok && ea == nil { - a.mu.RUnlock() - return false - } - if ok && ea != nil && ea.latency != nil { - a.mu.RUnlock() - return true - } - // FIXME(dlc) - Might want to cache this is in the hot path checking for latency tracking. - tokens := strings.Split(service, tsep) - for subj, ea := range a.exports.services { - if isSubsetMatch(tokens, subj) && ea != nil && ea.latency != nil { - a.mu.RUnlock() - return true - } - } - a.mu.RUnlock() - return false -} - -// ServiceLatency is the JSON message sent out in response to latency tracking for -// an accounts exported services. Additional client info is available in requestor -// and responder. Note that for a requestor, the only information shared by default -// is the RTT used to calculate the total latency. The requestor's account can -// designate to share the additional information in the service import. -type ServiceLatency struct { - TypedEvent - Status int `json:"status"` - Error string `json:"description,omitempty"` - Requestor *ClientInfo `json:"requestor,omitempty"` - Responder *ClientInfo `json:"responder,omitempty"` - RequestHeader http.Header `json:"header,omitempty"` // only contains header(s) triggering the measurement - RequestStart time.Time `json:"start"` - ServiceLatency time.Duration `json:"service"` - SystemLatency time.Duration `json:"system"` - TotalLatency time.Duration `json:"total"` -} - -// ServiceLatencyType is the NATS Event Type for ServiceLatency -const ServiceLatencyType = "io.nats.server.metric.v1.service_latency" - -// NATSTotalTime is a helper function that totals the NATS latencies. -func (m1 *ServiceLatency) NATSTotalTime() time.Duration { - return m1.Requestor.RTT + m1.Responder.RTT + m1.SystemLatency -} - -// Merge function to merge m1 and m2 (requestor and responder) measurements -// when there are two samples. This happens when the requestor and responder -// are on different servers. -// -// m2 ServiceLatency is correct, so use that. -// m1 TotalLatency is correct, so use that. -// Will use those to back into NATS latency. -func (m1 *ServiceLatency) merge(m2 *ServiceLatency) { - rtt := time.Duration(0) - if m2.Responder != nil { - rtt = m2.Responder.RTT - } - m1.SystemLatency = m1.ServiceLatency - (m2.ServiceLatency + rtt) - m1.ServiceLatency = m2.ServiceLatency - m1.Responder = m2.Responder - sanitizeLatencyMetric(m1) -} - -// sanitizeLatencyMetric adjusts latency metric values that could go -// negative in some edge conditions since we estimate client RTT -// for both requestor and responder. -// These numbers are never meant to be negative, it just could be -// how we back into the values based on estimated RTT. -func sanitizeLatencyMetric(sl *ServiceLatency) { - if sl.ServiceLatency < 0 { - sl.ServiceLatency = 0 - } - if sl.SystemLatency < 0 { - sl.SystemLatency = 0 - } -} - -// Used for transporting remote latency measurements. -type remoteLatency struct { - Account string `json:"account"` - ReqId string `json:"req_id"` - M2 ServiceLatency `json:"m2"` - respThresh time.Duration -} - -// sendLatencyResult will send a latency result and clear the si of the requestor(rc). -func (a *Account) sendLatencyResult(si *serviceImport, sl *ServiceLatency) { - sl.Type = ServiceLatencyType - sl.ID = a.nextEventID() - sl.Time = time.Now().UTC() - a.mu.Lock() - lsubj := si.latency.subject - si.rc = nil - a.mu.Unlock() - - a.srv.sendInternalAccountMsg(a, lsubj, sl) -} - -// Used to send a bad request metric when we do not have a reply subject -func (a *Account) sendBadRequestTrackingLatency(si *serviceImport, requestor *client, header http.Header) { - sl := &ServiceLatency{ - Status: 400, - Error: "Bad Request", - Requestor: requestor.getClientInfo(si.share), - } - sl.RequestHeader = header - sl.RequestStart = time.Now().Add(-sl.Requestor.RTT).UTC() - a.sendLatencyResult(si, sl) -} - -// Used to send a latency result when the requestor interest was lost before the -// response could be delivered. -func (a *Account) sendReplyInterestLostTrackLatency(si *serviceImport) { - sl := &ServiceLatency{ - Status: 408, - Error: "Request Timeout", - } - a.mu.RLock() - rc, share, ts := si.rc, si.share, si.ts - sl.RequestHeader = si.trackingHdr - a.mu.RUnlock() - if rc != nil { - sl.Requestor = rc.getClientInfo(share) - } - sl.RequestStart = time.Unix(0, ts-int64(sl.Requestor.RTT)).UTC() - a.sendLatencyResult(si, sl) -} - -func (a *Account) sendBackendErrorTrackingLatency(si *serviceImport, reason rsiReason) { - sl := &ServiceLatency{} - a.mu.RLock() - rc, share, ts := si.rc, si.share, si.ts - sl.RequestHeader = si.trackingHdr - a.mu.RUnlock() - if rc != nil { - sl.Requestor = rc.getClientInfo(share) - } - var reqRTT time.Duration - if sl.Requestor != nil { - reqRTT = sl.Requestor.RTT - } - sl.RequestStart = time.Unix(0, ts-int64(reqRTT)).UTC() - if reason == rsiNoDelivery { - sl.Status = 503 - sl.Error = "Service Unavailable" - } else if reason == rsiTimeout { - sl.Status = 504 - sl.Error = "Service Timeout" - } - a.sendLatencyResult(si, sl) -} - -// sendTrackingLatency will send out the appropriate tracking information for the -// service request/response latency. This is called when the requestor's server has -// received the response. -// TODO(dlc) - holding locks for RTTs may be too much long term. Should revisit. -func (a *Account) sendTrackingLatency(si *serviceImport, responder *client) bool { - a.mu.RLock() - rc := si.rc - a.mu.RUnlock() - if rc == nil { - return true - } - - ts := time.Now() - serviceRTT := time.Duration(ts.UnixNano() - si.ts) - requestor := si.rc - - sl := &ServiceLatency{ - Status: 200, - Requestor: requestor.getClientInfo(si.share), - Responder: responder.getClientInfo(true), - } - var respRTT, reqRTT time.Duration - if sl.Responder != nil { - respRTT = sl.Responder.RTT - } - if sl.Requestor != nil { - reqRTT = sl.Requestor.RTT - } - sl.RequestStart = time.Unix(0, si.ts-int64(reqRTT)).UTC() - sl.ServiceLatency = serviceRTT - respRTT - sl.TotalLatency = reqRTT + serviceRTT - if respRTT > 0 { - sl.SystemLatency = time.Since(ts) - sl.TotalLatency += sl.SystemLatency - } - sl.RequestHeader = si.trackingHdr - sanitizeLatencyMetric(sl) - - sl.Type = ServiceLatencyType - sl.ID = a.nextEventID() - sl.Time = time.Now().UTC() - - // If we are expecting a remote measurement, store our sl here. - // We need to account for the race between this and us receiving the - // remote measurement. - // FIXME(dlc) - We need to clean these up but this should happen - // already with the auto-expire logic. - if responder != nil && responder.kind != CLIENT { - si.acc.mu.Lock() - if si.m1 != nil { - m1, m2 := sl, si.m1 - m1.merge(m2) - si.acc.mu.Unlock() - a.srv.sendInternalAccountMsg(a, si.latency.subject, m1) - a.mu.Lock() - si.rc = nil - a.mu.Unlock() - return true - } - si.m1 = sl - si.acc.mu.Unlock() - return false - } else { - a.srv.sendInternalAccountMsg(a, si.latency.subject, sl) - a.mu.Lock() - si.rc = nil - a.mu.Unlock() - } - return true -} - -// This will check to make sure our response lower threshold is set -// properly in any clients doing rrTracking. -func updateAllClientsServiceExportResponseTime(clients []*client, lrt time.Duration) { - for _, c := range clients { - c.mu.Lock() - if c.rrTracking != nil && lrt != c.rrTracking.lrt { - c.rrTracking.lrt = lrt - if c.rrTracking.ptmr.Stop() { - c.rrTracking.ptmr.Reset(lrt) - } - } - c.mu.Unlock() - } -} - -// Will select the lowest respThresh from all service exports. -// Read lock should be held. -func (a *Account) lowestServiceExportResponseTime() time.Duration { - // Lowest we will allow is 5 minutes. Its an upper bound for this function. - lrt := 5 * time.Minute - for _, se := range a.exports.services { - if se.respThresh < lrt { - lrt = se.respThresh - } - } - return lrt -} - -// AddServiceImportWithClaim will add in the service import via the jwt claim. -func (a *Account) AddServiceImportWithClaim(destination *Account, from, to string, imClaim *jwt.Import) error { - return a.addServiceImportWithClaim(destination, from, to, imClaim, false) -} - -// addServiceImportWithClaim will add in the service import via the jwt claim. -// It will also skip the authorization check in cases where internal is true -func (a *Account) addServiceImportWithClaim(destination *Account, from, to string, imClaim *jwt.Import, internal bool) error { - if destination == nil { - return ErrMissingAccount - } - // Empty means use from. - if to == _EMPTY_ { - to = from - } - if !IsValidSubject(from) || !IsValidSubject(to) { - return ErrInvalidSubject - } - - // First check to see if the account has authorized us to route to the "to" subject. - if !internal && !destination.checkServiceImportAuthorized(a, to, imClaim) { - return ErrServiceImportAuthorization - } - - // Check if this introduces a cycle before proceeding. - // From will be the mapped subject. - // If the 'to' has a wildcard make sure we pre-transform the 'from' before we check for cycles, e.g. '$1' - fromT := from - if subjectHasWildcard(to) { - fromT, _ = transformUntokenize(from) - } - if err := a.serviceImportFormsCycle(destination, fromT); err != nil { - return err - } - - _, err := a.addServiceImport(destination, from, to, imClaim) - - return err -} - -const MaxAccountCycleSearchDepth = 1024 - -func (a *Account) serviceImportFormsCycle(dest *Account, from string) error { - return dest.checkServiceImportsForCycles(from, map[string]bool{a.Name: true}) -} - -func (a *Account) checkServiceImportsForCycles(from string, visited map[string]bool) error { - if len(visited) >= MaxAccountCycleSearchDepth { - return ErrCycleSearchDepth - } - a.mu.RLock() - for _, si := range a.imports.services { - if SubjectsCollide(from, si.to) { - a.mu.RUnlock() - if visited[si.acc.Name] { - return ErrImportFormsCycle - } - // Push ourselves and check si.acc - visited[a.Name] = true - if subjectIsSubsetMatch(si.from, from) { - from = si.from - } - if err := si.acc.checkServiceImportsForCycles(from, visited); err != nil { - return err - } - a.mu.RLock() - } - } - a.mu.RUnlock() - return nil -} - -func (a *Account) streamImportFormsCycle(dest *Account, to string) error { - return dest.checkStreamImportsForCycles(to, map[string]bool{a.Name: true}) -} - -// Lock should be held. -func (a *Account) hasServiceExportMatching(to string) bool { - for subj := range a.exports.services { - if subjectIsSubsetMatch(to, subj) { - return true - } - } - return false -} - -// Lock should be held. -func (a *Account) hasStreamExportMatching(to string) bool { - for subj := range a.exports.streams { - if subjectIsSubsetMatch(to, subj) { - return true - } - } - return false -} - -func (a *Account) checkStreamImportsForCycles(to string, visited map[string]bool) error { - if len(visited) >= MaxAccountCycleSearchDepth { - return ErrCycleSearchDepth - } - - a.mu.RLock() - - if !a.hasStreamExportMatching(to) { - a.mu.RUnlock() - return nil - } - - for _, si := range a.imports.streams { - if SubjectsCollide(to, si.to) { - a.mu.RUnlock() - if visited[si.acc.Name] { - return ErrImportFormsCycle - } - // Push ourselves and check si.acc - visited[a.Name] = true - if subjectIsSubsetMatch(si.to, to) { - to = si.to - } - if err := si.acc.checkStreamImportsForCycles(to, visited); err != nil { - return err - } - a.mu.RLock() - } - } - a.mu.RUnlock() - return nil -} - -// SetServiceImportSharing will allow sharing of information about requests with the export account. -// Used for service latency tracking at the moment. -func (a *Account) SetServiceImportSharing(destination *Account, to string, allow bool) error { - return a.setServiceImportSharing(destination, to, true, allow) -} - -// setServiceImportSharing will allow sharing of information about requests with the export account. -func (a *Account) setServiceImportSharing(destination *Account, to string, check, allow bool) error { - a.mu.Lock() - defer a.mu.Unlock() - if check && a.isClaimAccount() { - return fmt.Errorf("claim based accounts can not be updated directly") - } - for _, si := range a.imports.services { - if si.acc == destination && si.to == to { - si.share = allow - return nil - } - } - return fmt.Errorf("service import not found") -} - -// AddServiceImport will add a route to an account to send published messages / requests -// to the destination account. From is the local subject to map, To is the -// subject that will appear on the destination account. Destination will need -// to have an import rule to allow access via addService. -func (a *Account) AddServiceImport(destination *Account, from, to string) error { - return a.AddServiceImportWithClaim(destination, from, to, nil) -} - -// NumPendingReverseResponses returns the number of response mappings we have for all outstanding -// requests for service imports. -func (a *Account) NumPendingReverseResponses() int { - a.mu.RLock() - defer a.mu.RUnlock() - return len(a.imports.rrMap) -} - -// NumPendingAllResponses return the number of all responses outstanding for service exports. -func (a *Account) NumPendingAllResponses() int { - return a.NumPendingResponses(_EMPTY_) -} - -// NumPendingResponses returns the number of responses outstanding for service exports -// on this account. An empty filter string returns all responses regardless of which export. -// If you specify the filter we will only return ones that are for that export. -// NOTE this is only for what this server is tracking. -func (a *Account) NumPendingResponses(filter string) int { - a.mu.RLock() - defer a.mu.RUnlock() - if filter == _EMPTY_ { - return len(a.exports.responses) - } - se := a.getServiceExport(filter) - if se == nil { - return 0 - } - var nre int - for _, si := range a.exports.responses { - if si.se == se { - nre++ - } - } - return nre -} - -// NumServiceImports returns the number of service imports we have configured. -func (a *Account) NumServiceImports() int { - a.mu.RLock() - defer a.mu.RUnlock() - return len(a.imports.services) -} - -// Reason why we are removing this response serviceImport. -type rsiReason int - -const ( - rsiOk = rsiReason(iota) - rsiNoDelivery - rsiTimeout -) - -// removeRespServiceImport removes a response si mapping and the reverse entries for interest detection. -func (a *Account) removeRespServiceImport(si *serviceImport, reason rsiReason) { - if si == nil { - return - } - - a.mu.Lock() - c := a.ic - delete(a.exports.responses, si.from) - dest, to, tracking, rc, didDeliver := si.acc, si.to, si.tracking, si.rc, si.didDeliver - a.mu.Unlock() - - // If we have a sid make sure to unsub. - if len(si.sid) > 0 && c != nil { - c.processUnsub(si.sid) - } - - if tracking && rc != nil && !didDeliver { - a.sendBackendErrorTrackingLatency(si, reason) - } - - dest.checkForReverseEntry(to, si, false) -} - -// removeServiceImport will remove the route by subject. -func (a *Account) removeServiceImport(subject string) { - a.mu.Lock() - si, ok := a.imports.services[subject] - delete(a.imports.services, subject) - - var sid []byte - c := a.ic - - if ok && si != nil { - if a.ic != nil && si.sid != nil { - sid = si.sid - } - } - a.mu.Unlock() - - if sid != nil { - c.processUnsub(sid) - } -} - -// This tracks responses to service requests mappings. This is used for cleanup. -func (a *Account) addReverseRespMapEntry(acc *Account, reply, from string) { - a.mu.Lock() - if a.imports.rrMap == nil { - a.imports.rrMap = make(map[string][]*serviceRespEntry) - } - sre := &serviceRespEntry{acc, from} - sra := a.imports.rrMap[reply] - a.imports.rrMap[reply] = append(sra, sre) - a.mu.Unlock() -} - -// checkForReverseEntries is for when we are trying to match reverse entries to a wildcard. -// This will be called from checkForReverseEntry when the reply arg is a wildcard subject. -// This will usually be called in a go routine since we need to walk all the entries. -func (a *Account) checkForReverseEntries(reply string, checkInterest, recursed bool) { - if subjectIsLiteral(reply) { - a._checkForReverseEntry(reply, nil, checkInterest, recursed) - return - } - - a.mu.RLock() - if len(a.imports.rrMap) == 0 { - a.mu.RUnlock() - return - } - - var _rs [64]string - rs := _rs[:0] - if n := len(a.imports.rrMap); n > cap(rs) { - rs = make([]string, 0, n) - } - - for k := range a.imports.rrMap { - rs = append(rs, k) - } - a.mu.RUnlock() - - tsa := [32]string{} - tts := tokenizeSubjectIntoSlice(tsa[:0], reply) - - rsa := [32]string{} - for _, r := range rs { - rts := tokenizeSubjectIntoSlice(rsa[:0], r) - // isSubsetMatchTokenized is heavy so make sure we do this without the lock. - if isSubsetMatchTokenized(rts, tts) { - a._checkForReverseEntry(r, nil, checkInterest, recursed) - } - } -} - -// This checks for any response map entries. If you specify an si we will only match and -// clean up for that one, otherwise we remove them all. -func (a *Account) checkForReverseEntry(reply string, si *serviceImport, checkInterest bool) { - a._checkForReverseEntry(reply, si, checkInterest, false) -} - -// Callers should use checkForReverseEntry instead. This function exists to help prevent -// infinite recursion. -func (a *Account) _checkForReverseEntry(reply string, si *serviceImport, checkInterest, recursed bool) { - a.mu.RLock() - if len(a.imports.rrMap) == 0 { - a.mu.RUnlock() - return - } - - if subjectHasWildcard(reply) { - if recursed { - // If we have reached this condition then it is because the reverse entries also - // contain wildcards (that shouldn't happen but a client *could* provide an inbox - // prefix that is illegal because it ends in a wildcard character), at which point - // we will end up with infinite recursion between this func and checkForReverseEntries. - // To avoid a stack overflow panic, we'll give up instead. - a.mu.RUnlock() - return - } - - doInline := len(a.imports.rrMap) <= 64 - a.mu.RUnlock() - - if doInline { - a.checkForReverseEntries(reply, checkInterest, true) - } else { - go a.checkForReverseEntries(reply, checkInterest, true) - } - return - } - - if sres := a.imports.rrMap[reply]; sres == nil { - a.mu.RUnlock() - return - } - - // If we are here we have an entry we should check. - // If requested we will first check if there is any - // interest for this subject for the entire account. - // If there is we can not delete any entries yet. - // Note that if we are here reply has to be a literal subject. - if checkInterest { - // If interest still exists we can not clean these up yet. - if a.sl.HasInterest(reply) { - a.mu.RUnlock() - return - } - } - a.mu.RUnlock() - - // Delete the appropriate entries here based on optional si. - a.mu.Lock() - // We need a new lookup here because we have released the lock. - sres := a.imports.rrMap[reply] - if si == nil { - delete(a.imports.rrMap, reply) - } else if sres != nil { - // Find the one we are looking for.. - for i, sre := range sres { - if sre.msub == si.from { - sres = append(sres[:i], sres[i+1:]...) - break - } - } - if len(sres) > 0 { - a.imports.rrMap[si.to] = sres - } else { - delete(a.imports.rrMap, si.to) - } - } - a.mu.Unlock() - - // If we are here we no longer have interest and we have - // response entries that we should clean up. - if si == nil { - // sres is now known to have been removed from a.imports.rrMap, so we - // can safely (data race wise) iterate through. - for _, sre := range sres { - acc := sre.acc - var trackingCleanup bool - var rsi *serviceImport - acc.mu.Lock() - c := acc.ic - if rsi = acc.exports.responses[sre.msub]; rsi != nil && !rsi.didDeliver { - delete(acc.exports.responses, rsi.from) - trackingCleanup = rsi.tracking && rsi.rc != nil - } - acc.mu.Unlock() - // If we are doing explicit subs for all responses (e.g. bound to leafnode) - // we will have a non-empty sid here. - if rsi != nil && len(rsi.sid) > 0 && c != nil { - c.processUnsub(rsi.sid) - } - if trackingCleanup { - acc.sendReplyInterestLostTrackLatency(rsi) - } - } - } -} - -// Checks to see if a potential service import subject is already overshadowed. -func (a *Account) serviceImportShadowed(from string) bool { - a.mu.RLock() - defer a.mu.RUnlock() - if a.imports.services[from] != nil { - return true - } - // We did not find a direct match, so check individually. - for subj := range a.imports.services { - if subjectIsSubsetMatch(from, subj) { - return true - } - } - return false -} - -// Internal check to see if a service import exists. -func (a *Account) serviceImportExists(from string) bool { - a.mu.RLock() - dup := a.imports.services[from] - a.mu.RUnlock() - return dup != nil -} - -// Add a service import. -// This does no checks and should only be called by the msg processing code. -// Use AddServiceImport from above if responding to user input or config changes, etc. -func (a *Account) addServiceImport(dest *Account, from, to string, claim *jwt.Import) (*serviceImport, error) { - rt := Singleton - var lat *serviceLatency - - if dest == nil { - return nil, ErrMissingAccount - } - - var atrc bool - dest.mu.RLock() - se := dest.getServiceExport(to) - if se != nil { - rt = se.respType - lat = se.latency - atrc = se.atrc - } - dest.mu.RUnlock() - - a.mu.Lock() - if a.imports.services == nil { - a.imports.services = make(map[string]*serviceImport) - } else if dup := a.imports.services[from]; dup != nil { - a.mu.Unlock() - return nil, fmt.Errorf("duplicate service import subject %q, previously used in import for account %q, subject %q", - from, dup.acc.Name, dup.to) - } - - if to == _EMPTY_ { - to = from - } - // Check to see if we have a wildcard - var ( - usePub bool - tr *subjectTransform - err error - ) - - if subjectHasWildcard(to) { - // If to and from match, then we use the published subject. - if to == from { - usePub = true - } else { - to, _ = transformUntokenize(to) - // Create a transform. Do so in reverse such that $ symbols only exist in to - if tr, err = NewSubjectTransformStrict(to, transformTokenize(from)); err != nil { - a.mu.Unlock() - return nil, fmt.Errorf("failed to create mapping transform for service import subject from %q to %q: %v", - from, to, err) - } else { - // un-tokenize and reverse transform so we get the transform needed - from, _ = transformUntokenize(from) - tr = tr.reverse() - } - } - } - var share bool - if claim != nil { - share = claim.Share - } - si := &serviceImport{dest, claim, se, nil, from, to, tr, 0, rt, lat, nil, nil, usePub, false, false, share, false, false, atrc, nil} - a.imports.services[from] = si - a.mu.Unlock() - - if err := a.addServiceImportSub(si); err != nil { - a.removeServiceImport(si.from) - return nil, err - } - return si, nil -} - -// Returns the internal client, will create one if not present. -// Lock should be held. -func (a *Account) internalClient() *client { - if a.ic == nil && a.srv != nil { - a.ic = a.srv.createInternalAccountClient() - a.ic.acc = a - } - return a.ic -} - -// Internal account scoped subscriptions. -func (a *Account) subscribeInternal(subject string, cb msgHandler) (*subscription, error) { - return a.subscribeInternalEx(subject, cb, false) -} - -// Unsubscribe from an internal account subscription. -func (a *Account) unsubscribeInternal(sub *subscription) { - if ic := a.internalClient(); ic != nil { - ic.processUnsub(sub.sid) - } -} - -// Creates internal subscription for service import responses. -func (a *Account) subscribeServiceImportResponse(subject string) (*subscription, error) { - return a.subscribeInternalEx(subject, a.processServiceImportResponse, true) -} - -func (a *Account) subscribeInternalEx(subject string, cb msgHandler, ri bool) (*subscription, error) { - a.mu.Lock() - a.isid++ - c, sid := a.internalClient(), strconv.FormatUint(a.isid, 10) - a.mu.Unlock() - - // This will happen in parsing when the account has not been properly setup. - if c == nil { - return nil, fmt.Errorf("no internal account client") - } - - return c.processSubEx([]byte(subject), nil, []byte(sid), cb, false, false, ri) -} - -// This will add an account subscription that matches the "from" from a service import entry. -func (a *Account) addServiceImportSub(si *serviceImport) error { - a.mu.Lock() - c := a.internalClient() - // This will happen in parsing when the account has not been properly setup. - if c == nil { - a.mu.Unlock() - return nil - } - if si.sid != nil { - a.mu.Unlock() - return fmt.Errorf("duplicate call to create subscription for service import") - } - a.isid++ - sid := strconv.FormatUint(a.isid, 10) - si.sid = []byte(sid) - subject := si.from - a.mu.Unlock() - - cb := func(sub *subscription, c *client, acc *Account, subject, reply string, msg []byte) { - c.pa.delivered = c.processServiceImport(si, acc, msg) - } - sub, err := c.processSubEx([]byte(subject), nil, []byte(sid), cb, true, true, false) - if err != nil { - return err - } - // Leafnodes introduce a new way to introduce messages into the system. Therefore forward import subscription - // This is similar to what initLeafNodeSmapAndSendSubs does - // TODO we need to consider performing this update as we get client subscriptions. - // This behavior would result in subscription propagation only where actually used. - a.updateLeafNodes(sub, 1) - return nil -} - -// Remove all the subscriptions associated with service imports. -func (a *Account) removeAllServiceImportSubs() { - a.mu.RLock() - var sids [][]byte - for _, si := range a.imports.services { - if si.sid != nil { - sids = append(sids, si.sid) - si.sid = nil - } - } - c := a.ic - a.ic = nil - a.mu.RUnlock() - - if c == nil { - return - } - for _, sid := range sids { - c.processUnsub(sid) - } - c.closeConnection(InternalClient) -} - -// Add in subscriptions for all registered service imports. -func (a *Account) addAllServiceImportSubs() { - var sis [32]*serviceImport - serviceImports := sis[:0] - a.mu.RLock() - for _, si := range a.imports.services { - serviceImports = append(serviceImports, si) - } - a.mu.RUnlock() - for _, si := range serviceImports { - a.addServiceImportSub(si) - } -} - -var ( - // header where all information is encoded in one value. - trcUber = textproto.CanonicalMIMEHeaderKey("Uber-Trace-Id") - trcCtx = textproto.CanonicalMIMEHeaderKey("Traceparent") - trcB3 = textproto.CanonicalMIMEHeaderKey("B3") - // openzipkin header to check - trcB3Sm = textproto.CanonicalMIMEHeaderKey("X-B3-Sampled") - trcB3Id = textproto.CanonicalMIMEHeaderKey("X-B3-TraceId") - // additional header needed to include when present - trcB3PSId = textproto.CanonicalMIMEHeaderKey("X-B3-ParentSpanId") - trcB3SId = textproto.CanonicalMIMEHeaderKey("X-B3-SpanId") - trcCtxSt = textproto.CanonicalMIMEHeaderKey("Tracestate") - trcUberCtxPrefix = textproto.CanonicalMIMEHeaderKey("Uberctx-") -) - -func newB3Header(h http.Header) http.Header { - retHdr := http.Header{} - if v, ok := h[trcB3Sm]; ok { - retHdr[trcB3Sm] = v - } - if v, ok := h[trcB3Id]; ok { - retHdr[trcB3Id] = v - } - if v, ok := h[trcB3PSId]; ok { - retHdr[trcB3PSId] = v - } - if v, ok := h[trcB3SId]; ok { - retHdr[trcB3SId] = v - } - return retHdr -} - -func newUberHeader(h http.Header, tId []string) http.Header { - retHdr := http.Header{trcUber: tId} - for k, v := range h { - if strings.HasPrefix(k, trcUberCtxPrefix) { - retHdr[k] = v - } - } - return retHdr -} - -func newTraceCtxHeader(h http.Header, tId []string) http.Header { - retHdr := http.Header{trcCtx: tId} - if v, ok := h[trcCtxSt]; ok { - retHdr[trcCtxSt] = v - } - return retHdr -} - -// Helper to determine when to sample. When header has a value, sampling is driven by header -func shouldSample(l *serviceLatency, c *client) (bool, http.Header) { - if l == nil { - return false, nil - } - if l.sampling < 0 { - return false, nil - } - if l.sampling >= 100 { - return true, nil - } - if l.sampling > 0 && rand.Int31n(100) <= int32(l.sampling) { - return true, nil - } - h := c.parseState.getHeader() - if len(h) == 0 { - return false, nil - } - if tId := h[trcUber]; len(tId) != 0 { - // sample 479fefe9525eddb:5adb976bfc1f95c1:479fefe9525eddb:1 - tk := strings.Split(tId[0], ":") - if len(tk) == 4 && len(tk[3]) > 0 && len(tk[3]) <= 2 { - dst := [2]byte{} - src := [2]byte{'0', tk[3][0]} - if len(tk[3]) == 2 { - src[1] = tk[3][1] - } - if _, err := hex.Decode(dst[:], src[:]); err == nil && dst[0]&1 == 1 { - return true, newUberHeader(h, tId) - } - } - return false, nil - } else if sampled := h[trcB3Sm]; len(sampled) != 0 && sampled[0] == "1" { - return true, newB3Header(h) // allowed - } else if len(sampled) != 0 && sampled[0] == "0" { - return false, nil // denied - } else if _, ok := h[trcB3Id]; ok { - // sample 80f198ee56343ba864fe8b2a57d3eff7 - // presence (with X-B3-Sampled not being 0) means sampling left to recipient - return true, newB3Header(h) - } else if b3 := h[trcB3]; len(b3) != 0 { - // sample 80f198ee56343ba864fe8b2a57d3eff7-e457b5a2e4d86bd1-1-05e3ac9a4f6e3b90 - // sample 0 - tk := strings.Split(b3[0], "-") - if len(tk) > 2 && tk[2] == "0" { - return false, nil // denied - } else if len(tk) == 1 && tk[0] == "0" { - return false, nil // denied - } - return true, http.Header{trcB3: b3} // sampling allowed or left to recipient of header - } else if tId := h[trcCtx]; len(tId) != 0 { - var sample bool - // sample 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 - tk := strings.Split(tId[0], "-") - if len(tk) == 4 && len([]byte(tk[3])) == 2 { - if hexVal, err := strconv.ParseInt(tk[3], 16, 8); err == nil { - sample = hexVal&0x1 == 0x1 - } - } - if sample { - return true, newTraceCtxHeader(h, tId) - } else { - return false, nil - } - } - return false, nil -} - -// Used to mimic client like replies. -const ( - replyPrefix = "_R_." - replyPrefixLen = len(replyPrefix) - baseServerLen = 10 - replyLen = 6 - minReplyLen = 15 - digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - base = 62 -) - -// This is where all service export responses are handled. -func (a *Account) processServiceImportResponse(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) { - a.mu.RLock() - if a.expired.Load() || len(a.exports.responses) == 0 { - a.mu.RUnlock() - return - } - si := a.exports.responses[subject] - - if si == nil || si.invalid { - a.mu.RUnlock() - return - } - a.mu.RUnlock() - - // Send for normal processing. - c.processServiceImport(si, a, msg) -} - -// Will create the response prefix for fast generation of responses. -// A wildcard subscription may be used handle interest graph propagation -// for all service replies, unless we are bound to a leafnode. -// Lock should be held. -func (a *Account) createRespWildcard() { - var b = [baseServerLen]byte{'_', 'R', '_', '.'} - rn := fastrand.Uint64() - for i, l := replyPrefixLen, rn; i < len(b); i++ { - b[i] = digits[l%base] - l /= base - } - a.siReply = append(b[:], '.') -} - -// Test whether this is a tracked reply. -func isTrackedReply(reply []byte) bool { - lreply := len(reply) - 1 - return lreply > 3 && reply[lreply-1] == '.' && reply[lreply] == 'T' -} - -// Generate a new service reply from the wildcard prefix. -// FIXME(dlc) - probably do not have to use rand here. about 25ns per. -func (a *Account) newServiceReply(tracking bool) []byte { - a.mu.Lock() - s := a.srv - rn := fastrand.Uint64() - - // Check if we need to create the reply here. - var createdSiReply bool - if a.siReply == nil { - a.createRespWildcard() - createdSiReply = true - } - replyPre := a.siReply - a.mu.Unlock() - - // If we created the siReply and we are not bound to a leafnode - // we need to do the wildcard subscription. - if createdSiReply { - a.subscribeServiceImportResponse(string(append(replyPre, '>'))) - } - - var b [replyLen]byte - for i, l := 0, rn; i < len(b); i++ { - b[i] = digits[l%base] - l /= base - } - // Make sure to copy. - reply := make([]byte, 0, len(replyPre)+len(b)) - reply = append(reply, replyPre...) - reply = append(reply, b[:]...) - - if tracking && s.sys != nil { - // Add in our tracking identifier. This allows the metrics to get back to only - // this server without needless SUBS/UNSUBS. - reply = append(reply, '.') - reply = append(reply, s.sys.shash...) - reply = append(reply, '.', 'T') - } - - return reply -} - -// Checks if a serviceImport was created to map responses. -func (si *serviceImport) isRespServiceImport() bool { - return si != nil && si.response -} - -// Sets the response threshold timer for a service export. -// Account lock should be held -func (se *serviceExport) setResponseThresholdTimer() { - if se.rtmr != nil { - return // Already set - } - se.rtmr = time.AfterFunc(se.respThresh, se.checkExpiredResponses) -} - -// Account lock should be held -func (se *serviceExport) clearResponseThresholdTimer() bool { - if se.rtmr == nil { - return true - } - stopped := se.rtmr.Stop() - se.rtmr = nil - return stopped -} - -// checkExpiredResponses will check for any pending responses that need to -// be cleaned up. -func (se *serviceExport) checkExpiredResponses() { - acc := se.acc - if acc == nil { - se.clearResponseThresholdTimer() - return - } - - var expired []*serviceImport - mints := time.Now().UnixNano() - int64(se.respThresh) - - // TODO(dlc) - Should we release lock while doing this? Or only do these in batches? - // Should we break this up for responses only from this service export? - // Responses live on acc directly for fast inbound processsing for the _R_ wildcard. - // We could do another indirection at this level but just to get to the service export? - var totalResponses int - acc.mu.RLock() - for _, si := range acc.exports.responses { - if si.se == se { - totalResponses++ - if si.ts <= mints { - expired = append(expired, si) - } - } - } - acc.mu.RUnlock() - - for _, si := range expired { - acc.removeRespServiceImport(si, rsiTimeout) - } - - // Pull out expired to determine if we have any left for timer. - totalResponses -= len(expired) - - // Redo timer as needed. - acc.mu.Lock() - if totalResponses > 0 && se.rtmr != nil { - se.rtmr.Stop() - se.rtmr.Reset(se.respThresh) - } else { - se.clearResponseThresholdTimer() - } - acc.mu.Unlock() -} - -// ServiceExportResponseThreshold returns the current threshold. -func (a *Account) ServiceExportResponseThreshold(export string) (time.Duration, error) { - a.mu.Lock() - defer a.mu.Unlock() - se := a.getServiceExport(export) - if se == nil { - return 0, fmt.Errorf("no export defined for %q", export) - } - return se.respThresh, nil -} - -// SetServiceExportResponseThreshold sets the maximum time the system will a response to be delivered -// from a service export responder. -func (a *Account) SetServiceExportResponseThreshold(export string, maxTime time.Duration) error { - a.mu.Lock() - if a.isClaimAccount() { - a.mu.Unlock() - return fmt.Errorf("claim based accounts can not be updated directly") - } - lrt := a.lowestServiceExportResponseTime() - se := a.getServiceExport(export) - if se == nil { - a.mu.Unlock() - return fmt.Errorf("no export defined for %q", export) - } - se.respThresh = maxTime - - var clients []*client - nlrt := a.lowestServiceExportResponseTime() - if nlrt != lrt && len(a.clients) > 0 { - clients = a.getClientsLocked() - } - // Need to release because lock ordering is client -> Account - a.mu.Unlock() - if len(clients) > 0 { - updateAllClientsServiceExportResponseTime(clients, nlrt) - } - return nil -} - -func (a *Account) SetServiceExportAllowTrace(export string, allowTrace bool) error { - a.mu.Lock() - se := a.getServiceExport(export) - if se == nil { - a.mu.Unlock() - return fmt.Errorf("no export defined for %q", export) - } - se.atrc = allowTrace - a.mu.Unlock() - return nil -} - -// This is for internal service import responses. -func (a *Account) addRespServiceImport(dest *Account, to string, osi *serviceImport, tracking bool, header http.Header) *serviceImport { - nrr := string(osi.acc.newServiceReply(tracking)) - - a.mu.Lock() - rt := osi.rt - - // dest is the requestor's account. a is the service responder with the export. - // Marked as internal here, that is how we distinguish. - si := &serviceImport{dest, nil, osi.se, nil, nrr, to, nil, 0, rt, nil, nil, nil, false, true, false, osi.share, false, false, false, nil} - - if a.exports.responses == nil { - a.exports.responses = make(map[string]*serviceImport) - } - a.exports.responses[nrr] = si - - // Always grab time and make sure response threshold timer is running. - si.ts = time.Now().UnixNano() - if osi.se != nil { - osi.se.setResponseThresholdTimer() - } - - if rt == Singleton && tracking { - si.latency = osi.latency - si.tracking = true - si.trackingHdr = header - } - a.mu.Unlock() - - // We do add in the reverse map such that we can detect loss of interest and do proper - // cleanup of this si as interest goes away. - dest.addReverseRespMapEntry(a, to, nrr) - - return si -} - -// AddStreamImportWithClaim will add in the stream import from a specific account with optional token. -func (a *Account) AddStreamImportWithClaim(account *Account, from, prefix string, imClaim *jwt.Import) error { - return a.addStreamImportWithClaim(account, from, prefix, false, imClaim) -} - -func (a *Account) addStreamImportWithClaim(account *Account, from, prefix string, allowTrace bool, imClaim *jwt.Import) error { - if account == nil { - return ErrMissingAccount - } - - // First check to see if the account has authorized export of the subject. - if !account.checkStreamImportAuthorized(a, from, imClaim) { - return ErrStreamImportAuthorization - } - - // Check prefix if it exists and make sure its a literal. - // Append token separator if not already present. - if prefix != _EMPTY_ { - // Make sure there are no wildcards here, this prefix needs to be a literal - // since it will be prepended to a publish subject. - if !subjectIsLiteral(prefix) { - return ErrStreamImportBadPrefix - } - if prefix[len(prefix)-1] != btsep { - prefix = prefix + string(btsep) - } - } - - return a.addMappedStreamImportWithClaim(account, from, prefix+from, allowTrace, imClaim) -} - -// AddMappedStreamImport helper for AddMappedStreamImportWithClaim -func (a *Account) AddMappedStreamImport(account *Account, from, to string) error { - return a.AddMappedStreamImportWithClaim(account, from, to, nil) -} - -// AddMappedStreamImportWithClaim will add in the stream import from a specific account with optional token. -func (a *Account) AddMappedStreamImportWithClaim(account *Account, from, to string, imClaim *jwt.Import) error { - return a.addMappedStreamImportWithClaim(account, from, to, false, imClaim) -} - -func (a *Account) addMappedStreamImportWithClaim(account *Account, from, to string, allowTrace bool, imClaim *jwt.Import) error { - if account == nil { - return ErrMissingAccount - } - - // First check to see if the account has authorized export of the subject. - if !account.checkStreamImportAuthorized(a, from, imClaim) { - return ErrStreamImportAuthorization - } - - if to == _EMPTY_ { - to = from - } - - // Check if this forms a cycle. - if err := a.streamImportFormsCycle(account, to); err != nil { - return err - } - - if err := a.streamImportFormsCycle(account, from); err != nil { - return err - } - - var ( - usePub bool - tr *subjectTransform - err error - ) - if subjectHasWildcard(from) { - if to == from { - usePub = true - } else { - // Create a transform - if tr, err = NewSubjectTransformStrict(from, transformTokenize(to)); err != nil { - return fmt.Errorf("failed to create mapping transform for stream import subject from %q to %q: %v", - from, to, err) - } - to, _ = transformUntokenize(to) - } - } - - a.mu.Lock() - if a.isStreamImportDuplicate(account, from) { - a.mu.Unlock() - return ErrStreamImportDuplicate - } - if imClaim != nil { - allowTrace = imClaim.AllowTrace - } - a.imports.streams = append(a.imports.streams, &streamImport{account, from, to, tr, nil, imClaim, usePub, false, allowTrace}) - a.mu.Unlock() - return nil -} - -// isStreamImportDuplicate checks for duplicate. -// Lock should be held. -func (a *Account) isStreamImportDuplicate(acc *Account, from string) bool { - for _, si := range a.imports.streams { - if si.acc == acc && si.from == from { - return true - } - } - return false -} - -// AddStreamImport will add in the stream import from a specific account. -func (a *Account) AddStreamImport(account *Account, from, prefix string) error { - return a.addStreamImportWithClaim(account, from, prefix, false, nil) -} - -// IsPublicExport is a placeholder to denote a public export. -var IsPublicExport = []*Account(nil) - -// AddStreamExport will add an export to the account. If accounts is nil -// it will signify a public export, meaning anyone can import. -func (a *Account) AddStreamExport(subject string, accounts []*Account) error { - return a.addStreamExportWithAccountPos(subject, accounts, 0) -} - -// AddStreamExport will add an export to the account. If accounts is nil -// it will signify a public export, meaning anyone can import. -// if accountPos is > 0, all imports will be granted where the following holds: -// strings.Split(subject, tsep)[accountPos] == account id will be granted. -func (a *Account) addStreamExportWithAccountPos(subject string, accounts []*Account, accountPos uint) error { - if a == nil { - return ErrMissingAccount - } - - a.mu.Lock() - defer a.mu.Unlock() - - if a.exports.streams == nil { - a.exports.streams = make(map[string]*streamExport) - } - ea := a.exports.streams[subject] - if accounts != nil || accountPos > 0 { - if ea == nil { - ea = &streamExport{} - } - if err := setExportAuth(&ea.exportAuth, subject, accounts, accountPos); err != nil { - return err - } - } - a.exports.streams[subject] = ea - return nil -} - -// Check if another account is authorized to import from us. -func (a *Account) checkStreamImportAuthorized(account *Account, subject string, imClaim *jwt.Import) bool { - // Find the subject in the exports list. - a.mu.RLock() - auth := a.checkStreamImportAuthorizedNoLock(account, subject, imClaim) - a.mu.RUnlock() - return auth -} - -func (a *Account) checkStreamImportAuthorizedNoLock(account *Account, subject string, imClaim *jwt.Import) bool { - if a.exports.streams == nil || !IsValidSubject(subject) { - return false - } - return a.checkStreamExportApproved(account, subject, imClaim) -} - -func (a *Account) checkAuth(ea *exportAuth, account *Account, imClaim *jwt.Import, tokens []string) bool { - // if ea is nil or ea.approved is nil, that denotes a public export - if ea == nil || (len(ea.approved) == 0 && !ea.tokenReq && ea.accountPos == 0) { - return true - } - // Check if the export is protected and enforces presence of importing account identity - if ea.accountPos > 0 { - return ea.accountPos <= uint(len(tokens)) && tokens[ea.accountPos-1] == account.Name - } - // Check if token required - if ea.tokenReq { - return a.checkActivation(account, imClaim, ea, true) - } - if ea.approved == nil { - return false - } - // If we have a matching account we are authorized - _, ok := ea.approved[account.Name] - return ok -} - -func (a *Account) checkStreamExportApproved(account *Account, subject string, imClaim *jwt.Import) bool { - // Check direct match of subject first - ea, ok := a.exports.streams[subject] - if ok { - // if ea is nil or eq.approved is nil, that denotes a public export - if ea == nil { - return true - } - return a.checkAuth(&ea.exportAuth, account, imClaim, nil) - } - - // ok if we are here we did not match directly so we need to test each one. - // The import subject arg has to take precedence, meaning the export - // has to be a true subset of the import claim. We already checked for - // exact matches above. - tokens := strings.Split(subject, tsep) - for subj, ea := range a.exports.streams { - if isSubsetMatch(tokens, subj) { - if ea == nil { - return true - } - return a.checkAuth(&ea.exportAuth, account, imClaim, tokens) - } - } - return false -} - -func (a *Account) checkServiceExportApproved(account *Account, subject string, imClaim *jwt.Import) bool { - // Check direct match of subject first - se, ok := a.exports.services[subject] - if ok { - // if se is nil or eq.approved is nil, that denotes a public export - if se == nil { - return true - } - return a.checkAuth(&se.exportAuth, account, imClaim, nil) - } - // ok if we are here we did not match directly so we need to test each one. - // The import subject arg has to take precedence, meaning the export - // has to be a true subset of the import claim. We already checked for - // exact matches above. - tokens := strings.Split(subject, tsep) - for subj, se := range a.exports.services { - if isSubsetMatch(tokens, subj) { - if se == nil { - return true - } - return a.checkAuth(&se.exportAuth, account, imClaim, tokens) - } - } - return false -} - -// Helper function to get a serviceExport. -// Lock should be held on entry. -func (a *Account) getServiceExport(subj string) *serviceExport { - se, ok := a.exports.services[subj] - // The export probably has a wildcard, so lookup that up. - if !ok { - se = a.getWildcardServiceExport(subj) - } - return se -} - -// This helper is used when trying to match a serviceExport record that is -// represented by a wildcard. -// Lock should be held on entry. -func (a *Account) getWildcardServiceExport(from string) *serviceExport { - tokens := strings.Split(from, tsep) - for subj, se := range a.exports.services { - if isSubsetMatch(tokens, subj) { - return se - } - } - return nil -} - -// These are import stream specific versions for when an activation expires. -func (a *Account) streamActivationExpired(exportAcc *Account, subject string) { - a.mu.RLock() - if a.expired.Load() || a.imports.streams == nil { - a.mu.RUnlock() - return - } - var si *streamImport - for _, si = range a.imports.streams { - if si.acc == exportAcc && si.from == subject { - break - } - } - - if si == nil || si.invalid { - a.mu.RUnlock() - return - } - a.mu.RUnlock() - - if si.acc.checkActivation(a, si.claim, nil, false) { - // The token has been updated most likely and we are good to go. - return - } - - a.mu.Lock() - si.invalid = true - clients := a.getClientsLocked() - awcsti := map[string]struct{}{a.Name: {}} - a.mu.Unlock() - for _, c := range clients { - c.processSubsOnConfigReload(awcsti) - } -} - -// These are import service specific versions for when an activation expires. -func (a *Account) serviceActivationExpired(subject string) { - a.mu.RLock() - if a.expired.Load() || a.imports.services == nil { - a.mu.RUnlock() - return - } - si := a.imports.services[subject] - if si == nil || si.invalid { - a.mu.RUnlock() - return - } - a.mu.RUnlock() - - if si.acc.checkActivation(a, si.claim, nil, false) { - // The token has been updated most likely and we are good to go. - return - } - - a.mu.Lock() - si.invalid = true - a.mu.Unlock() -} - -// Fires for expired activation tokens. We could track this with timers etc. -// Instead we just re-analyze where we are and if we need to act. -func (a *Account) activationExpired(exportAcc *Account, subject string, kind jwt.ExportType) { - switch kind { - case jwt.Stream: - a.streamActivationExpired(exportAcc, subject) - case jwt.Service: - a.serviceActivationExpired(subject) - } -} - -func isRevoked(revocations map[string]int64, subject string, issuedAt int64) bool { - if len(revocations) == 0 { - return false - } - if t, ok := revocations[subject]; !ok || t < issuedAt { - if t, ok := revocations[jwt.All]; !ok || t < issuedAt { - return false - } - } - return true -} - -// checkActivation will check the activation token for validity. -// ea may only be nil in cases where revocation may not be checked, say triggered by expiration timer. -func (a *Account) checkActivation(importAcc *Account, claim *jwt.Import, ea *exportAuth, expTimer bool) bool { - if claim == nil || claim.Token == _EMPTY_ { - return false - } - // Create a quick clone so we can inline Token JWT. - clone := *claim - - vr := jwt.CreateValidationResults() - clone.Validate(importAcc.Name, vr) - if vr.IsBlocking(true) { - return false - } - act, err := jwt.DecodeActivationClaims(clone.Token) - if err != nil { - return false - } - if !a.isIssuerClaimTrusted(act) { - return false - } - vr = jwt.CreateValidationResults() - act.Validate(vr) - if vr.IsBlocking(true) { - return false - } - if act.Expires != 0 { - tn := time.Now().Unix() - if act.Expires <= tn { - return false - } - if expTimer { - expiresAt := time.Duration(act.Expires - tn) - time.AfterFunc(expiresAt*time.Second, func() { - importAcc.activationExpired(a, string(act.ImportSubject), claim.Type) - }) - } - } - if ea == nil { - return true - } - // Check for token revocation.. - return !isRevoked(ea.actsRevoked, act.Subject, act.IssuedAt) -} - -// Returns true if the activation claim is trusted. That is the issuer matches -// the account or is an entry in the signing keys. -func (a *Account) isIssuerClaimTrusted(claims *jwt.ActivationClaims) bool { - // if no issuer account, issuer is the account - if claims.IssuerAccount == _EMPTY_ { - return true - } - // If the IssuerAccount is not us, then this is considered an error. - if a.Name != claims.IssuerAccount { - if a.srv != nil { - a.srv.Errorf("Invalid issuer account %q in activation claim (subject: %q - type: %q) for account %q", - claims.IssuerAccount, claims.Activation.ImportSubject, claims.Activation.ImportType, a.Name) - } - return false - } - _, ok := a.hasIssuerNoLock(claims.Issuer) - return ok -} - -// Returns true if `a` and `b` stream imports are the same. Note that the -// check is done with the account's name, not the pointer. This is used -// during config reload where we are comparing current and new config -// in which pointers are different. -// Acquires `a` read lock, but `b` is assumed to not be accessed -// by anyone but the caller (`b` is not registered anywhere). -func (a *Account) checkStreamImportsEqual(b *Account) bool { - a.mu.RLock() - defer a.mu.RUnlock() - - if len(a.imports.streams) != len(b.imports.streams) { - return false - } - // Load the b imports into a map index by what we are looking for. - bm := make(map[string]*streamImport, len(b.imports.streams)) - for _, bim := range b.imports.streams { - bm[bim.acc.Name+bim.from+bim.to] = bim - } - for _, aim := range a.imports.streams { - if bim, ok := bm[aim.acc.Name+aim.from+aim.to]; !ok { - return false - } else if aim.atrc != bim.atrc { - return false - } - } - return true -} - -// Returns true if `a` and `b` stream exports are the same. -// Acquires `a` read lock, but `b` is assumed to not be accessed -// by anyone but the caller (`b` is not registered anywhere). -func (a *Account) checkStreamExportsEqual(b *Account) bool { - a.mu.RLock() - defer a.mu.RUnlock() - if len(a.exports.streams) != len(b.exports.streams) { - return false - } - for subj, aea := range a.exports.streams { - bea, ok := b.exports.streams[subj] - if !ok { - return false - } - if !isStreamExportEqual(aea, bea) { - return false - } - } - return true -} - -func isStreamExportEqual(a, b *streamExport) bool { - if a == nil && b == nil { - return true - } - if (a == nil && b != nil) || (a != nil && b == nil) { - return false - } - return isExportAuthEqual(&a.exportAuth, &b.exportAuth) -} - -// Returns true if `a` and `b` service exports are the same. -// Acquires `a` read lock, but `b` is assumed to not be accessed -// by anyone but the caller (`b` is not registered anywhere). -func (a *Account) checkServiceExportsEqual(b *Account) bool { - a.mu.RLock() - defer a.mu.RUnlock() - if len(a.exports.services) != len(b.exports.services) { - return false - } - for subj, aea := range a.exports.services { - bea, ok := b.exports.services[subj] - if !ok { - return false - } - if !isServiceExportEqual(aea, bea) { - return false - } - } - return true -} - -func isServiceExportEqual(a, b *serviceExport) bool { - if a == nil && b == nil { - return true - } - if (a == nil && b != nil) || (a != nil && b == nil) { - return false - } - if !isExportAuthEqual(&a.exportAuth, &b.exportAuth) { - return false - } - if a.acc.Name != b.acc.Name { - return false - } - if a.respType != b.respType { - return false - } - if a.latency != nil || b.latency != nil { - if (a.latency != nil && b.latency == nil) || (a.latency == nil && b.latency != nil) { - return false - } - if a.latency.sampling != b.latency.sampling { - return false - } - if a.latency.subject != b.latency.subject { - return false - } - } - if a.atrc != b.atrc { - return false - } - return true -} - -// Returns true if `a` and `b` exportAuth structures are equal. -// Both `a` and `b` are guaranteed to be non-nil. -// Locking is handled by the caller. -func isExportAuthEqual(a, b *exportAuth) bool { - if a.tokenReq != b.tokenReq { - return false - } - if a.accountPos != b.accountPos { - return false - } - if len(a.approved) != len(b.approved) { - return false - } - for ak, av := range a.approved { - if bv, ok := b.approved[ak]; !ok || av.Name != bv.Name { - return false - } - } - if len(a.actsRevoked) != len(b.actsRevoked) { - return false - } - for ak, av := range a.actsRevoked { - if bv, ok := b.actsRevoked[ak]; !ok || av != bv { - return false - } - } - return true -} - -// Check if another account is authorized to route requests to this service. -func (a *Account) checkServiceImportAuthorized(account *Account, subject string, imClaim *jwt.Import) bool { - a.mu.RLock() - authorized := a.checkServiceImportAuthorizedNoLock(account, subject, imClaim) - a.mu.RUnlock() - return authorized -} - -// Check if another account is authorized to route requests to this service. -func (a *Account) checkServiceImportAuthorizedNoLock(account *Account, subject string, imClaim *jwt.Import) bool { - // Find the subject in the services list. - if a.exports.services == nil { - return false - } - return a.checkServiceExportApproved(account, subject, imClaim) -} - -// IsExpired returns expiration status. -func (a *Account) IsExpired() bool { - return a.expired.Load() -} - -// Called when an account has expired. -func (a *Account) expiredTimeout() { - // Mark expired first. - a.expired.Store(true) - - // Collect the clients and expire them. - cs := a.getClients() - for _, c := range cs { - c.accountAuthExpired() - } -} - -// Sets the expiration timer for an account JWT that has it set. -func (a *Account) setExpirationTimer(d time.Duration) { - a.etmr = time.AfterFunc(d, a.expiredTimeout) -} - -// Lock should be held -func (a *Account) clearExpirationTimer() bool { - if a.etmr == nil { - return true - } - stopped := a.etmr.Stop() - a.etmr = nil - return stopped -} - -// checkUserRevoked will check if a user has been revoked. -func (a *Account) checkUserRevoked(nkey string, issuedAt int64) bool { - a.mu.RLock() - defer a.mu.RUnlock() - return isRevoked(a.usersRevoked, nkey, issuedAt) -} - -// failBearer will return if bearer token are allowed (false) or disallowed (true) -func (a *Account) failBearer() bool { - a.mu.RLock() - defer a.mu.RUnlock() - return a.disallowBearer -} - -// Check expiration and set the proper state as needed. -func (a *Account) checkExpiration(claims *jwt.ClaimsData) { - a.mu.Lock() - defer a.mu.Unlock() - - a.clearExpirationTimer() - if claims.Expires == 0 { - a.expired.Store(false) - return - } - tn := time.Now().Unix() - if claims.Expires <= tn { - a.expired.Store(true) - return - } - expiresAt := time.Duration(claims.Expires - tn) - a.setExpirationTimer(expiresAt * time.Second) - a.expired.Store(false) -} - -// hasIssuer returns true if the issuer matches the account -// If the issuer is a scoped signing key, the scope will be returned as well -// issuer or it is a signing key for the account. -func (a *Account) hasIssuer(issuer string) (jwt.Scope, bool) { - a.mu.RLock() - scope, ok := a.hasIssuerNoLock(issuer) - a.mu.RUnlock() - return scope, ok -} - -// hasIssuerNoLock is the unlocked version of hasIssuer -func (a *Account) hasIssuerNoLock(issuer string) (jwt.Scope, bool) { - scope, ok := a.signingKeys[issuer] - return scope, ok -} - -// Returns the loop detection subject used for leafnodes -func (a *Account) getLDSubject() string { - a.mu.RLock() - lds := a.lds - a.mu.RUnlock() - return lds -} - -// Placeholder for signaling token auth required. -var tokenAuthReq = []*Account{} - -func authAccounts(tokenReq bool) []*Account { - if tokenReq { - return tokenAuthReq - } - return nil -} - -// SetAccountResolver will assign the account resolver. -func (s *Server) SetAccountResolver(ar AccountResolver) { - s.mu.Lock() - s.accResolver = ar - s.mu.Unlock() -} - -// AccountResolver returns the registered account resolver. -func (s *Server) AccountResolver() AccountResolver { - s.mu.RLock() - ar := s.accResolver - s.mu.RUnlock() - return ar -} - -// isClaimAccount returns if this account is backed by a JWT claim. -// Lock should be held. -func (a *Account) isClaimAccount() bool { - return a.claimJWT != _EMPTY_ -} - -// UpdateAccountClaims will update an existing account with new claims. -// This will replace any exports or imports previously defined. -// Lock MUST NOT be held upon entry. -func (s *Server) UpdateAccountClaims(a *Account, ac *jwt.AccountClaims) { - s.updateAccountClaimsWithRefresh(a, ac, true) -} - -func (a *Account) traceLabel() string { - if a == nil { - return _EMPTY_ - } - if a.nameTag != _EMPTY_ { - return fmt.Sprintf("%s/%s", a.Name, a.nameTag) - } - return a.Name -} - -// Check if an account has external auth set. -// Operator/Account Resolver only. -func (a *Account) hasExternalAuth() bool { - if a == nil { - return false - } - a.mu.RLock() - defer a.mu.RUnlock() - return a.extAuth != nil -} - -// Deterimine if this is an external auth user. -func (a *Account) isExternalAuthUser(userID string) bool { - if a == nil { - return false - } - a.mu.RLock() - defer a.mu.RUnlock() - if a.extAuth != nil { - for _, u := range a.extAuth.AuthUsers { - if userID == u { - return true - } - } - } - return false -} - -// Return the external authorization xkey if external authorization is enabled and the xkey is set. -// Operator/Account Resolver only. -func (a *Account) externalAuthXKey() string { - if a == nil { - return _EMPTY_ - } - a.mu.RLock() - defer a.mu.RUnlock() - if a.extAuth != nil && a.extAuth.XKey != _EMPTY_ { - return a.extAuth.XKey - } - return _EMPTY_ -} - -// Check if an account switch for external authorization is allowed. -func (a *Account) isAllowedAcount(acc string) bool { - if a == nil { - return false - } - a.mu.RLock() - defer a.mu.RUnlock() - if a.extAuth != nil { - // if we have a single allowed account, and we have a wildcard - // we accept it - if len(a.extAuth.AllowedAccounts) == 1 && - a.extAuth.AllowedAccounts[0] == jwt.AnyAccount { - return true - } - // otherwise must match exactly - for _, a := range a.extAuth.AllowedAccounts { - if a == acc { - return true - } - } - } - return false -} - -// updateAccountClaimsWithRefresh will update an existing account with new claims. -// If refreshImportingAccounts is true it will also update incomplete dependent accounts -// This will replace any exports or imports previously defined. -// Lock MUST NOT be held upon entry. -func (s *Server) updateAccountClaimsWithRefresh(a *Account, ac *jwt.AccountClaims, refreshImportingAccounts bool) { - if a == nil { - return - } - s.Debugf("Updating account claims: %s/%s", a.Name, ac.Name) - a.checkExpiration(ac.Claims()) - - a.mu.Lock() - // Clone to update, only select certain fields. - old := &Account{Name: a.Name, exports: a.exports, limits: a.limits, signingKeys: a.signingKeys} - - // overwrite claim meta data - a.nameTag = ac.Name - a.tags = ac.Tags - - // Grab trace label under lock. - tl := a.traceLabel() - - var td string - var tds int - if ac.Trace != nil { - // Update trace destination and sampling - td, tds = string(ac.Trace.Destination), ac.Trace.Sampling - if !IsValidPublishSubject(td) { - td, tds = _EMPTY_, 0 - } else if tds <= 0 || tds > 100 { - tds = 100 - } - } - a.traceDest, a.traceDestSampling = td, tds - - // Check for external authorization. - if ac.HasExternalAuthorization() { - a.extAuth = &jwt.ExternalAuthorization{} - a.extAuth.AuthUsers.Add(ac.Authorization.AuthUsers...) - a.extAuth.AllowedAccounts.Add(ac.Authorization.AllowedAccounts...) - a.extAuth.XKey = ac.Authorization.XKey - } - - // Reset exports and imports here. - - // Exports is creating a whole new map. - a.exports = exportMap{} - - // Imports are checked unlocked in processInbound, so we can't change out the struct here. Need to process inline. - if a.imports.streams != nil { - old.imports.streams = a.imports.streams - a.imports.streams = nil - } - if a.imports.services != nil { - old.imports.services = make(map[string]*serviceImport, len(a.imports.services)) - for k, v := range a.imports.services { - old.imports.services[k] = v - delete(a.imports.services, k) - } - } - - alteredScope := map[string]struct{}{} - - // update account signing keys - a.signingKeys = nil - _, strict := s.strictSigningKeyUsage[a.Issuer] - if len(ac.SigningKeys) > 0 || !strict { - a.signingKeys = make(map[string]jwt.Scope) - } - signersChanged := false - for k, scope := range ac.SigningKeys { - a.signingKeys[k] = scope - } - if !strict { - a.signingKeys[a.Name] = nil - } - if len(a.signingKeys) != len(old.signingKeys) { - signersChanged = true - } - for k, scope := range a.signingKeys { - if oldScope, ok := old.signingKeys[k]; !ok { - signersChanged = true - } else if !reflect.DeepEqual(scope, oldScope) { - signersChanged = true - alteredScope[k] = struct{}{} - } - } - // collect mappings that need to be removed - removeList := []string{} - for _, m := range a.mappings { - if _, ok := ac.Mappings[jwt.Subject(m.src)]; !ok { - removeList = append(removeList, m.src) - } - } - a.mu.Unlock() - - for sub, wm := range ac.Mappings { - mappings := make([]*MapDest, len(wm)) - for i, m := range wm { - mappings[i] = &MapDest{ - Subject: string(m.Subject), - Weight: m.GetWeight(), - Cluster: m.Cluster, - } - } - // This will overwrite existing entries - a.AddWeightedMappings(string(sub), mappings...) - } - // remove mappings - for _, rmMapping := range removeList { - a.RemoveMapping(rmMapping) - } - - // Re-register system exports/imports. - if a == s.SystemAccount() { - s.addSystemAccountExports(a) - } else { - s.registerSystemImports(a) - } - - jsEnabled := s.JetStreamEnabled() - - streamTokenExpirationChanged := false - serviceTokenExpirationChanged := false - - for _, e := range ac.Exports { - switch e.Type { - case jwt.Stream: - s.Debugf("Adding stream export %q for %s", e.Subject, tl) - if err := a.addStreamExportWithAccountPos( - string(e.Subject), authAccounts(e.TokenReq), e.AccountTokenPosition); err != nil { - s.Debugf("Error adding stream export to account [%s]: %v", tl, err.Error()) - } - case jwt.Service: - s.Debugf("Adding service export %q for %s", e.Subject, tl) - rt := Singleton - switch e.ResponseType { - case jwt.ResponseTypeStream: - rt = Streamed - case jwt.ResponseTypeChunked: - rt = Chunked - } - if err := a.addServiceExportWithResponseAndAccountPos( - string(e.Subject), rt, authAccounts(e.TokenReq), e.AccountTokenPosition); err != nil { - s.Debugf("Error adding service export to account [%s]: %v", tl, err) - continue - } - sub := string(e.Subject) - if e.Latency != nil { - if err := a.TrackServiceExportWithSampling(sub, string(e.Latency.Results), int(e.Latency.Sampling)); err != nil { - hdrNote := _EMPTY_ - if e.Latency.Sampling == jwt.Headers { - hdrNote = " (using headers)" - } - s.Debugf("Error adding latency tracking%s for service export to account [%s]: %v", hdrNote, tl, err) - } - } - if e.ResponseThreshold != 0 { - // Response threshold was set in options. - if err := a.SetServiceExportResponseThreshold(sub, e.ResponseThreshold); err != nil { - s.Debugf("Error adding service export response threshold for [%s]: %v", tl, err) - } - } - if err := a.SetServiceExportAllowTrace(sub, e.AllowTrace); err != nil { - s.Debugf("Error adding allow_trace for %q: %v", sub, err) - } - } - - var revocationChanged *bool - var ea *exportAuth - - a.mu.Lock() - switch e.Type { - case jwt.Stream: - revocationChanged = &streamTokenExpirationChanged - if se, ok := a.exports.streams[string(e.Subject)]; ok && se != nil { - ea = &se.exportAuth - } - case jwt.Service: - revocationChanged = &serviceTokenExpirationChanged - if se, ok := a.exports.services[string(e.Subject)]; ok && se != nil { - ea = &se.exportAuth - } - } - if ea != nil { - oldRevocations := ea.actsRevoked - if len(e.Revocations) == 0 { - // remove all, no need to evaluate existing imports - ea.actsRevoked = nil - } else if len(oldRevocations) == 0 { - // add all, existing imports need to be re evaluated - ea.actsRevoked = e.Revocations - *revocationChanged = true - } else { - ea.actsRevoked = e.Revocations - // diff, existing imports need to be conditionally re evaluated, depending on: - // if a key was added, or it's timestamp increased - for k, t := range e.Revocations { - if tOld, ok := oldRevocations[k]; !ok || tOld < t { - *revocationChanged = true - } - } - } - } - a.mu.Unlock() - } - var incompleteImports []*jwt.Import - for _, i := range ac.Imports { - acc, err := s.lookupAccount(i.Account) - if acc == nil || err != nil { - s.Errorf("Can't locate account [%s] for import of [%v] %s (err=%v)", i.Account, i.Subject, i.Type, err) - incompleteImports = append(incompleteImports, i) - continue - } - // Capture trace labels. - acc.mu.RLock() - atl := acc.traceLabel() - acc.mu.RUnlock() - // Grab from and to - from, to := string(i.Subject), i.GetTo() - switch i.Type { - case jwt.Stream: - if i.LocalSubject != _EMPTY_ { - // set local subject implies to is empty - to = string(i.LocalSubject) - s.Debugf("Adding stream import %s:%q for %s:%q", atl, from, tl, to) - err = a.AddMappedStreamImportWithClaim(acc, from, to, i) - } else { - s.Debugf("Adding stream import %s:%q for %s:%q", atl, from, tl, to) - err = a.AddStreamImportWithClaim(acc, from, to, i) - } - if err != nil { - s.Debugf("Error adding stream import to account [%s]: %v", tl, err.Error()) - incompleteImports = append(incompleteImports, i) - } - case jwt.Service: - if i.LocalSubject != _EMPTY_ { - from = string(i.LocalSubject) - to = string(i.Subject) - } - s.Debugf("Adding service import %s:%q for %s:%q", atl, from, tl, to) - if err := a.AddServiceImportWithClaim(acc, from, to, i); err != nil { - s.Debugf("Error adding service import to account [%s]: %v", tl, err.Error()) - incompleteImports = append(incompleteImports, i) - } - } - } - // Now let's apply any needed changes from import/export changes. - if !a.checkStreamImportsEqual(old) { - awcsti := map[string]struct{}{a.Name: {}} - for _, c := range a.getClients() { - c.processSubsOnConfigReload(awcsti) - } - } - // Now check if stream exports have changed. - if !a.checkStreamExportsEqual(old) || signersChanged || streamTokenExpirationChanged { - clients := map[*client]struct{}{} - // We need to check all accounts that have an import claim from this account. - awcsti := map[string]struct{}{} - s.accounts.Range(func(k, v any) bool { - acc := v.(*Account) - // Move to the next if this account is actually account "a". - if acc.Name == a.Name { - return true - } - // TODO: checkStreamImportAuthorized() stack should not be trying - // to lock "acc". If we find that to be needed, we will need to - // rework this to ensure we don't lock acc. - acc.mu.Lock() - for _, im := range acc.imports.streams { - if im != nil && im.acc.Name == a.Name { - // Check for if we are still authorized for an import. - im.invalid = !a.checkStreamImportAuthorized(acc, im.from, im.claim) - awcsti[acc.Name] = struct{}{} - for c := range acc.clients { - clients[c] = struct{}{} - } - } - } - acc.mu.Unlock() - return true - }) - // Now walk clients. - for c := range clients { - c.processSubsOnConfigReload(awcsti) - } - } - // Now check if service exports have changed. - if !a.checkServiceExportsEqual(old) || signersChanged || serviceTokenExpirationChanged { - s.accounts.Range(func(k, v any) bool { - acc := v.(*Account) - // Move to the next if this account is actually account "a". - if acc.Name == a.Name { - return true - } - // TODO: checkServiceImportAuthorized() stack should not be trying - // to lock "acc". If we find that to be needed, we will need to - // rework this to ensure we don't lock acc. - acc.mu.Lock() - for _, si := range acc.imports.services { - if si != nil && si.acc.Name == a.Name { - // Check for if we are still authorized for an import. - si.invalid = !a.checkServiceImportAuthorized(acc, si.to, si.claim) - // Make sure we should still be tracking latency and if we - // are allowed to trace. - if !si.response { - if se := a.getServiceExport(si.to); se != nil { - if si.latency != nil { - si.latency = se.latency - } - // Update allow trace. - si.atrc = se.atrc - } - } - } - } - acc.mu.Unlock() - return true - }) - } - - // Now make sure we shutdown the old service import subscriptions. - var sids [][]byte - a.mu.RLock() - c := a.ic - for _, si := range old.imports.services { - if c != nil && si.sid != nil { - sids = append(sids, si.sid) - } - } - a.mu.RUnlock() - for _, sid := range sids { - c.processUnsub(sid) - } - - // Now do limits if they are present. - a.mu.Lock() - a.msubs = int32(ac.Limits.Subs) - a.mpay = int32(ac.Limits.Payload) - a.mconns = int32(ac.Limits.Conn) - a.mleafs = int32(ac.Limits.LeafNodeConn) - a.disallowBearer = ac.Limits.DisallowBearer - // Check for any revocations - if len(ac.Revocations) > 0 { - // We will always replace whatever we had with most current, so no - // need to look at what we have. - a.usersRevoked = make(map[string]int64, len(ac.Revocations)) - for pk, t := range ac.Revocations { - a.usersRevoked[pk] = t - } - } else { - a.usersRevoked = nil - } - a.defaultPerms = buildPermissionsFromJwt(&ac.DefaultPermissions) - a.incomplete = len(incompleteImports) != 0 - for _, i := range incompleteImports { - s.incompleteAccExporterMap.Store(i.Account, struct{}{}) - } - if a.srv == nil { - a.srv = s - } - - if ac.Limits.IsJSEnabled() { - toUnlimited := func(value int64) int64 { - if value > 0 { - return value - } - return -1 - } - if ac.Limits.JetStreamLimits.DiskStorage != 0 || ac.Limits.JetStreamLimits.MemoryStorage != 0 { - // JetStreamAccountLimits and jwt.JetStreamLimits use same value for unlimited - a.jsLimits = map[string]JetStreamAccountLimits{ - _EMPTY_: { - MaxMemory: ac.Limits.JetStreamLimits.MemoryStorage, - MaxStore: ac.Limits.JetStreamLimits.DiskStorage, - MaxStreams: int(ac.Limits.JetStreamLimits.Streams), - MaxConsumers: int(ac.Limits.JetStreamLimits.Consumer), - MemoryMaxStreamBytes: toUnlimited(ac.Limits.JetStreamLimits.MemoryMaxStreamBytes), - StoreMaxStreamBytes: toUnlimited(ac.Limits.JetStreamLimits.DiskMaxStreamBytes), - MaxBytesRequired: ac.Limits.JetStreamLimits.MaxBytesRequired, - MaxAckPending: int(toUnlimited(ac.Limits.JetStreamLimits.MaxAckPending)), - }, - } - } else { - a.jsLimits = map[string]JetStreamAccountLimits{} - for t, l := range ac.Limits.JetStreamTieredLimits { - a.jsLimits[t] = JetStreamAccountLimits{ - MaxMemory: l.MemoryStorage, - MaxStore: l.DiskStorage, - MaxStreams: int(l.Streams), - MaxConsumers: int(l.Consumer), - MemoryMaxStreamBytes: toUnlimited(l.MemoryMaxStreamBytes), - StoreMaxStreamBytes: toUnlimited(l.DiskMaxStreamBytes), - MaxBytesRequired: l.MaxBytesRequired, - MaxAckPending: int(toUnlimited(l.MaxAckPending)), - } - } - } - } else if a.jsLimits != nil { - // covers failed update followed by disable - a.jsLimits = nil - } - - a.updated = time.Now() - clients := a.getClientsLocked() - ajs := a.js - a.mu.Unlock() - - // Sort if we are over the limit. - if a.MaxTotalConnectionsReached() { - slices.SortFunc(clients, func(i, j *client) int { return -i.start.Compare(j.start) }) // sort in reverse order - } - - // If JetStream is enabled for this server we will call into configJetStream for the account - // regardless of enabled or disabled. It handles both cases. - if jsEnabled { - if err := s.configJetStream(a); err != nil { - s.Errorf("Error configuring jetstream for account [%s]: %v", tl, err.Error()) - a.mu.Lock() - // Absent reload of js server cfg, this is going to be broken until js is disabled - a.incomplete = true - a.mu.Unlock() - } - } else if a.jsLimits != nil { - // We do not have JS enabled for this server, but the account has it enabled so setup - // our imports properly. This allows this server to proxy JS traffic correctly. - s.checkJetStreamExports() - a.enableAllJetStreamServiceImportsAndMappings() - } - - if ajs != nil { - // Check whether the account NRG status changed. If it has then we need to notify the - // Raft groups running on the system so that they can move their subs if needed. - a.mu.Lock() - previous := ajs.nrgAccount - switch ac.ClusterTraffic { - case "system", _EMPTY_: - ajs.nrgAccount = _EMPTY_ - case "owner": - ajs.nrgAccount = a.Name - default: - s.Errorf("Account claim for %q has invalid value %q for cluster traffic account", a.Name, ac.ClusterTraffic) - } - changed := ajs.nrgAccount != previous - a.mu.Unlock() - if changed { - s.updateNRGAccountStatus() - } - } - - for i, c := range clients { - a.mu.RLock() - exceeded := a.mconns != jwt.NoLimit && i >= int(a.mconns) - a.mu.RUnlock() - if exceeded { - c.maxAccountConnExceeded() - continue - } - c.mu.Lock() - c.applyAccountLimits() - // if we have an nkey user we are a callout user - save - // the issuedAt, and nkey user id to honor revocations - var nkeyUserID string - var issuedAt int64 - if c.user != nil { - issuedAt = c.user.Issued - nkeyUserID = c.user.Nkey - } - theJWT := c.opts.JWT - c.mu.Unlock() - // Check for being revoked here. We use ac one to avoid the account lock. - if (ac.Revocations != nil || ac.Limits.DisallowBearer) && theJWT != _EMPTY_ { - if juc, err := jwt.DecodeUserClaims(theJWT); err != nil { - c.Debugf("User JWT not valid: %v", err) - c.authViolation() - continue - } else if juc.BearerToken && ac.Limits.DisallowBearer { - c.Debugf("Bearer User JWT not allowed") - c.authViolation() - continue - } else if ok := ac.IsClaimRevoked(juc); ok { - c.sendErrAndDebug("User Authentication Revoked") - c.closeConnection(Revocation) - continue - } - } - - // if we extracted nkeyUserID and issuedAt we are a callout type - // calloutIAT should only be set if we are in callout scenario as - // the user JWT is _NOT_ associated with the client for callouts, - // so we rely on the calloutIAT to know when the JWT was issued - // revocations simply state that JWT issued before or by that date - // are not valid - if ac.Revocations != nil && nkeyUserID != _EMPTY_ && issuedAt > 0 { - seconds, ok := ac.Revocations[jwt.All] - if ok && seconds >= issuedAt { - c.sendErrAndDebug("User Authentication Revoked") - c.closeConnection(Revocation) - continue - } - seconds, ok = ac.Revocations[nkeyUserID] - if ok && seconds >= issuedAt { - c.sendErrAndDebug("User Authentication Revoked") - c.closeConnection(Revocation) - continue - } - } - } - - // Check if the signing keys changed, might have to evict - if signersChanged { - for _, c := range clients { - c.mu.Lock() - if c.user == nil { - c.mu.Unlock() - continue - } - sk := c.user.SigningKey - c.mu.Unlock() - if sk == _EMPTY_ { - continue - } - if _, ok := alteredScope[sk]; ok { - c.closeConnection(AuthenticationViolation) - } else if _, ok := a.hasIssuer(sk); !ok { - c.closeConnection(AuthenticationViolation) - } - } - } - - if _, ok := s.incompleteAccExporterMap.Load(old.Name); ok && refreshImportingAccounts { - s.incompleteAccExporterMap.Delete(old.Name) - s.accounts.Range(func(key, value any) bool { - acc := value.(*Account) - acc.mu.RLock() - incomplete := acc.incomplete - name := acc.Name - label := acc.traceLabel() - // Must use jwt in account or risk failing on fetch - // This jwt may not be the same that caused exportingAcc to be in incompleteAccExporterMap - claimJWT := acc.claimJWT - acc.mu.RUnlock() - if incomplete && name != old.Name { - if accClaims, _, err := s.verifyAccountClaims(claimJWT); err == nil { - // Since claimJWT has not changed, acc can become complete - // but it won't alter incomplete for it's dependents accounts. - s.updateAccountClaimsWithRefresh(acc, accClaims, false) - // old.Name was deleted before ranging over accounts - // If it exists again, UpdateAccountClaims set it for failed imports of acc. - // So there was one import of acc that imported this account and failed again. - // Since this account just got updated, the import itself may be in error. So trace that. - if _, ok := s.incompleteAccExporterMap.Load(old.Name); ok { - s.incompleteAccExporterMap.Delete(old.Name) - s.Errorf("Account %s has issues importing account %s", label, old.Name) - } - } - } - return true - }) - } -} - -// Helper to build an internal account structure from a jwt.AccountClaims. -// Lock MUST NOT be held upon entry. -func (s *Server) buildInternalAccount(ac *jwt.AccountClaims) *Account { - acc := NewAccount(ac.Subject) - acc.Issuer = ac.Issuer - // Set this here since we are placing in s.tmpAccounts below and may be - // referenced by an route RS+, etc. - s.setAccountSublist(acc) - - // We don't want to register an account that is in the process of - // being built, however, to solve circular import dependencies, we - // need to store it here. - if v, loaded := s.tmpAccounts.LoadOrStore(ac.Subject, acc); loaded { - return v.(*Account) - } - - // Update based on claims. - s.UpdateAccountClaims(acc, ac) - - return acc -} - -// Helper to build Permissions from jwt.Permissions -// or return nil if none were specified -func buildPermissionsFromJwt(uc *jwt.Permissions) *Permissions { - if uc == nil { - return nil - } - var p *Permissions - if len(uc.Pub.Allow) > 0 || len(uc.Pub.Deny) > 0 { - p = &Permissions{} - p.Publish = &SubjectPermission{} - p.Publish.Allow = uc.Pub.Allow - p.Publish.Deny = uc.Pub.Deny - } - if len(uc.Sub.Allow) > 0 || len(uc.Sub.Deny) > 0 { - if p == nil { - p = &Permissions{} - } - p.Subscribe = &SubjectPermission{} - p.Subscribe.Allow = uc.Sub.Allow - p.Subscribe.Deny = uc.Sub.Deny - } - if uc.Resp != nil { - if p == nil { - p = &Permissions{} - } - p.Response = &ResponsePermission{ - MaxMsgs: uc.Resp.MaxMsgs, - Expires: uc.Resp.Expires, - } - validateResponsePermissions(p) - } - return p -} - -// Helper to build internal NKeyUser. -func buildInternalNkeyUser(uc *jwt.UserClaims, acts map[string]struct{}, acc *Account) *NkeyUser { - nu := &NkeyUser{Nkey: uc.Subject, Account: acc, AllowedConnectionTypes: acts, Issued: uc.IssuedAt} - if uc.IssuerAccount != _EMPTY_ { - nu.SigningKey = uc.Issuer - } - - // Now check for permissions. - var p = buildPermissionsFromJwt(&uc.Permissions) - if p == nil && acc.defaultPerms != nil { - p = acc.defaultPerms.clone() - } - nu.Permissions = p - return nu -} - -func fetchAccount(res AccountResolver, name string) (string, error) { - if !nkeys.IsValidPublicAccountKey(name) { - return _EMPTY_, fmt.Errorf("will only fetch valid account keys") - } - return res.Fetch(copyString(name)) -} - -// AccountResolver interface. This is to fetch Account JWTs by public nkeys -type AccountResolver interface { - Fetch(name string) (string, error) - Store(name, jwt string) error - IsReadOnly() bool - Start(server *Server) error - IsTrackingUpdate() bool - Reload() error - Close() -} - -// Default implementations of IsReadOnly/Start so only need to be written when changed -type resolverDefaultsOpsImpl struct{} - -func (*resolverDefaultsOpsImpl) IsReadOnly() bool { - return true -} - -func (*resolverDefaultsOpsImpl) IsTrackingUpdate() bool { - return false -} - -func (*resolverDefaultsOpsImpl) Start(*Server) error { - return nil -} - -func (*resolverDefaultsOpsImpl) Reload() error { - return nil -} - -func (*resolverDefaultsOpsImpl) Close() { -} - -func (*resolverDefaultsOpsImpl) Store(_, _ string) error { - return fmt.Errorf("store operation not supported for URL Resolver") -} - -// MemAccResolver is a memory only resolver. -// Mostly for testing. -type MemAccResolver struct { - sm sync.Map - resolverDefaultsOpsImpl -} - -// Fetch will fetch the account jwt claims from the internal sync.Map. -func (m *MemAccResolver) Fetch(name string) (string, error) { - if j, ok := m.sm.Load(name); ok { - return j.(string), nil - } - return _EMPTY_, ErrMissingAccount -} - -// Store will store the account jwt claims in the internal sync.Map. -func (m *MemAccResolver) Store(name, jwt string) error { - m.sm.Store(name, jwt) - return nil -} - -func (m *MemAccResolver) IsReadOnly() bool { - return false -} - -// URLAccResolver implements an http fetcher. -type URLAccResolver struct { - url string - c *http.Client - resolverDefaultsOpsImpl -} - -// NewURLAccResolver returns a new resolver for the given base URL. -func NewURLAccResolver(url string) (*URLAccResolver, error) { - if !strings.HasSuffix(url, "/") { - url += "/" - } - // FIXME(dlc) - Make timeout and others configurable. - // We create our own transport to amortize TLS. - tr := &http.Transport{ - MaxIdleConns: 10, - IdleConnTimeout: 30 * time.Second, - } - ur := &URLAccResolver{ - url: url, - c: &http.Client{Timeout: DEFAULT_ACCOUNT_FETCH_TIMEOUT, Transport: tr}, - } - return ur, nil -} - -// Fetch will fetch the account jwt claims from the base url, appending the -// account name onto the end. -func (ur *URLAccResolver) Fetch(name string) (string, error) { - url := ur.url + name - resp, err := ur.c.Get(url) - if err != nil { - return _EMPTY_, fmt.Errorf("could not fetch <%q>: %v", redactURLString(url), err) - } else if resp == nil { - return _EMPTY_, fmt.Errorf("could not fetch <%q>: no response", redactURLString(url)) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return _EMPTY_, fmt.Errorf("could not fetch <%q>: %v", redactURLString(url), resp.Status) - } - body, err := io.ReadAll(resp.Body) - if err != nil { - return _EMPTY_, err - } - return string(body), nil -} - -// Resolver based on nats for synchronization and backing directory for storage. -type DirAccResolver struct { - *DirJWTStore - *Server - syncInterval time.Duration - fetchTimeout time.Duration -} - -func (dr *DirAccResolver) IsTrackingUpdate() bool { - return true -} - -func (dr *DirAccResolver) Reload() error { - return dr.DirJWTStore.Reload() -} - -// ServerAPIClaimUpdateResponse is the response to $SYS.REQ.ACCOUNT..CLAIMS.UPDATE and $SYS.REQ.CLAIMS.UPDATE -type ServerAPIClaimUpdateResponse struct { - Server *ServerInfo `json:"server"` - Data *ClaimUpdateStatus `json:"data,omitempty"` - Error *ClaimUpdateError `json:"error,omitempty"` -} - -type ClaimUpdateError struct { - Account string `json:"account,omitempty"` - Code int `json:"code"` - Description string `json:"description,omitempty"` -} - -type ClaimUpdateStatus struct { - Account string `json:"account,omitempty"` - Code int `json:"code,omitempty"` - Message string `json:"message,omitempty"` -} - -func respondToUpdate(s *Server, respSubj string, acc string, message string, err error) { - if err == nil { - if acc == _EMPTY_ { - s.Debugf("%s", message) - } else { - s.Debugf("%s - %s", message, acc) - } - } else { - if acc == _EMPTY_ { - s.Errorf("%s - %s", message, err) - } else { - s.Errorf("%s - %s - %s", message, acc, err) - } - } - if respSubj == _EMPTY_ { - return - } - - response := ServerAPIClaimUpdateResponse{ - Server: &ServerInfo{}, - } - - if err == nil { - response.Data = &ClaimUpdateStatus{ - Account: acc, - Code: http.StatusOK, - Message: message, - } - } else { - response.Error = &ClaimUpdateError{ - Account: acc, - Code: http.StatusInternalServerError, - Description: fmt.Sprintf("%s - %v", message, err), - } - } - - s.sendInternalMsgLocked(respSubj, _EMPTY_, response.Server, response) -} - -func handleListRequest(store *DirJWTStore, s *Server, reply string) { - if reply == _EMPTY_ { - return - } - accIds := make([]string, 0, 1024) - if err := store.PackWalk(1, func(partialPackMsg string) { - if tk := strings.Split(partialPackMsg, "|"); len(tk) == 2 { - accIds = append(accIds, tk[0]) - } - }); err != nil { - // let them timeout - s.Errorf("list request error: %v", err) - } else { - s.Debugf("list request responded with %d account ids", len(accIds)) - server := &ServerInfo{} - response := map[string]any{"server": server, "data": accIds} - s.sendInternalMsgLocked(reply, _EMPTY_, server, response) - } -} - -func handleDeleteRequest(store *DirJWTStore, s *Server, msg []byte, reply string) { - var accIds []any - var subj, sysAccName string - if sysAcc := s.SystemAccount(); sysAcc != nil { - sysAccName = sysAcc.GetName() - } - // Only operator and operator signing key are allowed to delete - gk, err := jwt.DecodeGeneric(string(msg)) - if err == nil { - subj = gk.Subject - if store.deleteType == NoDelete { - err = fmt.Errorf("delete must be enabled in server config") - } else if subj != gk.Issuer { - err = fmt.Errorf("not self signed") - } else if _, ok := store.operator[gk.Issuer]; !ok { - err = fmt.Errorf("not trusted") - } else if list, ok := gk.Data["accounts"]; !ok { - err = fmt.Errorf("malformed request") - } else if accIds, ok = list.([]any); !ok { - err = fmt.Errorf("malformed request") - } else { - for _, entry := range accIds { - if acc, ok := entry.(string); !ok || - acc == _EMPTY_ || !nkeys.IsValidPublicAccountKey(acc) { - err = fmt.Errorf("malformed request") - break - } else if acc == sysAccName { - err = fmt.Errorf("not allowed to delete system account") - break - } - } - } - } - if err != nil { - respondToUpdate(s, reply, _EMPTY_, fmt.Sprintf("delete accounts request by %s failed", subj), err) - return - } - errs := []string{} - passCnt := 0 - for _, acc := range accIds { - if err := store.delete(acc.(string)); err != nil { - errs = append(errs, err.Error()) - } else { - passCnt++ - } - } - if len(errs) == 0 { - respondToUpdate(s, reply, _EMPTY_, fmt.Sprintf("deleted %d accounts", passCnt), nil) - } else { - respondToUpdate(s, reply, _EMPTY_, fmt.Sprintf("deleted %d accounts, failed for %d", passCnt, len(errs)), - errors.New(strings.Join(errs, "\n"))) - } -} - -func getOperatorKeys(s *Server) (string, map[string]struct{}, bool, error) { - var op string - var strict bool - keys := make(map[string]struct{}) - if opts := s.getOpts(); opts != nil && len(opts.TrustedOperators) > 0 { - op = opts.TrustedOperators[0].Subject - strict = opts.TrustedOperators[0].StrictSigningKeyUsage - if !strict { - keys[opts.TrustedOperators[0].Subject] = struct{}{} - } - for _, key := range opts.TrustedOperators[0].SigningKeys { - keys[key] = struct{}{} - } - } - if len(keys) == 0 { - return _EMPTY_, nil, false, fmt.Errorf("no operator key found") - } - return op, keys, strict, nil -} - -func claimValidate(claim *jwt.AccountClaims) error { - vr := &jwt.ValidationResults{} - claim.Validate(vr) - if vr.IsBlocking(false) { - return fmt.Errorf("validation errors: %v", vr.Errors()) - } - return nil -} - -func removeCb(s *Server, pubKey string) { - v, ok := s.accounts.Load(pubKey) - if !ok { - return - } - a := v.(*Account) - s.Debugf("Disable account %s due to remove", pubKey) - a.mu.Lock() - // lock out new clients - a.msubs = 0 - a.mpay = 0 - a.mconns = 0 - a.mleafs = 0 - a.updated = time.Now() - jsa := a.js - a.mu.Unlock() - // set the account to be expired and disconnect clients - a.expiredTimeout() - // For JS, we need also to disable it. - if js := s.getJetStream(); js != nil && jsa != nil { - js.disableJetStream(jsa) - // Remove JetStream state in memory, this will be reset - // on the changed callback from the account in case it is - // enabled again. - a.js = nil - } - // We also need to remove all ServerImport subscriptions - a.removeAllServiceImportSubs() - a.mu.Lock() - a.clearExpirationTimer() - a.mu.Unlock() -} - -func (dr *DirAccResolver) Start(s *Server) error { - op, opKeys, strict, err := getOperatorKeys(s) - if err != nil { - return err - } - dr.Lock() - defer dr.Unlock() - dr.Server = s - dr.operator = opKeys - dr.DirJWTStore.changed = func(pubKey string) { - if v, ok := s.accounts.Load(pubKey); ok { - if theJwt, err := dr.LoadAcc(pubKey); err != nil { - s.Errorf("DirResolver - Update got error on load: %v", err) - } else { - acc := v.(*Account) - if err = s.updateAccountWithClaimJWT(acc, theJwt); err != nil { - s.Errorf("DirResolver - Update for account %q resulted in error %v", pubKey, err) - } else { - if _, jsa, err := acc.checkForJetStream(); err != nil { - if !IsNatsErr(err, JSNotEnabledForAccountErr) { - s.Warnf("DirResolver - Error checking for JetStream support for account %q: %v", pubKey, err) - } - } else if jsa == nil { - if err = s.configJetStream(acc); err != nil { - s.Errorf("DirResolver - Error configuring JetStream for account %q: %v", pubKey, err) - } - } - } - } - } - } - dr.DirJWTStore.deleted = func(pubKey string) { - removeCb(s, pubKey) - } - packRespIb := s.newRespInbox() - for _, reqSub := range []string{accUpdateEventSubjOld, accUpdateEventSubjNew} { - // subscribe to account jwt update requests - if _, err := s.sysSubscribe(fmt.Sprintf(reqSub, "*"), func(_ *subscription, _ *client, _ *Account, subj, resp string, msg []byte) { - var pubKey string - tk := strings.Split(subj, tsep) - if len(tk) == accUpdateTokensNew { - pubKey = tk[accReqAccIndex] - } else if len(tk) == accUpdateTokensOld { - pubKey = tk[accUpdateAccIdxOld] - } else { - s.Debugf("DirResolver - jwt update skipped due to bad subject %q", subj) - return - } - if claim, err := jwt.DecodeAccountClaims(string(msg)); err != nil { - respondToUpdate(s, resp, "n/a", "jwt update resulted in error", err) - } else if err := claimValidate(claim); err != nil { - respondToUpdate(s, resp, claim.Subject, "jwt validation failed", err) - } else if claim.Subject != pubKey { - err := errors.New("subject does not match jwt content") - respondToUpdate(s, resp, pubKey, "jwt update resulted in error", err) - } else if claim.Issuer == op && strict { - err := errors.New("operator requires issuer to be a signing key") - respondToUpdate(s, resp, pubKey, "jwt update resulted in error", err) - } else if err := dr.save(pubKey, string(msg)); err != nil { - respondToUpdate(s, resp, pubKey, "jwt update resulted in error", err) - } else { - respondToUpdate(s, resp, pubKey, "jwt updated", nil) - } - }); err != nil { - return fmt.Errorf("error setting up update handling: %v", err) - } - } - if _, err := s.sysSubscribe(accClaimsReqSubj, func(_ *subscription, c *client, _ *Account, _, resp string, msg []byte) { - // As this is a raw message, we need to extract payload and only decode claims from it, - // in case request is sent with headers. - _, msg = c.msgParts(msg) - if claim, err := jwt.DecodeAccountClaims(string(msg)); err != nil { - respondToUpdate(s, resp, "n/a", "jwt update resulted in error", err) - } else if claim.Issuer == op && strict { - err := errors.New("operator requires issuer to be a signing key") - respondToUpdate(s, resp, claim.Subject, "jwt update resulted in error", err) - } else if err := claimValidate(claim); err != nil { - respondToUpdate(s, resp, claim.Subject, "jwt validation failed", err) - } else if err := dr.save(claim.Subject, string(msg)); err != nil { - respondToUpdate(s, resp, claim.Subject, "jwt update resulted in error", err) - } else { - respondToUpdate(s, resp, claim.Subject, "jwt updated", nil) - } - }); err != nil { - return fmt.Errorf("error setting up update handling: %v", err) - } - // respond to lookups with our version - if _, err := s.sysSubscribe(fmt.Sprintf(accLookupReqSubj, "*"), func(_ *subscription, _ *client, _ *Account, subj, reply string, msg []byte) { - if reply == _EMPTY_ { - return - } - tk := strings.Split(subj, tsep) - if len(tk) != accLookupReqTokens { - return - } - accName := tk[accReqAccIndex] - if theJWT, err := dr.DirJWTStore.LoadAcc(accName); err != nil { - if errors.Is(err, fs.ErrNotExist) { - s.Debugf("DirResolver - Could not find account %q", accName) - // Reply with empty response to signal absence of JWT to others. - s.sendInternalMsgLocked(reply, _EMPTY_, nil, nil) - } else { - s.Errorf("DirResolver - Error looking up account %q: %v", accName, err) - } - } else { - s.sendInternalMsgLocked(reply, _EMPTY_, nil, []byte(theJWT)) - } - }); err != nil { - return fmt.Errorf("error setting up lookup request handling: %v", err) - } - // respond to pack requests with one or more pack messages - // an empty message signifies the end of the response responder. - if _, err := s.sysSubscribeQ(accPackReqSubj, "responder", func(_ *subscription, _ *client, _ *Account, _, reply string, theirHash []byte) { - if reply == _EMPTY_ { - return - } - ourHash := dr.DirJWTStore.Hash() - if bytes.Equal(theirHash, ourHash[:]) { - s.sendInternalMsgLocked(reply, _EMPTY_, nil, []byte{}) - s.Debugf("DirResolver - Pack request matches hash %x", ourHash[:]) - } else if err := dr.DirJWTStore.PackWalk(1, func(partialPackMsg string) { - s.sendInternalMsgLocked(reply, _EMPTY_, nil, []byte(partialPackMsg)) - }); err != nil { - // let them timeout - s.Errorf("DirResolver - Pack request error: %v", err) - } else { - s.Debugf("DirResolver - Pack request hash %x - finished responding with hash %x", theirHash, ourHash) - s.sendInternalMsgLocked(reply, _EMPTY_, nil, []byte{}) - } - }); err != nil { - return fmt.Errorf("error setting up pack request handling: %v", err) - } - // respond to list requests with one message containing all account ids - if _, err := s.sysSubscribe(accListReqSubj, func(_ *subscription, _ *client, _ *Account, _, reply string, _ []byte) { - handleListRequest(dr.DirJWTStore, s, reply) - }); err != nil { - return fmt.Errorf("error setting up list request handling: %v", err) - } - if _, err := s.sysSubscribe(accDeleteReqSubj, func(_ *subscription, _ *client, _ *Account, _, reply string, msg []byte) { - handleDeleteRequest(dr.DirJWTStore, s, msg, reply) - }); err != nil { - return fmt.Errorf("error setting up delete request handling: %v", err) - } - // embed pack responses into store - if _, err := s.sysSubscribe(packRespIb, func(_ *subscription, _ *client, _ *Account, _, _ string, msg []byte) { - hash := dr.DirJWTStore.Hash() - if len(msg) == 0 { // end of response stream - s.Debugf("DirResolver - Merging finished and resulting in: %x", dr.DirJWTStore.Hash()) - return - } else if err := dr.DirJWTStore.Merge(string(msg)); err != nil { - s.Errorf("DirResolver - Merging resulted in error: %v", err) - } else { - s.Debugf("DirResolver - Merging succeeded and changed %x to %x", hash, dr.DirJWTStore.Hash()) - } - }); err != nil { - return fmt.Errorf("error setting up pack response handling: %v", err) - } - // periodically send out pack message - quit := s.quitCh - s.startGoRoutine(func() { - defer s.grWG.Done() - ticker := time.NewTicker(dr.syncInterval) - for { - select { - case <-quit: - ticker.Stop() - return - case <-ticker.C: - } - ourHash := dr.DirJWTStore.Hash() - s.Debugf("DirResolver - Checking store state: %x", ourHash) - s.sendInternalMsgLocked(accPackReqSubj, packRespIb, nil, ourHash[:]) - } - }) - s.Noticef("Managing all jwt in exclusive directory %s", dr.directory) - return nil -} - -func (dr *DirAccResolver) Fetch(name string) (string, error) { - if theJWT, err := dr.LoadAcc(name); theJWT != _EMPTY_ { - return theJWT, nil - } else { - dr.Lock() - srv := dr.Server - to := dr.fetchTimeout - dr.Unlock() - if srv == nil { - return _EMPTY_, err - } - return srv.fetch(dr, name, to) // lookup from other server - } -} - -func (dr *DirAccResolver) Store(name, jwt string) error { - return dr.saveIfNewer(name, jwt) -} - -type DirResOption func(s *DirAccResolver) error - -// limits the amount of time spent waiting for an account fetch to complete -func FetchTimeout(to time.Duration) DirResOption { - return func(r *DirAccResolver) error { - if to <= time.Duration(0) { - return fmt.Errorf("Fetch timeout %v is too smal", to) - } - r.fetchTimeout = to - return nil - } -} - -func (dr *DirAccResolver) apply(opts ...DirResOption) error { - for _, o := range opts { - if err := o(dr); err != nil { - return err - } - } - return nil -} - -func NewDirAccResolver(path string, limit int64, syncInterval time.Duration, delete deleteType, opts ...DirResOption) (*DirAccResolver, error) { - if limit == 0 { - limit = math.MaxInt64 - } - if syncInterval <= 0 { - syncInterval = time.Minute - } - store, err := NewExpiringDirJWTStore(path, false, true, delete, 0, limit, false, 0, nil) - if err != nil { - return nil, err - } - - res := &DirAccResolver{store, nil, syncInterval, DEFAULT_ACCOUNT_FETCH_TIMEOUT} - if err := res.apply(opts...); err != nil { - return nil, err - } - return res, nil -} - -// Caching resolver using nats for lookups and making use of a directory for storage -type CacheDirAccResolver struct { - DirAccResolver - ttl time.Duration -} - -func (s *Server) fetch(res AccountResolver, name string, timeout time.Duration) (string, error) { - if s == nil { - return _EMPTY_, ErrNoAccountResolver - } - respC := make(chan []byte, 1) - accountLookupRequest := fmt.Sprintf(accLookupReqSubj, name) - s.mu.Lock() - if s.sys == nil || s.sys.replies == nil { - s.mu.Unlock() - return _EMPTY_, fmt.Errorf("eventing shut down") - } - // Resolver will wait for detected active servers to reply - // before serving an error in case there weren't any found. - expectedServers := len(s.sys.servers) - replySubj := s.newRespInbox() - replies := s.sys.replies - - // Store our handler. - replies[replySubj] = func(sub *subscription, _ *client, _ *Account, subject, _ string, msg []byte) { - var clone []byte - isEmpty := len(msg) == 0 - if !isEmpty { - clone = make([]byte, len(msg)) - copy(clone, msg) - } - s.mu.Lock() - defer s.mu.Unlock() - expectedServers-- - // Skip empty responses until getting all the available servers. - if isEmpty && expectedServers > 0 { - return - } - // Use the first valid response if there is still interest or - // one of the empty responses to signal that it was not found. - if _, ok := replies[replySubj]; ok { - select { - case respC <- clone: - default: - } - } - } - s.sendInternalMsg(accountLookupRequest, replySubj, nil, []byte{}) - quit := s.quitCh - s.mu.Unlock() - var err error - var theJWT string - select { - case <-quit: - err = errors.New("fetching jwt failed due to shutdown") - case <-time.After(timeout): - err = errors.New("fetching jwt timed out") - case m := <-respC: - if len(m) == 0 { - err = errors.New("account jwt not found") - } else if err = res.Store(name, string(m)); err == nil { - theJWT = string(m) - } - } - s.mu.Lock() - delete(replies, replySubj) - s.mu.Unlock() - close(respC) - return theJWT, err -} - -func NewCacheDirAccResolver(path string, limit int64, ttl time.Duration, opts ...DirResOption) (*CacheDirAccResolver, error) { - if limit <= 0 { - limit = 1_000 - } - store, err := NewExpiringDirJWTStore(path, false, true, HardDelete, 0, limit, true, ttl, nil) - if err != nil { - return nil, err - } - res := &CacheDirAccResolver{DirAccResolver{store, nil, 0, DEFAULT_ACCOUNT_FETCH_TIMEOUT}, ttl} - if err := res.apply(opts...); err != nil { - return nil, err - } - return res, nil -} - -func (dr *CacheDirAccResolver) Start(s *Server) error { - op, opKeys, strict, err := getOperatorKeys(s) - if err != nil { - return err - } - dr.Lock() - defer dr.Unlock() - dr.Server = s - dr.operator = opKeys - dr.DirJWTStore.changed = func(pubKey string) { - if v, ok := s.accounts.Load(pubKey); !ok { - } else if theJwt, err := dr.LoadAcc(pubKey); err != nil { - s.Errorf("DirResolver - Update got error on load: %v", err) - } else if err := s.updateAccountWithClaimJWT(v.(*Account), theJwt); err != nil { - s.Errorf("DirResolver - Update resulted in error %v", err) - } - } - dr.DirJWTStore.deleted = func(pubKey string) { - removeCb(s, pubKey) - } - for _, reqSub := range []string{accUpdateEventSubjOld, accUpdateEventSubjNew} { - // subscribe to account jwt update requests - if _, err := s.sysSubscribe(fmt.Sprintf(reqSub, "*"), func(_ *subscription, _ *client, _ *Account, subj, resp string, msg []byte) { - var pubKey string - tk := strings.Split(subj, tsep) - if len(tk) == accUpdateTokensNew { - pubKey = tk[accReqAccIndex] - } else if len(tk) == accUpdateTokensOld { - pubKey = tk[accUpdateAccIdxOld] - } else { - s.Debugf("DirResolver - jwt update cache skipped due to bad subject %q", subj) - return - } - if claim, err := jwt.DecodeAccountClaims(string(msg)); err != nil { - respondToUpdate(s, resp, pubKey, "jwt update cache resulted in error", err) - } else if claim.Subject != pubKey { - err := errors.New("subject does not match jwt content") - respondToUpdate(s, resp, pubKey, "jwt update cache resulted in error", err) - } else if claim.Issuer == op && strict { - err := errors.New("operator requires issuer to be a signing key") - respondToUpdate(s, resp, pubKey, "jwt update cache resulted in error", err) - } else if _, ok := s.accounts.Load(pubKey); !ok { - respondToUpdate(s, resp, pubKey, "jwt update cache skipped", nil) - } else if err := claimValidate(claim); err != nil { - respondToUpdate(s, resp, claim.Subject, "jwt update cache validation failed", err) - } else if err := dr.save(pubKey, string(msg)); err != nil { - respondToUpdate(s, resp, pubKey, "jwt update cache resulted in error", err) - } else { - respondToUpdate(s, resp, pubKey, "jwt updated cache", nil) - } - }); err != nil { - return fmt.Errorf("error setting up update handling: %v", err) - } - } - if _, err := s.sysSubscribe(accClaimsReqSubj, func(_ *subscription, c *client, _ *Account, _, resp string, msg []byte) { - // As this is a raw message, we need to extract payload and only decode claims from it, - // in case request is sent with headers. - _, msg = c.msgParts(msg) - if claim, err := jwt.DecodeAccountClaims(string(msg)); err != nil { - respondToUpdate(s, resp, "n/a", "jwt update cache resulted in error", err) - } else if claim.Issuer == op && strict { - err := errors.New("operator requires issuer to be a signing key") - respondToUpdate(s, resp, claim.Subject, "jwt update cache resulted in error", err) - } else if _, ok := s.accounts.Load(claim.Subject); !ok { - respondToUpdate(s, resp, claim.Subject, "jwt update cache skipped", nil) - } else if err := claimValidate(claim); err != nil { - respondToUpdate(s, resp, claim.Subject, "jwt update cache validation failed", err) - } else if err := dr.save(claim.Subject, string(msg)); err != nil { - respondToUpdate(s, resp, claim.Subject, "jwt update cache resulted in error", err) - } else { - respondToUpdate(s, resp, claim.Subject, "jwt updated cache", nil) - } - }); err != nil { - return fmt.Errorf("error setting up update handling: %v", err) - } - // respond to list requests with one message containing all account ids - if _, err := s.sysSubscribe(accListReqSubj, func(_ *subscription, _ *client, _ *Account, _, reply string, _ []byte) { - handleListRequest(dr.DirJWTStore, s, reply) - }); err != nil { - return fmt.Errorf("error setting up list request handling: %v", err) - } - if _, err := s.sysSubscribe(accDeleteReqSubj, func(_ *subscription, _ *client, _ *Account, _, reply string, msg []byte) { - handleDeleteRequest(dr.DirJWTStore, s, msg, reply) - }); err != nil { - return fmt.Errorf("error setting up list request handling: %v", err) - } - s.Noticef("Managing some jwt in exclusive directory %s", dr.directory) - return nil -} - -func (dr *CacheDirAccResolver) Reload() error { - return dr.DirAccResolver.Reload() -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/auth.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/auth.go deleted file mode 100644 index 6578dee1..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/auth.go +++ /dev/null @@ -1,1535 +0,0 @@ -// Copyright 2012-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "crypto/sha256" - "crypto/subtle" - "crypto/tls" - "crypto/x509/pkix" - "encoding/asn1" - "encoding/base64" - "encoding/hex" - "fmt" - "net" - "net/url" - "regexp" - "strings" - "sync/atomic" - "time" - - "github.com/nats-io/jwt/v2" - "github.com/nats-io/nats-server/v2/internal/ldap" - "github.com/nats-io/nkeys" - "golang.org/x/crypto/bcrypt" -) - -// Authentication is an interface for implementing authentication -type Authentication interface { - // Check if a client is authorized to connect - Check(c ClientAuthentication) bool -} - -// ClientAuthentication is an interface for client authentication -type ClientAuthentication interface { - // GetOpts gets options associated with a client - GetOpts() *ClientOpts - // GetTLSConnectionState if TLS is enabled, TLS ConnectionState, nil otherwise - GetTLSConnectionState() *tls.ConnectionState - // RegisterUser optionally map a user after auth. - RegisterUser(*User) - // RemoteAddress expose the connection information of the client - RemoteAddress() net.Addr - // GetNonce is the nonce presented to the user in the INFO line - GetNonce() []byte - // Kind indicates what type of connection this is matching defined constants like CLIENT, ROUTER, GATEWAY, LEAF etc - Kind() int -} - -// NkeyUser is for multiple nkey based users -type NkeyUser struct { - Nkey string `json:"user"` - Issued int64 `json:"issued,omitempty"` // this is a copy of the issued at (iat) field in the jwt - Permissions *Permissions `json:"permissions,omitempty"` - Account *Account `json:"account,omitempty"` - SigningKey string `json:"signing_key,omitempty"` - AllowedConnectionTypes map[string]struct{} `json:"connection_types,omitempty"` -} - -// User is for multiple accounts/users. -type User struct { - Username string `json:"user"` - Password string `json:"password"` - Permissions *Permissions `json:"permissions,omitempty"` - Account *Account `json:"account,omitempty"` - ConnectionDeadline time.Time `json:"connection_deadline,omitempty"` - AllowedConnectionTypes map[string]struct{} `json:"connection_types,omitempty"` -} - -// clone performs a deep copy of the User struct, returning a new clone with -// all values copied. -func (u *User) clone() *User { - if u == nil { - return nil - } - clone := &User{} - *clone = *u - // Account is not cloned because it is always by reference to an existing struct. - clone.Permissions = u.Permissions.clone() - - if u.AllowedConnectionTypes != nil { - clone.AllowedConnectionTypes = make(map[string]struct{}) - for k, v := range u.AllowedConnectionTypes { - clone.AllowedConnectionTypes[k] = v - } - } - - return clone -} - -// clone performs a deep copy of the NkeyUser struct, returning a new clone with -// all values copied. -func (n *NkeyUser) clone() *NkeyUser { - if n == nil { - return nil - } - clone := &NkeyUser{} - *clone = *n - // Account is not cloned because it is always by reference to an existing struct. - clone.Permissions = n.Permissions.clone() - - if n.AllowedConnectionTypes != nil { - clone.AllowedConnectionTypes = make(map[string]struct{}) - for k, v := range n.AllowedConnectionTypes { - clone.AllowedConnectionTypes[k] = v - } - } - - return clone -} - -// SubjectPermission is an individual allow and deny struct for publish -// and subscribe authorizations. -type SubjectPermission struct { - Allow []string `json:"allow,omitempty"` - Deny []string `json:"deny,omitempty"` -} - -// ResponsePermission can be used to allow responses to any reply subject -// that is received on a valid subscription. -type ResponsePermission struct { - MaxMsgs int `json:"max"` - Expires time.Duration `json:"ttl"` -} - -// Permissions are the allowed subjects on a per -// publish or subscribe basis. -type Permissions struct { - Publish *SubjectPermission `json:"publish"` - Subscribe *SubjectPermission `json:"subscribe"` - Response *ResponsePermission `json:"responses,omitempty"` -} - -// RoutePermissions are similar to user permissions -// but describe what a server can import/export from and to -// another server. -type RoutePermissions struct { - Import *SubjectPermission `json:"import"` - Export *SubjectPermission `json:"export"` -} - -// clone will clone an individual subject permission. -func (p *SubjectPermission) clone() *SubjectPermission { - if p == nil { - return nil - } - clone := &SubjectPermission{} - if p.Allow != nil { - clone.Allow = make([]string, len(p.Allow)) - copy(clone.Allow, p.Allow) - } - if p.Deny != nil { - clone.Deny = make([]string, len(p.Deny)) - copy(clone.Deny, p.Deny) - } - return clone -} - -// clone performs a deep copy of the Permissions struct, returning a new clone -// with all values copied. -func (p *Permissions) clone() *Permissions { - if p == nil { - return nil - } - clone := &Permissions{} - if p.Publish != nil { - clone.Publish = p.Publish.clone() - } - if p.Subscribe != nil { - clone.Subscribe = p.Subscribe.clone() - } - if p.Response != nil { - clone.Response = &ResponsePermission{ - MaxMsgs: p.Response.MaxMsgs, - Expires: p.Response.Expires, - } - } - return clone -} - -// checkAuthforWarnings will look for insecure settings and log concerns. -// Lock is assumed held. -func (s *Server) checkAuthforWarnings() { - warn := false - opts := s.getOpts() - if opts.Password != _EMPTY_ && !isBcrypt(opts.Password) { - warn = true - } - for _, u := range s.users { - // Skip warn if using TLS certs based auth - // unless a password has been left in the config. - if u.Password == _EMPTY_ && opts.TLSMap { - continue - } - // Check if this is our internal sys client created on the fly. - if s.sysAccOnlyNoAuthUser != _EMPTY_ && u.Username == s.sysAccOnlyNoAuthUser { - continue - } - if !isBcrypt(u.Password) { - warn = true - break - } - } - if warn { - // Warning about using plaintext passwords. - s.Warnf("Plaintext passwords detected, use nkeys or bcrypt") - } -} - -// If Users or Nkeys options have definitions without an account defined, -// assign them to the default global account. -// Lock should be held. -func (s *Server) assignGlobalAccountToOrphanUsers(nkeys map[string]*NkeyUser, users map[string]*User) { - for _, u := range users { - if u.Account == nil { - u.Account = s.gacc - } - } - for _, u := range nkeys { - if u.Account == nil { - u.Account = s.gacc - } - } -} - -// If the given permissions has a ResponsePermission -// set, ensure that defaults are set (if values are 0) -// and that a Publish permission is set, and Allow -// is disabled if not explicitly set. -func validateResponsePermissions(p *Permissions) { - if p == nil || p.Response == nil { - return - } - if p.Publish == nil { - p.Publish = &SubjectPermission{} - } - if p.Publish.Allow == nil { - // We turn off the blanket allow statement. - p.Publish.Allow = []string{} - } - // If there is a response permission, ensure - // that if value is 0, we set the default value. - if p.Response.MaxMsgs == 0 { - p.Response.MaxMsgs = DEFAULT_ALLOW_RESPONSE_MAX_MSGS - } - if p.Response.Expires == 0 { - p.Response.Expires = DEFAULT_ALLOW_RESPONSE_EXPIRATION - } -} - -// configureAuthorization will do any setup needed for authorization. -// Lock is assumed held. -func (s *Server) configureAuthorization() { - opts := s.getOpts() - if opts == nil { - return - } - - // Check for multiple users first - // This just checks and sets up the user map if we have multiple users. - if opts.CustomClientAuthentication != nil { - s.info.AuthRequired = true - } else if s.trustedKeys != nil { - s.info.AuthRequired = true - } else if opts.Nkeys != nil || opts.Users != nil { - s.nkeys, s.users = s.buildNkeysAndUsersFromOptions(opts.Nkeys, opts.Users) - s.info.AuthRequired = true - } else if opts.Username != _EMPTY_ || opts.Authorization != _EMPTY_ { - s.info.AuthRequired = true - } else { - s.users = nil - s.nkeys = nil - s.info.AuthRequired = false - } - - // Do similar for websocket config - s.wsConfigAuth(&opts.Websocket) - // And for mqtt config - s.mqttConfigAuth(&opts.MQTT) - - // Check for server configured auth callouts. - if opts.AuthCallout != nil { - s.mu.Unlock() - // Give operator log entries if not valid account and auth_users. - _, err := s.lookupAccount(opts.AuthCallout.Account) - s.mu.Lock() - if err != nil { - s.Errorf("Authorization callout account %q not valid", opts.AuthCallout.Account) - } - for _, u := range opts.AuthCallout.AuthUsers { - // Check for user in users and nkeys since this is server config. - var found bool - if len(s.users) > 0 { - _, found = s.users[u] - } - if !found && len(s.nkeys) > 0 { - _, found = s.nkeys[u] - } - if !found { - s.Errorf("Authorization callout user %q not valid: %v", u, err) - } - } - } -} - -// Takes the given slices of NkeyUser and User options and build -// corresponding maps used by the server. The users are cloned -// so that server does not reference options. -// The global account is assigned to users that don't have an -// existing account. -// Server lock is held on entry. -func (s *Server) buildNkeysAndUsersFromOptions(nko []*NkeyUser, uo []*User) (map[string]*NkeyUser, map[string]*User) { - var nkeys map[string]*NkeyUser - var users map[string]*User - - if nko != nil { - nkeys = make(map[string]*NkeyUser, len(nko)) - for _, u := range nko { - copy := u.clone() - if u.Account != nil { - if v, ok := s.accounts.Load(u.Account.Name); ok { - copy.Account = v.(*Account) - } - } - if copy.Permissions != nil { - validateResponsePermissions(copy.Permissions) - } - nkeys[u.Nkey] = copy - } - } - if uo != nil { - users = make(map[string]*User, len(uo)) - for _, u := range uo { - copy := u.clone() - if u.Account != nil { - if v, ok := s.accounts.Load(u.Account.Name); ok { - copy.Account = v.(*Account) - } - } - if copy.Permissions != nil { - validateResponsePermissions(copy.Permissions) - } - users[u.Username] = copy - } - } - s.assignGlobalAccountToOrphanUsers(nkeys, users) - return nkeys, users -} - -// checkAuthentication will check based on client type and -// return boolean indicating if client is authorized. -func (s *Server) checkAuthentication(c *client) bool { - switch c.kind { - case CLIENT: - return s.isClientAuthorized(c) - case ROUTER: - return s.isRouterAuthorized(c) - case GATEWAY: - return s.isGatewayAuthorized(c) - case LEAF: - return s.isLeafNodeAuthorized(c) - default: - return false - } -} - -// isClientAuthorized will check the client against the proper authorization method and data. -// This could be nkey, token, or username/password based. -func (s *Server) isClientAuthorized(c *client) bool { - opts := s.getOpts() - - // Check custom auth first, then jwts, then nkeys, then - // multiple users with TLS map if enabled, then token, - // then single user/pass. - if opts.CustomClientAuthentication != nil && !opts.CustomClientAuthentication.Check(c) { - return false - } - - if opts.CustomClientAuthentication == nil && !s.processClientOrLeafAuthentication(c, opts) { - return false - } - - if c.kind == CLIENT || c.kind == LEAF { - // Generate an event if we have a system account. - s.accountConnectEvent(c) - } - - return true -} - -// returns false if the client needs to be disconnected -func (c *client) matchesPinnedCert(tlsPinnedCerts PinnedCertSet) bool { - if tlsPinnedCerts == nil { - return true - } - tlsState := c.GetTLSConnectionState() - if tlsState == nil || len(tlsState.PeerCertificates) == 0 || tlsState.PeerCertificates[0] == nil { - c.Debugf("Failed pinned cert test as client did not provide a certificate") - return false - } - sha := sha256.Sum256(tlsState.PeerCertificates[0].RawSubjectPublicKeyInfo) - keyId := hex.EncodeToString(sha[:]) - if _, ok := tlsPinnedCerts[keyId]; !ok { - c.Debugf("Failed pinned cert test for key id: %s", keyId) - return false - } - return true -} - -var ( - mustacheRE = regexp.MustCompile(`{{2}([^}]+)}{2}`) -) - -func processUserPermissionsTemplate(lim jwt.UserPermissionLimits, ujwt *jwt.UserClaims, acc *Account) (jwt.UserPermissionLimits, error) { - nArrayCartesianProduct := func(a ...[]string) [][]string { - c := 1 - for _, a := range a { - c *= len(a) - } - if c == 0 { - return nil - } - p := make([][]string, c) - b := make([]string, c*len(a)) - n := make([]int, len(a)) - s := 0 - for i := range p { - e := s + len(a) - pi := b[s:e] - p[i] = pi - s = e - for j, n := range n { - pi[j] = a[j][n] - } - for j := len(n) - 1; j >= 0; j-- { - n[j]++ - if n[j] < len(a[j]) { - break - } - n[j] = 0 - } - } - return p - } - isTag := func(op string) []string { - if strings.EqualFold("tag(", op[:4]) && strings.HasSuffix(op, ")") { - v := strings.TrimPrefix(op, "tag(") - v = strings.TrimSuffix(v, ")") - return []string{"tag", v} - } else if strings.EqualFold("account-tag(", op[:12]) && strings.HasSuffix(op, ")") { - v := strings.TrimPrefix(op, "account-tag(") - v = strings.TrimSuffix(v, ")") - return []string{"account-tag", v} - } - return nil - } - applyTemplate := func(list jwt.StringList, failOnBadSubject bool) (jwt.StringList, error) { - found := false - FOR_FIND: - for i := 0; i < len(list); i++ { - // check if templates are present - if mustacheRE.MatchString(list[i]) { - found = true - break FOR_FIND - } - } - if !found { - return list, nil - } - // process the templates - emittedList := make([]string, 0, len(list)) - for i := 0; i < len(list); i++ { - // find all the templates {{}} in this acl - tokens := mustacheRE.FindAllString(list[i], -1) - srcs := make([]string, len(tokens)) - values := make([][]string, len(tokens)) - hasTags := false - for tokenNum, tk := range tokens { - srcs[tokenNum] = tk - op := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(tk, "{{"), "}}")) - if strings.EqualFold("name()", op) { - values[tokenNum] = []string{ujwt.Name} - } else if strings.EqualFold("subject()", op) { - values[tokenNum] = []string{ujwt.Subject} - } else if strings.EqualFold("account-name()", op) { - acc.mu.RLock() - values[tokenNum] = []string{acc.nameTag} - acc.mu.RUnlock() - } else if strings.EqualFold("account-subject()", op) { - // this always has an issuer account since this is a scoped signer - values[tokenNum] = []string{ujwt.IssuerAccount} - } else if isTag(op) != nil { - hasTags = true - match := isTag(op) - var tags jwt.TagList - if match[0] == "account-tag" { - acc.mu.RLock() - tags = acc.tags - acc.mu.RUnlock() - } else { - tags = ujwt.Tags - } - tagPrefix := fmt.Sprintf("%s:", strings.ToLower(match[1])) - var valueList []string - for _, tag := range tags { - if strings.HasPrefix(tag, tagPrefix) { - tagValue := strings.TrimPrefix(tag, tagPrefix) - valueList = append(valueList, tagValue) - } - } - if len(valueList) != 0 { - values[tokenNum] = valueList - } else if failOnBadSubject { - return nil, fmt.Errorf("generated invalid subject %q: %q is not defined", list[i], match[1]) - } else { - // generate an invalid subject? - values[tokenNum] = []string{" "} - } - } else if failOnBadSubject { - return nil, fmt.Errorf("template operation in %q: %q is not defined", list[i], op) - } - } - if !hasTags { - subj := list[i] - for idx, m := range srcs { - subj = strings.Replace(subj, m, values[idx][0], -1) - } - if IsValidSubject(subj) { - emittedList = append(emittedList, subj) - } else if failOnBadSubject { - return nil, fmt.Errorf("generated invalid subject") - } - } else { - a := nArrayCartesianProduct(values...) - for _, aa := range a { - subj := list[i] - for j := 0; j < len(srcs); j++ { - subj = strings.Replace(subj, srcs[j], aa[j], -1) - } - if IsValidSubject(subj) { - emittedList = append(emittedList, subj) - } else if failOnBadSubject { - return nil, fmt.Errorf("generated invalid subject") - } - } - } - } - return emittedList, nil - } - - subAllowWasNotEmpty := len(lim.Permissions.Sub.Allow) > 0 - pubAllowWasNotEmpty := len(lim.Permissions.Pub.Allow) > 0 - - var err error - if lim.Permissions.Sub.Allow, err = applyTemplate(lim.Permissions.Sub.Allow, false); err != nil { - return jwt.UserPermissionLimits{}, err - } else if lim.Permissions.Sub.Deny, err = applyTemplate(lim.Permissions.Sub.Deny, true); err != nil { - return jwt.UserPermissionLimits{}, err - } else if lim.Permissions.Pub.Allow, err = applyTemplate(lim.Permissions.Pub.Allow, false); err != nil { - return jwt.UserPermissionLimits{}, err - } else if lim.Permissions.Pub.Deny, err = applyTemplate(lim.Permissions.Pub.Deny, true); err != nil { - return jwt.UserPermissionLimits{}, err - } - - // if pub/sub allow were not empty, but are empty post template processing, add in a "deny >" to compensate - if subAllowWasNotEmpty && len(lim.Permissions.Sub.Allow) == 0 { - lim.Permissions.Sub.Deny.Add(">") - } - if pubAllowWasNotEmpty && len(lim.Permissions.Pub.Allow) == 0 { - lim.Permissions.Pub.Deny.Add(">") - } - return lim, nil -} - -func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) (authorized bool) { - var ( - nkey *NkeyUser - juc *jwt.UserClaims - acc *Account - user *User - ok bool - err error - ao bool // auth override - ) - - // Check if we have auth callouts enabled at the server level or in the bound account. - defer func() { - // Default reason - reason := AuthenticationViolation.String() - // No-op - if juc == nil && opts.AuthCallout == nil { - if !authorized { - s.sendAccountAuthErrorEvent(c, c.acc, reason) - } - return - } - // We have a juc, check if externally managed, i.e. should be delegated - // to the auth callout service. - if juc != nil && !acc.hasExternalAuth() { - if !authorized { - s.sendAccountAuthErrorEvent(c, c.acc, reason) - } - return - } - // Check config-mode. The global account is a condition since users that - // are not found in the config are implicitly bound to the global account. - // This means those users should be implicitly delegated to auth callout - // if configured. Exclude LEAF connections from this check. - if c.kind != LEAF && juc == nil && opts.AuthCallout != nil && c.acc.Name != globalAccountName { - // If no allowed accounts are defined, then all accounts are in scope. - // Otherwise see if the account is in the list. - delegated := len(opts.AuthCallout.AllowedAccounts) == 0 - if !delegated { - for _, n := range opts.AuthCallout.AllowedAccounts { - if n == c.acc.Name { - delegated = true - break - } - } - } - - // Not delegated, so return with previous authorized result. - if !delegated { - if !authorized { - s.sendAccountAuthErrorEvent(c, c.acc, reason) - } - return - } - } - - // We have auth callout set here. - var skip bool - // Check if we are on the list of auth_users. - userID := c.getRawAuthUser() - if juc != nil { - skip = acc.isExternalAuthUser(userID) - } else { - for _, u := range opts.AuthCallout.AuthUsers { - if userID == u { - skip = true - break - } - } - } - - // If we are here we have an auth callout defined and we have failed auth so far - // so we will callout to our auth backend for processing. - if !skip { - authorized, reason = s.processClientOrLeafCallout(c, opts) - } - // Check if we are authorized and in the auth callout account, and if so add in deny publish permissions for the auth subject. - if authorized { - var authAccountName string - if juc == nil && opts.AuthCallout != nil { - authAccountName = opts.AuthCallout.Account - } else if juc != nil { - authAccountName = acc.Name - } - c.mu.Lock() - if c.acc != nil && c.acc.Name == authAccountName { - c.mergeDenyPermissions(pub, []string{AuthCalloutSubject}) - } - c.mu.Unlock() - } else { - // If we are here we failed external authorization. - // Send an account scoped event. Server config mode acc will be nil, - // so lookup the auth callout assigned account, that is where this will be sent. - if acc == nil { - acc, _ = s.lookupAccount(opts.AuthCallout.Account) - } - s.sendAccountAuthErrorEvent(c, acc, reason) - } - }() - - s.mu.Lock() - authRequired := s.info.AuthRequired - if !authRequired { - // If no auth required for regular clients, then check if - // we have an override for MQTT or Websocket clients. - switch c.clientType() { - case MQTT: - authRequired = s.mqtt.authOverride - case WS: - authRequired = s.websocket.authOverride - } - } - if !authRequired { - // TODO(dlc) - If they send us credentials should we fail? - s.mu.Unlock() - return true - } - var ( - username string - password string - token string - noAuthUser string - pinnedAcounts map[string]struct{} - ) - tlsMap := opts.TLSMap - if c.kind == CLIENT { - switch c.clientType() { - case MQTT: - mo := &opts.MQTT - // Always override TLSMap. - tlsMap = mo.TLSMap - // The rest depends on if there was any auth override in - // the mqtt's config. - if s.mqtt.authOverride { - noAuthUser = mo.NoAuthUser - username = mo.Username - password = mo.Password - token = mo.Token - ao = true - } - case WS: - wo := &opts.Websocket - // Always override TLSMap. - tlsMap = wo.TLSMap - // The rest depends on if there was any auth override in - // the websocket's config. - if s.websocket.authOverride { - noAuthUser = wo.NoAuthUser - username = wo.Username - password = wo.Password - token = wo.Token - ao = true - } - } - } else { - tlsMap = opts.LeafNode.TLSMap - } - - if !ao { - noAuthUser = opts.NoAuthUser - // If a leaf connects using websocket, and websocket{} block has a no_auth_user - // use that one instead. - if c.kind == LEAF && c.isWebsocket() && opts.Websocket.NoAuthUser != _EMPTY_ { - noAuthUser = opts.Websocket.NoAuthUser - } - username = opts.Username - password = opts.Password - token = opts.Authorization - } - - // Check if we have trustedKeys defined in the server. If so we require a user jwt. - if s.trustedKeys != nil { - if c.opts.JWT == _EMPTY_ { - s.mu.Unlock() - c.Debugf("Authentication requires a user JWT") - return false - } - // So we have a valid user jwt here. - juc, err = jwt.DecodeUserClaims(c.opts.JWT) - if err != nil { - s.mu.Unlock() - c.Debugf("User JWT not valid: %v", err) - return false - } - vr := jwt.CreateValidationResults() - juc.Validate(vr) - if vr.IsBlocking(true) { - s.mu.Unlock() - c.Debugf("User JWT no longer valid: %+v", vr) - return false - } - pinnedAcounts = opts.resolverPinnedAccounts - } - - // Check if we have nkeys or users for client. - hasNkeys := len(s.nkeys) > 0 - hasUsers := len(s.users) > 0 - if hasNkeys { - if (c.kind == CLIENT || c.kind == LEAF) && noAuthUser != _EMPTY_ && - c.opts.Username == _EMPTY_ && c.opts.Password == _EMPTY_ && c.opts.Token == _EMPTY_ && c.opts.Nkey == _EMPTY_ { - if _, exists := s.nkeys[noAuthUser]; exists { - c.mu.Lock() - c.opts.Nkey = noAuthUser - c.mu.Unlock() - } - } - if c.opts.Nkey != _EMPTY_ { - nkey, ok = s.nkeys[c.opts.Nkey] - if !ok || !c.connectionTypeAllowed(nkey.AllowedConnectionTypes) { - s.mu.Unlock() - return false - } - } - } - if hasUsers && nkey == nil { - // Check if we are tls verify and are mapping users from the client_certificate. - if tlsMap { - authorized := checkClientTLSCertSubject(c, func(u string, certDN *ldap.DN, _ bool) (string, bool) { - // First do literal lookup using the resulting string representation - // of RDNSequence as implemented by the pkix package from Go. - if u != _EMPTY_ { - usr, ok := s.users[u] - if !ok || !c.connectionTypeAllowed(usr.AllowedConnectionTypes) { - return _EMPTY_, false - } - user = usr - return usr.Username, true - } - - if certDN == nil { - return _EMPTY_, false - } - - // Look through the accounts for a DN that is equal to the one - // presented by the certificate. - dns := make(map[*User]*ldap.DN) - for _, usr := range s.users { - if !c.connectionTypeAllowed(usr.AllowedConnectionTypes) { - continue - } - // TODO: Use this utility to make a full validation pass - // on start in case tlsmap feature is being used. - inputDN, err := ldap.ParseDN(usr.Username) - if err != nil { - continue - } - if inputDN.Equal(certDN) { - user = usr - return usr.Username, true - } - - // In case it did not match exactly, then collect the DNs - // and try to match later in case the DN was reordered. - dns[usr] = inputDN - } - - // Check in case the DN was reordered. - for usr, inputDN := range dns { - if inputDN.RDNsMatch(certDN) { - user = usr - return usr.Username, true - } - } - return _EMPTY_, false - }) - if !authorized { - s.mu.Unlock() - return false - } - if c.opts.Username != _EMPTY_ { - s.Warnf("User %q found in connect proto, but user required from cert", c.opts.Username) - } - // Already checked that the client didn't send a user in connect - // but we set it here to be able to identify it in the logs. - c.opts.Username = user.Username - } else { - if (c.kind == CLIENT || c.kind == LEAF) && noAuthUser != _EMPTY_ && - c.opts.Username == _EMPTY_ && c.opts.Password == _EMPTY_ && c.opts.Token == _EMPTY_ { - if u, exists := s.users[noAuthUser]; exists { - c.mu.Lock() - c.opts.Username = u.Username - c.opts.Password = u.Password - c.mu.Unlock() - } - } - if c.opts.Username != _EMPTY_ { - user, ok = s.users[c.opts.Username] - if !ok || !c.connectionTypeAllowed(user.AllowedConnectionTypes) { - s.mu.Unlock() - return false - } - } - } - } - s.mu.Unlock() - - // If we have a jwt and a userClaim, make sure we have the Account, etc associated. - // We need to look up the account. This will use an account resolver if one is present. - if juc != nil { - issuer := juc.Issuer - if juc.IssuerAccount != _EMPTY_ { - issuer = juc.IssuerAccount - } - if pinnedAcounts != nil { - if _, ok := pinnedAcounts[issuer]; !ok { - c.Debugf("Account %s not listed as operator pinned account", issuer) - atomic.AddUint64(&s.pinnedAccFail, 1) - return false - } - } - if acc, err = s.LookupAccount(issuer); acc == nil { - c.Debugf("Account JWT lookup error: %v", err) - return false - } - acc.mu.RLock() - aissuer := acc.Issuer - acc.mu.RUnlock() - if !s.isTrustedIssuer(aissuer) { - c.Debugf("Account JWT not signed by trusted operator") - return false - } - if scope, ok := acc.hasIssuer(juc.Issuer); !ok { - c.Debugf("User JWT issuer is not known") - return false - } else if scope != nil { - if err := scope.ValidateScopedSigner(juc); err != nil { - c.Debugf("User JWT is not valid: %v", err) - return false - } else if uSc, ok := scope.(*jwt.UserScope); !ok { - c.Debugf("User JWT is not valid") - return false - } else if juc.UserPermissionLimits, err = processUserPermissionsTemplate(uSc.Template, juc, acc); err != nil { - c.Debugf("User JWT generated invalid permissions") - return false - } - } - if acc.IsExpired() { - c.Debugf("Account JWT has expired") - return false - } - if juc.BearerToken && acc.failBearer() { - c.Debugf("Account does not allow bearer tokens") - return false - } - // We check the allowed connection types, but only after processing - // of scoped signer (so that it updates `juc` with what is defined - // in the account. - allowedConnTypes, err := convertAllowedConnectionTypes(juc.AllowedConnectionTypes) - if err != nil { - // We got an error, which means some connection types were unknown. As long as - // a valid one is returned, we proceed with auth. If not, we have to reject. - // In other words, suppose that JWT allows "WEBSOCKET" in the array. No error - // is returned and allowedConnTypes will contain "WEBSOCKET" only. - // Client will be rejected if not a websocket client, or proceed with rest of - // auth if it is. - // Now suppose JWT allows "WEBSOCKET, MQTT" and say MQTT is not known by this - // server. In this case, allowedConnTypes would contain "WEBSOCKET" and we - // would get `err` indicating that "MQTT" is an unknown connection type. - // If a websocket client connects, it should still be allowed, since after all - // the admin wanted to allow websocket and mqtt connection types. - // However, say that the JWT only allows "MQTT" (and again suppose this server - // does not know about MQTT connection type), then since the allowedConnTypes - // map would be empty (no valid types found), and since empty means allow-all, - // then we should reject because the intent was to allow connections for this - // user only as an MQTT client. - c.Debugf("%v", err) - if len(allowedConnTypes) == 0 { - return false - } - } - if !c.connectionTypeAllowed(allowedConnTypes) { - c.Debugf("Connection type not allowed") - return false - } - // skip validation of nonce when presented with a bearer token - // FIXME: if BearerToken is only for WSS, need check for server with that port enabled - if !juc.BearerToken { - // Verify the signature against the nonce. - if c.opts.Sig == _EMPTY_ { - c.Debugf("Signature missing") - return false - } - sig, err := base64.RawURLEncoding.DecodeString(c.opts.Sig) - if err != nil { - // Allow fallback to normal base64. - sig, err = base64.StdEncoding.DecodeString(c.opts.Sig) - if err != nil { - c.Debugf("Signature not valid base64") - return false - } - } - pub, err := nkeys.FromPublicKey(juc.Subject) - if err != nil { - c.Debugf("User nkey not valid: %v", err) - return false - } - if err := pub.Verify(c.nonce, sig); err != nil { - c.Debugf("Signature not verified") - return false - } - } - if acc.checkUserRevoked(juc.Subject, juc.IssuedAt) { - c.Debugf("User authentication revoked") - return false - } - if !validateSrc(juc, c.host) { - c.Errorf("Bad src Ip %s", c.host) - return false - } - allowNow, validFor := validateTimes(juc) - if !allowNow { - c.Errorf("Outside connect times") - return false - } - - nkey = buildInternalNkeyUser(juc, allowedConnTypes, acc) - if err := c.RegisterNkeyUser(nkey); err != nil { - return false - } - - // Warn about JetStream restrictions - if c.perms != nil { - deniedPub := []string{} - deniedSub := []string{} - for _, sub := range denyAllJs { - if c.perms.pub.deny != nil { - if c.perms.pub.deny.HasInterest(sub) { - deniedPub = append(deniedPub, sub) - } - } - if c.perms.sub.deny != nil { - if c.perms.sub.deny.HasInterest(sub) { - deniedSub = append(deniedSub, sub) - } - } - } - if len(deniedPub) > 0 || len(deniedSub) > 0 { - c.Noticef("Connected %s has JetStream denied on pub: %v sub: %v", c.kindString(), deniedPub, deniedSub) - } - } - - // Hold onto the user's public key. - c.mu.Lock() - c.pubKey = juc.Subject - c.tags = juc.Tags - c.nameTag = juc.Name - c.mu.Unlock() - - // Check if we need to set an auth timer if the user jwt expires. - c.setExpiration(juc.Claims(), validFor) - - acc.mu.RLock() - c.Debugf("Authenticated JWT: %s %q (claim-name: %q, claim-tags: %q) "+ - "signed with %q by Account %q (claim-name: %q, claim-tags: %q) signed with %q has mappings %t accused %p", - c.kindString(), juc.Subject, juc.Name, juc.Tags, juc.Issuer, issuer, acc.nameTag, acc.tags, acc.Issuer, acc.hasMappings(), acc) - acc.mu.RUnlock() - return true - } - - if nkey != nil { - // If we did not match noAuthUser check signature which is required. - if nkey.Nkey != noAuthUser { - if c.opts.Sig == _EMPTY_ { - c.Debugf("Signature missing") - return false - } - sig, err := base64.RawURLEncoding.DecodeString(c.opts.Sig) - if err != nil { - // Allow fallback to normal base64. - sig, err = base64.StdEncoding.DecodeString(c.opts.Sig) - if err != nil { - c.Debugf("Signature not valid base64") - return false - } - } - pub, err := nkeys.FromPublicKey(c.opts.Nkey) - if err != nil { - c.Debugf("User nkey not valid: %v", err) - return false - } - if err := pub.Verify(c.nonce, sig); err != nil { - c.Debugf("Signature not verified") - return false - } - } - if err := c.RegisterNkeyUser(nkey); err != nil { - return false - } - return true - } - if user != nil { - ok = comparePasswords(user.Password, c.opts.Password) - // If we are authorized, register the user which will properly setup any permissions - // for pub/sub authorizations. - if ok { - c.RegisterUser(user) - } - return ok - } - - if c.kind == CLIENT { - if token != _EMPTY_ { - return comparePasswords(token, c.opts.Token) - } else if username != _EMPTY_ { - if username != c.opts.Username { - return false - } - return comparePasswords(password, c.opts.Password) - } - } else if c.kind == LEAF { - // There is no required username/password to connect and - // there was no u/p in the CONNECT or none that matches the - // know users. Register the leaf connection with global account - // or the one specified in config (if provided). - return s.registerLeafWithAccount(c, opts.LeafNode.Account) - } - return false -} - -func getTLSAuthDCs(rdns *pkix.RDNSequence) string { - dcOID := asn1.ObjectIdentifier{0, 9, 2342, 19200300, 100, 1, 25} - dcs := []string{} - for _, rdn := range *rdns { - if len(rdn) == 0 { - continue - } - for _, atv := range rdn { - value, ok := atv.Value.(string) - if !ok { - continue - } - if atv.Type.Equal(dcOID) { - dcs = append(dcs, "DC="+value) - } - } - } - return strings.Join(dcs, ",") -} - -type tlsMapAuthFn func(string, *ldap.DN, bool) (string, bool) - -func checkClientTLSCertSubject(c *client, fn tlsMapAuthFn) bool { - tlsState := c.GetTLSConnectionState() - if tlsState == nil { - c.Debugf("User required in cert, no TLS connection state") - return false - } - if len(tlsState.PeerCertificates) == 0 { - c.Debugf("User required in cert, no peer certificates found") - return false - } - cert := tlsState.PeerCertificates[0] - if len(tlsState.PeerCertificates) > 1 { - c.Debugf("Multiple peer certificates found, selecting first") - } - - hasSANs := len(cert.DNSNames) > 0 - hasEmailAddresses := len(cert.EmailAddresses) > 0 - hasSubject := len(cert.Subject.String()) > 0 - hasURIs := len(cert.URIs) > 0 - if !hasEmailAddresses && !hasSubject && !hasURIs { - c.Debugf("User required in cert, none found") - return false - } - - switch { - case hasEmailAddresses: - for _, u := range cert.EmailAddresses { - if match, ok := fn(u, nil, false); ok { - c.Debugf("Using email found in cert for auth [%q]", match) - return true - } - } - fallthrough - case hasSANs: - for _, u := range cert.DNSNames { - if match, ok := fn(u, nil, true); ok { - c.Debugf("Using SAN found in cert for auth [%q]", match) - return true - } - } - fallthrough - case hasURIs: - for _, u := range cert.URIs { - if match, ok := fn(u.String(), nil, false); ok { - c.Debugf("Using URI found in cert for auth [%q]", match) - return true - } - } - } - - // Use the string representation of the full RDN Sequence including - // the domain components in case there are any. - rdn := cert.Subject.ToRDNSequence().String() - - // Match using the raw subject to avoid ignoring attributes. - // https://github.com/golang/go/issues/12342 - dn, err := ldap.FromRawCertSubject(cert.RawSubject) - if err == nil { - if match, ok := fn(_EMPTY_, dn, false); ok { - c.Debugf("Using DistinguishedNameMatch for auth [%q]", match) - return true - } - c.Debugf("DistinguishedNameMatch could not be used for auth [%q]", rdn) - } - - var rdns pkix.RDNSequence - if _, err := asn1.Unmarshal(cert.RawSubject, &rdns); err == nil { - // If found domain components then include roughly following - // the order from https://tools.ietf.org/html/rfc2253 - // - // NOTE: The original sequence from string representation by ToRDNSequence does not follow - // the correct ordering, so this addition ofdomainComponents would likely be deprecated in - // another release in favor of using the correct ordered as parsed by the go-ldap library. - // - dcs := getTLSAuthDCs(&rdns) - if len(dcs) > 0 { - u := strings.Join([]string{rdn, dcs}, ",") - if match, ok := fn(u, nil, false); ok { - c.Debugf("Using RDNSequence for auth [%q]", match) - return true - } - c.Debugf("RDNSequence could not be used for auth [%q]", u) - } - } - - // If no match, then use the string representation of the RDNSequence - // from the subject without the domainComponents. - if match, ok := fn(rdn, nil, false); ok { - c.Debugf("Using certificate subject for auth [%q]", match) - return true - } - - c.Debugf("User in cert [%q], not found", rdn) - return false -} - -func dnsAltNameLabels(dnsAltName string) []string { - return strings.Split(strings.ToLower(dnsAltName), ".") -} - -// Check DNS name according to https://tools.ietf.org/html/rfc6125#section-6.4.1 -func dnsAltNameMatches(dnsAltNameLabels []string, urls []*url.URL) bool { -URLS: - for _, url := range urls { - if url == nil { - continue URLS - } - hostLabels := strings.Split(strings.ToLower(url.Hostname()), ".") - // Following https://tools.ietf.org/html/rfc6125#section-6.4.3, should not => will not, may => will not - // The wildcard * never matches multiple label and only matches the left most label. - if len(hostLabels) != len(dnsAltNameLabels) { - continue URLS - } - i := 0 - // only match wildcard on left most label - if dnsAltNameLabels[0] == "*" { - i++ - } - for ; i < len(dnsAltNameLabels); i++ { - if dnsAltNameLabels[i] != hostLabels[i] { - continue URLS - } - } - return true - } - return false -} - -// checkRouterAuth checks optional router authorization which can be nil or username/password. -func (s *Server) isRouterAuthorized(c *client) bool { - // Snapshot server options. - opts := s.getOpts() - - // Check custom auth first, then TLS map if enabled - // then single user/pass. - if opts.CustomRouterAuthentication != nil { - return opts.CustomRouterAuthentication.Check(c) - } - - if opts.Cluster.TLSMap || opts.Cluster.TLSCheckKnownURLs { - return checkClientTLSCertSubject(c, func(user string, _ *ldap.DN, isDNSAltName bool) (string, bool) { - if user == _EMPTY_ { - return _EMPTY_, false - } - if opts.Cluster.TLSCheckKnownURLs && isDNSAltName { - if dnsAltNameMatches(dnsAltNameLabels(user), opts.Routes) { - return _EMPTY_, true - } - } - if opts.Cluster.TLSMap && opts.Cluster.Username == user { - return _EMPTY_, true - } - return _EMPTY_, false - }) - } - - if opts.Cluster.Username == _EMPTY_ { - return true - } - - if opts.Cluster.Username != c.opts.Username { - return false - } - if !comparePasswords(opts.Cluster.Password, c.opts.Password) { - return false - } - return true -} - -// isGatewayAuthorized checks optional gateway authorization which can be nil or username/password. -func (s *Server) isGatewayAuthorized(c *client) bool { - // Snapshot server options. - opts := s.getOpts() - - // Check whether TLS map is enabled, otherwise use single user/pass. - if opts.Gateway.TLSMap || opts.Gateway.TLSCheckKnownURLs { - return checkClientTLSCertSubject(c, func(user string, _ *ldap.DN, isDNSAltName bool) (string, bool) { - if user == _EMPTY_ { - return _EMPTY_, false - } - if opts.Gateway.TLSCheckKnownURLs && isDNSAltName { - labels := dnsAltNameLabels(user) - for _, gw := range opts.Gateway.Gateways { - if gw != nil && dnsAltNameMatches(labels, gw.URLs) { - return _EMPTY_, true - } - } - } - if opts.Gateway.TLSMap && opts.Gateway.Username == user { - return _EMPTY_, true - } - return _EMPTY_, false - }) - } - - if opts.Gateway.Username == _EMPTY_ { - return true - } - - if opts.Gateway.Username != c.opts.Username { - return false - } - return comparePasswords(opts.Gateway.Password, c.opts.Password) -} - -func (s *Server) registerLeafWithAccount(c *client, account string) bool { - var err error - acc := s.globalAccount() - if account != _EMPTY_ { - acc, err = s.lookupAccount(account) - if err != nil { - s.Errorf("authentication of user %q failed, unable to lookup account %q: %v", - c.opts.Username, account, err) - return false - } - } - if err = c.registerWithAccount(acc); err != nil { - return false - } - return true -} - -// isLeafNodeAuthorized will check for auth for an inbound leaf node connection. -func (s *Server) isLeafNodeAuthorized(c *client) bool { - opts := s.getOpts() - - isAuthorized := func(username, password, account string) bool { - if username != c.opts.Username { - return false - } - if !comparePasswords(password, c.opts.Password) { - return false - } - return s.registerLeafWithAccount(c, account) - } - - // If leafnodes config has an authorization{} stanza, this takes precedence. - // The user in CONNECT must match. We will bind to the account associated - // with that user (from the leafnode's authorization{} config). - if opts.LeafNode.Username != _EMPTY_ { - return isAuthorized(opts.LeafNode.Username, opts.LeafNode.Password, opts.LeafNode.Account) - } else if opts.LeafNode.Nkey != _EMPTY_ { - if c.opts.Nkey != opts.LeafNode.Nkey { - return false - } - if c.opts.Sig == _EMPTY_ { - c.Debugf("Signature missing") - return false - } - sig, err := base64.RawURLEncoding.DecodeString(c.opts.Sig) - if err != nil { - // Allow fallback to normal base64. - sig, err = base64.StdEncoding.DecodeString(c.opts.Sig) - if err != nil { - c.Debugf("Signature not valid base64") - return false - } - } - pub, err := nkeys.FromPublicKey(c.opts.Nkey) - if err != nil { - c.Debugf("User nkey not valid: %v", err) - return false - } - if err := pub.Verify(c.nonce, sig); err != nil { - c.Debugf("Signature not verified") - return false - } - return s.registerLeafWithAccount(c, opts.LeafNode.Account) - } else if len(opts.LeafNode.Users) > 0 { - if opts.LeafNode.TLSMap { - var user *User - found := checkClientTLSCertSubject(c, func(u string, _ *ldap.DN, _ bool) (string, bool) { - // This is expected to be a very small array. - for _, usr := range opts.LeafNode.Users { - if u == usr.Username { - user = usr - return u, true - } - } - return _EMPTY_, false - }) - if !found { - return false - } - if c.opts.Username != _EMPTY_ { - s.Warnf("User %q found in connect proto, but user required from cert", c.opts.Username) - } - c.opts.Username = user.Username - // EMPTY will result in $G - accName := _EMPTY_ - if user.Account != nil { - accName = user.Account.GetName() - } - // This will authorize since are using an existing user, - // but it will also register with proper account. - return isAuthorized(user.Username, user.Password, accName) - } - - // This is expected to be a very small array. - for _, u := range opts.LeafNode.Users { - if u.Username == c.opts.Username { - var accName string - if u.Account != nil { - accName = u.Account.Name - } - return isAuthorized(u.Username, u.Password, accName) - } - } - return false - } - - // We are here if we accept leafnode connections without any credentials. - - // Still, if the CONNECT has some user info, we will bind to the - // user's account or to the specified default account (if provided) - // or to the global account. - return s.isClientAuthorized(c) -} - -// Support for bcrypt stored passwords and tokens. -var validBcryptPrefix = regexp.MustCompile(`^\$2[abxy]\$\d{2}\$.*`) - -// isBcrypt checks whether the given password or token is bcrypted. -func isBcrypt(password string) bool { - if strings.HasPrefix(password, "$") { - return validBcryptPrefix.MatchString(password) - } - - return false -} - -func comparePasswords(serverPassword, clientPassword string) bool { - // Check to see if the server password is a bcrypt hash - if isBcrypt(serverPassword) { - if err := bcrypt.CompareHashAndPassword([]byte(serverPassword), []byte(clientPassword)); err != nil { - return false - } - } else { - // stringToBytes should be constant-time near enough compared to - // turning a string into []byte normally. - spass := stringToBytes(serverPassword) - cpass := stringToBytes(clientPassword) - if subtle.ConstantTimeCompare(spass, cpass) == 0 { - return false - } - } - return true -} - -func validateAuth(o *Options) error { - if err := validatePinnedCerts(o.TLSPinnedCerts); err != nil { - return err - } - for _, u := range o.Users { - if err := validateAllowedConnectionTypes(u.AllowedConnectionTypes); err != nil { - return err - } - } - for _, u := range o.Nkeys { - if err := validateAllowedConnectionTypes(u.AllowedConnectionTypes); err != nil { - return err - } - } - return validateNoAuthUser(o, o.NoAuthUser) -} - -func validateAllowedConnectionTypes(m map[string]struct{}) error { - for ct := range m { - ctuc := strings.ToUpper(ct) - switch ctuc { - case jwt.ConnectionTypeStandard, jwt.ConnectionTypeWebsocket, - jwt.ConnectionTypeLeafnode, jwt.ConnectionTypeLeafnodeWS, - jwt.ConnectionTypeMqtt, jwt.ConnectionTypeMqttWS, - jwt.ConnectionTypeInProcess: - default: - return fmt.Errorf("unknown connection type %q", ct) - } - if ctuc != ct { - delete(m, ct) - m[ctuc] = struct{}{} - } - } - return nil -} - -func validateNoAuthUser(o *Options, noAuthUser string) error { - if noAuthUser == _EMPTY_ { - return nil - } - if len(o.TrustedOperators) > 0 { - return fmt.Errorf("no_auth_user not compatible with Trusted Operator") - } - - if o.Nkeys == nil && o.Users == nil { - return fmt.Errorf(`no_auth_user: "%s" present, but users/nkeys are not defined`, noAuthUser) - } - for _, u := range o.Users { - if u.Username == noAuthUser { - return nil - } - } - for _, u := range o.Nkeys { - if u.Nkey == noAuthUser { - return nil - } - } - return fmt.Errorf( - `no_auth_user: "%s" not present as user or nkey in authorization block or account configuration`, - noAuthUser) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/auth_callout.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/auth_callout.go deleted file mode 100644 index 3801d9ea..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/auth_callout.go +++ /dev/null @@ -1,480 +0,0 @@ -// Copyright 2022-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "crypto/tls" - "encoding/pem" - "errors" - "fmt" - "time" - "unicode" - - "github.com/nats-io/jwt/v2" - "github.com/nats-io/nkeys" -) - -const ( - AuthCalloutSubject = "$SYS.REQ.USER.AUTH" - AuthRequestSubject = "nats-authorization-request" - AuthRequestXKeyHeader = "Nats-Server-Xkey" -) - -// Process a callout on this client's behalf. -func (s *Server) processClientOrLeafCallout(c *client, opts *Options) (authorized bool, errStr string) { - isOperatorMode := len(opts.TrustedKeys) > 0 - - // this is the account the user connected in, or the one running the callout - var acc *Account - if !isOperatorMode && opts.AuthCallout != nil && opts.AuthCallout.Account != _EMPTY_ { - aname := opts.AuthCallout.Account - var err error - acc, err = s.LookupAccount(aname) - if err != nil { - errStr = fmt.Sprintf("No valid account %q for auth callout request: %v", aname, err) - s.Warnf(errStr) - return false, errStr - } - } else { - acc = c.acc - } - - // Check if we have been requested to encrypt. - var xkp nkeys.KeyPair - var xkey string - var pubAccXKey string - if !isOperatorMode && opts.AuthCallout != nil && opts.AuthCallout.XKey != _EMPTY_ { - pubAccXKey = opts.AuthCallout.XKey - } else if isOperatorMode { - pubAccXKey = acc.externalAuthXKey() - } - // If set grab server's xkey keypair and public key. - if pubAccXKey != _EMPTY_ { - // These are only set on creation, so lock not needed. - xkp, xkey = s.xkp, s.info.XKey - } - - // FIXME: so things like the server ID that get assigned, are used as a sort of nonce - but - // reality is that the keypair here, is generated, so the response generated a JWT has to be - // this user - no replay possible - // Create a keypair for the user. We will expect this public user to be in the signed response. - // This prevents replay attacks. - ukp, _ := nkeys.CreateUser() - pub, _ := ukp.PublicKey() - - reply := s.newRespInbox() - respCh := make(chan string, 1) - - decodeResponse := func(rc *client, rmsg []byte, acc *Account) (*jwt.UserClaims, error) { - account := acc.Name - _, msg := rc.msgParts(rmsg) - - // This signals not authorized. - // Since this is an account subscription will always have "\r\n". - if len(msg) <= LEN_CR_LF { - return nil, fmt.Errorf("auth callout violation: %q on account %q", "no reason supplied", account) - } - // Strip trailing CRLF. - msg = msg[:len(msg)-LEN_CR_LF] - encrypted := false - // If we sent an encrypted request the response could be encrypted as well. - // we are expecting the input to be `eyJ` if it is a JWT - if xkp != nil && len(msg) > 0 && !bytes.HasPrefix(msg, []byte(jwtPrefix)) { - var err error - msg, err = xkp.Open(msg, pubAccXKey) - if err != nil { - return nil, fmt.Errorf("error decrypting auth callout response on account %q: %v", account, err) - } - encrypted = true - } - - cr, err := jwt.DecodeAuthorizationResponseClaims(string(msg)) - if err != nil { - return nil, err - } - vr := jwt.CreateValidationResults() - cr.Validate(vr) - if len(vr.Issues) > 0 { - return nil, fmt.Errorf("authorization response had validation errors: %v", vr.Issues[0]) - } - - // the subject is the user id - if cr.Subject != pub { - return nil, errors.New("auth callout violation: auth callout response is not for expected user") - } - - // check the audience to be the server ID - if cr.Audience != s.info.ID { - return nil, errors.New("auth callout violation: auth callout response is not for server") - } - - // check if had an error message from the auth account - if cr.Error != _EMPTY_ { - return nil, fmt.Errorf("auth callout service returned an error: %v", cr.Error) - } - - // if response is encrypted none of this is needed - if isOperatorMode && !encrypted { - pkStr := cr.Issuer - if cr.IssuerAccount != _EMPTY_ { - pkStr = cr.IssuerAccount - } - if pkStr != account { - if _, ok := acc.signingKeys[pkStr]; !ok { - return nil, errors.New("auth callout signing key is unknown") - } - } - } - - return jwt.DecodeUserClaims(cr.Jwt) - } - - // getIssuerAccount returns the issuer (as per JWT) - it also asserts that - // only in operator mode we expect to receive `issuer_account`. - getIssuerAccount := func(arc *jwt.UserClaims, account string) (string, error) { - // Make sure correct issuer. - var issuer string - if opts.AuthCallout != nil { - issuer = opts.AuthCallout.Issuer - } else { - // Operator mode is who we send the request on unless switching accounts. - issuer = acc.Name - } - - // the jwt issuer can be a signing key - jwtIssuer := arc.Issuer - if arc.IssuerAccount != _EMPTY_ { - if !isOperatorMode { - // this should be invalid - effectively it would allow the auth callout - // to issue on another account which may be allowed given the configuration - // where the auth callout account can handle multiple different ones.. - return _EMPTY_, fmt.Errorf("error non operator mode account %q: attempted to use issuer_account", account) - } - jwtIssuer = arc.IssuerAccount - } - - if jwtIssuer != issuer { - if !isOperatorMode { - return _EMPTY_, fmt.Errorf("wrong issuer for auth callout response on account %q, expected %q got %q", account, issuer, jwtIssuer) - } else if !acc.isAllowedAcount(jwtIssuer) { - return _EMPTY_, fmt.Errorf("account %q not permitted as valid account option for auth callout for account %q", - arc.Issuer, account) - } - } - return jwtIssuer, nil - } - - getExpirationAndAllowedConnections := func(arc *jwt.UserClaims, account string) (time.Duration, map[string]struct{}, error) { - allowNow, expiration := validateTimes(arc) - if !allowNow { - c.Errorf("Outside connect times") - return 0, nil, fmt.Errorf("authorized user on account %q outside of valid connect times", account) - } - - allowedConnTypes, err := convertAllowedConnectionTypes(arc.User.AllowedConnectionTypes) - if err != nil { - c.Debugf("%v", err) - if len(allowedConnTypes) == 0 { - return 0, nil, fmt.Errorf("authorized user on account %q using invalid connection type", account) - } - } - return expiration, allowedConnTypes, nil - } - - assignAccountAndPermissions := func(arc *jwt.UserClaims, account string) (*Account, error) { - // Apply to this client. - var err error - issuerAccount, err := getIssuerAccount(arc, account) - if err != nil { - return nil, err - } - - // if we are not in operator mode, they can specify placement as a tag - var placement string - if !isOperatorMode { - // only allow placement if we are not in operator mode - placement = arc.Audience - } else { - placement = issuerAccount - } - - targetAcc, err := s.LookupAccount(placement) - if err != nil { - return nil, fmt.Errorf("no valid account %q for auth callout response on account %q: %v", placement, account, err) - } - if isOperatorMode { - // this will validate the signing key that emitted the user, and if it is a signing - // key it assigns the permissions from the target account - if scope, ok := targetAcc.hasIssuer(arc.Issuer); !ok { - return nil, fmt.Errorf("user JWT issuer %q is not known", arc.Issuer) - } else if scope != nil { - // this possibly has to be different because it could just be a plain issued by a non-scoped signing key - if err := scope.ValidateScopedSigner(arc); err != nil { - return nil, fmt.Errorf("user JWT is not valid: %v", err) - } else if uSc, ok := scope.(*jwt.UserScope); !ok { - return nil, fmt.Errorf("user JWT is not a valid scoped user") - } else if arc.User.UserPermissionLimits, err = processUserPermissionsTemplate(uSc.Template, arc, targetAcc); err != nil { - return nil, fmt.Errorf("user JWT generated invalid permissions: %v", err) - } - } - } - return targetAcc, nil - } - - processReply := func(_ *subscription, rc *client, racc *Account, subject, reply string, rmsg []byte) { - titleCase := func(m string) string { - r := []rune(m) - return string(append([]rune{unicode.ToUpper(r[0])}, r[1:]...)) - } - - arc, err := decodeResponse(rc, rmsg, racc) - if err != nil { - c.authViolation() - respCh <- titleCase(err.Error()) - return - } - vr := jwt.CreateValidationResults() - arc.Validate(vr) - if len(vr.Issues) > 0 { - c.authViolation() - respCh <- fmt.Sprintf("Error validating user JWT: %v", vr.Issues[0]) - return - } - - // Make sure that the user is what we requested. - if arc.Subject != pub { - c.authViolation() - respCh <- fmt.Sprintf("Expected authorized user of %q but got %q on account %q", pub, arc.Subject, racc.Name) - return - } - - expiration, allowedConnTypes, err := getExpirationAndAllowedConnections(arc, racc.Name) - if err != nil { - c.authViolation() - respCh <- titleCase(err.Error()) - return - } - - targetAcc, err := assignAccountAndPermissions(arc, racc.Name) - if err != nil { - c.authViolation() - respCh <- titleCase(err.Error()) - return - } - - // the JWT is cleared, because if in operator mode it may hold the JWT - // for the bearer token that connected to the callout if in operator mode - // the permissions are already set on the client, this prevents a decode - // on c.RegisterNKeyUser which would have wrong values - c.mu.Lock() - c.opts.JWT = _EMPTY_ - c.mu.Unlock() - - // Build internal user and bind to the targeted account. - nkuser := buildInternalNkeyUser(arc, allowedConnTypes, targetAcc) - if err := c.RegisterNkeyUser(nkuser); err != nil { - c.authViolation() - respCh <- fmt.Sprintf("Could not register auth callout user: %v", err) - return - } - - // See if the response wants to override the username. - if arc.Name != _EMPTY_ { - c.mu.Lock() - c.opts.Username = arc.Name - // Clear any others. - c.opts.Nkey = _EMPTY_ - c.pubKey = _EMPTY_ - c.opts.Token = _EMPTY_ - c.mu.Unlock() - } - - // Check if we need to set an auth timer if the user jwt expires. - c.setExpiration(arc.Claims(), expiration) - - respCh <- _EMPTY_ - } - - // create a subscription to receive a response from the authcallout - sub, err := acc.subscribeInternal(reply, processReply) - if err != nil { - errStr = fmt.Sprintf("Error setting up reply subscription for auth request: %v", err) - s.Warnf(errStr) - return false, errStr - } - defer acc.unsubscribeInternal(sub) - - // Build our request claims - jwt subject should be nkey - jwtSub := acc.Name - if opts.AuthCallout != nil { - jwtSub = opts.AuthCallout.Issuer - } - - // The public key of the server, if set is available on Varz.Key - // This means that when a service connects, it can now peer - // authenticate if it wants to - but that also means that it needs to be - // listening to cluster changes - claim := jwt.NewAuthorizationRequestClaims(jwtSub) - claim.Audience = AuthRequestSubject - // Set expected public user nkey. - claim.UserNkey = pub - - s.mu.RLock() - claim.Server = jwt.ServerID{ - Name: s.info.Name, - Host: s.info.Host, - ID: s.info.ID, - Version: s.info.Version, - Cluster: s.info.Cluster, - } - s.mu.RUnlock() - - // Tags - claim.Server.Tags = s.getOpts().Tags - - // Check if we have been requested to encrypt. - // FIXME: possibly this public key also needs to be on the - // Varz, because then it can be peer verified? - if xkp != nil { - claim.Server.XKey = xkey - } - - authTimeout := secondsToDuration(s.getOpts().AuthTimeout) - claim.Expires = time.Now().Add(time.Duration(authTimeout)).UTC().Unix() - - // Grab client info for the request. - c.mu.Lock() - c.fillClientInfo(&claim.ClientInformation) - c.fillConnectOpts(&claim.ConnectOptions) - // If we have a sig in the client opts, fill in nonce. - if claim.ConnectOptions.SignedNonce != _EMPTY_ { - claim.ClientInformation.Nonce = string(c.nonce) - } - - // TLS - if c.flags.isSet(handshakeComplete) && c.nc != nil { - var ct jwt.ClientTLS - conn := c.nc.(*tls.Conn) - cs := conn.ConnectionState() - ct.Version = tlsVersion(cs.Version) - ct.Cipher = tlsCipher(cs.CipherSuite) - // Check verified chains. - for _, vs := range cs.VerifiedChains { - var certs []string - for _, c := range vs { - blk := &pem.Block{ - Type: "CERTIFICATE", - Bytes: c.Raw, - } - certs = append(certs, string(pem.EncodeToMemory(blk))) - } - ct.VerifiedChains = append(ct.VerifiedChains, certs) - } - // If we do not have verified chains put in peer certs. - if len(ct.VerifiedChains) == 0 { - for _, c := range cs.PeerCertificates { - blk := &pem.Block{ - Type: "CERTIFICATE", - Bytes: c.Raw, - } - ct.Certs = append(ct.Certs, string(pem.EncodeToMemory(blk))) - } - } - claim.TLS = &ct - } - c.mu.Unlock() - - b, err := claim.Encode(s.kp) - if err != nil { - errStr = fmt.Sprintf("Error encoding auth request claim on account %q: %v", acc.Name, err) - s.Warnf(errStr) - return false, errStr - } - req := []byte(b) - var hdr map[string]string - - // Check if we have been asked to encrypt. - if xkp != nil { - req, err = xkp.Seal([]byte(req), pubAccXKey) - if err != nil { - errStr = fmt.Sprintf("Error encrypting auth request claim on account %q: %v", acc.Name, err) - s.Warnf(errStr) - return false, errStr - } - hdr = map[string]string{AuthRequestXKeyHeader: xkey} - } - - // Send out our request. - if err := s.sendInternalAccountMsgWithReply(acc, AuthCalloutSubject, reply, hdr, req, false); err != nil { - errStr = fmt.Sprintf("Error sending authorization request: %v", err) - s.Debugf(errStr) - return false, errStr - } - select { - case errStr = <-respCh: - if authorized = errStr == _EMPTY_; !authorized { - s.Warnf(errStr) - } - case <-time.After(authTimeout): - s.Debugf(fmt.Sprintf("Authorization callout response not received in time on account %q", acc.Name)) - } - - return authorized, errStr -} - -// Fill in client information for the request. -// Lock should be held. -func (c *client) fillClientInfo(ci *jwt.ClientInformation) { - if c == nil || (c.kind != CLIENT && c.kind != LEAF && c.kind != JETSTREAM && c.kind != ACCOUNT) { - return - } - - // Do it this way to fail to compile if fields are added to jwt.ClientInformation. - *ci = jwt.ClientInformation{ - Host: c.host, - ID: c.cid, - User: c.getRawAuthUser(), - Name: c.opts.Name, - Tags: c.tags, - NameTag: c.nameTag, - Kind: c.kindString(), - Type: c.clientTypeString(), - MQTT: c.getMQTTClientID(), - } -} - -// Fill in client options. -// Lock should be held. -func (c *client) fillConnectOpts(opts *jwt.ConnectOptions) { - if c == nil || (c.kind != CLIENT && c.kind != LEAF && c.kind != JETSTREAM && c.kind != ACCOUNT) { - return - } - - o := c.opts - - // Do it this way to fail to compile if fields are added to jwt.ClientInformation. - *opts = jwt.ConnectOptions{ - JWT: o.JWT, - Nkey: o.Nkey, - SignedNonce: o.Sig, - Token: o.Token, - Username: o.Username, - Password: o.Password, - Name: o.Name, - Lang: o.Lang, - Version: o.Version, - Protocol: o.Protocol, - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/avl/seqset.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/avl/seqset.go deleted file mode 100644 index 96ff3767..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/avl/seqset.go +++ /dev/null @@ -1,678 +0,0 @@ -// Copyright 2023-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package avl - -import ( - "cmp" - "encoding/binary" - "errors" - "math/bits" - "slices" -) - -// SequenceSet is a memory and encoding optimized set for storing unsigned ints. -// -// SequenceSet is ~80-100 times more efficient memory wise than a map[uint64]struct{}. -// SequenceSet is ~1.75 times slower at inserts than the same map. -// SequenceSet is not thread safe. -// -// We use an AVL tree with nodes that hold bitmasks for set membership. -// -// Encoding will convert to a space optimized encoding using bitmasks. -type SequenceSet struct { - root *node // root node - size int // number of items - nodes int // number of nodes - // Having this here vs on the stack in Insert/Delete - // makes a difference in memory usage. - changed bool -} - -// Insert will insert the sequence into the set. -// The tree will be balanced inline. -func (ss *SequenceSet) Insert(seq uint64) { - if ss.root = ss.root.insert(seq, &ss.changed, &ss.nodes); ss.changed { - ss.changed = false - ss.size++ - } -} - -// Exists will return true iff the sequence is a member of this set. -func (ss *SequenceSet) Exists(seq uint64) bool { - for n := ss.root; n != nil; { - if seq < n.base { - n = n.l - continue - } else if seq >= n.base+numEntries { - n = n.r - continue - } - return n.exists(seq) - } - return false -} - -// SetInitialMin should be used to set the initial minimum sequence when known. -// This will more effectively utilize space versus self selecting. -// The set should be empty. -func (ss *SequenceSet) SetInitialMin(min uint64) error { - if !ss.IsEmpty() { - return ErrSetNotEmpty - } - ss.root, ss.nodes = &node{base: min, h: 1}, 1 - return nil -} - -// Delete will remove the sequence from the set. -// Will optionally remove nodes and rebalance. -// Returns where the sequence was set. -func (ss *SequenceSet) Delete(seq uint64) bool { - if ss == nil || ss.root == nil { - return false - } - ss.root = ss.root.delete(seq, &ss.changed, &ss.nodes) - if ss.changed { - ss.changed = false - ss.size-- - if ss.size == 0 { - ss.Empty() - } - return true - } - return false -} - -// Size returns the number of items in the set. -func (ss *SequenceSet) Size() int { - return ss.size -} - -// Nodes returns the number of nodes in the tree. -func (ss *SequenceSet) Nodes() int { - return ss.nodes -} - -// Empty will clear all items from a set. -func (ss *SequenceSet) Empty() { - ss.root = nil - ss.size = 0 - ss.nodes = 0 -} - -// IsEmpty is a fast check of the set being empty. -func (ss *SequenceSet) IsEmpty() bool { - if ss == nil || ss.root == nil { - return true - } - return false -} - -// Range will invoke the given function for each item in the set. -// They will range over the set in ascending order. -// If the callback returns false we terminate the iteration. -func (ss *SequenceSet) Range(f func(uint64) bool) { - ss.root.iter(f) -} - -// Heights returns the left and right heights of the tree. -func (ss *SequenceSet) Heights() (l, r int) { - if ss.root == nil { - return 0, 0 - } - if ss.root.l != nil { - l = ss.root.l.h - } - if ss.root.r != nil { - r = ss.root.r.h - } - return l, r -} - -// Returns min, max and number of set items. -func (ss *SequenceSet) State() (min, max, num uint64) { - if ss == nil || ss.root == nil { - return 0, 0, 0 - } - min, max = ss.MinMax() - return min, max, uint64(ss.Size()) -} - -// MinMax will return the minunum and maximum values in the set. -func (ss *SequenceSet) MinMax() (min, max uint64) { - if ss.root == nil { - return 0, 0 - } - for l := ss.root; l != nil; l = l.l { - if l.l == nil { - min = l.min() - } - } - for r := ss.root; r != nil; r = r.r { - if r.r == nil { - max = r.max() - } - } - return min, max -} - -func clone(src *node, target **node) { - if src == nil { - return - } - n := &node{base: src.base, bits: src.bits, h: src.h} - *target = n - clone(src.l, &n.l) - clone(src.r, &n.r) -} - -// Clone will return a clone of the given SequenceSet. -func (ss *SequenceSet) Clone() *SequenceSet { - if ss == nil { - return nil - } - css := &SequenceSet{nodes: ss.nodes, size: ss.size} - clone(ss.root, &css.root) - - return css -} - -// Union will union this SequenceSet with ssa. -func (ss *SequenceSet) Union(ssa ...*SequenceSet) { - for _, sa := range ssa { - sa.root.nodeIter(func(n *node) { - for nb, b := range n.bits { - for pos := uint64(0); b != 0; pos++ { - if b&1 == 1 { - seq := n.base + (uint64(nb) * uint64(bitsPerBucket)) + pos - ss.Insert(seq) - } - b >>= 1 - } - } - }) - } -} - -// Union will return a union of all sets. -func Union(ssa ...*SequenceSet) *SequenceSet { - if len(ssa) == 0 { - return nil - } - // Sort so we can clone largest. - slices.SortFunc(ssa, func(i, j *SequenceSet) int { return -cmp.Compare(i.Size(), j.Size()) }) // reverse order - ss := ssa[0].Clone() - - // Insert the rest through range call. - for i := 1; i < len(ssa); i++ { - ssa[i].Range(func(n uint64) bool { - ss.Insert(n) - return true - }) - } - return ss -} - -const ( - // Magic is used to identify the encode binary state.. - magic = uint8(22) - // Version - version = uint8(2) - // hdrLen - hdrLen = 2 - // minimum length of an encoded SequenceSet. - minLen = 2 + 8 // magic + version + num nodes + num entries. -) - -// EncodeLen returns the bytes needed for encoding. -func (ss SequenceSet) EncodeLen() int { - return minLen + (ss.Nodes() * ((numBuckets+1)*8 + 2)) -} - -func (ss SequenceSet) Encode(buf []byte) ([]byte, error) { - nn, encLen := ss.Nodes(), ss.EncodeLen() - - if cap(buf) < encLen { - buf = make([]byte, encLen) - } else { - buf = buf[:encLen] - } - - // TODO(dlc) - Go 1.19 introduced Append to not have to keep track. - // Once 1.20 is out we could change this over. - // Also binary.Write() is way slower, do not use. - - var le = binary.LittleEndian - buf[0], buf[1] = magic, version - i := hdrLen - le.PutUint32(buf[i:], uint32(nn)) - le.PutUint32(buf[i+4:], uint32(ss.size)) - i += 8 - ss.root.nodeIter(func(n *node) { - le.PutUint64(buf[i:], n.base) - i += 8 - for _, b := range n.bits { - le.PutUint64(buf[i:], b) - i += 8 - } - le.PutUint16(buf[i:], uint16(n.h)) - i += 2 - }) - return buf[:i], nil -} - -// ErrBadEncoding is returned when we can not decode properly. -var ( - ErrBadEncoding = errors.New("ss: bad encoding") - ErrBadVersion = errors.New("ss: bad version") - ErrSetNotEmpty = errors.New("ss: set not empty") -) - -// Decode returns the sequence set and number of bytes read from the buffer on success. -func Decode(buf []byte) (*SequenceSet, int, error) { - if len(buf) < minLen || buf[0] != magic { - return nil, -1, ErrBadEncoding - } - - switch v := buf[1]; v { - case 1: - return decodev1(buf) - case 2: - return decodev2(buf) - default: - return nil, -1, ErrBadVersion - } -} - -// Helper to decode v2. -func decodev2(buf []byte) (*SequenceSet, int, error) { - var le = binary.LittleEndian - index := 2 - nn := int(le.Uint32(buf[index:])) - sz := int(le.Uint32(buf[index+4:])) - index += 8 - - expectedLen := minLen + (nn * ((numBuckets+1)*8 + 2)) - if len(buf) < expectedLen { - return nil, -1, ErrBadEncoding - } - - ss, nodes := SequenceSet{size: sz}, make([]node, nn) - - for i := 0; i < nn; i++ { - n := &nodes[i] - n.base = le.Uint64(buf[index:]) - index += 8 - for bi := range n.bits { - n.bits[bi] = le.Uint64(buf[index:]) - index += 8 - } - n.h = int(le.Uint16(buf[index:])) - index += 2 - ss.insertNode(n) - } - - return &ss, index, nil -} - -// Helper to decode v1 into v2 which has fixed buckets of 32 vs 64 originally. -func decodev1(buf []byte) (*SequenceSet, int, error) { - var le = binary.LittleEndian - index := 2 - nn := int(le.Uint32(buf[index:])) - sz := int(le.Uint32(buf[index+4:])) - index += 8 - - const v1NumBuckets = 64 - - expectedLen := minLen + (nn * ((v1NumBuckets+1)*8 + 2)) - if len(buf) < expectedLen { - return nil, -1, ErrBadEncoding - } - - var ss SequenceSet - for i := 0; i < nn; i++ { - base := le.Uint64(buf[index:]) - index += 8 - for nb := uint64(0); nb < v1NumBuckets; nb++ { - n := le.Uint64(buf[index:]) - // Walk all set bits and insert sequences manually for this decode from v1. - for pos := uint64(0); n != 0; pos++ { - if n&1 == 1 { - seq := base + (nb * uint64(bitsPerBucket)) + pos - ss.Insert(seq) - } - n >>= 1 - } - index += 8 - } - // Skip over encoded height. - index += 2 - } - - // Sanity check. - if ss.Size() != sz { - return nil, -1, ErrBadEncoding - } - - return &ss, index, nil - -} - -// insertNode places a decoded node into the tree. -// These should be done in tree order as defined by Encode() -// This allows us to not have to calculate height or do rebalancing. -// So much better performance this way. -func (ss *SequenceSet) insertNode(n *node) { - ss.nodes++ - - if ss.root == nil { - ss.root = n - return - } - // Walk our way to the insertion point. - for p := ss.root; p != nil; { - if n.base < p.base { - if p.l == nil { - p.l = n - return - } - p = p.l - } else { - if p.r == nil { - p.r = n - return - } - p = p.r - } - } -} - -const ( - bitsPerBucket = 64 // bits in uint64 - numBuckets = 32 - numEntries = numBuckets * bitsPerBucket -) - -type node struct { - //v dvalue - base uint64 - bits [numBuckets]uint64 - l *node - r *node - h int -} - -// Set the proper bit. -// seq should have already been qualified and inserted should be non nil. -func (n *node) set(seq uint64, inserted *bool) { - seq -= n.base - i := seq / bitsPerBucket - mask := uint64(1) << (seq % bitsPerBucket) - if (n.bits[i] & mask) == 0 { - n.bits[i] |= mask - *inserted = true - } -} - -func (n *node) insert(seq uint64, inserted *bool, nodes *int) *node { - if n == nil { - base := (seq / numEntries) * numEntries - n := &node{base: base, h: 1} - n.set(seq, inserted) - *nodes++ - return n - } - - if seq < n.base { - n.l = n.l.insert(seq, inserted, nodes) - } else if seq >= n.base+numEntries { - n.r = n.r.insert(seq, inserted, nodes) - } else { - n.set(seq, inserted) - } - - n.h = maxH(n) + 1 - - // Don't make a function, impacts performance. - if bf := balanceF(n); bf > 1 { - // Left unbalanced. - if balanceF(n.l) < 0 { - n.l = n.l.rotateL() - } - return n.rotateR() - } else if bf < -1 { - // Right unbalanced. - if balanceF(n.r) > 0 { - n.r = n.r.rotateR() - } - return n.rotateL() - } - return n -} - -func (n *node) rotateL() *node { - r := n.r - if r != nil { - n.r = r.l - r.l = n - n.h = maxH(n) + 1 - r.h = maxH(r) + 1 - } else { - n.r = nil - n.h = maxH(n) + 1 - } - return r -} - -func (n *node) rotateR() *node { - l := n.l - if l != nil { - n.l = l.r - l.r = n - n.h = maxH(n) + 1 - l.h = maxH(l) + 1 - } else { - n.l = nil - n.h = maxH(n) + 1 - } - return l -} - -func balanceF(n *node) int { - if n == nil { - return 0 - } - var lh, rh int - if n.l != nil { - lh = n.l.h - } - if n.r != nil { - rh = n.r.h - } - return lh - rh -} - -func maxH(n *node) int { - if n == nil { - return 0 - } - var lh, rh int - if n.l != nil { - lh = n.l.h - } - if n.r != nil { - rh = n.r.h - } - if lh > rh { - return lh - } - return rh -} - -// Clear the proper bit. -// seq should have already been qualified and deleted should be non nil. -// Will return true if this node is now empty. -func (n *node) clear(seq uint64, deleted *bool) bool { - seq -= n.base - i := seq / bitsPerBucket - mask := uint64(1) << (seq % bitsPerBucket) - if (n.bits[i] & mask) != 0 { - n.bits[i] &^= mask - *deleted = true - } - for _, b := range n.bits { - if b != 0 { - return false - } - } - return true -} - -func (n *node) delete(seq uint64, deleted *bool, nodes *int) *node { - if n == nil { - return nil - } - - if seq < n.base { - n.l = n.l.delete(seq, deleted, nodes) - } else if seq >= n.base+numEntries { - n.r = n.r.delete(seq, deleted, nodes) - } else if empty := n.clear(seq, deleted); empty { - *nodes-- - if n.l == nil { - n = n.r - } else if n.r == nil { - n = n.l - } else { - // We have both children. - n.r = n.r.insertNodePrev(n.l) - n = n.r - } - } - - if n != nil { - n.h = maxH(n) + 1 - } - - // Check balance. - if bf := balanceF(n); bf > 1 { - // Left unbalanced. - if balanceF(n.l) < 0 { - n.l = n.l.rotateL() - } - return n.rotateR() - } else if bf < -1 { - // right unbalanced. - if balanceF(n.r) > 0 { - n.r = n.r.rotateR() - } - return n.rotateL() - } - - return n -} - -// Will insert nn into the node assuming it is less than all other nodes in n. -// Will re-calculate height and balance. -func (n *node) insertNodePrev(nn *node) *node { - if n.l == nil { - n.l = nn - } else { - n.l = n.l.insertNodePrev(nn) - } - n.h = maxH(n) + 1 - - // Check balance. - if bf := balanceF(n); bf > 1 { - // Left unbalanced. - if balanceF(n.l) < 0 { - n.l = n.l.rotateL() - } - return n.rotateR() - } else if bf < -1 { - // right unbalanced. - if balanceF(n.r) > 0 { - n.r = n.r.rotateR() - } - return n.rotateL() - } - return n -} - -func (n *node) exists(seq uint64) bool { - seq -= n.base - i := seq / bitsPerBucket - mask := uint64(1) << (seq % bitsPerBucket) - return n.bits[i]&mask != 0 -} - -// Return minimum sequence in the set. -// This node can not be empty. -func (n *node) min() uint64 { - for i, b := range n.bits { - if b != 0 { - return n.base + - uint64(i*bitsPerBucket) + - uint64(bits.TrailingZeros64(b)) - } - } - return 0 -} - -// Return maximum sequence in the set. -// This node can not be empty. -func (n *node) max() uint64 { - for i := numBuckets - 1; i >= 0; i-- { - if b := n.bits[i]; b != 0 { - return n.base + - uint64(i*bitsPerBucket) + - uint64(bitsPerBucket-bits.LeadingZeros64(b>>1)) - } - } - return 0 -} - -// This is done in tree order. -func (n *node) nodeIter(f func(n *node)) { - if n == nil { - return - } - f(n) - n.l.nodeIter(f) - n.r.nodeIter(f) -} - -// iter will iterate through the set's items in this node. -// If the supplied function returns false we terminate the iteration. -func (n *node) iter(f func(uint64) bool) bool { - if n == nil { - return true - } - - if ok := n.l.iter(f); !ok { - return false - } - for num := n.base; num < n.base+numEntries; num++ { - if n.exists(num) { - if ok := f(num); !ok { - return false - } - } - } - if ok := n.r.iter(f); !ok { - return false - } - - return true -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certidp/certidp.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certidp/certidp.go deleted file mode 100644 index a2661857..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certidp/certidp.go +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright 2023-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package certidp - -import ( - "crypto/sha256" - "crypto/x509" - "encoding/base64" - "encoding/json" - "fmt" - "net/url" - "strings" - "time" - - "golang.org/x/crypto/ocsp" -) - -const ( - DefaultAllowedClockSkew = 30 * time.Second - DefaultOCSPResponderTimeout = 2 * time.Second - DefaultTTLUnsetNextUpdate = 1 * time.Hour -) - -type StatusAssertion int - -var ( - StatusAssertionStrToVal = map[string]StatusAssertion{ - "good": ocsp.Good, - "revoked": ocsp.Revoked, - "unknown": ocsp.Unknown, - } - StatusAssertionValToStr = map[StatusAssertion]string{ - ocsp.Good: "good", - ocsp.Revoked: "revoked", - ocsp.Unknown: "unknown", - } - StatusAssertionIntToVal = map[int]StatusAssertion{ - 0: ocsp.Good, - 1: ocsp.Revoked, - 2: ocsp.Unknown, - } -) - -// GetStatusAssertionStr returns the corresponding string representation of the StatusAssertion. -func GetStatusAssertionStr(sa int) string { - // If the provided status assertion value is not found in the map (StatusAssertionIntToVal), - // the function defaults to "unknown" to avoid defaulting to "good," which is the default iota value - // for the ocsp.StatusAssertion enumeration (https://pkg.go.dev/golang.org/x/crypto/ocsp#pkg-constants). - // This ensures that we don't unintentionally default to "good" when there's no map entry. - v, ok := StatusAssertionIntToVal[sa] - if !ok { - // set unknown as fallback - v = ocsp.Unknown - } - - return StatusAssertionValToStr[v] -} - -func (sa StatusAssertion) MarshalJSON() ([]byte, error) { - // This ensures that we don't unintentionally default to "good" when there's no map entry. - // (see more details in the GetStatusAssertionStr() comment) - str, ok := StatusAssertionValToStr[sa] - if !ok { - // set unknown as fallback - str = StatusAssertionValToStr[ocsp.Unknown] - } - return json.Marshal(str) -} - -func (sa *StatusAssertion) UnmarshalJSON(in []byte) error { - // This ensures that we don't unintentionally default to "good" when there's no map entry. - // (see more details in the GetStatusAssertionStr() comment) - v, ok := StatusAssertionStrToVal[strings.ReplaceAll(string(in), "\"", "")] - if !ok { - // set unknown as fallback - v = StatusAssertionStrToVal["unknown"] - } - *sa = v - return nil -} - -type ChainLink struct { - Leaf *x509.Certificate - Issuer *x509.Certificate - OCSPWebEndpoints *[]*url.URL -} - -// OCSPPeerConfig holds the parsed OCSP peer configuration section of TLS configuration -type OCSPPeerConfig struct { - Verify bool - Timeout float64 - ClockSkew float64 - WarnOnly bool - UnknownIsGood bool - AllowWhenCAUnreachable bool - TTLUnsetNextUpdate float64 -} - -func NewOCSPPeerConfig() *OCSPPeerConfig { - return &OCSPPeerConfig{ - Verify: false, - Timeout: DefaultOCSPResponderTimeout.Seconds(), - ClockSkew: DefaultAllowedClockSkew.Seconds(), - WarnOnly: false, - UnknownIsGood: false, - AllowWhenCAUnreachable: false, - TTLUnsetNextUpdate: DefaultTTLUnsetNextUpdate.Seconds(), - } -} - -// Log is a neutral method of passing server loggers to plugins -type Log struct { - Debugf func(format string, v ...any) - Noticef func(format string, v ...any) - Warnf func(format string, v ...any) - Errorf func(format string, v ...any) - Tracef func(format string, v ...any) -} - -type CertInfo struct { - Subject string `json:"subject,omitempty"` - Issuer string `json:"issuer,omitempty"` - Fingerprint string `json:"fingerprint,omitempty"` - Raw []byte `json:"raw,omitempty"` -} - -var OCSPPeerUsage = ` -For client, leaf spoke (remotes), and leaf hub connections, you may enable OCSP peer validation: - - tls { - ... - # mTLS must be enabled (with exception of Leaf remotes) - verify: true - ... - # short form enables peer verify and takes option defaults - ocsp_peer: true - - # long form includes settable options - ocsp_peer { - # Enable OCSP peer validation (default false) - verify: true - - # OCSP responder timeout in seconds (may be fractional, default 2 seconds) - ca_timeout: 2 - - # Allowed skew between server and OCSP responder time in seconds (may be fractional, default 30 seconds) - allowed_clockskew: 30 - - # Warn-only and never reject connections (default false) - warn_only: false - - # Treat response Unknown status as valid certificate (default false) - unknown_is_good: false - - # Warn-only if no CA response can be obtained and no cached revocation exists (default false) - allow_when_ca_unreachable: false - - # If response NextUpdate unset by CA, set a default cache TTL in seconds from ThisUpdate (default 1 hour) - cache_ttl_when_next_update_unset: 3600 - } - ... - } - -Note: OCSP validation for route and gateway connections is enabled using the 'ocsp' configuration option. -` - -// GenerateFingerprint returns a base64-encoded SHA256 hash of the raw certificate -func GenerateFingerprint(cert *x509.Certificate) string { - data := sha256.Sum256(cert.Raw) - return base64.StdEncoding.EncodeToString(data[:]) -} - -func getWebEndpoints(uris []string) []*url.URL { - var urls []*url.URL - for _, uri := range uris { - endpoint, err := url.ParseRequestURI(uri) - if err != nil { - // skip invalid URLs - continue - } - if endpoint.Scheme != "http" && endpoint.Scheme != "https" { - // skip non-web URLs - continue - } - urls = append(urls, endpoint) - } - return urls -} - -// GetSubjectDNForm returns RDN sequence concatenation of the certificate's subject to be -// used in logs, events, etc. Should never be used for reliable cache matching or other crypto purposes. -func GetSubjectDNForm(cert *x509.Certificate) string { - if cert == nil { - return "" - } - return strings.TrimSuffix(fmt.Sprintf("%s+", cert.Subject.ToRDNSequence()), "+") -} - -// GetIssuerDNForm returns RDN sequence concatenation of the certificate's issuer to be -// used in logs, events, etc. Should never be used for reliable cache matching or other crypto purposes. -func GetIssuerDNForm(cert *x509.Certificate) string { - if cert == nil { - return "" - } - return strings.TrimSuffix(fmt.Sprintf("%s+", cert.Issuer.ToRDNSequence()), "+") -} - -// CertOCSPEligible checks if the certificate's issuer has populated AIA with OCSP responder endpoint(s) -// and is thus eligible for OCSP validation -func CertOCSPEligible(link *ChainLink) bool { - if link == nil || link.Leaf.Raw == nil || len(link.Leaf.Raw) == 0 { - return false - } - if len(link.Leaf.OCSPServer) == 0 { - return false - } - urls := getWebEndpoints(link.Leaf.OCSPServer) - if len(urls) == 0 { - return false - } - link.OCSPWebEndpoints = &urls - return true -} - -// GetLeafIssuerCert returns the issuer certificate of the leaf (positional) certificate in the chain -func GetLeafIssuerCert(chain []*x509.Certificate, leafPos int) *x509.Certificate { - if len(chain) == 0 || leafPos < 0 { - return nil - } - // self-signed certificate or too-big leafPos - if leafPos >= len(chain)-1 { - return nil - } - // returns pointer to issuer cert or nil - return (chain)[leafPos+1] -} - -// OCSPResponseCurrent checks if the OCSP response is current (i.e. not expired and not future effective) -func OCSPResponseCurrent(ocspr *ocsp.Response, opts *OCSPPeerConfig, log *Log) bool { - skew := time.Duration(opts.ClockSkew * float64(time.Second)) - if skew < 0*time.Second { - skew = DefaultAllowedClockSkew - } - now := time.Now().UTC() - // Typical effectivity check based on CA response ThisUpdate and NextUpdate semantics - if !ocspr.NextUpdate.IsZero() && ocspr.NextUpdate.Before(now.Add(-1*skew)) { - t := ocspr.NextUpdate.Format(time.RFC3339Nano) - nt := now.Format(time.RFC3339Nano) - log.Debugf(DbgResponseExpired, t, nt, skew) - return false - } - // CA responder can assert NextUpdate unset, in which case use config option to set a default cache TTL - if ocspr.NextUpdate.IsZero() { - ttl := time.Duration(opts.TTLUnsetNextUpdate * float64(time.Second)) - if ttl < 0*time.Second { - ttl = DefaultTTLUnsetNextUpdate - } - expiryTime := ocspr.ThisUpdate.Add(ttl) - if expiryTime.Before(now.Add(-1 * skew)) { - t := expiryTime.Format(time.RFC3339Nano) - nt := now.Format(time.RFC3339Nano) - log.Debugf(DbgResponseTTLExpired, t, nt, skew) - return false - } - } - if ocspr.ThisUpdate.After(now.Add(skew)) { - t := ocspr.ThisUpdate.Format(time.RFC3339Nano) - nt := now.Format(time.RFC3339Nano) - log.Debugf(DbgResponseFutureDated, t, nt, skew) - return false - } - return true -} - -// ValidDelegationCheck checks if the CA OCSP Response was signed by a valid CA Issuer delegate as per (RFC 6960, section 4.2.2.2) -// If a valid delegate or direct-signed by CA Issuer, true returned. -func ValidDelegationCheck(iss *x509.Certificate, ocspr *ocsp.Response) bool { - // This call assumes prior successful parse and signature validation of the OCSP response - // The Go OCSP library (as of x/crypto/ocsp v0.9) will detect and perform a 1-level delegate signature check but does not - // implement the additional criteria for delegation specified in RFC 6960, section 4.2.2.2. - if iss == nil || ocspr == nil { - return false - } - // not a delegation, no-op - if ocspr.Certificate == nil { - return true - } - // delegate is self-same with CA Issuer, not a delegation although response issued in that form - if ocspr.Certificate.Equal(iss) { - return true - } - // we need to verify CA Issuer stamped id-kp-OCSPSigning on delegate - delegatedSigner := false - for _, keyUseExt := range ocspr.Certificate.ExtKeyUsage { - if keyUseExt == x509.ExtKeyUsageOCSPSigning { - delegatedSigner = true - break - } - } - return delegatedSigner -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certidp/messages.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certidp/messages.go deleted file mode 100644 index 52a799ac..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certidp/messages.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2023 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package certidp - -var ( - // Returned errors - ErrIllegalPeerOptsConfig = "expected map to define OCSP peer options, got [%T]" - ErrIllegalCacheOptsConfig = "expected map to define OCSP peer cache options, got [%T]" - ErrParsingPeerOptFieldGeneric = "error parsing tls peer config, unknown field [%q]" - ErrParsingPeerOptFieldTypeConversion = "error parsing tls peer config, conversion error: %s" - ErrParsingCacheOptFieldTypeConversion = "error parsing OCSP peer cache config, conversion error: %s" - ErrUnableToPlugTLSEmptyConfig = "unable to plug TLS verify connection, config is nil" - ErrMTLSRequired = "OCSP peer verification for client connections requires TLS verify (mTLS) to be enabled" - ErrUnableToPlugTLSClient = "unable to register client OCSP verification" - ErrUnableToPlugTLSServer = "unable to register server OCSP verification" - ErrCannotWriteCompressed = "error writing to compression writer: %w" - ErrCannotReadCompressed = "error reading compression reader: %w" - ErrTruncatedWrite = "short write on body (%d != %d)" - ErrCannotCloseWriter = "error closing compression writer: %w" - ErrParsingCacheOptFieldGeneric = "error parsing OCSP peer cache config, unknown field [%q]" - ErrUnknownCacheType = "error parsing OCSP peer cache config, unknown type [%s]" - ErrInvalidChainlink = "invalid chain link" - ErrBadResponderHTTPStatus = "bad OCSP responder http status: [%d]" - ErrNoAvailOCSPServers = "no available OCSP servers" - ErrFailedWithAllRequests = "exhausted OCSP responders: %w" - - // Direct logged errors - ErrLoadCacheFail = "Unable to load OCSP peer cache: %s" - ErrSaveCacheFail = "Unable to save OCSP peer cache: %s" - ErrBadCacheTypeConfig = "Unimplemented OCSP peer cache type [%v]" - ErrResponseCompressFail = "Unable to compress OCSP response for key [%s]: %s" - ErrResponseDecompressFail = "Unable to decompress OCSP response for key [%s]: %s" - ErrPeerEmptyNoEvent = "Peer certificate is nil, cannot send OCSP peer reject event" - ErrPeerEmptyAutoReject = "Peer certificate is nil, rejecting OCSP peer" - - // Debug information - DbgPlugTLSForKind = "Plugging TLS OCSP peer for [%s]" - DbgNumServerChains = "Peer OCSP enabled: %d TLS server chain(s) will be evaluated" - DbgNumClientChains = "Peer OCSP enabled: %d TLS client chain(s) will be evaluated" - DbgLinksInChain = "Chain [%d]: %d total link(s)" - DbgSelfSignedValid = "Chain [%d] is self-signed, thus peer is valid" - DbgValidNonOCSPChain = "Chain [%d] has no OCSP eligible links, thus peer is valid" - DbgChainIsOCSPEligible = "Chain [%d] has %d OCSP eligible link(s)" - DbgChainIsOCSPValid = "Chain [%d] is OCSP valid for all eligible links, thus peer is valid" - DbgNoOCSPValidChains = "No OCSP valid chains, thus peer is invalid" - DbgCheckingCacheForCert = "Checking OCSP peer cache for [%s], key [%s]" - DbgCurrentResponseCached = "Cached OCSP response is current, status [%s]" - DbgExpiredResponseCached = "Cached OCSP response is expired, status [%s]" - DbgOCSPValidPeerLink = "OCSP verify pass for [%s]" - DbgCachingResponse = "Caching OCSP response for [%s], key [%s]" - DbgAchievedCompression = "OCSP response compression ratio: [%f]" - DbgCacheHit = "OCSP peer cache hit for key [%s]" - DbgCacheMiss = "OCSP peer cache miss for key [%s]" - DbgPreservedRevocation = "Revoked OCSP response for key [%s] preserved by cache policy" - DbgDeletingCacheResponse = "Deleting OCSP peer cached response for key [%s]" - DbgStartingCache = "Starting OCSP peer cache" - DbgStoppingCache = "Stopping OCSP peer cache" - DbgLoadingCache = "Loading OCSP peer cache [%s]" - DbgNoCacheFound = "No OCSP peer cache found, starting with empty cache" - DbgSavingCache = "Saving OCSP peer cache [%s]" - DbgCacheSaved = "Saved OCSP peer cache successfully (%d bytes)" - DbgMakingCARequest = "Trying OCSP responder url [%s]" - DbgResponseExpired = "OCSP response NextUpdate [%s] is before now [%s] with clockskew [%s]" - DbgResponseTTLExpired = "OCSP response cache expiry [%s] is before now [%s] with clockskew [%s]" - DbgResponseFutureDated = "OCSP response ThisUpdate [%s] is before now [%s] with clockskew [%s]" - DbgCacheSaveTimerExpired = "OCSP peer cache save timer expired" - DbgCacheDirtySave = "OCSP peer cache is dirty, saving" - - // Returned to peer as TLS reject reason - MsgTLSClientRejectConnection = "client not OCSP valid" - MsgTLSServerRejectConnection = "server not OCSP valid" - - // Expected runtime errors (direct logged) - ErrCAResponderCalloutFail = "Attempt to obtain OCSP response from CA responder for [%s] failed: %s" - ErrNewCAResponseNotCurrent = "New OCSP CA response obtained for [%s] but not current" - ErrCAResponseParseFailed = "Could not parse OCSP CA response for [%s]: %s" - ErrOCSPInvalidPeerLink = "OCSP verify fail for [%s] with CA status [%s]" - - // Policy override warnings (direct logged) - MsgAllowWhenCAUnreachableOccurred = "Failed to obtain OCSP CA response for [%s] but AllowWhenCAUnreachable set; no cached revocation so allowing" - MsgAllowWhenCAUnreachableOccurredCachedRevoke = "Failed to obtain OCSP CA response for [%s] but AllowWhenCAUnreachable set; cached revocation exists so rejecting" - MsgAllowWarnOnlyOccurred = "OCSP verify fail for [%s] but WarnOnly is true so allowing" - - // Info (direct logged) - MsgCacheOnline = "OCSP peer cache online, type [%s]" - MsgCacheOffline = "OCSP peer cache offline, type [%s]" - - // OCSP cert invalid reasons (debug and event reasons) - MsgFailedOCSPResponseFetch = "Failed OCSP response fetch" - MsgOCSPResponseNotEffective = "OCSP response not in effectivity window" - MsgFailedOCSPResponseParse = "Failed OCSP response parse" - MsgOCSPResponseInvalidStatus = "Invalid OCSP response status: %s" - MsgOCSPResponseDelegationInvalid = "Invalid OCSP response delegation: %s" - MsgCachedOCSPResponseInvalid = "Invalid cached OCSP response for [%s] with fingerprint [%s]" -) diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certidp/ocsp_responder.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certidp/ocsp_responder.go deleted file mode 100644 index ad6c2651..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certidp/ocsp_responder.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2023-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package certidp - -import ( - "encoding/base64" - "errors" - "fmt" - "io" - "net/http" - "strings" - "time" - - "golang.org/x/crypto/ocsp" -) - -func FetchOCSPResponse(link *ChainLink, opts *OCSPPeerConfig, log *Log) ([]byte, error) { - if link == nil || link.Leaf == nil || link.Issuer == nil || opts == nil || log == nil { - return nil, errors.New(ErrInvalidChainlink) - } - - timeout := time.Duration(opts.Timeout * float64(time.Second)) - if timeout <= 0*time.Second { - timeout = DefaultOCSPResponderTimeout - } - - getRequestBytes := func(u string, hc *http.Client) ([]byte, error) { - resp, err := hc.Get(u) - if err != nil { - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf(ErrBadResponderHTTPStatus, resp.StatusCode) - } - return io.ReadAll(resp.Body) - } - - // Request documentation: - // https://tools.ietf.org/html/rfc6960#appendix-A.1 - - reqDER, err := ocsp.CreateRequest(link.Leaf, link.Issuer, nil) - if err != nil { - return nil, err - } - - reqEnc := base64.StdEncoding.EncodeToString(reqDER) - - responders := *link.OCSPWebEndpoints - - if len(responders) == 0 { - return nil, errors.New(ErrNoAvailOCSPServers) - } - - var raw []byte - hc := &http.Client{ - Timeout: timeout, - } - for _, u := range responders { - url := u.String() - log.Debugf(DbgMakingCARequest, url) - url = strings.TrimSuffix(url, "/") - raw, err = getRequestBytes(fmt.Sprintf("%s/%s", url, reqEnc), hc) - if err == nil { - break - } - } - if err != nil { - return nil, fmt.Errorf(ErrFailedWithAllRequests, err) - } - - return raw, nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/certstore.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/certstore.go deleted file mode 100644 index 42e228e8..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/certstore.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2022-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package certstore - -import ( - "crypto" - "crypto/x509" - "io" - "runtime" - "strings" -) - -type StoreType int - -const MATCHBYEMPTY = 0 -const STOREEMPTY = 0 - -const ( - windowsCurrentUser StoreType = iota + 1 - windowsLocalMachine -) - -var StoreMap = map[string]StoreType{ - "windowscurrentuser": windowsCurrentUser, - "windowslocalmachine": windowsLocalMachine, -} - -var StoreOSMap = map[StoreType]string{ - windowsCurrentUser: "windows", - windowsLocalMachine: "windows", -} - -type MatchByType int - -const ( - matchByIssuer MatchByType = iota + 1 - matchBySubject - matchByThumbprint -) - -var MatchByMap = map[string]MatchByType{ - "issuer": matchByIssuer, - "subject": matchBySubject, - "thumbprint": matchByThumbprint, -} - -var Usage = ` -In place of cert_file and key_file you may use the windows certificate store: - - tls { - cert_store: "WindowsCurrentUser" - cert_match_by: "Subject" - cert_match: "MyServer123" - } -` - -func ParseCertStore(certStore string) (StoreType, error) { - certStoreType, exists := StoreMap[strings.ToLower(certStore)] - if !exists { - return 0, ErrBadCertStore - } - validOS, exists := StoreOSMap[certStoreType] - if !exists || validOS != runtime.GOOS { - return 0, ErrOSNotCompatCertStore - } - return certStoreType, nil -} - -func ParseCertMatchBy(certMatchBy string) (MatchByType, error) { - certMatchByType, exists := MatchByMap[strings.ToLower(certMatchBy)] - if !exists { - return 0, ErrBadMatchByType - } - return certMatchByType, nil -} - -func GetLeafIssuer(leaf *x509.Certificate, vOpts x509.VerifyOptions) (issuer *x509.Certificate) { - chains, err := leaf.Verify(vOpts) - if err != nil || len(chains) == 0 { - issuer = nil - } else { - issuer = chains[0][1] - } - return -} - -// credential provides access to a public key and is a crypto.Signer. -type credential interface { - // Public returns the public key corresponding to the leaf certificate. - Public() crypto.PublicKey - // Sign signs digest with the private key. - Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/certstore_other.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/certstore_other.go deleted file mode 100644 index 18d62f8f..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/certstore_other.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2022-2023 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build !windows - -package certstore - -import ( - "crypto" - "crypto/tls" - "io" -) - -var _ = MATCHBYEMPTY - -// otherKey implements crypto.Signer and crypto.Decrypter to satisfy linter on platforms that don't implement certstore -type otherKey struct{} - -func TLSConfig(_ StoreType, _ MatchByType, _ string, _ []string, _ bool, _ *tls.Config) error { - return ErrOSNotCompatCertStore -} - -// Public always returns nil public key since this is a stub on non-supported platform -func (k otherKey) Public() crypto.PublicKey { - return nil -} - -// Sign always returns a nil signature since this is a stub on non-supported platform -func (k otherKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { - _, _, _ = rand, digest, opts - return nil, nil -} - -// Verify interface conformance. -var _ credential = &otherKey{} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/certstore_windows.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/certstore_windows.go deleted file mode 100644 index d47adb6e..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/certstore_windows.go +++ /dev/null @@ -1,965 +0,0 @@ -// Copyright 2022-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Adapted, updated, and enhanced from CertToStore, https://github.com/google/certtostore/releases/tag/v1.0.2 -// Apache License, Version 2.0, Copyright 2017 Google Inc. - -package certstore - -import ( - "bytes" - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rsa" - "crypto/tls" - "crypto/x509" - "encoding/binary" - "fmt" - "io" - "math/big" - "reflect" - "sync" - "syscall" - "unicode/utf16" - "unsafe" - - "golang.org/x/crypto/cryptobyte" - "golang.org/x/crypto/cryptobyte/asn1" - "golang.org/x/sys/windows" -) - -const ( - // wincrypt.h constants - winAcquireCached = windows.CRYPT_ACQUIRE_CACHE_FLAG - winAcquireSilent = windows.CRYPT_ACQUIRE_SILENT_FLAG - winAcquireOnlyNCryptKey = windows.CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG - winEncodingX509ASN = windows.X509_ASN_ENCODING - winEncodingPKCS7 = windows.PKCS_7_ASN_ENCODING - winCertStoreProvSystem = windows.CERT_STORE_PROV_SYSTEM - winCertStoreCurrentUser = windows.CERT_SYSTEM_STORE_CURRENT_USER - winCertStoreLocalMachine = windows.CERT_SYSTEM_STORE_LOCAL_MACHINE - winCertStoreReadOnly = windows.CERT_STORE_READONLY_FLAG - winInfoIssuerFlag = windows.CERT_INFO_ISSUER_FLAG - winInfoSubjectFlag = windows.CERT_INFO_SUBJECT_FLAG - winCompareNameStrW = windows.CERT_COMPARE_NAME_STR_W - winCompareShift = windows.CERT_COMPARE_SHIFT - - // Reference https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateinstore - winFindIssuerStr = windows.CERT_FIND_ISSUER_STR_W - winFindSubjectStr = windows.CERT_FIND_SUBJECT_STR_W - winFindHashStr = windows.CERT_FIND_HASH_STR - - winNcryptKeySpec = windows.CERT_NCRYPT_KEY_SPEC - - winBCryptPadPKCS1 uintptr = 0x2 - winBCryptPadPSS uintptr = 0x8 // Modern TLS 1.2+ - winBCryptPadPSSSalt uint32 = 32 // default 20, 32 optimal for typical SHA256 hash - - winRSA1Magic = 0x31415352 // "RSA1" BCRYPT_RSAPUBLIC_MAGIC - - winECS1Magic = 0x31534345 // "ECS1" BCRYPT_ECDSA_PUBLIC_P256_MAGIC - winECS3Magic = 0x33534345 // "ECS3" BCRYPT_ECDSA_PUBLIC_P384_MAGIC - winECS5Magic = 0x35534345 // "ECS5" BCRYPT_ECDSA_PUBLIC_P521_MAGIC - - winECK1Magic = 0x314B4345 // "ECK1" BCRYPT_ECDH_PUBLIC_P256_MAGIC - winECK3Magic = 0x334B4345 // "ECK3" BCRYPT_ECDH_PUBLIC_P384_MAGIC - winECK5Magic = 0x354B4345 // "ECK5" BCRYPT_ECDH_PUBLIC_P521_MAGIC - - winCryptENotFound = windows.CRYPT_E_NOT_FOUND - - providerMSSoftware = "Microsoft Software Key Storage Provider" -) - -var ( - winBCryptRSAPublicBlob = winWide("RSAPUBLICBLOB") - winBCryptECCPublicBlob = winWide("ECCPUBLICBLOB") - - winNCryptAlgorithmGroupProperty = winWide("Algorithm Group") // NCRYPT_ALGORITHM_GROUP_PROPERTY - winNCryptUniqueNameProperty = winWide("Unique Name") // NCRYPT_UNIQUE_NAME_PROPERTY - winNCryptECCCurveNameProperty = winWide("ECCCurveName") // NCRYPT_ECC_CURVE_NAME_PROPERTY - - winCurveIDs = map[uint32]elliptic.Curve{ - winECS1Magic: elliptic.P256(), // BCRYPT_ECDSA_PUBLIC_P256_MAGIC - winECS3Magic: elliptic.P384(), // BCRYPT_ECDSA_PUBLIC_P384_MAGIC - winECS5Magic: elliptic.P521(), // BCRYPT_ECDSA_PUBLIC_P521_MAGIC - winECK1Magic: elliptic.P256(), // BCRYPT_ECDH_PUBLIC_P256_MAGIC - winECK3Magic: elliptic.P384(), // BCRYPT_ECDH_PUBLIC_P384_MAGIC - winECK5Magic: elliptic.P521(), // BCRYPT_ECDH_PUBLIC_P521_MAGIC - } - - winCurveNames = map[string]elliptic.Curve{ - "nistP256": elliptic.P256(), // BCRYPT_ECC_CURVE_NISTP256 - "nistP384": elliptic.P384(), // BCRYPT_ECC_CURVE_NISTP384 - "nistP521": elliptic.P521(), // BCRYPT_ECC_CURVE_NISTP521 - } - - winAlgIDs = map[crypto.Hash]*uint16{ - crypto.SHA1: winWide("SHA1"), // BCRYPT_SHA1_ALGORITHM - crypto.SHA256: winWide("SHA256"), // BCRYPT_SHA256_ALGORITHM - crypto.SHA384: winWide("SHA384"), // BCRYPT_SHA384_ALGORITHM - crypto.SHA512: winWide("SHA512"), // BCRYPT_SHA512_ALGORITHM - } - - // MY is well-known system store on Windows that holds personal certificates. Read - // More about the CA locations here: - // https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/wcf/certificate-of-clientcertificate-element?redirectedfrom=MSDN - // https://superuser.com/questions/217719/what-are-the-windows-system-certificate-stores - // https://docs.microsoft.com/en-us/windows/win32/seccrypto/certificate-stores - // https://learn.microsoft.com/en-us/windows/win32/seccrypto/system-store-locations - // https://stackoverflow.com/questions/63286085/which-x509-storename-refers-to-the-certificates-stored-beneath-trusted-root-cert#:~:text=4-,StoreName.,is%20%22Intermediate%20Certification%20Authorities%22. - winMyStore = winWide("MY") - winIntermediateCAStore = winWide("CA") - winRootStore = winWide("Root") - winAuthRootStore = winWide("AuthRoot") - - // These DLLs must be available on all Windows hosts - winCrypt32 = windows.NewLazySystemDLL("crypt32.dll") - winNCrypt = windows.NewLazySystemDLL("ncrypt.dll") - - winCertFindCertificateInStore = winCrypt32.NewProc("CertFindCertificateInStore") - winCertVerifyTimeValidity = winCrypt32.NewProc("CertVerifyTimeValidity") - winCryptAcquireCertificatePrivateKey = winCrypt32.NewProc("CryptAcquireCertificatePrivateKey") - winNCryptExportKey = winNCrypt.NewProc("NCryptExportKey") - winNCryptOpenStorageProvider = winNCrypt.NewProc("NCryptOpenStorageProvider") - winNCryptGetProperty = winNCrypt.NewProc("NCryptGetProperty") - winNCryptSignHash = winNCrypt.NewProc("NCryptSignHash") - - winFnGetProperty = winGetProperty -) - -func init() { - for _, d := range []*windows.LazyDLL{ - winCrypt32, winNCrypt, - } { - if err := d.Load(); err != nil { - panic(err) - } - } - for _, p := range []*windows.LazyProc{ - winCertFindCertificateInStore, winCryptAcquireCertificatePrivateKey, - winNCryptExportKey, winNCryptOpenStorageProvider, - winNCryptGetProperty, winNCryptSignHash, - } { - if err := p.Find(); err != nil { - panic(err) - } - } -} - -type winPKCS1PaddingInfo struct { - pszAlgID *uint16 -} - -type winPSSPaddingInfo struct { - pszAlgID *uint16 - cbSalt uint32 -} - -// createCACertsPool generates a CertPool from the Windows certificate store, -// adding all matching certificates from the caCertsMatch array to the pool. -// All matching certificates (vs first) are added to the pool based on a user -// request. If no certificates are found an error is returned. -func createCACertsPool(cs *winCertStore, storeType uint32, caCertsMatch []string, skipInvalid bool) (*x509.CertPool, error) { - var errs []error - caPool := x509.NewCertPool() - for _, s := range caCertsMatch { - lfs, err := cs.caCertsBySubjectMatch(s, storeType, skipInvalid) - if err != nil { - errs = append(errs, err) - } else { - for _, lf := range lfs { - caPool.AddCert(lf) - } - } - } - // If every lookup failed return the errors. - if len(errs) == len(caCertsMatch) { - return nil, fmt.Errorf("unable to match any CA certificate: %v", errs) - } - return caPool, nil -} - -// TLSConfig fulfills the same function as reading cert and key pair from -// pem files but sources the Windows certificate store instead. The -// certMatchBy and certMatch fields search the "MY" certificate location -// for the first certificate that matches the certMatch field. The -// caCertsMatch field is used to search the Trusted Root, Third Party Root, -// and Intermediate Certificate Authority locations for certificates with -// Subjects matching the provided strings. If a match is found, the -// certificate is added to the pool that is used to verify the certificate -// chain. -func TLSConfig(certStore StoreType, certMatchBy MatchByType, certMatch string, caCertsMatch []string, skipInvalid bool, config *tls.Config) error { - var ( - leaf *x509.Certificate - leafCtx *windows.CertContext - pk *winKey - vOpts = x509.VerifyOptions{} - chains [][]*x509.Certificate - chain []*x509.Certificate - rawChain [][]byte - ) - - // By StoreType, open a store - if certStore == windowsCurrentUser || certStore == windowsLocalMachine { - var scope uint32 - cs, err := winOpenCertStore(providerMSSoftware) - if err != nil || cs == nil { - return err - } - if certStore == windowsCurrentUser { - scope = winCertStoreCurrentUser - } - if certStore == windowsLocalMachine { - scope = winCertStoreLocalMachine - } - - // certByIssuer or certBySubject - if certMatchBy == matchBySubject || certMatchBy == MATCHBYEMPTY { - leaf, leafCtx, err = cs.certBySubject(certMatch, scope, skipInvalid) - } else if certMatchBy == matchByIssuer { - leaf, leafCtx, err = cs.certByIssuer(certMatch, scope, skipInvalid) - } else if certMatchBy == matchByThumbprint { - leaf, leafCtx, err = cs.certByThumbprint(certMatch, scope, skipInvalid) - } else { - return ErrBadMatchByType - } - if err != nil { - // pass through error from cert search - return err - } - if leaf == nil || leafCtx == nil { - return ErrFailedCertSearch - } - pk, err = cs.certKey(leafCtx) - if err != nil { - return err - } - if pk == nil { - return ErrNoPrivateKeyStoreRef - } - // Look for CA Certificates - if len(caCertsMatch) != 0 { - caPool, err := createCACertsPool(cs, scope, caCertsMatch, skipInvalid) - if err != nil { - return err - } - config.ClientCAs = caPool - } - } else { - return ErrBadCertStore - } - - // Get intermediates in the cert store for the found leaf IFF there is a full chain of trust in the store - // otherwise just use leaf as the final chain. - // - // Using std lib Verify as a reliable way to get valid chains out of the win store for the leaf; however, - // using empty options since server TLS stanza could be TLS role as server identity or client identity. - chains, err := leaf.Verify(vOpts) - if err != nil || len(chains) == 0 { - chains = append(chains, []*x509.Certificate{leaf}) - } - - // We have at least one verified chain so pop the first chain and remove the self-signed CA cert (if present) - // from the end of the chain - chain = chains[0] - if len(chain) > 1 { - chain = chain[:len(chain)-1] - } - - // For tls.Certificate.Certificate need a [][]byte from []*x509.Certificate - // Approximate capacity for efficiency - rawChain = make([][]byte, 0, len(chain)) - for _, link := range chain { - rawChain = append(rawChain, link.Raw) - } - - tlsCert := tls.Certificate{ - Certificate: rawChain, - PrivateKey: pk, - Leaf: leaf, - } - config.Certificates = []tls.Certificate{tlsCert} - - // note: pk is a windows pointer (not freed by Go) but needs to live the life of the server for Signing. - // The cert context (leafCtx) windows pointer must not be freed underneath the pk so also life of the server. - return nil -} - -// winWide returns a pointer to uint16 representing the equivalent -// to a Windows LPCWSTR. -func winWide(s string) *uint16 { - w := utf16.Encode([]rune(s)) - w = append(w, 0) - return &w[0] -} - -// winOpenProvider gets a provider handle for subsequent calls -func winOpenProvider(provider string) (uintptr, error) { - var hProv uintptr - pname := winWide(provider) - // Open the provider, the last parameter is not used - r, _, err := winNCryptOpenStorageProvider.Call(uintptr(unsafe.Pointer(&hProv)), uintptr(unsafe.Pointer(pname)), 0) - if r == 0 { - return hProv, nil - } - return hProv, fmt.Errorf("NCryptOpenStorageProvider returned %X: %v", r, err) -} - -// winFindCert wraps the CertFindCertificateInStore library call. Note that any cert context passed -// into prev will be freed. If no certificate was found, nil will be returned. -func winFindCert(store windows.Handle, enc, findFlags, findType uint32, para *uint16, prev *windows.CertContext) (*windows.CertContext, error) { - h, _, err := winCertFindCertificateInStore.Call( - uintptr(store), - uintptr(enc), - uintptr(findFlags), - uintptr(findType), - uintptr(unsafe.Pointer(para)), - uintptr(unsafe.Pointer(prev)), - ) - if h == 0 { - // Actual error, or simply not found? - if errno, ok := err.(syscall.Errno); ok && errno == syscall.Errno(winCryptENotFound) { - return nil, ErrFailedCertSearch - } - return nil, ErrFailedCertSearch - } - // nolint:govet - return (*windows.CertContext)(unsafe.Pointer(h)), nil -} - -// winVerifyCertValid wraps the CertVerifyTimeValidity and simply returns true if the certificate is valid -func winVerifyCertValid(timeToVerify *windows.Filetime, certInfo *windows.CertInfo) bool { - // this function does not document returning errors / setting lasterror - r, _, _ := winCertVerifyTimeValidity.Call( - uintptr(unsafe.Pointer(timeToVerify)), - uintptr(unsafe.Pointer(certInfo)), - ) - return r == 0 -} - -// winCertStore is a store implementation for the Windows Certificate Store -type winCertStore struct { - Prov uintptr - ProvName string - stores map[string]*winStoreHandle - mu sync.Mutex -} - -// winOpenCertStore creates a winCertStore -func winOpenCertStore(provider string) (*winCertStore, error) { - cngProv, err := winOpenProvider(provider) - if err != nil { - // pass through error from winOpenProvider - return nil, err - } - - wcs := &winCertStore{ - Prov: cngProv, - ProvName: provider, - stores: make(map[string]*winStoreHandle), - } - - return wcs, nil -} - -// winCertContextToX509 creates an x509.Certificate from a Windows cert context. -func winCertContextToX509(ctx *windows.CertContext) (*x509.Certificate, error) { - var der []byte - slice := (*reflect.SliceHeader)(unsafe.Pointer(&der)) - slice.Data = uintptr(unsafe.Pointer(ctx.EncodedCert)) - slice.Len = int(ctx.Length) - slice.Cap = int(ctx.Length) - return x509.ParseCertificate(der) -} - -// certByIssuer matches and returns the first certificate found by passed issuer. -// CertContext pointer returned allows subsequent key operations like Sign. Caller specifies -// current user's personal certs or local machine's personal certs using storeType. -// See CERT_FIND_ISSUER_STR description at https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateinstore -func (w *winCertStore) certByIssuer(issuer string, storeType uint32, skipInvalid bool) (*x509.Certificate, *windows.CertContext, error) { - return w.certSearch(winFindIssuerStr, issuer, winMyStore, storeType, skipInvalid) -} - -// certBySubject matches and returns the first certificate found by passed subject field. -// CertContext pointer returned allows subsequent key operations like Sign. Caller specifies -// current user's personal certs or local machine's personal certs using storeType. -// See CERT_FIND_SUBJECT_STR description at https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateinstore -func (w *winCertStore) certBySubject(subject string, storeType uint32, skipInvalid bool) (*x509.Certificate, *windows.CertContext, error) { - return w.certSearch(winFindSubjectStr, subject, winMyStore, storeType, skipInvalid) -} - -// certByThumbprint matches and returns the first certificate found by passed SHA1 thumbprint. -// CertContext pointer returned allows subsequent key operations like Sign. Caller specifies -// current user's personal certs or local machine's personal certs using storeType. -// See CERT_FIND_SUBJECT_STR description at https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateinstore -func (w *winCertStore) certByThumbprint(hash string, storeType uint32, skipInvalid bool) (*x509.Certificate, *windows.CertContext, error) { - return w.certSearch(winFindHashStr, hash, winMyStore, storeType, skipInvalid) -} - -// caCertsBySubjectMatch matches and returns all matching certificates of the subject field. -// -// The following locations are searched: -// 1) Root (Trusted Root Certification Authorities) -// 2) AuthRoot (Third-Party Root Certification Authorities) -// 3) CA (Intermediate Certification Authorities) -// -// Caller specifies current user's personal certs or local machine's personal certs using storeType. -// See CERT_FIND_SUBJECT_STR description at https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certfindcertificateinstore -func (w *winCertStore) caCertsBySubjectMatch(subject string, storeType uint32, skipInvalid bool) ([]*x509.Certificate, error) { - var ( - leaf *x509.Certificate - searchLocations = [3]*uint16{winRootStore, winAuthRootStore, winIntermediateCAStore} - rv []*x509.Certificate - ) - // surprisingly, an empty string returns a result. We'll treat this as an error. - if subject == "" { - return nil, ErrBadCaCertMatchField - } - for _, sr := range searchLocations { - var err error - if leaf, _, err = w.certSearch(winFindSubjectStr, subject, sr, storeType, skipInvalid); err == nil { - rv = append(rv, leaf) - } else { - // Ignore the failed search from a single location. Errors we catch include - // ErrFailedX509Extract (resulting from a malformed certificate) and errors - // around invalid attributes, unsupported algorithms, etc. These are corner - // cases as certificates with these errors shouldn't have been allowed - // to be added to the store in the first place. - if err != ErrFailedCertSearch { - return nil, err - } - } - } - // Not found anywhere - if len(rv) == 0 { - return nil, ErrFailedCertSearch - } - return rv, nil -} - -// certSearch is a helper function to lookup certificates based on search type and match value. -// store is used to specify which store to perform the lookup in (system or user). -func (w *winCertStore) certSearch(searchType uint32, matchValue string, searchRoot *uint16, store uint32, skipInvalid bool) (*x509.Certificate, *windows.CertContext, error) { - // store handle to "MY" store - h, err := w.storeHandle(store, searchRoot) - if err != nil { - return nil, nil, err - } - - var prev *windows.CertContext - var cert *x509.Certificate - - i, err := windows.UTF16PtrFromString(matchValue) - if err != nil { - return nil, nil, ErrFailedCertSearch - } - - // pass 0 as the third parameter because it is not used - // https://msdn.microsoft.com/en-us/library/windows/desktop/aa376064(v=vs.85).aspx - - for { - nc, err := winFindCert(h, winEncodingX509ASN|winEncodingPKCS7, 0, searchType, i, prev) - if err != nil { - return nil, nil, err - } - if nc != nil { - // certificate found - prev = nc - - var now *windows.Filetime - if skipInvalid && !winVerifyCertValid(now, nc.CertInfo) { - continue - } - - // Extract the DER-encoded certificate from the cert context - xc, err := winCertContextToX509(nc) - if err == nil { - cert = xc - break - } else { - return nil, nil, ErrFailedX509Extract - } - } else { - return nil, nil, ErrFailedCertSearch - } - } - - if cert == nil { - return nil, nil, ErrFailedX509Extract - } - - return cert, prev, nil -} - -type winStoreHandle struct { - handle *windows.Handle -} - -func winNewStoreHandle(provider uint32, store *uint16) (*winStoreHandle, error) { - var s winStoreHandle - if s.handle != nil { - return &s, nil - } - st, err := windows.CertOpenStore( - winCertStoreProvSystem, - 0, - 0, - provider|winCertStoreReadOnly, - uintptr(unsafe.Pointer(store))) - if err != nil { - return nil, ErrBadCryptoStoreProvider - } - s.handle = &st - return &s, nil -} - -// winKey implements crypto.Signer and crypto.Decrypter for key based operations. -type winKey struct { - handle uintptr - pub crypto.PublicKey - Container string - AlgorithmGroup string -} - -// Public exports a public key to implement crypto.Signer -func (k winKey) Public() crypto.PublicKey { - return k.pub -} - -// Sign returns the signature of a hash to implement crypto.Signer -func (k winKey) Sign(_ io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { - switch k.AlgorithmGroup { - case "ECDSA", "ECDH": - return winSignECDSA(k.handle, digest) - case "RSA": - hf := opts.HashFunc() - algID, ok := winAlgIDs[hf] - if !ok { - return nil, ErrBadRSAHashAlgorithm - } - switch opts.(type) { - case *rsa.PSSOptions: - return winSignRSAPSSPadding(k.handle, digest, algID) - default: - return winSignRSAPKCS1Padding(k.handle, digest, algID) - } - default: - return nil, ErrBadSigningAlgorithm - } -} - -func winSignECDSA(kh uintptr, digest []byte) ([]byte, error) { - var size uint32 - // Obtain the size of the signature - r, _, _ := winNCryptSignHash.Call( - kh, - 0, - uintptr(unsafe.Pointer(&digest[0])), - uintptr(len(digest)), - 0, - 0, - uintptr(unsafe.Pointer(&size)), - 0) - if r != 0 { - return nil, ErrStoreECDSASigningError - } - - // Obtain the signature data - buf := make([]byte, size) - r, _, _ = winNCryptSignHash.Call( - kh, - 0, - uintptr(unsafe.Pointer(&digest[0])), - uintptr(len(digest)), - uintptr(unsafe.Pointer(&buf[0])), - uintptr(size), - uintptr(unsafe.Pointer(&size)), - 0) - if r != 0 { - return nil, ErrStoreECDSASigningError - } - if len(buf) != int(size) { - return nil, ErrStoreECDSASigningError - } - - return winPackECDSASigValue(bytes.NewReader(buf[:size]), len(digest)) -} - -func winPackECDSASigValue(r io.Reader, digestLength int) ([]byte, error) { - sigR := make([]byte, digestLength) - if _, err := io.ReadFull(r, sigR); err != nil { - return nil, ErrStoreECDSASigningError - } - - sigS := make([]byte, digestLength) - if _, err := io.ReadFull(r, sigS); err != nil { - return nil, ErrStoreECDSASigningError - } - - var b cryptobyte.Builder - b.AddASN1(asn1.SEQUENCE, func(b *cryptobyte.Builder) { - b.AddASN1BigInt(new(big.Int).SetBytes(sigR)) - b.AddASN1BigInt(new(big.Int).SetBytes(sigS)) - }) - return b.Bytes() -} - -func winSignRSAPKCS1Padding(kh uintptr, digest []byte, algID *uint16) ([]byte, error) { - // PKCS#1 v1.5 padding for some TLS 1.2 - padInfo := winPKCS1PaddingInfo{pszAlgID: algID} - var size uint32 - // Obtain the size of the signature - r, _, _ := winNCryptSignHash.Call( - kh, - uintptr(unsafe.Pointer(&padInfo)), - uintptr(unsafe.Pointer(&digest[0])), - uintptr(len(digest)), - 0, - 0, - uintptr(unsafe.Pointer(&size)), - winBCryptPadPKCS1) - if r != 0 { - return nil, ErrStoreRSASigningError - } - - // Obtain the signature data - sig := make([]byte, size) - r, _, _ = winNCryptSignHash.Call( - kh, - uintptr(unsafe.Pointer(&padInfo)), - uintptr(unsafe.Pointer(&digest[0])), - uintptr(len(digest)), - uintptr(unsafe.Pointer(&sig[0])), - uintptr(size), - uintptr(unsafe.Pointer(&size)), - winBCryptPadPKCS1) - if r != 0 { - return nil, ErrStoreRSASigningError - } - - return sig[:size], nil -} - -func winSignRSAPSSPadding(kh uintptr, digest []byte, algID *uint16) ([]byte, error) { - // PSS padding for TLS 1.3 and some TLS 1.2 - padInfo := winPSSPaddingInfo{pszAlgID: algID, cbSalt: winBCryptPadPSSSalt} - - var size uint32 - // Obtain the size of the signature - r, _, _ := winNCryptSignHash.Call( - kh, - uintptr(unsafe.Pointer(&padInfo)), - uintptr(unsafe.Pointer(&digest[0])), - uintptr(len(digest)), - 0, - 0, - uintptr(unsafe.Pointer(&size)), - winBCryptPadPSS) - if r != 0 { - return nil, ErrStoreRSASigningError - } - - // Obtain the signature data - sig := make([]byte, size) - r, _, _ = winNCryptSignHash.Call( - kh, - uintptr(unsafe.Pointer(&padInfo)), - uintptr(unsafe.Pointer(&digest[0])), - uintptr(len(digest)), - uintptr(unsafe.Pointer(&sig[0])), - uintptr(size), - uintptr(unsafe.Pointer(&size)), - winBCryptPadPSS) - if r != 0 { - return nil, ErrStoreRSASigningError - } - - return sig[:size], nil -} - -// certKey wraps CryptAcquireCertificatePrivateKey. It obtains the CNG private -// key of a known certificate and returns a pointer to a winKey which implements -// both crypto.Signer. When a nil cert context is passed -// a nil key is intentionally returned, to model the expected behavior of a -// non-existent cert having no private key. -// https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptacquirecertificateprivatekey -func (w *winCertStore) certKey(cert *windows.CertContext) (*winKey, error) { - // Return early if a nil cert was passed. - if cert == nil { - return nil, nil - } - var ( - kh uintptr - spec uint32 - mustFree int - ) - r, _, _ := winCryptAcquireCertificatePrivateKey.Call( - uintptr(unsafe.Pointer(cert)), - winAcquireCached|winAcquireSilent|winAcquireOnlyNCryptKey, - 0, // Reserved, must be null. - uintptr(unsafe.Pointer(&kh)), - uintptr(unsafe.Pointer(&spec)), - uintptr(unsafe.Pointer(&mustFree)), - ) - // If the function succeeds, the return value is nonzero (TRUE). - if r == 0 { - return nil, ErrNoPrivateKeyStoreRef - } - if mustFree != 0 { - return nil, ErrNoPrivateKeyStoreRef - } - if spec != winNcryptKeySpec { - return nil, ErrNoPrivateKeyStoreRef - } - - return winKeyMetadata(kh) -} - -func winKeyMetadata(kh uintptr) (*winKey, error) { - // uc is used to populate the unique container name attribute of the private key - uc, err := winGetPropertyStr(kh, winNCryptUniqueNameProperty) - if err != nil { - // unable to determine key unique name - return nil, ErrExtractingPrivateKeyMetadata - } - - alg, err := winGetPropertyStr(kh, winNCryptAlgorithmGroupProperty) - if err != nil { - // unable to determine key algorithm - return nil, ErrExtractingPrivateKeyMetadata - } - - var pub crypto.PublicKey - - switch alg { - case "ECDSA", "ECDH": - buf, err := winExport(kh, winBCryptECCPublicBlob) - if err != nil { - // failed to export ECC public key - return nil, ErrExtractingECCPublicKey - } - pub, err = unmarshalECC(buf, kh) - if err != nil { - return nil, ErrExtractingECCPublicKey - } - case "RSA": - buf, err := winExport(kh, winBCryptRSAPublicBlob) - if err != nil { - return nil, ErrExtractingRSAPublicKey - } - pub, err = winUnmarshalRSA(buf) - if err != nil { - return nil, ErrExtractingRSAPublicKey - } - default: - return nil, ErrBadPublicKeyAlgorithm - } - - return &winKey{handle: kh, pub: pub, Container: uc, AlgorithmGroup: alg}, nil -} - -func winGetProperty(kh uintptr, property *uint16) ([]byte, error) { - var strSize uint32 - r, _, _ := winNCryptGetProperty.Call( - kh, - uintptr(unsafe.Pointer(property)), - 0, - 0, - uintptr(unsafe.Pointer(&strSize)), - 0, - 0) - if r != 0 { - return nil, ErrExtractPropertyFromKey - } - - buf := make([]byte, strSize) - r, _, _ = winNCryptGetProperty.Call( - kh, - uintptr(unsafe.Pointer(property)), - uintptr(unsafe.Pointer(&buf[0])), - uintptr(strSize), - uintptr(unsafe.Pointer(&strSize)), - 0, - 0) - if r != 0 { - return nil, ErrExtractPropertyFromKey - } - - return buf, nil -} - -func winGetPropertyStr(kh uintptr, property *uint16) (string, error) { - buf, err := winFnGetProperty(kh, property) - if err != nil { - return "", ErrExtractPropertyFromKey - } - uc := bytes.ReplaceAll(buf, []byte{0x00}, []byte("")) - return string(uc), nil -} - -func winExport(kh uintptr, blobType *uint16) ([]byte, error) { - var size uint32 - // When obtaining the size of a public key, most parameters are not required - r, _, _ := winNCryptExportKey.Call( - kh, - 0, - uintptr(unsafe.Pointer(blobType)), - 0, - 0, - 0, - uintptr(unsafe.Pointer(&size)), - 0) - if r != 0 { - return nil, ErrExtractingPublicKey - } - - // Place the exported key in buf now that we know the size required - buf := make([]byte, size) - r, _, _ = winNCryptExportKey.Call( - kh, - 0, - uintptr(unsafe.Pointer(blobType)), - 0, - uintptr(unsafe.Pointer(&buf[0])), - uintptr(size), - uintptr(unsafe.Pointer(&size)), - 0) - if r != 0 { - return nil, ErrExtractingPublicKey - } - return buf, nil -} - -func unmarshalECC(buf []byte, kh uintptr) (*ecdsa.PublicKey, error) { - // BCRYPT_ECCKEY_BLOB from bcrypt.h - header := struct { - Magic uint32 - Key uint32 - }{} - - r := bytes.NewReader(buf) - if err := binary.Read(r, binary.LittleEndian, &header); err != nil { - return nil, ErrExtractingECCPublicKey - } - - curve, ok := winCurveIDs[header.Magic] - if !ok { - // Fix for b/185945636, where despite specifying the curve, nCrypt returns - // an incorrect response with BCRYPT_ECDSA_PUBLIC_GENERIC_MAGIC. - var err error - curve, err = winCurveName(kh) - if err != nil { - // unsupported header magic or cannot match the curve by name - return nil, err - } - } - - keyX := make([]byte, header.Key) - if n, err := r.Read(keyX); n != int(header.Key) || err != nil { - // failed to read key X - return nil, ErrExtractingECCPublicKey - } - - keyY := make([]byte, header.Key) - if n, err := r.Read(keyY); n != int(header.Key) || err != nil { - // failed to read key Y - return nil, ErrExtractingECCPublicKey - } - - pub := &ecdsa.PublicKey{ - Curve: curve, - X: new(big.Int).SetBytes(keyX), - Y: new(big.Int).SetBytes(keyY), - } - return pub, nil -} - -// winCurveName reads the curve name property and returns the corresponding curve. -func winCurveName(kh uintptr) (elliptic.Curve, error) { - cn, err := winGetPropertyStr(kh, winNCryptECCCurveNameProperty) - if err != nil { - // unable to determine the curve property name - return nil, ErrExtractPropertyFromKey - } - curve, ok := winCurveNames[cn] - if !ok { - // unknown curve name - return nil, ErrBadECCCurveName - } - return curve, nil -} - -func winUnmarshalRSA(buf []byte) (*rsa.PublicKey, error) { - // BCRYPT_RSA_BLOB from bcrypt.h - header := struct { - Magic uint32 - BitLength uint32 - PublicExpSize uint32 - ModulusSize uint32 - UnusedPrime1 uint32 - UnusedPrime2 uint32 - }{} - - r := bytes.NewReader(buf) - if err := binary.Read(r, binary.LittleEndian, &header); err != nil { - return nil, ErrExtractingRSAPublicKey - } - - if header.Magic != winRSA1Magic { - // invalid header magic - return nil, ErrExtractingRSAPublicKey - } - - if header.PublicExpSize > 8 { - // unsupported public exponent size - return nil, ErrExtractingRSAPublicKey - } - - exp := make([]byte, 8) - if n, err := r.Read(exp[8-header.PublicExpSize:]); n != int(header.PublicExpSize) || err != nil { - // failed to read public exponent - return nil, ErrExtractingRSAPublicKey - } - - mod := make([]byte, header.ModulusSize) - if n, err := r.Read(mod); n != int(header.ModulusSize) || err != nil { - // failed to read modulus - return nil, ErrExtractingRSAPublicKey - } - - pub := &rsa.PublicKey{ - N: new(big.Int).SetBytes(mod), - E: int(binary.BigEndian.Uint64(exp)), - } - return pub, nil -} - -// storeHandle returns a handle to a given cert store, opening the handle as needed. -func (w *winCertStore) storeHandle(provider uint32, store *uint16) (windows.Handle, error) { - w.mu.Lock() - defer w.mu.Unlock() - - key := fmt.Sprintf("%d%s", provider, windows.UTF16PtrToString(store)) - var err error - if w.stores[key] == nil { - w.stores[key], err = winNewStoreHandle(provider, store) - if err != nil { - return 0, ErrBadCryptoStoreProvider - } - } - return *w.stores[key].handle, nil -} - -// Verify interface conformance. -var _ credential = &winKey{} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/errors.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/errors.go deleted file mode 100644 index be545cf0..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/certstore/errors.go +++ /dev/null @@ -1,79 +0,0 @@ -package certstore - -import ( - "errors" -) - -var ( - // ErrBadCryptoStoreProvider represents inablity to establish link with a certificate store - ErrBadCryptoStoreProvider = errors.New("unable to open certificate store or store not available") - - // ErrBadRSAHashAlgorithm represents a bad or unsupported RSA hash algorithm - ErrBadRSAHashAlgorithm = errors.New("unsupported RSA hash algorithm") - - // ErrBadSigningAlgorithm represents a bad or unsupported signing algorithm - ErrBadSigningAlgorithm = errors.New("unsupported signing algorithm") - - // ErrStoreRSASigningError represents an error returned from store during RSA signature - ErrStoreRSASigningError = errors.New("unable to obtain RSA signature from store") - - // ErrStoreECDSASigningError represents an error returned from store during ECDSA signature - ErrStoreECDSASigningError = errors.New("unable to obtain ECDSA signature from store") - - // ErrNoPrivateKeyStoreRef represents an error getting a handle to a private key in store - ErrNoPrivateKeyStoreRef = errors.New("unable to obtain private key handle from store") - - // ErrExtractingPrivateKeyMetadata represents a family of errors extracting metadata about the private key in store - ErrExtractingPrivateKeyMetadata = errors.New("unable to extract private key metadata") - - // ErrExtractingECCPublicKey represents an error exporting ECC-type public key from store - ErrExtractingECCPublicKey = errors.New("unable to extract ECC public key from store") - - // ErrExtractingRSAPublicKey represents an error exporting RSA-type public key from store - ErrExtractingRSAPublicKey = errors.New("unable to extract RSA public key from store") - - // ErrExtractingPublicKey represents a general error exporting public key from store - ErrExtractingPublicKey = errors.New("unable to extract public key from store") - - // ErrBadPublicKeyAlgorithm represents a bad or unsupported public key algorithm - ErrBadPublicKeyAlgorithm = errors.New("unsupported public key algorithm") - - // ErrExtractPropertyFromKey represents a general failure to extract a metadata property field - ErrExtractPropertyFromKey = errors.New("unable to extract property from key") - - // ErrBadECCCurveName represents an ECC signature curve name that is bad or unsupported - ErrBadECCCurveName = errors.New("unsupported ECC curve name") - - // ErrFailedCertSearch represents not able to find certificate in store - ErrFailedCertSearch = errors.New("unable to find certificate in store") - - // ErrFailedX509Extract represents not being able to extract x509 certificate from found cert in store - ErrFailedX509Extract = errors.New("unable to extract x509 from certificate") - - // ErrBadMatchByType represents unknown CERT_MATCH_BY passed - ErrBadMatchByType = errors.New("cert match by type not implemented") - - // ErrBadCertStore represents unknown CERT_STORE passed - ErrBadCertStore = errors.New("cert store type not implemented") - - // ErrConflictCertFileAndStore represents ambiguous configuration of both file and store - ErrConflictCertFileAndStore = errors.New("'cert_file' and 'cert_store' may not both be configured") - - // ErrBadCertStoreField represents malformed cert_store option - ErrBadCertStoreField = errors.New("expected 'cert_store' to be a valid non-empty string") - - // ErrBadCertMatchByField represents malformed cert_match_by option - ErrBadCertMatchByField = errors.New("expected 'cert_match_by' to be a valid non-empty string") - - // ErrBadCertMatchField represents malformed cert_match option - ErrBadCertMatchField = errors.New("expected 'cert_match' to be a valid non-empty string") - - // ErrBadCaCertMatchField represents malformed cert_match option - ErrBadCaCertMatchField = errors.New("expected 'ca_certs_match' to be a valid non-empty string array") - - // ErrBadCertMatchSkipInvalidField represents malformed cert_match_skip_invalid option - ErrBadCertMatchSkipInvalidField = errors.New("expected 'cert_match_skip_invalid' to be a boolean") - - // ErrOSNotCompatCertStore represents cert_store passed that exists but is not valid on current OS - ErrOSNotCompatCertStore = errors.New("cert_store not compatible with current operating system") -) diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ciphersuites.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ciphersuites.go deleted file mode 100644 index bc594c51..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ciphersuites.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2016-2020 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "crypto/tls" -) - -// Where we maintain all of the available ciphers -var cipherMap = map[string]uint16{ - "TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA, - "TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, - "TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA, - "TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256, - "TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA, - "TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384, - "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, - "TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, - "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - "TLS_AES_128_GCM_SHA256": tls.TLS_AES_128_GCM_SHA256, - "TLS_AES_256_GCM_SHA384": tls.TLS_AES_256_GCM_SHA384, - "TLS_CHACHA20_POLY1305_SHA256": tls.TLS_CHACHA20_POLY1305_SHA256, -} - -var cipherMapByID = map[uint16]string{ - tls.TLS_RSA_WITH_RC4_128_SHA: "TLS_RSA_WITH_RC4_128_SHA", - tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA: "TLS_RSA_WITH_3DES_EDE_CBC_SHA", - tls.TLS_RSA_WITH_AES_128_CBC_SHA: "TLS_RSA_WITH_AES_128_CBC_SHA", - tls.TLS_RSA_WITH_AES_128_CBC_SHA256: "TLS_RSA_WITH_AES_128_CBC_SHA256", - tls.TLS_RSA_WITH_AES_256_CBC_SHA: "TLS_RSA_WITH_AES_256_CBC_SHA", - tls.TLS_RSA_WITH_AES_256_GCM_SHA384: "TLS_RSA_WITH_AES_256_GCM_SHA384", - tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", - tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", - tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA: "TLS_ECDHE_RSA_WITH_RC4_128_SHA", - tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", - tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", - tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", - tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305: "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305: "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", - tls.TLS_AES_128_GCM_SHA256: "TLS_AES_128_GCM_SHA256", - tls.TLS_AES_256_GCM_SHA384: "TLS_AES_256_GCM_SHA384", - tls.TLS_CHACHA20_POLY1305_SHA256: "TLS_CHACHA20_POLY1305_SHA256", -} - -func defaultCipherSuites() []uint16 { - return []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - } -} - -// Where we maintain available curve preferences -var curvePreferenceMap = map[string]tls.CurveID{ - "X25519": tls.X25519, - "CurveP256": tls.CurveP256, - "CurveP384": tls.CurveP384, - "CurveP521": tls.CurveP521, -} - -// reorder to default to the highest level of security. See: -// https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go -func defaultCurvePreferences() []tls.CurveID { - return []tls.CurveID{ - tls.X25519, // faster than P256, arguably more secure - tls.CurveP256, - tls.CurveP384, - tls.CurveP521, - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/client.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/client.go deleted file mode 100644 index 138164eb..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/client.go +++ /dev/null @@ -1,6263 +0,0 @@ -// Copyright 2012-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "crypto/tls" - "crypto/x509" - "encoding/json" - "errors" - "fmt" - "io" - "math/rand" - "net" - "net/http" - "net/url" - "regexp" - "runtime" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/klauspost/compress/s2" - "github.com/nats-io/jwt/v2" - "github.com/nats-io/nats-server/v2/internal/fastrand" -) - -// Type of client connection. -const ( - // CLIENT is an end user. - CLIENT = iota - // ROUTER represents another server in the cluster. - ROUTER - // GATEWAY is a link between 2 clusters. - GATEWAY - // SYSTEM is an internal system client. - SYSTEM - // LEAF is for leaf node connections. - LEAF - // JETSTREAM is an internal jetstream client. - JETSTREAM - // ACCOUNT is for the internal client for accounts. - ACCOUNT -) - -// Extended type of a CLIENT connection. This is returned by c.clientType() -// and indicate what type of client connection we are dealing with. -// If invoked on a non CLIENT connection, NON_CLIENT type is returned. -const ( - // If the connection is not a CLIENT connection. - NON_CLIENT = iota - // Regular NATS client. - NATS - // MQTT client. - MQTT - // Websocket client. - WS -) - -const ( - // ClientProtoZero is the original Client protocol from 2009. - // http://nats.io/documentation/internals/nats-protocol/ - ClientProtoZero = iota - // ClientProtoInfo signals a client can receive more then the original INFO block. - // This can be used to update clients on other cluster members, etc. - ClientProtoInfo -) - -const ( - pingProto = "PING" + _CRLF_ - pongProto = "PONG" + _CRLF_ - errProto = "-ERR '%s'" + _CRLF_ - okProto = "+OK" + _CRLF_ -) - -// TLS Hanshake client types -const ( - tlsHandshakeLeaf = "leafnode" - tlsHandshakeMQTT = "mqtt" -) - -const ( - // Scratch buffer size for the processMsg() calls. - msgScratchSize = 1024 - msgHeadProto = "RMSG " - msgHeadProtoLen = len(msgHeadProto) - - // For controlling dynamic buffer sizes. - startBufSize = 512 // For INFO/CONNECT block - minBufSize = 64 // Smallest to shrink to for PING/PONG - maxBufSize = 65536 // 64k - shortsToShrink = 2 // Trigger to shrink dynamic buffers - maxFlushPending = 10 // Max fsps to have in order to wait for writeLoop - readLoopReport = 2 * time.Second - - // Server should not send a PING (for RTT) before the first PONG has - // been sent to the client. However, in case some client libs don't - // send CONNECT+PING, cap the maximum time before server can send - // the RTT PING. - maxNoRTTPingBeforeFirstPong = 2 * time.Second - - // For stalling fast producers - stallClientMinDuration = 2 * time.Millisecond - stallClientMaxDuration = 5 * time.Millisecond - stallTotalAllowed = 10 * time.Millisecond -) - -var readLoopReportThreshold = readLoopReport - -// Represent client booleans with a bitmask -type clientFlag uint16 - -const ( - hdrLine = "NATS/1.0\r\n" - emptyHdrLine = "NATS/1.0\r\n\r\n" -) - -// Some client state represented as flags -const ( - connectReceived clientFlag = 1 << iota // The CONNECT proto has been received - infoReceived // The INFO protocol has been received - firstPongSent // The first PONG has been sent - handshakeComplete // For TLS clients, indicate that the handshake is complete - flushOutbound // Marks client as having a flushOutbound call in progress. - noReconnect // Indicate that on close, this connection should not attempt a reconnect - closeConnection // Marks that closeConnection has already been called. - connMarkedClosed // Marks that markConnAsClosed has already been called. - writeLoopStarted // Marks that the writeLoop has been started. - skipFlushOnClose // Marks that flushOutbound() should not be called on connection close. - expectConnect // Marks if this connection is expected to send a CONNECT - connectProcessFinished // Marks if this connection has finished the connect process. - compressionNegotiated // Marks if this connection has negotiated compression level with remote. - didTLSFirst // Marks if this connection requested and was accepted doing the TLS handshake first (prior to INFO). - isSlowConsumer // Marks connection as a slow consumer. -) - -// set the flag (would be equivalent to set the boolean to true) -func (cf *clientFlag) set(c clientFlag) { - *cf |= c -} - -// clear the flag (would be equivalent to set the boolean to false) -func (cf *clientFlag) clear(c clientFlag) { - *cf &= ^c -} - -// isSet returns true if the flag is set, false otherwise -func (cf clientFlag) isSet(c clientFlag) bool { - return cf&c != 0 -} - -// setIfNotSet will set the flag `c` only if that flag was not already -// set and return true to indicate that the flag has been set. Returns -// false otherwise. -func (cf *clientFlag) setIfNotSet(c clientFlag) bool { - if *cf&c == 0 { - *cf |= c - return true - } - return false -} - -// ClosedState is the reason client was closed. This will -// be passed into calls to clearConnection, but will only -// be stored in ConnInfo for monitoring. -type ClosedState int - -const ( - ClientClosed = ClosedState(iota + 1) - AuthenticationTimeout - AuthenticationViolation - TLSHandshakeError - SlowConsumerPendingBytes - SlowConsumerWriteDeadline - WriteError - ReadError - ParseError - StaleConnection - ProtocolViolation - BadClientProtocolVersion - WrongPort - MaxAccountConnectionsExceeded - MaxConnectionsExceeded - MaxPayloadExceeded - MaxControlLineExceeded - MaxSubscriptionsExceeded - DuplicateRoute - RouteRemoved - ServerShutdown - AuthenticationExpired - WrongGateway - MissingAccount - Revocation - InternalClient - MsgHeaderViolation - NoRespondersRequiresHeaders - ClusterNameConflict - DuplicateRemoteLeafnodeConnection - DuplicateClientID - DuplicateServerName - MinimumVersionRequired - ClusterNamesIdentical - Kicked -) - -// Some flags passed to processMsgResults -const pmrNoFlag int = 0 -const ( - pmrCollectQueueNames int = 1 << iota - pmrIgnoreEmptyQueueFilter - pmrAllowSendFromRouteToRoute - pmrMsgImportedFromService -) - -type client struct { - // Here first because of use of atomics, and memory alignment. - stats - gwReplyMapping - kind int - srv *Server - acc *Account - perms *permissions - in readCache - parseState - opts ClientOpts - rrTracking *rrTracking - mpay int32 - msubs int32 - mcl int32 - mu sync.Mutex - cid uint64 - start time.Time - nonce []byte - pubKey string - nc net.Conn - ncs atomic.Value - out outbound - user *NkeyUser - host string - port uint16 - subs map[string]*subscription - replies map[string]*resp - mperms *msgDeny - darray []string - pcd map[*client]struct{} - atmr *time.Timer - expires time.Time - ping pinfo - msgb [msgScratchSize]byte - last time.Time - lastIn time.Time - - repliesSincePrune uint16 - lastReplyPrune time.Time - - headers bool - - rtt time.Duration - rttStart time.Time - - route *route - gw *gateway - leaf *leaf - ws *websocket - mqtt *mqtt - - flags clientFlag // Compact booleans into a single field. Size will be increased when needed. - - rref byte - - trace bool - echo bool - noIcb bool - iproc bool // In-Process connection, set at creation and immutable. - - tags jwt.TagList - nameTag string - - tlsTo *time.Timer -} - -type rrTracking struct { - rmap map[string]*remoteLatency - ptmr *time.Timer - lrt time.Duration -} - -// Struct for PING initiation from the server. -type pinfo struct { - tmr *time.Timer - out int -} - -// outbound holds pending data for a socket. -type outbound struct { - nb net.Buffers // Pending buffers for send, each has fixed capacity as per nbPool below. - wnb net.Buffers // Working copy of "nb", reused on each flushOutbound call, partial writes may leave entries here for next iteration. - pb int64 // Total pending/queued bytes. - fsp int32 // Flush signals that are pending per producer from readLoop's pcd. - sg *sync.Cond // To signal writeLoop that there is data to flush. - wdl time.Duration // Snapshot of write deadline. - mp int64 // Snapshot of max pending for client. - lft time.Duration // Last flush time for Write. - stc chan struct{} // Stall chan we create to slow down producers on overrun, e.g. fan-in. - cw *s2.Writer -} - -const nbMaxVectorSize = 1024 // == IOV_MAX on Linux/Darwin and most other Unices (except Solaris/AIX) - -const nbPoolSizeSmall = 512 // Underlying array size of small buffer -const nbPoolSizeMedium = 4096 // Underlying array size of medium buffer -const nbPoolSizeLarge = 65536 // Underlying array size of large buffer - -var nbPoolSmall = &sync.Pool{ - New: func() any { - b := [nbPoolSizeSmall]byte{} - return &b - }, -} - -var nbPoolMedium = &sync.Pool{ - New: func() any { - b := [nbPoolSizeMedium]byte{} - return &b - }, -} - -var nbPoolLarge = &sync.Pool{ - New: func() any { - b := [nbPoolSizeLarge]byte{} - return &b - }, -} - -// nbPoolGet returns a frame that is a best-effort match for the given size. -// Once a pooled frame is no longer needed, it should be recycled by passing -// it to nbPoolPut. -func nbPoolGet(sz int) []byte { - switch { - case sz <= nbPoolSizeSmall: - return nbPoolSmall.Get().(*[nbPoolSizeSmall]byte)[:0] - case sz <= nbPoolSizeMedium: - return nbPoolMedium.Get().(*[nbPoolSizeMedium]byte)[:0] - default: - return nbPoolLarge.Get().(*[nbPoolSizeLarge]byte)[:0] - } -} - -// nbPoolPut recycles a frame that was retrieved from nbPoolGet. It is not -// safe to return multiple slices referring to chunks of the same underlying -// array as this may create overlaps when the buffers are returned to their -// original size, resulting in race conditions. -func nbPoolPut(b []byte) { - switch cap(b) { - case nbPoolSizeSmall: - b := (*[nbPoolSizeSmall]byte)(b[0:nbPoolSizeSmall]) - nbPoolSmall.Put(b) - case nbPoolSizeMedium: - b := (*[nbPoolSizeMedium]byte)(b[0:nbPoolSizeMedium]) - nbPoolMedium.Put(b) - case nbPoolSizeLarge: - b := (*[nbPoolSizeLarge]byte)(b[0:nbPoolSizeLarge]) - nbPoolLarge.Put(b) - default: - // Ignore frames that are the wrong size, this might happen - // with WebSocket/MQTT messages as they are framed - } -} - -type perm struct { - allow *Sublist - deny *Sublist -} - -type permissions struct { - // Have these 2 first for memory alignment due to the use of atomic. - pcsz int32 - prun int32 - sub perm - pub perm - resp *ResponsePermission - pcache sync.Map -} - -// This is used to dynamically track responses and reply subjects -// for dynamic permissioning. -type resp struct { - t time.Time - n int -} - -// msgDeny is used when a user permission for subscriptions has a deny -// clause but a subscription could be made that is of broader scope. -// e.g. deny = "foo", but user subscribes to "*". That subscription should -// succeed but no message sent on foo should be delivered. -type msgDeny struct { - deny *Sublist - dcache map[string]bool -} - -// routeTarget collects information regarding routes and queue groups for -// sending information to a remote. -type routeTarget struct { - sub *subscription - qs []byte - _qs [32]byte -} - -const ( - maxResultCacheSize = 512 - maxDenyPermCacheSize = 256 - maxPermCacheSize = 128 - pruneSize = 32 - routeTargetInit = 8 - replyPermLimit = 4096 - replyPruneTime = time.Second -) - -// Represent read cache booleans with a bitmask -type readCacheFlag uint16 - -const ( - hasMappings readCacheFlag = 1 << iota // For account subject mappings. - switchToCompression readCacheFlag = 1 << 1 -) - -const sysGroup = "_sys_" - -// Used in readloop to cache hot subject lookups and group statistics. -type readCache struct { - // These are for clients who are bound to a single account. - genid uint64 - results map[string]*SublistResult - - // This is for routes and gateways to have their own L1 as well that is account aware. - pacache map[string]*perAccountCache - - // This is for when we deliver messages across a route. We use this structure - // to make sure to only send one message and properly scope to queues as needed. - rts []routeTarget - - // These are all temporary totals for an invocation of a read in readloop. - msgs int32 - bytes int32 - subs int32 - - rsz int32 // Read buffer size - srs int32 // Short reads, used for dynamic buffer resizing. - - // These are for readcache flags to avoid locks. - flags readCacheFlag - - // Capture the time we started processing our readLoop. - start time.Time - - // Total time stalled so far for readLoop processing. - tst time.Duration -} - -// set the flag (would be equivalent to set the boolean to true) -func (rcf *readCacheFlag) set(c readCacheFlag) { - *rcf |= c -} - -// clear the flag (would be equivalent to set the boolean to false) -func (rcf *readCacheFlag) clear(c readCacheFlag) { - *rcf &= ^c -} - -// isSet returns true if the flag is set, false otherwise -func (rcf readCacheFlag) isSet(c readCacheFlag) bool { - return rcf&c != 0 -} - -const ( - defaultMaxPerAccountCacheSize = 8192 - defaultPrunePerAccountCacheSize = 1024 - defaultClosedSubsCheckInterval = 5 * time.Minute -) - -var ( - maxPerAccountCacheSize = defaultMaxPerAccountCacheSize - prunePerAccountCacheSize = defaultPrunePerAccountCacheSize - closedSubsCheckInterval = defaultClosedSubsCheckInterval -) - -// perAccountCache is for L1 semantics for inbound messages from a route or gateway to mimic the performance of clients. -type perAccountCache struct { - acc *Account - results *SublistResult - genid uint64 -} - -func (c *client) String() (id string) { - loaded := c.ncs.Load() - if loaded != nil { - return loaded.(string) - } - - return _EMPTY_ -} - -// GetNonce returns the nonce that was presented to the user on connection -func (c *client) GetNonce() []byte { - c.mu.Lock() - defer c.mu.Unlock() - - return c.nonce -} - -// GetName returns the application supplied name for the connection. -func (c *client) GetName() string { - c.mu.Lock() - name := c.opts.Name - c.mu.Unlock() - return name -} - -// GetOpts returns the client options provided by the application. -func (c *client) GetOpts() *ClientOpts { - return &c.opts -} - -// GetTLSConnectionState returns the TLS ConnectionState if TLS is enabled, nil -// otherwise. Implements the ClientAuth interface. -func (c *client) GetTLSConnectionState() *tls.ConnectionState { - c.mu.Lock() - defer c.mu.Unlock() - if c.nc == nil { - return nil - } - tc, ok := c.nc.(*tls.Conn) - if !ok { - return nil - } - state := tc.ConnectionState() - return &state -} - -// For CLIENT connections, this function returns the client type, that is, -// NATS (for regular clients), MQTT or WS for websocket. -// If this is invoked for a non CLIENT connection, NON_CLIENT is returned. -// -// This function does not lock the client and accesses fields that are supposed -// to be immutable and therefore it can be invoked outside of the client's lock. -func (c *client) clientType() int { - switch c.kind { - case CLIENT: - if c.isMqtt() { - return MQTT - } else if c.isWebsocket() { - return WS - } - return NATS - default: - return NON_CLIENT - } -} - -var clientTypeStringMap = map[int]string{ - NON_CLIENT: _EMPTY_, - NATS: "nats", - WS: "websocket", - MQTT: "mqtt", -} - -func (c *client) clientTypeString() string { - if typeStringVal, ok := clientTypeStringMap[c.clientType()]; ok { - return typeStringVal - } - return _EMPTY_ -} - -// This is the main subscription struct that indicates -// interest in published messages. -// FIXME(dlc) - This is getting bloated for normal subs, need -// to optionally have an opts section for non-normal stuff. -type subscription struct { - client *client - im *streamImport // This is for import stream support. - rsi bool - si bool - shadow []*subscription // This is to track shadowed accounts. - icb msgHandler - subject []byte - queue []byte - sid []byte - origin []byte - nm int64 - max int64 - qw int32 - closed int32 - mqtt *mqttSub -} - -// Indicate that this subscription is closed. -// This is used in pruning of route and gateway cache items. -func (s *subscription) close() { - atomic.StoreInt32(&s.closed, 1) -} - -// Return true if this subscription was unsubscribed -// or its connection has been closed. -func (s *subscription) isClosed() bool { - return atomic.LoadInt32(&s.closed) == 1 -} - -type ClientOpts struct { - Echo bool `json:"echo"` - Verbose bool `json:"verbose"` - Pedantic bool `json:"pedantic"` - TLSRequired bool `json:"tls_required"` - Nkey string `json:"nkey,omitempty"` - JWT string `json:"jwt,omitempty"` - Sig string `json:"sig,omitempty"` - Token string `json:"auth_token,omitempty"` - Username string `json:"user,omitempty"` - Password string `json:"pass,omitempty"` - Name string `json:"name"` - Lang string `json:"lang"` - Version string `json:"version"` - Protocol int `json:"protocol"` - Account string `json:"account,omitempty"` - AccountNew bool `json:"new_account,omitempty"` - Headers bool `json:"headers,omitempty"` - NoResponders bool `json:"no_responders,omitempty"` - - // Routes and Leafnodes only - Import *SubjectPermission `json:"import,omitempty"` - Export *SubjectPermission `json:"export,omitempty"` - - // Leafnodes - RemoteAccount string `json:"remote_account,omitempty"` -} - -var defaultOpts = ClientOpts{Verbose: true, Pedantic: true, Echo: true} -var internalOpts = ClientOpts{Verbose: false, Pedantic: false, Echo: false} - -func (c *client) setTraceLevel() { - if c.kind == SYSTEM && !(atomic.LoadInt32(&c.srv.logging.traceSysAcc) != 0) { - c.trace = false - } else { - c.trace = (atomic.LoadInt32(&c.srv.logging.trace) != 0) - } -} - -// Lock should be held -func (c *client) initClient() { - s := c.srv - c.cid = atomic.AddUint64(&s.gcid, 1) - - // Outbound data structure setup - c.out.sg = sync.NewCond(&(c.mu)) - opts := s.getOpts() - // Snapshots to avoid mutex access in fast paths. - c.out.wdl = opts.WriteDeadline - c.out.mp = opts.MaxPending - // Snapshot max control line since currently can not be changed on reload and we - // were checking it on each call to parse. If this changes and we allow MaxControlLine - // to be reloaded without restart, this code will need to change. - c.mcl = int32(opts.MaxControlLine) - if c.mcl == 0 { - c.mcl = MAX_CONTROL_LINE_SIZE - } - - c.subs = make(map[string]*subscription) - c.echo = true - - c.setTraceLevel() - - // This is a scratch buffer used for processMsg() - // The msg header starts with "RMSG ", which can be used - // for both local and routes. - // in bytes that is [82 77 83 71 32]. - c.msgb = [msgScratchSize]byte{82, 77, 83, 71, 32} - - // This is to track pending clients that have data to be flushed - // after we process inbound msgs from our own connection. - c.pcd = make(map[*client]struct{}) - - // snapshot the string version of the connection - var conn string - if c.nc != nil { - if addr := c.nc.RemoteAddr(); addr != nil { - if conn = addr.String(); conn != _EMPTY_ { - host, port, _ := net.SplitHostPort(conn) - iPort, _ := strconv.Atoi(port) - c.host, c.port = host, uint16(iPort) - if c.isWebsocket() && c.ws.clientIP != _EMPTY_ { - cip := c.ws.clientIP - // Surround IPv6 addresses with square brackets, as - // net.JoinHostPort would do... - if strings.Contains(cip, ":") { - cip = "[" + cip + "]" - } - conn = fmt.Sprintf("%s/%s", cip, conn) - } - // Now that we have extracted host and port, escape - // the string because it is going to be used in Sprintf - conn = strings.ReplaceAll(conn, "%", "%%") - } - } - } - - switch c.kind { - case CLIENT: - switch c.clientType() { - case NATS: - c.ncs.Store(fmt.Sprintf("%s - cid:%d", conn, c.cid)) - case WS: - c.ncs.Store(fmt.Sprintf("%s - wid:%d", conn, c.cid)) - case MQTT: - var ws string - if c.isWebsocket() { - ws = "_ws" - } - c.ncs.Store(fmt.Sprintf("%s - mid%s:%d", conn, ws, c.cid)) - } - case ROUTER: - c.ncs.Store(fmt.Sprintf("%s - rid:%d", conn, c.cid)) - case GATEWAY: - c.ncs.Store(fmt.Sprintf("%s - gid:%d", conn, c.cid)) - case LEAF: - var ws string - if c.isWebsocket() { - ws = "_ws" - } - c.ncs.Store(fmt.Sprintf("%s - lid%s:%d", conn, ws, c.cid)) - case SYSTEM: - c.ncs.Store("SYSTEM") - case JETSTREAM: - c.ncs.Store("JETSTREAM") - case ACCOUNT: - c.ncs.Store("ACCOUNT") - } -} - -// RemoteAddress expose the Address of the client connection, -// nil when not connected or unknown -func (c *client) RemoteAddress() net.Addr { - c.mu.Lock() - defer c.mu.Unlock() - - if c.nc == nil { - return nil - } - - return c.nc.RemoteAddr() -} - -// Helper function to report errors. -func (c *client) reportErrRegisterAccount(acc *Account, err error) { - if err == ErrTooManyAccountConnections { - c.maxAccountConnExceeded() - return - } - c.Errorf("Problem registering with account %q: %s", acc.Name, err) - c.sendErr("Failed Account Registration") -} - -// Kind returns the client kind and will be one of the defined constants like CLIENT, ROUTER, GATEWAY, LEAF -func (c *client) Kind() int { - c.mu.Lock() - kind := c.kind - c.mu.Unlock() - - return kind -} - -// registerWithAccount will register the given user with a specific -// account. This will change the subject namespace. -func (c *client) registerWithAccount(acc *Account) error { - if acc == nil { - return ErrBadAccount - } - acc.mu.RLock() - bad := acc.sl == nil - acc.mu.RUnlock() - if bad { - return ErrBadAccount - } - // If we were previously registered, usually to $G, do accounting here to remove. - if c.acc != nil { - if prev := c.acc.removeClient(c); prev == 1 && c.srv != nil { - c.srv.decActiveAccounts() - } - } - - c.mu.Lock() - kind := c.kind - srv := c.srv - c.acc = acc - c.applyAccountLimits() - c.mu.Unlock() - - // Check if we have a max connections violation - if kind == CLIENT && acc.MaxTotalConnectionsReached() { - return ErrTooManyAccountConnections - } else if kind == LEAF { - // Check if we are already connected to this cluster. - if rc := c.remoteCluster(); rc != _EMPTY_ && acc.hasLeafNodeCluster(rc) { - return ErrLeafNodeLoop - } - if acc.MaxTotalLeafNodesReached() { - return ErrTooManyAccountConnections - } - } - - // Add in new one. - if prev := acc.addClient(c); prev == 0 && srv != nil { - srv.incActiveAccounts() - } - - return nil -} - -// Helper to determine if we have met or exceeded max subs. -func (c *client) subsAtLimit() bool { - return c.msubs != jwt.NoLimit && len(c.subs) >= int(c.msubs) -} - -func minLimit(value *int32, limit int32) bool { - v := atomic.LoadInt32(value) - if v != jwt.NoLimit { - if limit != jwt.NoLimit { - if limit < v { - atomic.StoreInt32(value, limit) - return true - } - } - } else if limit != jwt.NoLimit { - atomic.StoreInt32(value, limit) - return true - } - return false -} - -// Apply account limits -// Lock is held on entry. -// FIXME(dlc) - Should server be able to override here? -func (c *client) applyAccountLimits() { - if c.acc == nil || (c.kind != CLIENT && c.kind != LEAF) { - return - } - atomic.StoreInt32(&c.mpay, jwt.NoLimit) - c.msubs = jwt.NoLimit - if c.opts.JWT != _EMPTY_ { // user jwt implies account - if uc, _ := jwt.DecodeUserClaims(c.opts.JWT); uc != nil { - atomic.StoreInt32(&c.mpay, int32(uc.Limits.Payload)) - c.msubs = int32(uc.Limits.Subs) - if uc.IssuerAccount != _EMPTY_ && uc.IssuerAccount != uc.Issuer { - if scope, ok := c.acc.signingKeys[uc.Issuer]; ok { - if userScope, ok := scope.(*jwt.UserScope); ok { - // if signing key disappeared or changed and we don't get here, the client will be disconnected - c.mpay = int32(userScope.Template.Limits.Payload) - c.msubs = int32(userScope.Template.Limits.Subs) - } - } - } - } - } - - c.acc.mu.RLock() - minLimit(&c.mpay, c.acc.mpay) - minLimit(&c.msubs, c.acc.msubs) - c.acc.mu.RUnlock() - - s := c.srv - opts := s.getOpts() - mPay := opts.MaxPayload - // options encode unlimited differently - if mPay == 0 { - mPay = jwt.NoLimit - } - mSubs := int32(opts.MaxSubs) - if mSubs == 0 { - mSubs = jwt.NoLimit - } - wasUnlimited := c.mpay == jwt.NoLimit - if minLimit(&c.mpay, mPay) && !wasUnlimited { - c.Errorf("Max Payload set to %d from server overrides account or user config", opts.MaxPayload) - } - wasUnlimited = c.msubs == jwt.NoLimit - if minLimit(&c.msubs, mSubs) && !wasUnlimited { - c.Errorf("Max Subscriptions set to %d from server overrides account or user config", opts.MaxSubs) - } - if c.subsAtLimit() { - go func() { - c.maxSubsExceeded() - time.Sleep(20 * time.Millisecond) - c.closeConnection(MaxSubscriptionsExceeded) - }() - } -} - -// RegisterUser allows auth to call back into a new client -// with the authenticated user. This is used to map -// any permissions into the client and setup accounts. -func (c *client) RegisterUser(user *User) { - // Register with proper account and sublist. - if user.Account != nil { - if err := c.registerWithAccount(user.Account); err != nil { - c.reportErrRegisterAccount(user.Account, err) - return - } - } - - c.mu.Lock() - - // Assign permissions. - if user.Permissions == nil { - // Reset perms to nil in case client previously had them. - c.perms = nil - c.mperms = nil - } else { - c.setPermissions(user.Permissions) - } - - // allows custom authenticators to set a username to be reported in - // server events and more - if user.Username != _EMPTY_ { - c.opts.Username = user.Username - } - - // if a deadline time stamp is set we start a timer to disconnect the user at that time - if !user.ConnectionDeadline.IsZero() { - c.setExpirationTimerUnlocked(time.Until(user.ConnectionDeadline)) - } - - c.mu.Unlock() -} - -// RegisterNkeyUser allows auth to call back into a new nkey -// client with the authenticated user. This is used to map -// any permissions into the client and setup accounts. -func (c *client) RegisterNkeyUser(user *NkeyUser) error { - // Register with proper account and sublist. - if user.Account != nil { - if err := c.registerWithAccount(user.Account); err != nil { - c.reportErrRegisterAccount(user.Account, err) - return err - } - } - - c.mu.Lock() - c.user = user - // Assign permissions. - if user.Permissions == nil { - // Reset perms to nil in case client previously had them. - c.perms = nil - c.mperms = nil - } else { - c.setPermissions(user.Permissions) - } - c.mu.Unlock() - return nil -} - -func splitSubjectQueue(sq string) ([]byte, []byte, error) { - vals := strings.Fields(strings.TrimSpace(sq)) - s := []byte(vals[0]) - var q []byte - if len(vals) == 2 { - q = []byte(vals[1]) - } else if len(vals) > 2 { - return nil, nil, fmt.Errorf("invalid subject-queue %q", sq) - } - return s, q, nil -} - -// Initializes client.perms structure. -// Lock is held on entry. -func (c *client) setPermissions(perms *Permissions) { - if perms == nil { - return - } - c.perms = &permissions{} - - // Loop over publish permissions - if perms.Publish != nil { - if perms.Publish.Allow != nil { - c.perms.pub.allow = NewSublistWithCache() - } - for _, pubSubject := range perms.Publish.Allow { - sub := &subscription{subject: []byte(pubSubject)} - c.perms.pub.allow.Insert(sub) - } - if len(perms.Publish.Deny) > 0 { - c.perms.pub.deny = NewSublistWithCache() - } - for _, pubSubject := range perms.Publish.Deny { - sub := &subscription{subject: []byte(pubSubject)} - c.perms.pub.deny.Insert(sub) - } - } - - // Check if we are allowed to send responses. - if perms.Response != nil { - rp := *perms.Response - c.perms.resp = &rp - c.replies = make(map[string]*resp) - } - - // Loop over subscribe permissions - if perms.Subscribe != nil { - var err error - if len(perms.Subscribe.Allow) > 0 { - c.perms.sub.allow = NewSublistWithCache() - } - for _, subSubject := range perms.Subscribe.Allow { - sub := &subscription{} - sub.subject, sub.queue, err = splitSubjectQueue(subSubject) - if err != nil { - c.Errorf("%s", err.Error()) - continue - } - c.perms.sub.allow.Insert(sub) - } - if len(perms.Subscribe.Deny) > 0 { - c.perms.sub.deny = NewSublistWithCache() - // Also hold onto this array for later. - c.darray = perms.Subscribe.Deny - } - for _, subSubject := range perms.Subscribe.Deny { - sub := &subscription{} - sub.subject, sub.queue, err = splitSubjectQueue(subSubject) - if err != nil { - c.Errorf("%s", err.Error()) - continue - } - c.perms.sub.deny.Insert(sub) - } - } - - // If we are a leafnode and we are the hub copy the extracted perms - // to resend back to soliciting server. These are reversed from the - // way routes interpret them since this is how the soliciting server - // will receive these back in an update INFO. - if c.isHubLeafNode() { - c.opts.Import = perms.Subscribe - c.opts.Export = perms.Publish - } -} - -// Build public permissions from internal ones. -// Used for user info requests. -func (c *client) publicPermissions() *Permissions { - c.mu.Lock() - defer c.mu.Unlock() - - if c.perms == nil { - return nil - } - perms := &Permissions{ - Publish: &SubjectPermission{}, - Subscribe: &SubjectPermission{}, - } - - _subs := [32]*subscription{} - - // Publish - if c.perms.pub.allow != nil { - subs := _subs[:0] - c.perms.pub.allow.All(&subs) - for _, sub := range subs { - perms.Publish.Allow = append(perms.Publish.Allow, string(sub.subject)) - } - } - if c.perms.pub.deny != nil { - subs := _subs[:0] - c.perms.pub.deny.All(&subs) - for _, sub := range subs { - perms.Publish.Deny = append(perms.Publish.Deny, string(sub.subject)) - } - } - // Subsribe - if c.perms.sub.allow != nil { - subs := _subs[:0] - c.perms.sub.allow.All(&subs) - for _, sub := range subs { - perms.Subscribe.Allow = append(perms.Subscribe.Allow, string(sub.subject)) - } - } - if c.perms.sub.deny != nil { - subs := _subs[:0] - c.perms.sub.deny.All(&subs) - for _, sub := range subs { - perms.Subscribe.Deny = append(perms.Subscribe.Deny, string(sub.subject)) - } - } - // Responses. - if c.perms.resp != nil { - rp := *c.perms.resp - perms.Response = &rp - } - - return perms -} - -type denyType int - -const ( - pub = denyType(iota + 1) - sub - both -) - -// Merge client.perms structure with additional pub deny permissions -// Lock is held on entry. -func (c *client) mergeDenyPermissions(what denyType, denyPubs []string) { - if len(denyPubs) == 0 { - return - } - if c.perms == nil { - c.perms = &permissions{} - } - var perms []*perm - switch what { - case pub: - perms = []*perm{&c.perms.pub} - case sub: - perms = []*perm{&c.perms.sub} - case both: - perms = []*perm{&c.perms.pub, &c.perms.sub} - } - for _, p := range perms { - if p.deny == nil { - p.deny = NewSublistWithCache() - } - FOR_DENY: - for _, subj := range denyPubs { - r := p.deny.Match(subj) - for _, v := range r.qsubs { - for _, s := range v { - if string(s.subject) == subj { - continue FOR_DENY - } - } - } - for _, s := range r.psubs { - if string(s.subject) == subj { - continue FOR_DENY - } - } - sub := &subscription{subject: []byte(subj)} - p.deny.Insert(sub) - } - } -} - -// Merge client.perms structure with additional pub deny permissions -// Client lock must not be held on entry -func (c *client) mergeDenyPermissionsLocked(what denyType, denyPubs []string) { - c.mu.Lock() - c.mergeDenyPermissions(what, denyPubs) - c.mu.Unlock() -} - -// Check to see if we have an expiration for the user JWT via base claims. -// FIXME(dlc) - Clear on connect with new JWT. -func (c *client) setExpiration(claims *jwt.ClaimsData, validFor time.Duration) { - if claims.Expires == 0 { - if validFor != 0 { - c.setExpirationTimer(validFor) - } - return - } - expiresAt := time.Duration(0) - tn := time.Now().Unix() - if claims.Expires > tn { - expiresAt = time.Duration(claims.Expires-tn) * time.Second - } - if validFor != 0 && validFor < expiresAt { - c.setExpirationTimer(validFor) - } else { - c.setExpirationTimer(expiresAt) - } -} - -// This will load up the deny structure used for filtering delivered -// messages based on a deny clause for subscriptions. -// Lock should be held. -func (c *client) loadMsgDenyFilter() { - c.mperms = &msgDeny{NewSublistWithCache(), make(map[string]bool)} - for _, sub := range c.darray { - c.mperms.deny.Insert(&subscription{subject: []byte(sub)}) - } -} - -// writeLoop is the main socket write functionality. -// Runs in its own Go routine. -func (c *client) writeLoop() { - defer c.srv.grWG.Done() - c.mu.Lock() - if c.isClosed() { - c.mu.Unlock() - return - } - c.flags.set(writeLoopStarted) - c.mu.Unlock() - - // Used to check that we did flush from last wake up. - waitOk := true - var closed bool - - // Main loop. Will wait to be signaled and then will use - // buffered outbound structure for efficient writev to the underlying socket. - for { - c.mu.Lock() - if closed = c.isClosed(); !closed { - owtf := c.out.fsp > 0 && c.out.pb < maxBufSize && c.out.fsp < maxFlushPending - if waitOk && (c.out.pb == 0 || owtf) { - c.out.sg.Wait() - // Check that connection has not been closed while lock was released - // in the conditional wait. - closed = c.isClosed() - } - } - if closed { - c.flushAndClose(false) - c.mu.Unlock() - - // We should always call closeConnection() to ensure that state is - // properly cleaned-up. It will be a no-op if already done. - c.closeConnection(WriteError) - - // Now explicitly call reconnect(). Thanks to ref counting, we know - // that the reconnect will execute only after connection has been - // removed from the server state. - c.reconnect() - return - } - // Flush data - waitOk = c.flushOutbound() - c.mu.Unlock() - } -} - -// flushClients will make sure to flush any clients we may have -// sent to during processing. We pass in a budget as a time.Duration -// for how much time to spend in place flushing for this client. -func (c *client) flushClients(budget time.Duration) time.Time { - last := time.Now() - - // Check pending clients for flush. - for cp := range c.pcd { - // TODO(dlc) - Wonder if it makes more sense to create a new map? - delete(c.pcd, cp) - - // Queue up a flush for those in the set - cp.mu.Lock() - // Update last activity for message delivery - cp.last = last - // Remove ourselves from the pending list. - cp.out.fsp-- - - // Just ignore if this was closed. - if cp.isClosed() { - cp.mu.Unlock() - continue - } - - if budget > 0 && cp.out.lft < 2*budget && cp.flushOutbound() { - budget -= cp.out.lft - } else { - cp.flushSignal() - } - - cp.mu.Unlock() - } - return last -} - -// readLoop is the main socket read functionality. -// Runs in its own Go routine. -func (c *client) readLoop(pre []byte) { - // Grab the connection off the client, it will be cleared on a close. - // We check for that after the loop, but want to avoid a nil dereference - c.mu.Lock() - s := c.srv - defer s.grWG.Done() - if c.isClosed() { - c.mu.Unlock() - return - } - nc := c.nc - ws := c.isWebsocket() - if c.isMqtt() { - c.mqtt.r = &mqttReader{reader: nc} - } - c.in.rsz = startBufSize - - // Check the per-account-cache for closed subscriptions - cpacc := c.kind == ROUTER || c.kind == GATEWAY - // Last per-account-cache check for closed subscriptions - lpacc := time.Now() - acc := c.acc - var masking bool - if ws { - masking = c.ws.maskread - } - checkCompress := c.kind == ROUTER || c.kind == LEAF - c.mu.Unlock() - - defer func() { - if c.isMqtt() { - s.mqttHandleClosedClient(c) - } - // These are used only in the readloop, so we can set them to nil - // on exit of the readLoop. - c.in.results, c.in.pacache = nil, nil - }() - - // Start read buffer. - b := make([]byte, c.in.rsz) - - // Websocket clients will return several slices if there are multiple - // websocket frames in the blind read. For non WS clients though, we - // will always have 1 slice per loop iteration. So we define this here - // so non WS clients will use bufs[0] = b[:n]. - var _bufs [1][]byte - bufs := _bufs[:1] - - var wsr *wsReadInfo - if ws { - wsr = &wsReadInfo{mask: masking} - wsr.init() - } - - var decompress bool - var reader io.Reader - reader = nc - - for { - var n int - var err error - - // If we have a pre buffer parse that first. - if len(pre) > 0 { - b = pre - n = len(pre) - pre = nil - } else { - n, err = reader.Read(b) - // If we have any data we will try to parse and exit at the end. - if n == 0 && err != nil { - c.closeConnection(closedStateForErr(err)) - return - } - } - if ws { - bufs, err = c.wsRead(wsr, reader, b[:n]) - if bufs == nil && err != nil { - if err != io.EOF { - c.Errorf("read error: %v", err) - } - c.closeConnection(closedStateForErr(err)) - return - } else if bufs == nil { - continue - } - } else { - bufs[0] = b[:n] - } - - // Check if the account has mappings and if so set the local readcache flag. - // We check here to make sure any changes such as config reload are reflected here. - if c.kind == CLIENT || c.kind == LEAF { - if acc.hasMappings() { - c.in.flags.set(hasMappings) - } else { - c.in.flags.clear(hasMappings) - } - } - - c.in.start = time.Now() - - // Clear inbound stats cache - c.in.msgs = 0 - c.in.bytes = 0 - c.in.subs = 0 - - // Main call into parser for inbound data. This will generate callouts - // to process messages, etc. - for i := 0; i < len(bufs); i++ { - if err := c.parse(bufs[i]); err != nil { - if err == ErrMinimumVersionRequired { - // Special case here, currently only for leaf node connections. - // When process the CONNECT protocol, if the minimum version - // required was not met, an error was printed and sent back to - // the remote, and connection was closed after a certain delay - // (to avoid "rapid" reconnection from the remote). - // We don't need to do any of the things below, simply return. - return - } - if dur := time.Since(c.in.start); dur >= readLoopReportThreshold { - c.Warnf("Readloop processing time: %v", dur) - } - // Need to call flushClients because some of the clients have been - // assigned messages and their "fsp" incremented, and need now to be - // decremented and their writeLoop signaled. - c.flushClients(0) - // handled inline - if err != ErrMaxPayload && err != ErrAuthentication { - c.Error(err) - c.closeConnection(ProtocolViolation) - } - return - } - // Clear total stalled time here. - if c.in.tst >= stallClientMaxDuration { - c.rateLimitFormatWarnf("Producer was stalled for a total of %v", c.in.tst.Round(time.Millisecond)) - } - c.in.tst = 0 - } - - // If we are a ROUTER/LEAF and have processed an INFO, it is possible that - // we are asked to switch to compression now. - if checkCompress && c.in.flags.isSet(switchToCompression) { - c.in.flags.clear(switchToCompression) - // For now we support only s2 compression... - reader = s2.NewReader(nc) - decompress = true - } - - // Updates stats for client and server that were collected - // from parsing through the buffer. - if c.in.msgs > 0 { - atomic.AddInt64(&c.inMsgs, int64(c.in.msgs)) - atomic.AddInt64(&c.inBytes, int64(c.in.bytes)) - if acc != nil { - atomic.AddInt64(&acc.inMsgs, int64(c.in.msgs)) - atomic.AddInt64(&acc.inBytes, int64(c.in.bytes)) - } - atomic.AddInt64(&s.inMsgs, int64(c.in.msgs)) - atomic.AddInt64(&s.inBytes, int64(c.in.bytes)) - } - - // Signal to writeLoop to flush to socket. - last := c.flushClients(0) - - // Update activity, check read buffer size. - c.mu.Lock() - - // Activity based on interest changes or data/msgs. - // Also update last receive activity for ping sender - if c.in.msgs > 0 || c.in.subs > 0 { - c.last = last - c.lastIn = last - } - - if n >= cap(b) { - c.in.srs = 0 - } else if n < cap(b)/2 { // divide by 2 b/c we want less than what we would shrink to. - c.in.srs++ - } - - // Update read buffer size as/if needed. - if n >= cap(b) && cap(b) < maxBufSize { - // Grow - c.in.rsz = int32(cap(b) * 2) - b = make([]byte, c.in.rsz) - } else if n < cap(b) && cap(b) > minBufSize && c.in.srs > shortsToShrink { - // Shrink, for now don't accelerate, ping/pong will eventually sort it out. - c.in.rsz = int32(cap(b) / 2) - b = make([]byte, c.in.rsz) - } - // re-snapshot the account since it can change during reload, etc. - acc = c.acc - // Refresh nc because in some cases, we have upgraded c.nc to TLS. - if nc != c.nc { - nc = c.nc - if decompress && nc != nil { - // For now we support only s2 compression... - reader.(*s2.Reader).Reset(nc) - } else if !decompress { - reader = nc - } - } - c.mu.Unlock() - - // Connection was closed - if nc == nil { - return - } - - if dur := time.Since(c.in.start); dur >= readLoopReportThreshold { - c.Warnf("Readloop processing time: %v", dur) - } - - // We could have had a read error from above but still read some data. - // If so do the close here unconditionally. - if err != nil { - c.closeConnection(closedStateForErr(err)) - return - } - - if cpacc && (c.in.start.Sub(lpacc)) >= closedSubsCheckInterval { - c.pruneClosedSubFromPerAccountCache() - lpacc = time.Now() - } - } -} - -// Returns the appropriate closed state for a given read error. -func closedStateForErr(err error) ClosedState { - if err == io.EOF { - return ClientClosed - } - return ReadError -} - -// collapsePtoNB will either returned framed WebSocket buffers or it will -// return a reference to c.out.nb. -func (c *client) collapsePtoNB() (net.Buffers, int64) { - if c.isWebsocket() { - return c.wsCollapsePtoNB() - } - return c.out.nb, c.out.pb -} - -// flushOutbound will flush outbound buffer to a client. -// Will return true if data was attempted to be written. -// Lock must be held -func (c *client) flushOutbound() bool { - if c.flags.isSet(flushOutbound) { - // For CLIENT connections, it is possible that the readLoop calls - // flushOutbound(). If writeLoop and readLoop compete and we are - // here we should release the lock to reduce the risk of spinning. - c.mu.Unlock() - runtime.Gosched() - c.mu.Lock() - return false - } - c.flags.set(flushOutbound) - defer func() { - // Check flushAndClose() for explanation on why we do this. - if c.isClosed() { - for i := range c.out.wnb { - nbPoolPut(c.out.wnb[i]) - } - c.out.wnb = nil - } - c.flags.clear(flushOutbound) - }() - - // Check for nothing to do. - if c.nc == nil || c.srv == nil || c.out.pb == 0 { - return true // true because no need to queue a signal. - } - - // In the case of a normal socket connection, "collapsed" is just a ref - // to "nb". In the case of WebSockets, additional framing is added to - // anything that is waiting in "nb". Also keep a note of how many bytes - // were queued before we release the mutex. - collapsed, attempted := c.collapsePtoNB() - - // Frustratingly, (net.Buffers).WriteTo() modifies the receiver so we - // can't work on "nb" directly — while the mutex is unlocked during IO, - // something else might call queueOutbound and modify it. So instead we - // need a working copy — we'll operate on "wnb" instead. Note that in - // the case of a partial write, "wnb" may have remaining data from the - // previous write, and in the case of WebSockets, that data may already - // be framed, so we are careful not to re-frame "wnb" here. Instead we - // will just frame up "nb" and append it onto whatever is left on "wnb". - // "nb" will be set to nil so that we can manipulate "collapsed" outside - // of the client's lock, which is interesting in case of compression. - c.out.nb = nil - - // In case it goes away after releasing the lock. - nc := c.nc - - // Capture this (we change the value in some tests) - wdl := c.out.wdl - - // Check for compression - cw := c.out.cw - if cw != nil { - // We will have to adjust once we have compressed, so remove for now. - c.out.pb -= attempted - if c.isWebsocket() { - c.ws.fs -= attempted - } - } - - // Do NOT hold lock during actual IO. - c.mu.Unlock() - - // Compress outside of the lock - if cw != nil { - var err error - bb := bytes.Buffer{} - - cw.Reset(&bb) - for _, buf := range collapsed { - if _, err = cw.Write(buf); err != nil { - break - } - } - if err == nil { - err = cw.Close() - } - if err != nil { - c.Errorf("Error compressing data: %v", err) - // We need to grab the lock now before marking as closed and exiting - c.mu.Lock() - c.markConnAsClosed(WriteError) - return false - } - collapsed = append(net.Buffers(nil), bb.Bytes()) - attempted = int64(len(collapsed[0])) - } - - // This is safe to do outside of the lock since "collapsed" is no longer - // referenced in c.out.nb (which can be modified in queueOutboud() while - // the lock is released). - c.out.wnb = append(c.out.wnb, collapsed...) - var _orig [nbMaxVectorSize][]byte - orig := append(_orig[:0], c.out.wnb...) - - // Since WriteTo is lopping things off the beginning, we need to remember - // the start position of the underlying array so that we can get back to it. - // Otherwise we'll always "slide forward" and that will result in reallocs. - startOfWnb := c.out.wnb[0:] - - // flush here - start := time.Now() - - var n int64 // Total bytes written - var wn int64 // Bytes written per loop - var err error // Error from last write, if any - for len(c.out.wnb) > 0 { - // Limit the number of vectors to no more than nbMaxVectorSize, - // which if 1024, will mean a maximum of 64MB in one go. - wnb := c.out.wnb - if len(wnb) > nbMaxVectorSize { - wnb = wnb[:nbMaxVectorSize] - } - consumed := len(wnb) - - // Actual write to the socket. The deadline applies to each batch - // rather than the total write, such that the configured deadline - // can be tuned to a known maximum quantity (64MB). - nc.SetWriteDeadline(time.Now().Add(wdl)) - wn, err = wnb.WriteTo(nc) - nc.SetWriteDeadline(time.Time{}) - - // Update accounting, move wnb slice onwards if needed, or stop - // if a write error was reported that wasn't a short write. - n += wn - c.out.wnb = c.out.wnb[consumed-len(wnb):] - if err != nil && err != io.ErrShortWrite { - break - } - } - - lft := time.Since(start) - - // Re-acquire client lock. - c.mu.Lock() - - // Adjust if we were compressing. - if cw != nil { - c.out.pb += attempted - if c.isWebsocket() { - c.ws.fs += attempted - } - } - - // At this point, "wnb" has been mutated by WriteTo and any consumed - // buffers have been lopped off the beginning, so in order to return - // them to the pool, we need to look at the difference between "orig" - // and "wnb". - for i := 0; i < len(orig)-len(c.out.wnb); i++ { - nbPoolPut(orig[i]) - } - - // At this point it's possible that "nb" has been modified by another - // call to queueOutbound while the lock was released, so we'll leave - // those for the next iteration. Meanwhile it's possible that we only - // managed a partial write of "wnb", so we'll shift anything that - // remains up to the beginning of the array to prevent reallocating. - // Anything left in "wnb" has already been framed for WebSocket conns - // so leave them alone for the next call to flushOutbound. - c.out.wnb = append(startOfWnb[:0], c.out.wnb...) - - // If we've written everything but the underlying array of our working - // buffer has grown excessively then free it — the GC will tidy it up - // and we can allocate a new one next time. - if len(c.out.wnb) == 0 && cap(c.out.wnb) > nbPoolSizeLarge*8 { - c.out.wnb = nil - } - - // Ignore ErrShortWrite errors, they will be handled as partials. - var gotWriteTimeout bool - if err != nil && err != io.ErrShortWrite { - // Handle timeout error (slow consumer) differently - if ne, ok := err.(net.Error); ok && ne.Timeout() { - gotWriteTimeout = true - if closed := c.handleWriteTimeout(n, attempted, len(orig)); closed { - return true - } - } else { - // Other errors will cause connection to be closed. - // For clients, report as debug but for others report as error. - report := c.Debugf - if c.kind != CLIENT { - report = c.Errorf - } - report("Error flushing: %v", err) - c.markConnAsClosed(WriteError) - return true - } - } - - // Update flush time statistics. - c.out.lft = lft - - // Subtract from pending bytes and messages. - c.out.pb -= n - if c.isWebsocket() { - c.ws.fs -= n - } - - // Check that if there is still data to send and writeLoop is in wait, - // then we need to signal. - if c.out.pb > 0 { - c.flushSignal() - } - - // Check if we have a stalled gate and if so and we are recovering release - // any stalled producers. Only kind==CLIENT will stall. - if c.out.stc != nil && (n == attempted || c.out.pb < c.out.mp/4*3) { - close(c.out.stc) - c.out.stc = nil - } - // Check if the connection is recovering from being a slow consumer. - if !gotWriteTimeout && c.flags.isSet(isSlowConsumer) { - c.Noticef("Slow Consumer Recovered: Flush took %.3fs with %d chunks of %d total bytes.", time.Since(start).Seconds(), len(orig), attempted) - c.flags.clear(isSlowConsumer) - } - - return true -} - -// This is invoked from flushOutbound() for io/timeout error (slow consumer). -// Returns a boolean to indicate if the connection has been closed or not. -// Lock is held on entry. -func (c *client) handleWriteTimeout(written, attempted int64, numChunks int) bool { - if tlsConn, ok := c.nc.(*tls.Conn); ok { - if !tlsConn.ConnectionState().HandshakeComplete { - // Likely a TLSTimeout error instead... - c.markConnAsClosed(TLSHandshakeError) - // Would need to coordinate with tlstimeout() - // to avoid double logging, so skip logging - // here, and don't report a slow consumer error. - return true - } - } else if c.flags.isSet(expectConnect) && !c.flags.isSet(connectReceived) { - // Under some conditions, a connection may hit a slow consumer write deadline - // before the authorization timeout. If that is the case, then we handle - // as slow consumer though we do not increase the counter as that can be - // misleading. - c.markConnAsClosed(SlowConsumerWriteDeadline) - return true - } - alreadySC := c.flags.isSet(isSlowConsumer) - scState := "Detected" - if alreadySC { - scState = "State" - } - - // Aggregate slow consumers. - atomic.AddInt64(&c.srv.slowConsumers, 1) - switch c.kind { - case CLIENT: - c.srv.scStats.clients.Add(1) - case ROUTER: - // Only count each Slow Consumer event once. - if !alreadySC { - c.srv.scStats.routes.Add(1) - } - case GATEWAY: - c.srv.scStats.gateways.Add(1) - case LEAF: - c.srv.scStats.leafs.Add(1) - } - if c.acc != nil { - atomic.AddInt64(&c.acc.slowConsumers, 1) - } - c.Noticef("Slow Consumer %s: WriteDeadline of %v exceeded with %d chunks of %d total bytes.", - scState, c.out.wdl, numChunks, attempted) - - // We always close CLIENT connections, or when nothing was written at all... - if c.kind == CLIENT || written == 0 { - c.markConnAsClosed(SlowConsumerWriteDeadline) - return true - } else { - c.flags.setIfNotSet(isSlowConsumer) - } - return false -} - -// Marks this connection has closed with the given reason. -// Sets the connMarkedClosed flag and skipFlushOnClose depending on the reason. -// Depending on the kind of connection, the connection will be saved. -// If a writeLoop has been started, the final flush will be done there, otherwise -// flush and close of TCP connection is done here in place. -// Returns true if closed in place, flase otherwise. -// Lock is held on entry. -func (c *client) markConnAsClosed(reason ClosedState) { - // Possibly set skipFlushOnClose flag even if connection has already been - // mark as closed. The rationale is that a connection may be closed with - // a reason that justifies a flush (say after sending an -ERR), but then - // the flushOutbound() gets a write error. If that happens, connection - // being lost, there is no reason to attempt to flush again during the - // teardown when the writeLoop exits. - var skipFlush bool - switch reason { - case ReadError, WriteError, SlowConsumerPendingBytes, SlowConsumerWriteDeadline, TLSHandshakeError: - c.flags.set(skipFlushOnClose) - skipFlush = true - } - if c.flags.isSet(connMarkedClosed) { - return - } - c.flags.set(connMarkedClosed) - // For a websocket client, unless we are told not to flush, enqueue - // a websocket CloseMessage based on the reason. - if !skipFlush && c.isWebsocket() && !c.ws.closeSent { - c.wsEnqueueCloseMessage(reason) - } - // Be consistent with the creation: for routes, gateways and leaf, - // we use Noticef on create, so use that too for delete. - if c.srv != nil { - if c.kind == LEAF { - if c.acc != nil { - c.Noticef("%s connection closed: %s - Account: %s", c.kindString(), reason, c.acc.traceLabel()) - } else { - c.Noticef("%s connection closed: %s", c.kindString(), reason) - } - } else if c.kind == ROUTER || c.kind == GATEWAY { - c.Noticef("%s connection closed: %s", c.kindString(), reason) - } else { // Client, System, Jetstream, and Account connections. - c.Debugf("%s connection closed: %s", c.kindString(), reason) - } - } - - // Save off the connection if its a client or leafnode. - if c.kind == CLIENT || c.kind == LEAF { - if nc := c.nc; nc != nil && c.srv != nil { - // TODO: May want to send events to single go routine instead - // of creating a new go routine for each save. - // Pass the c.subs as a reference. It may be set to nil in - // closeConnection. - go c.srv.saveClosedClient(c, nc, c.subs, reason) - } - } - // If writeLoop exists, let it do the final flush, close and teardown. - if c.flags.isSet(writeLoopStarted) { - // Since we want the writeLoop to do the final flush and tcp close, - // we want the reconnect to be done there too. However, it should'nt - // happen before the connection has been removed from the server - // state (end of closeConnection()). This ref count allows us to - // guarantee that. - c.rref++ - c.flushSignal() - return - } - // Flush (if skipFlushOnClose is not set) and close in place. If flushing, - // use a small WriteDeadline. - c.flushAndClose(true) -} - -// flushSignal will use server to queue the flush IO operation to a pool of flushers. -// Lock must be held. -func (c *client) flushSignal() { - // Check that sg is not nil, which will happen if the connection is closed. - if c.out.sg != nil { - c.out.sg.Signal() - } -} - -// Traces a message. -// Will NOT check if tracing is enabled, does NOT need the client lock. -func (c *client) traceMsg(msg []byte) { - maxTrace := c.srv.getOpts().MaxTracedMsgLen - if maxTrace > 0 && (len(msg)-LEN_CR_LF) > maxTrace { - tm := fmt.Sprintf("%q", msg[:maxTrace]) - c.Tracef("<<- MSG_PAYLOAD: [\"%s...\"]", tm[1:maxTrace+1]) - } else { - c.Tracef("<<- MSG_PAYLOAD: [%q]", msg[:len(msg)-LEN_CR_LF]) - } -} - -// Traces an incoming operation. -// Will NOT check if tracing is enabled, does NOT need the client lock. -func (c *client) traceInOp(op string, arg []byte) { - c.traceOp("<<- %s", op, arg) -} - -// Traces an outgoing operation. -// Will NOT check if tracing is enabled, does NOT need the client lock. -func (c *client) traceOutOp(op string, arg []byte) { - c.traceOp("->> %s", op, arg) -} - -func (c *client) traceOp(format, op string, arg []byte) { - opa := []any{} - if op != _EMPTY_ { - opa = append(opa, op) - } - if arg != nil { - opa = append(opa, bytesToString(arg)) - } - c.Tracef(format, opa) -} - -// Process the information messages from Clients and other Routes. -func (c *client) processInfo(arg []byte) error { - info := Info{} - if err := json.Unmarshal(arg, &info); err != nil { - return err - } - switch c.kind { - case ROUTER: - c.processRouteInfo(&info) - case GATEWAY: - c.processGatewayInfo(&info) - case LEAF: - c.processLeafnodeInfo(&info) - } - return nil -} - -func (c *client) processErr(errStr string) { - close := true - switch c.kind { - case CLIENT: - c.Errorf("Client Error %s", errStr) - case ROUTER: - c.Errorf("Route Error %s", errStr) - case GATEWAY: - c.Errorf("Gateway Error %s", errStr) - case LEAF: - c.Errorf("Leafnode Error %s", errStr) - c.leafProcessErr(errStr) - close = false - case JETSTREAM: - c.Errorf("JetStream Error %s", errStr) - } - if close { - c.closeConnection(ParseError) - } -} - -// Password pattern matcher. -var passPat = regexp.MustCompile(`"?\s*pass\S*?"?\s*[:=]\s*"?(([^",\r\n}])*)`) - -// removePassFromTrace removes any notion of passwords from trace -// messages for logging. -func removePassFromTrace(arg []byte) []byte { - if !bytes.Contains(arg, []byte(`pass`)) { - return arg - } - // Take a copy of the connect proto just for the trace message. - var _arg [4096]byte - buf := append(_arg[:0], arg...) - - m := passPat.FindAllSubmatchIndex(buf, -1) - if len(m) == 0 { - return arg - } - - redactedPass := []byte("[REDACTED]") - for _, i := range m { - if len(i) < 4 { - continue - } - start := i[2] - end := i[3] - - // Replace password substring. - buf = append(buf[:start], append(redactedPass, buf[end:]...)...) - break - } - return buf -} - -// Returns the RTT by computing the elapsed time since now and `start`. -// On Windows VM where I (IK) run tests, time.Since() will return 0 -// (I suspect some time granularity issues). So return at minimum 1ns. -func computeRTT(start time.Time) time.Duration { - rtt := time.Since(start) - if rtt <= 0 { - rtt = time.Nanosecond - } - return rtt -} - -// processConnect will process a client connect op. -func (c *client) processConnect(arg []byte) error { - supportsHeaders := c.srv.supportsHeaders() - c.mu.Lock() - // If we can't stop the timer because the callback is in progress... - if !c.clearAuthTimer() { - // wait for it to finish and handle sending the failure back to - // the client. - for !c.isClosed() { - c.mu.Unlock() - time.Sleep(25 * time.Millisecond) - c.mu.Lock() - } - c.mu.Unlock() - return nil - } - c.last = time.Now().UTC() - // Estimate RTT to start. - if c.kind == CLIENT { - c.rtt = computeRTT(c.start) - if c.srv != nil { - c.clearPingTimer() - c.setFirstPingTimer() - } - } - kind := c.kind - srv := c.srv - - // Moved unmarshalling of clients' Options under the lock. - // The client has already been added to the server map, so it is possible - // that other routines lookup the client, and access its options under - // the client's lock, so unmarshalling the options outside of the lock - // would cause data RACEs. - if err := json.Unmarshal(arg, &c.opts); err != nil { - c.mu.Unlock() - return err - } - // Indicate that the CONNECT protocol has been received, and that the - // server now knows which protocol this client supports. - c.flags.set(connectReceived) - // Capture these under lock - c.echo = c.opts.Echo - proto := c.opts.Protocol - verbose := c.opts.Verbose - lang := c.opts.Lang - account := c.opts.Account - accountNew := c.opts.AccountNew - - if c.kind == CLIENT { - var ncs string - if c.opts.Version != _EMPTY_ { - ncs = fmt.Sprintf("v%s", c.opts.Version) - } - if c.opts.Lang != _EMPTY_ { - if c.opts.Version == _EMPTY_ { - ncs = c.opts.Lang - } else { - ncs = fmt.Sprintf("%s:%s", ncs, c.opts.Lang) - } - } - if c.opts.Name != _EMPTY_ { - if c.opts.Version == _EMPTY_ && c.opts.Lang == _EMPTY_ { - ncs = c.opts.Name - } else { - ncs = fmt.Sprintf("%s:%s", ncs, c.opts.Name) - } - } - if ncs != _EMPTY_ { - c.ncs.CompareAndSwap(nil, fmt.Sprintf("%s - %q", c, ncs)) - } - } - - // if websocket client, maybe some options through cookies - if ws := c.ws; ws != nil { - // if JWT not in the CONNECT, use the cookie JWT (possibly empty). - if c.opts.JWT == _EMPTY_ { - c.opts.JWT = ws.cookieJwt - } - // if user not in the CONNECT, use the cookie user (possibly empty) - if c.opts.Username == _EMPTY_ { - c.opts.Username = ws.cookieUsername - } - // if pass not in the CONNECT, use the cookie password (possibly empty). - if c.opts.Password == _EMPTY_ { - c.opts.Password = ws.cookiePassword - } - // if token not in the CONNECT, use the cookie token (possibly empty). - if c.opts.Token == _EMPTY_ { - c.opts.Token = ws.cookieToken - } - } - - // when not in operator mode, discard the jwt - if srv != nil && srv.trustedKeys == nil { - c.opts.JWT = _EMPTY_ - } - ujwt := c.opts.JWT - - // For headers both client and server need to support. - c.headers = supportsHeaders && c.opts.Headers - c.mu.Unlock() - - if srv != nil { - // Applicable to clients only: - // As soon as c.opts is unmarshalled and if the proto is at - // least ClientProtoInfo, we need to increment the following counter. - // This is decremented when client is removed from the server's - // clients map. - if kind == CLIENT && proto >= ClientProtoInfo { - srv.mu.Lock() - srv.cproto++ - srv.mu.Unlock() - } - - // Check for Auth - if ok := srv.checkAuthentication(c); !ok { - // We may fail here because we reached max limits on an account. - if ujwt != _EMPTY_ { - c.mu.Lock() - acc := c.acc - c.mu.Unlock() - srv.mu.Lock() - tooManyAccCons := acc != nil && acc != srv.gacc - srv.mu.Unlock() - if tooManyAccCons { - return ErrTooManyAccountConnections - } - } - c.authViolation() - return ErrAuthentication - } - - // Check for Account designation, we used to have this as an optional feature for dynamic - // sandbox environments. Now its considered an error. - if accountNew || account != _EMPTY_ { - c.authViolation() - return ErrAuthentication - } - - // If no account designation. - // Do this only for CLIENT and LEAF connections. - if c.acc == nil && (c.kind == CLIENT || c.kind == LEAF) { - // By default register with the global account. - c.registerWithAccount(srv.globalAccount()) - } - } - - switch kind { - case CLIENT: - // Check client protocol request if it exists. - if proto < ClientProtoZero || proto > ClientProtoInfo { - c.sendErr(ErrBadClientProtocol.Error()) - c.closeConnection(BadClientProtocolVersion) - return ErrBadClientProtocol - } - // Check to see that if no_responders is requested - // they have header support on as well. - c.mu.Lock() - misMatch := c.opts.NoResponders && !c.headers - c.mu.Unlock() - if misMatch { - c.sendErr(ErrNoRespondersRequiresHeaders.Error()) - c.closeConnection(NoRespondersRequiresHeaders) - return ErrNoRespondersRequiresHeaders - } - if verbose { - c.sendOK() - } - case ROUTER: - // Delegate the rest of processing to the route - return c.processRouteConnect(srv, arg, lang) - case GATEWAY: - // Delegate the rest of processing to the gateway - return c.processGatewayConnect(arg) - case LEAF: - // Delegate the rest of processing to the leaf node - return c.processLeafNodeConnect(srv, arg, lang) - } - return nil -} - -func (c *client) sendErrAndErr(err string) { - c.sendErr(err) - c.Errorf(err) -} - -func (c *client) sendErrAndDebug(err string) { - c.sendErr(err) - c.Debugf(err) -} - -func (c *client) authTimeout() { - c.sendErrAndDebug("Authentication Timeout") - c.closeConnection(AuthenticationTimeout) -} - -func (c *client) authExpired() { - c.sendErrAndDebug("User Authentication Expired") - c.closeConnection(AuthenticationExpired) -} - -func (c *client) accountAuthExpired() { - c.sendErrAndDebug("Account Authentication Expired") - c.closeConnection(AuthenticationExpired) -} - -func (c *client) authViolation() { - var s *Server - var hasTrustedNkeys, hasNkeys, hasUsers bool - if s = c.srv; s != nil { - s.mu.RLock() - hasTrustedNkeys = s.trustedKeys != nil - hasNkeys = s.nkeys != nil - hasUsers = s.users != nil - s.mu.RUnlock() - defer s.sendAuthErrorEvent(c) - } - - if hasTrustedNkeys { - c.Errorf("%v", ErrAuthentication) - } else if hasNkeys { - c.Errorf("%s - Nkey %q", - ErrAuthentication.Error(), - c.opts.Nkey) - } else if hasUsers { - c.Errorf("%s - User %q", - ErrAuthentication.Error(), - c.opts.Username) - } else { - if c.srv != nil { - c.Errorf(ErrAuthentication.Error()) - } - } - if c.isMqtt() { - c.mqttEnqueueConnAck(mqttConnAckRCNotAuthorized, false) - } else { - c.sendErr("Authorization Violation") - } - c.closeConnection(AuthenticationViolation) -} - -func (c *client) maxAccountConnExceeded() { - c.sendErrAndErr(ErrTooManyAccountConnections.Error()) - c.closeConnection(MaxAccountConnectionsExceeded) -} - -func (c *client) maxConnExceeded() { - c.sendErrAndErr(ErrTooManyConnections.Error()) - c.closeConnection(MaxConnectionsExceeded) -} - -func (c *client) maxSubsExceeded() { - if c.acc.shouldLogMaxSubErr() { - c.Errorf(ErrTooManySubs.Error()) - } - c.sendErr(ErrTooManySubs.Error()) -} - -func (c *client) maxPayloadViolation(sz int, max int32) { - c.Errorf("%s: %d vs %d", ErrMaxPayload.Error(), sz, max) - c.sendErr("Maximum Payload Violation") - c.closeConnection(MaxPayloadExceeded) -} - -// queueOutbound queues data for a clientconnection. -// Lock should be held. -func (c *client) queueOutbound(data []byte) { - // Do not keep going if closed - if c.isClosed() { - return - } - - // Add to pending bytes total. - c.out.pb += int64(len(data)) - - // Take a copy of the slice ref so that we can chop bits off the beginning - // without affecting the original "data" slice. - toBuffer := data - - // All of the queued []byte have a fixed capacity, so if there's a []byte - // at the tail of the buffer list that isn't full yet, we should top that - // up first. This helps to ensure we aren't pulling more []bytes from the - // pool than we need to. - if len(c.out.nb) > 0 { - last := &c.out.nb[len(c.out.nb)-1] - if free := cap(*last) - len(*last); free > 0 { - if l := len(toBuffer); l < free { - free = l - } - *last = append(*last, toBuffer[:free]...) - toBuffer = toBuffer[free:] - } - } - - // Now we can push the rest of the data into new []bytes from the pool - // in fixed size chunks. This ensures we don't go over the capacity of any - // of the buffers and end up reallocating. - for len(toBuffer) > 0 { - new := nbPoolGet(len(toBuffer)) - n := copy(new[:cap(new)], toBuffer) - c.out.nb = append(c.out.nb, new[:n]) - toBuffer = toBuffer[n:] - } - - // Check for slow consumer via pending bytes limit. - // ok to return here, client is going away. - if c.kind == CLIENT && c.out.pb > c.out.mp { - // Perf wise, it looks like it is faster to optimistically add than - // checking current pb+len(data) and then add to pb. - c.out.pb -= int64(len(data)) - - // Increment the total and client's slow consumer counters. - atomic.AddInt64(&c.srv.slowConsumers, 1) - c.srv.scStats.clients.Add(1) - if c.acc != nil { - atomic.AddInt64(&c.acc.slowConsumers, 1) - } - c.Noticef("Slow Consumer Detected: MaxPending of %d Exceeded", c.out.mp) - c.markConnAsClosed(SlowConsumerPendingBytes) - return - } - - // Check here if we should create a stall channel if we are falling behind. - // We do this here since if we wait for consumer's writeLoop it could be - // too late with large number of fan in producers. - // If the outbound connection is > 75% of maximum pending allowed, create a stall gate. - if c.out.pb > c.out.mp/4*3 && c.out.stc == nil { - c.out.stc = make(chan struct{}) - } -} - -// Assume the lock is held upon entry. -func (c *client) enqueueProtoAndFlush(proto []byte, doFlush bool) { - if c.isClosed() { - return - } - c.queueOutbound(proto) - if !(doFlush && c.flushOutbound()) { - c.flushSignal() - } -} - -// Queues and then flushes the connection. This should only be called when -// the writeLoop cannot be started yet. Use enqueueProto() otherwise. -// Lock is held on entry. -func (c *client) sendProtoNow(proto []byte) { - c.enqueueProtoAndFlush(proto, true) -} - -// Enqueues the given protocol and signal the writeLoop if necessary. -// Lock is held on entry. -func (c *client) enqueueProto(proto []byte) { - c.enqueueProtoAndFlush(proto, false) -} - -// Assume the lock is held upon entry. -func (c *client) sendPong() { - if c.trace { - c.traceOutOp("PONG", nil) - } - c.enqueueProto([]byte(pongProto)) -} - -// Used to kick off a RTT measurement for latency tracking. -func (c *client) sendRTTPing() bool { - c.mu.Lock() - sent := c.sendRTTPingLocked() - c.mu.Unlock() - return sent -} - -// Used to kick off a RTT measurement for latency tracking. -// This is normally called only when the caller has checked that -// the c.rtt is 0 and wants to force an update by sending a PING. -// Client lock held on entry. -func (c *client) sendRTTPingLocked() bool { - if c.isMqtt() { - return false - } - // Most client libs send a CONNECT+PING and wait for a PONG from the - // server. So if firstPongSent flag is set, it is ok for server to - // send the PING. But in case we have client libs that don't do that, - // allow the send of the PING if more than 2 secs have elapsed since - // the client TCP connection was accepted. - if !c.isClosed() && - (c.flags.isSet(firstPongSent) || time.Since(c.start) > maxNoRTTPingBeforeFirstPong) { - c.sendPing() - return true - } - return false -} - -// Assume the lock is held upon entry. -func (c *client) sendPing() { - c.rttStart = time.Now().UTC() - c.ping.out++ - if c.trace { - c.traceOutOp("PING", nil) - } - c.enqueueProto([]byte(pingProto)) -} - -// Generates the INFO to be sent to the client with the client ID included. -// info arg will be copied since passed by value. -// Assume lock is held. -func (c *client) generateClientInfoJSON(info Info) []byte { - info.CID = c.cid - info.ClientIP = c.host - info.MaxPayload = c.mpay - if c.isWebsocket() { - info.ClientConnectURLs = info.WSConnectURLs - // Otherwise lame duck info can panic - if c.srv != nil { - ws := &c.srv.websocket - info.TLSAvailable, info.TLSRequired = ws.tls, ws.tls - info.Host, info.Port = ws.host, ws.port - } - } - info.WSConnectURLs = nil - return generateInfoJSON(&info) -} - -func (c *client) sendErr(err string) { - c.mu.Lock() - if c.trace { - c.traceOutOp("-ERR", []byte(err)) - } - if !c.isMqtt() { - c.enqueueProto([]byte(fmt.Sprintf(errProto, err))) - } - c.mu.Unlock() -} - -func (c *client) sendOK() { - c.mu.Lock() - if c.trace { - c.traceOutOp("OK", nil) - } - c.enqueueProto([]byte(okProto)) - c.mu.Unlock() -} - -func (c *client) processPing() { - c.mu.Lock() - - if c.isClosed() { - c.mu.Unlock() - return - } - - c.sendPong() - - // Record this to suppress us sending one if this - // is within a given time interval for activity. - c.lastIn = time.Now() - - // If not a CLIENT, we are done. Also the CONNECT should - // have been received, but make sure it is so before proceeding - if c.kind != CLIENT || !c.flags.isSet(connectReceived) { - c.mu.Unlock() - return - } - - // If we are here, the CONNECT has been received so we know - // if this client supports async INFO or not. - var ( - checkInfoChange bool - srv = c.srv - ) - // For older clients, just flip the firstPongSent flag if not already - // set and we are done. - if c.opts.Protocol < ClientProtoInfo || srv == nil { - c.flags.setIfNotSet(firstPongSent) - } else { - // This is a client that supports async INFO protocols. - // If this is the first PING (so firstPongSent is not set yet), - // we will need to check if there was a change in cluster topology - // or we have a different max payload. We will send this first before - // pong since most clients do flush after connect call. - checkInfoChange = !c.flags.isSet(firstPongSent) - } - c.mu.Unlock() - - if checkInfoChange { - opts := srv.getOpts() - srv.mu.Lock() - c.mu.Lock() - // Now that we are under both locks, we can flip the flag. - // This prevents sendAsyncInfoToClients() and code here to - // send a double INFO protocol. - c.flags.set(firstPongSent) - // If there was a cluster update since this client was created, - // send an updated INFO protocol now. - if srv.lastCURLsUpdate >= c.start.UnixNano() || c.mpay != int32(opts.MaxPayload) { - c.enqueueProto(c.generateClientInfoJSON(srv.copyInfo())) - } - c.mu.Unlock() - srv.mu.Unlock() - } -} - -func (c *client) processPong() { - c.mu.Lock() - c.ping.out = 0 - c.rtt = computeRTT(c.rttStart) - srv := c.srv - reorderGWs := c.kind == GATEWAY && c.gw.outbound - // If compression is currently active for a route/leaf connection, if the - // compression configuration is s2_auto, check if we should change - // the compression level. - if c.kind == ROUTER && needsCompression(c.route.compression) { - c.updateS2AutoCompressionLevel(&srv.getOpts().Cluster.Compression, &c.route.compression) - } else if c.kind == LEAF && needsCompression(c.leaf.compression) { - var co *CompressionOpts - if r := c.leaf.remote; r != nil { - co = &r.Compression - } else { - co = &srv.getOpts().LeafNode.Compression - } - c.updateS2AutoCompressionLevel(co, &c.leaf.compression) - } - c.mu.Unlock() - if reorderGWs { - srv.gateway.orderOutboundConnections() - } -} - -// Select the s2 compression level based on the client's current RTT and the configured -// RTT thresholds slice. If current level is different than selected one, save the -// new compression level string and create a new s2 writer. -// Lock held on entry. -func (c *client) updateS2AutoCompressionLevel(co *CompressionOpts, compression *string) { - if co.Mode != CompressionS2Auto { - return - } - if cm := selectS2AutoModeBasedOnRTT(c.rtt, co.RTTThresholds); cm != *compression { - *compression = cm - c.out.cw = s2.NewWriter(nil, s2WriterOptions(cm)...) - } -} - -// Will return the parts from the raw wire msg. -func (c *client) msgParts(data []byte) (hdr []byte, msg []byte) { - if c != nil && c.pa.hdr > 0 { - return data[:c.pa.hdr], data[c.pa.hdr:] - } - return nil, data -} - -// Header pubs take form HPUB [reply] \r\n -func (c *client) processHeaderPub(arg, remaining []byte) error { - if !c.headers { - return ErrMsgHeadersNotSupported - } - - // Unroll splitArgs to avoid runtime/heap issues - a := [MAX_HPUB_ARGS][]byte{} - args := a[:0] - start := -1 - for i, b := range arg { - switch b { - case ' ', '\t': - if start >= 0 { - args = append(args, arg[start:i]) - start = -1 - } - default: - if start < 0 { - start = i - } - } - } - if start >= 0 { - args = append(args, arg[start:]) - } - - c.pa.arg = arg - switch len(args) { - case 3: - c.pa.subject = args[0] - c.pa.reply = nil - c.pa.hdr = parseSize(args[1]) - c.pa.size = parseSize(args[2]) - c.pa.hdb = args[1] - c.pa.szb = args[2] - case 4: - c.pa.subject = args[0] - c.pa.reply = args[1] - c.pa.hdr = parseSize(args[2]) - c.pa.size = parseSize(args[3]) - c.pa.hdb = args[2] - c.pa.szb = args[3] - default: - return fmt.Errorf("processHeaderPub Parse Error: %q", arg) - } - if c.pa.hdr < 0 { - return fmt.Errorf("processHeaderPub Bad or Missing Header Size: %q", arg) - } - // If number overruns an int64, parseSize() will have returned a negative value - if c.pa.size < 0 { - return fmt.Errorf("processHeaderPub Bad or Missing Total Size: %q", arg) - } - if c.pa.hdr > c.pa.size { - return fmt.Errorf("processHeaderPub Header Size larger then TotalSize: %q", arg) - } - maxPayload := atomic.LoadInt32(&c.mpay) - // Use int64() to avoid int32 overrun... - if maxPayload != jwt.NoLimit && int64(c.pa.size) > int64(maxPayload) { - // If we are given the remaining read buffer (since we do blind reads - // we may have the beginning of the message header/payload), we will - // look for the tracing header and if found, we will generate a - // trace event with the max payload ingress error. - // Do this only for CLIENT connections. - if c.kind == CLIENT && len(remaining) > 0 { - if td := getHeader(MsgTraceDest, remaining); len(td) > 0 { - c.initAndSendIngressErrEvent(remaining, string(td), ErrMaxPayload) - } - } - c.maxPayloadViolation(c.pa.size, maxPayload) - return ErrMaxPayload - } - if c.opts.Pedantic && !IsValidLiteralSubject(bytesToString(c.pa.subject)) { - c.sendErr("Invalid Publish Subject") - } - return nil -} - -func (c *client) processPub(arg []byte) error { - // Unroll splitArgs to avoid runtime/heap issues - a := [MAX_PUB_ARGS][]byte{} - args := a[:0] - start := -1 - for i, b := range arg { - switch b { - case ' ', '\t': - if start >= 0 { - args = append(args, arg[start:i]) - start = -1 - } - default: - if start < 0 { - start = i - } - } - } - if start >= 0 { - args = append(args, arg[start:]) - } - - c.pa.arg = arg - switch len(args) { - case 2: - c.pa.subject = args[0] - c.pa.reply = nil - c.pa.size = parseSize(args[1]) - c.pa.szb = args[1] - case 3: - c.pa.subject = args[0] - c.pa.reply = args[1] - c.pa.size = parseSize(args[2]) - c.pa.szb = args[2] - default: - return fmt.Errorf("processPub Parse Error: %q", arg) - } - // If number overruns an int64, parseSize() will have returned a negative value - if c.pa.size < 0 { - return fmt.Errorf("processPub Bad or Missing Size: %q", arg) - } - maxPayload := atomic.LoadInt32(&c.mpay) - // Use int64() to avoid int32 overrun... - if maxPayload != jwt.NoLimit && int64(c.pa.size) > int64(maxPayload) { - c.maxPayloadViolation(c.pa.size, maxPayload) - return ErrMaxPayload - } - if c.opts.Pedantic && !IsValidLiteralSubject(bytesToString(c.pa.subject)) { - c.sendErr("Invalid Publish Subject") - } - return nil -} - -func splitArg(arg []byte) [][]byte { - a := [MAX_MSG_ARGS][]byte{} - args := a[:0] - start := -1 - for i, b := range arg { - switch b { - case ' ', '\t', '\r', '\n': - if start >= 0 { - args = append(args, arg[start:i]) - start = -1 - } - default: - if start < 0 { - start = i - } - } - } - if start >= 0 { - args = append(args, arg[start:]) - } - return args -} - -func (c *client) parseSub(argo []byte, noForward bool) error { - // Copy so we do not reference a potentially large buffer - // FIXME(dlc) - make more efficient. - arg := make([]byte, len(argo)) - copy(arg, argo) - args := splitArg(arg) - var ( - subject []byte - queue []byte - sid []byte - ) - switch len(args) { - case 2: - subject = args[0] - queue = nil - sid = args[1] - case 3: - subject = args[0] - queue = args[1] - sid = args[2] - default: - return fmt.Errorf("processSub Parse Error: %q", arg) - } - // If there was an error, it has been sent to the client. We don't return an - // error here to not close the connection as a parsing error. - c.processSub(subject, queue, sid, nil, noForward) - return nil -} - -func (c *client) processSub(subject, queue, bsid []byte, cb msgHandler, noForward bool) (*subscription, error) { - return c.processSubEx(subject, queue, bsid, cb, noForward, false, false) -} - -func (c *client) processSubEx(subject, queue, bsid []byte, cb msgHandler, noForward, si, rsi bool) (*subscription, error) { - // Create the subscription - sub := &subscription{client: c, subject: subject, queue: queue, sid: bsid, icb: cb, si: si, rsi: rsi} - - c.mu.Lock() - - // Indicate activity. - c.in.subs++ - - // Grab connection type, account and server info. - kind := c.kind - acc := c.acc - srv := c.srv - - sid := bytesToString(sub.sid) - - // This check does not apply to SYSTEM or JETSTREAM or ACCOUNT clients (because they don't have a `nc`...) - // When a connection is closed though, we set c.subs to nil. So check for the map to not be nil. - if (c.isClosed() && (kind != SYSTEM && kind != JETSTREAM && kind != ACCOUNT)) || (c.subs == nil) { - c.mu.Unlock() - return nil, ErrConnectionClosed - } - - // Check permissions if applicable. - if kind == CLIENT { - // First do a pass whether queue subscription is valid. This does not necessarily - // mean that it will not be able to plain subscribe. - // - // allow = ["foo"] -> can subscribe or queue subscribe to foo using any queue - // allow = ["foo v1"] -> can only queue subscribe to 'foo v1', no plain subs allowed. - // allow = ["foo", "foo v1"] -> can subscribe to 'foo' but can only queue subscribe to 'foo v1' - // - if sub.queue != nil { - if !c.canSubscribe(string(sub.subject), string(sub.queue)) || string(sub.queue) == sysGroup { - c.mu.Unlock() - c.subPermissionViolation(sub) - return nil, ErrSubscribePermissionViolation - } - } else if !c.canSubscribe(string(sub.subject)) { - c.mu.Unlock() - c.subPermissionViolation(sub) - return nil, ErrSubscribePermissionViolation - } - - if opts := srv.getOpts(); opts != nil && opts.MaxSubTokens > 0 { - if len(bytes.Split(sub.subject, []byte(tsep))) > int(opts.MaxSubTokens) { - c.mu.Unlock() - c.maxTokensViolation(sub) - return nil, ErrTooManySubTokens - } - } - } - - // Check if we have a maximum on the number of subscriptions. - if c.subsAtLimit() { - c.mu.Unlock() - c.maxSubsExceeded() - return nil, ErrTooManySubs - } - - var updateGWs bool - var err error - - // Subscribe here. - es := c.subs[sid] - if es == nil { - c.subs[sid] = sub - if acc != nil && acc.sl != nil { - err = acc.sl.Insert(sub) - if err != nil { - delete(c.subs, sid) - } else { - updateGWs = c.srv.gateway.enabled - } - } - } - // Unlocked from here onward - c.mu.Unlock() - - if err != nil { - c.sendErr("Invalid Subject") - return nil, ErrMalformedSubject - } else if c.opts.Verbose && kind != SYSTEM { - c.sendOK() - } - - // If it was already registered, return it. - if es != nil { - return es, nil - } - - // No account just return. - if acc == nil { - return sub, nil - } - - if err := c.addShadowSubscriptions(acc, sub, true); err != nil { - c.Errorf(err.Error()) - } - - if noForward { - return sub, nil - } - - // If we are routing and this is a local sub, add to the route map for the associated account. - if kind == CLIENT || kind == SYSTEM || kind == JETSTREAM || kind == ACCOUNT { - srv.updateRouteSubscriptionMap(acc, sub, 1) - if updateGWs { - srv.gatewayUpdateSubInterest(acc.Name, sub, 1) - } - } - // Now check on leafnode updates. - acc.updateLeafNodes(sub, 1) - return sub, nil -} - -// Used to pass stream import matches to addShadowSub -type ime struct { - im *streamImport - overlapSubj string - dyn bool -} - -// If the client's account has stream imports and there are matches for this -// subscription's subject, then add shadow subscriptions in the other accounts -// that export this subject. -// -// enact=false allows MQTT clients to get the list of shadow subscriptions -// without enacting them, in order to first obtain matching "retained" messages. -func (c *client) addShadowSubscriptions(acc *Account, sub *subscription, enact bool) error { - if acc == nil { - return ErrMissingAccount - } - - var ( - _ims [16]ime - ims = _ims[:0] - imTsa [32]string - tokens []string - tsa [32]string - hasWC bool - tokensModified bool - ) - - acc.mu.RLock() - // If this is from a service import, ignore. - if sub.si { - acc.mu.RUnlock() - return nil - } - subj := bytesToString(sub.subject) - if len(acc.imports.streams) > 0 { - tokens = tokenizeSubjectIntoSlice(tsa[:0], subj) - for _, tk := range tokens { - if tk == pwcs { - hasWC = true - break - } - } - if !hasWC && tokens[len(tokens)-1] == fwcs { - hasWC = true - } - } - // Loop over the import subjects. We have 4 scenarios. If we have an - // exact match or a superset match we should use the from field from - // the import. If we are a subset or overlap, we have to dynamically calculate - // the subject. On overlap, ime requires the overlap subject. - for _, im := range acc.imports.streams { - if im.invalid { - continue - } - if subj == im.to { - ims = append(ims, ime{im, _EMPTY_, false}) - continue - } - if tokensModified { - // re-tokenize subj to overwrite modifications from a previous iteration - tokens = tokenizeSubjectIntoSlice(tsa[:0], subj) - tokensModified = false - } - imTokens := tokenizeSubjectIntoSlice(imTsa[:0], im.to) - - if isSubsetMatchTokenized(tokens, imTokens) { - ims = append(ims, ime{im, _EMPTY_, true}) - } else if hasWC { - if isSubsetMatchTokenized(imTokens, tokens) { - ims = append(ims, ime{im, _EMPTY_, false}) - } else { - imTokensLen := len(imTokens) - for i, t := range tokens { - if i >= imTokensLen { - break - } - if t == pwcs && imTokens[i] != fwcs { - tokens[i] = imTokens[i] - tokensModified = true - } - } - tokensLen := len(tokens) - lastIdx := tokensLen - 1 - if tokens[lastIdx] == fwcs { - if imTokensLen >= tokensLen { - // rewrite ">" in tokens to be more specific - tokens[lastIdx] = imTokens[lastIdx] - tokensModified = true - if imTokensLen > tokensLen { - // copy even more specific parts from import - tokens = append(tokens, imTokens[tokensLen:]...) - } - } - } - if isSubsetMatchTokenized(tokens, imTokens) { - // As isSubsetMatchTokenized was already called with tokens and imTokens, - // we wouldn't be here if it where not for tokens being modified. - // Hence, Join to re compute the subject string - ims = append(ims, ime{im, strings.Join(tokens, tsep), true}) - } - } - } - } - acc.mu.RUnlock() - - var shadow []*subscription - - if len(ims) > 0 { - shadow = make([]*subscription, 0, len(ims)) - } - - // Now walk through collected stream imports that matched. - for i := 0; i < len(ims); i++ { - ime := &ims[i] - // We will create a shadow subscription. - nsub, err := c.addShadowSub(sub, ime, enact) - if err != nil { - return err - } - shadow = append(shadow, nsub) - } - - if shadow != nil { - c.mu.Lock() - sub.shadow = shadow - c.mu.Unlock() - } - - return nil -} - -// Add in the shadow subscription. -func (c *client) addShadowSub(sub *subscription, ime *ime, enact bool) (*subscription, error) { - c.mu.Lock() - nsub := *sub // copy - c.mu.Unlock() - - im := ime.im - nsub.im = im - - if !im.usePub && ime.dyn && im.tr != nil { - if im.rtr == nil { - im.rtr = im.tr.reverse() - } - s := bytesToString(nsub.subject) - if ime.overlapSubj != _EMPTY_ { - s = ime.overlapSubj - } - subj := im.rtr.TransformSubject(s) - - nsub.subject = []byte(subj) - } else if !im.usePub || (im.usePub && ime.overlapSubj != _EMPTY_) || !ime.dyn { - if ime.overlapSubj != _EMPTY_ { - nsub.subject = []byte(ime.overlapSubj) - } else { - nsub.subject = []byte(im.from) - } - } - // Else use original subject - - if !enact { - return &nsub, nil - } - - c.Debugf("Creating import subscription on %q from account %q", nsub.subject, im.acc.Name) - - if err := im.acc.sl.Insert(&nsub); err != nil { - errs := fmt.Sprintf("Could not add shadow import subscription for account %q", im.acc.Name) - c.Debugf(errs) - return nil, errors.New(errs) - } - - // Update our route map here. But only if we are not a leaf node or a hub leafnode. - if c.kind != LEAF || c.isHubLeafNode() { - c.srv.updateRemoteSubscription(im.acc, &nsub, 1) - } - - return &nsub, nil -} - -// canSubscribe determines if the client is authorized to subscribe to the -// given subject. Assumes caller is holding lock. -func (c *client) canSubscribe(subject string, optQueue ...string) bool { - if c.perms == nil { - return true - } - - allowed := true - - // Optional queue group. - var queue string - if len(optQueue) > 0 { - queue = optQueue[0] - } - - // Check allow list. If no allow list that means all are allowed. Deny can overrule. - if c.perms.sub.allow != nil { - r := c.perms.sub.allow.Match(subject) - allowed = len(r.psubs) > 0 - if queue != _EMPTY_ && len(r.qsubs) > 0 { - // If the queue appears in the allow list, then DO allow. - allowed = queueMatches(queue, r.qsubs) - } - // Leafnodes operate slightly differently in that they allow broader scoped subjects. - // They will prune based on publish perms before sending to a leafnode client. - if !allowed && c.kind == LEAF && subjectHasWildcard(subject) { - r := c.perms.sub.allow.ReverseMatch(subject) - allowed = len(r.psubs) != 0 - } - } - // If we have a deny list and we think we are allowed, check that as well. - if allowed && c.perms.sub.deny != nil { - r := c.perms.sub.deny.Match(subject) - allowed = len(r.psubs) == 0 - - if queue != _EMPTY_ && len(r.qsubs) > 0 { - // If the queue appears in the deny list, then DO NOT allow. - allowed = !queueMatches(queue, r.qsubs) - } - - // We use the actual subscription to signal us to spin up the deny mperms - // and cache. We check if the subject is a wildcard that contains any of - // the deny clauses. - // FIXME(dlc) - We could be smarter and track when these go away and remove. - if allowed && c.mperms == nil && subjectHasWildcard(subject) { - // Whip through the deny array and check if this wildcard subject is within scope. - for _, sub := range c.darray { - if subjectIsSubsetMatch(sub, subject) { - c.loadMsgDenyFilter() - break - } - } - } - } - return allowed -} - -func queueMatches(queue string, qsubs [][]*subscription) bool { - if len(qsubs) == 0 { - return true - } - for _, qsub := range qsubs { - qs := qsub[0] - qname := bytesToString(qs.queue) - - // NOTE: '*' and '>' tokens can also be valid - // queue names so we first check against the - // literal name. e.g. v1.* == v1.* - if queue == qname || (subjectHasWildcard(qname) && subjectIsSubsetMatch(queue, qname)) { - return true - } - } - return false -} - -// Low level unsubscribe for a given client. -func (c *client) unsubscribe(acc *Account, sub *subscription, force, remove bool) { - if s := c.srv; s != nil && s.isShuttingDown() { - return - } - - c.mu.Lock() - if !force && sub.max > 0 && sub.nm < sub.max { - c.Debugf("Deferring actual UNSUB(%s): %d max, %d received", sub.subject, sub.max, sub.nm) - c.mu.Unlock() - return - } - - if c.trace { - c.traceOp("<-> %s", "DELSUB", sub.sid) - } - - // Remove accounting if requested. This will be false when we close a connection - // with open subscriptions. - if remove { - delete(c.subs, bytesToString(sub.sid)) - if acc != nil { - acc.sl.Remove(sub) - } - } - - // Check to see if we have shadow subscriptions. - var updateRoute bool - var updateGWs bool - shadowSubs := sub.shadow - sub.shadow = nil - if len(shadowSubs) > 0 { - updateRoute = (c.kind == CLIENT || c.kind == SYSTEM || c.kind == LEAF) && c.srv != nil - if updateRoute { - updateGWs = c.srv.gateway.enabled - } - } - sub.close() - c.mu.Unlock() - - // Process shadow subs if we have them. - for _, nsub := range shadowSubs { - if err := nsub.im.acc.sl.Remove(nsub); err != nil { - c.Debugf("Could not remove shadow import subscription for account %q", nsub.im.acc.Name) - } else { - if updateRoute { - c.srv.updateRouteSubscriptionMap(nsub.im.acc, nsub, -1) - } - if updateGWs { - c.srv.gatewayUpdateSubInterest(nsub.im.acc.Name, nsub, -1) - } - } - // Now check on leafnode updates. - nsub.im.acc.updateLeafNodes(nsub, -1) - } - - // Now check to see if this was part of a respMap entry for service imports. - // We can skip subscriptions on reserved replies. - if acc != nil && !isReservedReply(sub.subject) { - acc.checkForReverseEntry(string(sub.subject), nil, true) - } -} - -func (c *client) processUnsub(arg []byte) error { - args := splitArg(arg) - var sid []byte - max := int64(-1) - - switch len(args) { - case 1: - sid = args[0] - case 2: - sid = args[0] - max = int64(parseSize(args[1])) - default: - return fmt.Errorf("processUnsub Parse Error: %q", arg) - } - - var sub *subscription - var ok, unsub bool - - c.mu.Lock() - - // Indicate activity. - c.in.subs++ - - // Grab connection type. - kind := c.kind - srv := c.srv - var acc *Account - - updateGWs := false - if sub, ok = c.subs[string(sid)]; ok { - acc = c.acc - if max > 0 && max > sub.nm { - sub.max = max - } else { - // Clear it here to override - sub.max = 0 - unsub = true - } - updateGWs = srv.gateway.enabled - } - c.mu.Unlock() - - if c.opts.Verbose { - c.sendOK() - } - - if unsub { - c.unsubscribe(acc, sub, false, true) - if acc != nil && (kind == CLIENT || kind == SYSTEM || kind == ACCOUNT || kind == JETSTREAM) { - srv.updateRouteSubscriptionMap(acc, sub, -1) - if updateGWs { - srv.gatewayUpdateSubInterest(acc.Name, sub, -1) - } - } - // Now check on leafnode updates. - acc.updateLeafNodes(sub, -1) - } - - return nil -} - -// checkDenySub will check if we are allowed to deliver this message in the -// presence of deny clauses for subscriptions. Deny clauses will not prevent -// larger scoped wildcard subscriptions, so we need to check at delivery time. -// Lock should be held. -func (c *client) checkDenySub(subject string) bool { - if denied, ok := c.mperms.dcache[subject]; ok { - return denied - } else if np, _ := c.mperms.deny.NumInterest(subject); np != 0 { - c.mperms.dcache[subject] = true - return true - } else { - c.mperms.dcache[subject] = false - } - if len(c.mperms.dcache) > maxDenyPermCacheSize { - c.pruneDenyCache() - } - return false -} - -// Create a message header for routes or leafnodes. Header and origin cluster aware. -func (c *client) msgHeaderForRouteOrLeaf(subj, reply []byte, rt *routeTarget, acc *Account) []byte { - hasHeader := c.pa.hdr > 0 - subclient := rt.sub.client - canReceiveHeader := subclient.headers - - mh := c.msgb[:msgHeadProtoLen] - kind := subclient.kind - var lnoc bool - - if kind == ROUTER { - // If we are coming from a leaf with an origin cluster we need to handle differently - // if we can. We will send a route based LMSG which has origin cluster and headers - // by default. - if c.kind == LEAF && c.remoteCluster() != _EMPTY_ { - subclient.mu.Lock() - lnoc = subclient.route.lnoc - subclient.mu.Unlock() - } - if lnoc { - mh[0] = 'L' - mh = append(mh, c.remoteCluster()...) - mh = append(mh, ' ') - } else { - // Router (and Gateway) nodes are RMSG. Set here since leafnodes may rewrite. - mh[0] = 'R' - } - if len(subclient.route.accName) == 0 { - mh = append(mh, acc.Name...) - mh = append(mh, ' ') - } - } else { - // Leaf nodes are LMSG - mh[0] = 'L' - // Remap subject if its a shadow subscription, treat like a normal client. - if rt.sub.im != nil { - if rt.sub.im.tr != nil { - to := rt.sub.im.tr.TransformSubject(bytesToString(subj)) - subj = []byte(to) - } else if !rt.sub.im.usePub { - subj = []byte(rt.sub.im.to) - } - } - } - mh = append(mh, subj...) - mh = append(mh, ' ') - - if len(rt.qs) > 0 { - if len(reply) > 0 { - mh = append(mh, "+ "...) // Signal that there is a reply. - mh = append(mh, reply...) - mh = append(mh, ' ') - } else { - mh = append(mh, "| "...) // Only queues - } - mh = append(mh, rt.qs...) - } else if len(reply) > 0 { - mh = append(mh, reply...) - mh = append(mh, ' ') - } - - if lnoc { - // leafnode origin LMSG always have a header entry even if zero. - if c.pa.hdr <= 0 { - mh = append(mh, '0') - } else { - mh = append(mh, c.pa.hdb...) - } - mh = append(mh, ' ') - mh = append(mh, c.pa.szb...) - } else if hasHeader { - if canReceiveHeader { - mh[0] = 'H' - mh = append(mh, c.pa.hdb...) - mh = append(mh, ' ') - mh = append(mh, c.pa.szb...) - } else { - // If we are here we need to truncate the payload size - nsz := strconv.Itoa(c.pa.size - c.pa.hdr) - mh = append(mh, nsz...) - } - } else { - mh = append(mh, c.pa.szb...) - } - return append(mh, _CRLF_...) -} - -// Create a message header for clients. Header aware. -func (c *client) msgHeader(subj, reply []byte, sub *subscription) []byte { - // See if we should do headers. We have to have a headers msg and - // the client we are going to deliver to needs to support headers as well. - hasHeader := c.pa.hdr > 0 - canReceiveHeader := sub.client != nil && sub.client.headers - - var mh []byte - if hasHeader && canReceiveHeader { - mh = c.msgb[:msgHeadProtoLen] - mh[0] = 'H' - } else { - mh = c.msgb[1:msgHeadProtoLen] - } - mh = append(mh, subj...) - mh = append(mh, ' ') - - if len(sub.sid) > 0 { - mh = append(mh, sub.sid...) - mh = append(mh, ' ') - } - if reply != nil { - mh = append(mh, reply...) - mh = append(mh, ' ') - } - if hasHeader { - if canReceiveHeader { - mh = append(mh, c.pa.hdb...) - mh = append(mh, ' ') - mh = append(mh, c.pa.szb...) - } else { - // If we are here we need to truncate the payload size - nsz := strconv.Itoa(c.pa.size - c.pa.hdr) - mh = append(mh, nsz...) - } - } else { - mh = append(mh, c.pa.szb...) - } - mh = append(mh, _CRLF_...) - return mh -} - -func (c *client) stalledWait(producer *client) { - // Check to see if we have exceeded our total wait time per readLoop invocation. - if producer.in.tst > stallTotalAllowed { - return - } - - // Grab stall channel which the slow consumer will close when caught up. - stall := c.out.stc - - // Calculate stall time. - ttl := stallClientMinDuration - if c.out.pb >= c.out.mp { - ttl = stallClientMaxDuration - } - - c.mu.Unlock() - defer c.mu.Lock() - - // Now check if we are close to total allowed. - if producer.in.tst+ttl > stallTotalAllowed { - ttl = stallTotalAllowed - producer.in.tst - } - delay := time.NewTimer(ttl) - defer delay.Stop() - - start := time.Now() - select { - case <-stall: - case <-delay.C: - producer.Debugf("Timed out of fast producer stall (%v)", ttl) - } - producer.in.tst += time.Since(start) -} - -// Used to treat maps as efficient set -var needFlush = struct{}{} - -// deliverMsg will deliver a message to a matching subscription and its underlying client. -// We process all connection/client types. mh is the part that will be protocol/client specific. -func (c *client) deliverMsg(prodIsMQTT bool, sub *subscription, acc *Account, subject, reply, mh, msg []byte, gwrply bool) bool { - // Check if message tracing is enabled. - mt, traceOnly := c.isMsgTraceEnabled() - - client := sub.client - // Check sub client and check echo. Only do this if not a service import. - if client == nil || (c == client && !client.echo && !sub.si) { - if client != nil && mt != nil { - client.mu.Lock() - mt.addEgressEvent(client, sub, errMsgTraceNoEcho) - client.mu.Unlock() - } - return false - } - - client.mu.Lock() - - // Check if we have a subscribe deny clause. This will trigger us to check the subject - // for a match against the denied subjects. - if client.mperms != nil && client.checkDenySub(string(subject)) { - mt.addEgressEvent(client, sub, errMsgTraceSubDeny) - client.mu.Unlock() - return false - } - - // New race detector forces this now. - if sub.isClosed() { - mt.addEgressEvent(client, sub, errMsgTraceSubClosed) - client.mu.Unlock() - return false - } - - // Check if we are a leafnode and have perms to check. - if client.kind == LEAF && client.perms != nil { - if !client.pubAllowedFullCheck(string(subject), true, true) { - mt.addEgressEvent(client, sub, errMsgTracePubViolation) - client.mu.Unlock() - client.Debugf("Not permitted to deliver to %q", subject) - return false - } - } - - var mtErr string - if mt != nil { - // For non internal subscription, and if the remote does not support - // the tracing feature... - if sub.icb == nil && !client.msgTraceSupport() { - if traceOnly { - // We are not sending the message at all because the user - // expects a trace-only and the remote does not support - // tracing, which means that it would process/deliver this - // message, which may break applications. - // Add the Egress with the no-support error message. - mt.addEgressEvent(client, sub, errMsgTraceOnlyNoSupport) - client.mu.Unlock() - return false - } - // If we are doing delivery, we will still forward the message, - // but we add an error to the Egress event to hint that one should - // not expect a tracing event from that remote. - mtErr = errMsgTraceNoSupport - } - // For ROUTER, GATEWAY and LEAF, even if we intend to do tracing only, - // we will still deliver the message. The remote side will - // generate an event based on what happened on that server. - if traceOnly && (client.kind == ROUTER || client.kind == GATEWAY || client.kind == LEAF) { - traceOnly = false - } - // If we skip delivery and this is not for a service import, we are done. - if traceOnly && (sub.icb == nil || c.noIcb) { - mt.addEgressEvent(client, sub, _EMPTY_) - client.mu.Unlock() - // Although the message is not actually delivered, for the - // purpose of "didDeliver", we need to return "true" here. - return true - } - } - - srv := client.srv - - // We don't want to bump the number of delivered messages to the subscription - // if we are doing trace-only (since really we are not sending it to the sub). - if !traceOnly { - sub.nm++ - } - - // Check if we should auto-unsubscribe. - if sub.max > 0 { - if client.kind == ROUTER && sub.nm >= sub.max { - // The only router based messages that we will see here are remoteReplies. - // We handle these slightly differently. - defer client.removeReplySub(sub) - } else { - // For routing.. - shouldForward := client.kind == CLIENT || client.kind == SYSTEM && client.srv != nil - // If we are at the exact number, unsubscribe but - // still process the message in hand, otherwise - // unsubscribe and drop message on the floor. - if sub.nm == sub.max { - client.Debugf("Auto-unsubscribe limit of %d reached for sid '%s'", sub.max, sub.sid) - // Due to defer, reverse the code order so that execution - // is consistent with other cases where we unsubscribe. - if shouldForward { - defer srv.updateRemoteSubscription(client.acc, sub, -1) - } - defer client.unsubscribe(client.acc, sub, true, true) - } else if sub.nm > sub.max { - client.Debugf("Auto-unsubscribe limit [%d] exceeded", sub.max) - mt.addEgressEvent(client, sub, errMsgTraceAutoSubExceeded) - client.mu.Unlock() - client.unsubscribe(client.acc, sub, true, true) - if shouldForward { - srv.updateRemoteSubscription(client.acc, sub, -1) - } - return false - } - } - } - - // Check here if we have a header with our message. If this client can not - // support we need to strip the headers from the payload. - // The actual header would have been processed correctly for us, so just - // need to update payload. - if c.pa.hdr > 0 && !sub.client.headers { - msg = msg[c.pa.hdr:] - } - - // Update statistics - - // The msg includes the CR_LF, so pull back out for accounting. - msgSize := int64(len(msg)) - // MQTT producers send messages without CR_LF, so don't remove it for them. - if !prodIsMQTT { - msgSize -= int64(LEN_CR_LF) - } - - // We do not update the outbound stats if we are doing trace only since - // this message will not be sent out. - // Also do not update on internal callbacks. - if !traceOnly && sub.icb == nil { - // No atomic needed since accessed under client lock. - // Monitor is reading those also under client's lock. - client.outMsgs++ - client.outBytes += msgSize - } - - // Check for internal subscriptions. - if sub.icb != nil && !c.noIcb { - if gwrply { - // We will store in the account, not the client since it will likely - // be a different client that will send the reply. - srv.trackGWReply(nil, client.acc, reply, c.pa.reply) - } - client.mu.Unlock() - - // For service imports, track if we delivered. - didDeliver := true - - // Internal account clients are for service imports and need the '\r\n'. - start := time.Now() - if client.kind == ACCOUNT { - sub.icb(sub, c, acc, string(subject), string(reply), msg) - // If we are a service import check to make sure we delivered the message somewhere. - if sub.si { - didDeliver = c.pa.delivered - } - } else { - sub.icb(sub, c, acc, string(subject), string(reply), msg[:msgSize]) - } - if dur := time.Since(start); dur >= readLoopReportThreshold { - srv.Warnf("Internal subscription on %q took too long: %v", subject, dur) - } - - return didDeliver - } - - // If we are a client and we detect that the consumer we are - // sending to is in a stalled state, go ahead and wait here - // with a limit. - if c.kind == CLIENT && client.out.stc != nil { - if srv.getOpts().NoFastProducerStall { - mt.addEgressEvent(client, sub, errMsgTraceFastProdNoStall) - client.mu.Unlock() - return false - } - client.stalledWait(c) - } - - // Check for closed connection - if client.isClosed() { - mt.addEgressEvent(client, sub, errMsgTraceClientClosed) - client.mu.Unlock() - return false - } - - // We have passed cases where we could possibly fail to deliver. - // Do not call for service-import. - if mt != nil && sub.icb == nil { - mt.addEgressEvent(client, sub, mtErr) - } - - // Do a fast check here to see if we should be tracking this from a latency - // perspective. This will be for a request being received for an exported service. - // This needs to be from a non-client (otherwise tracking happens at requestor). - // - // Also this check captures if the original reply (c.pa.reply) is a GW routed - // reply (since it is known to be > minReplyLen). If that is the case, we need to - // track the binding between the routed reply and the reply set in the message - // header (which is c.pa.reply without the GNR routing prefix). - if client.kind == CLIENT && len(c.pa.reply) > minReplyLen { - if gwrply { - // Note that we keep track of the GW routed reply in the destination - // connection (`client`). The routed reply subject is in `c.pa.reply`, - // should that change, we would have to pass the GW routed reply as - // a parameter of deliverMsg(). - srv.trackGWReply(client, nil, reply, c.pa.reply) - } - - // If we do not have a registered RTT queue that up now. - if client.rtt == 0 { - client.sendRTTPingLocked() - } - // FIXME(dlc) - We may need to optimize this. - // We will have tagged this with a suffix ('.T') if we are tracking. This is - // needed from sampling. Not all will be tracked. - if c.kind != CLIENT && isTrackedReply(c.pa.reply) { - client.trackRemoteReply(string(subject), string(c.pa.reply)) - } - } - - // Queue to outbound buffer - client.queueOutbound(mh) - client.queueOutbound(msg) - if prodIsMQTT { - // Need to add CR_LF since MQTT producers don't send CR_LF - client.queueOutbound([]byte(CR_LF)) - } - - // If we are tracking dynamic publish permissions that track reply subjects, - // do that accounting here. We only look at client.replies which will be non-nil. - // Only reply subject permissions if the client is not already allowed to publish to the reply subject. - if client.replies != nil && len(reply) > 0 && !client.pubAllowedFullCheck(string(reply), true, true) { - client.replies[string(reply)] = &resp{time.Now(), 0} - client.repliesSincePrune++ - if client.repliesSincePrune > replyPermLimit || time.Since(client.lastReplyPrune) > replyPruneTime { - client.pruneReplyPerms() - } - } - - // Check outbound threshold and queue IO flush if needed. - // This is specifically looking at situations where we are getting behind and may want - // to intervene before this producer goes back to top of readloop. We are in the producer's - // readloop go routine at this point. - // FIXME(dlc) - We may call this alot, maybe suppress after first call? - if len(client.out.nb) != 0 { - client.flushSignal() - } - - // Add the data size we are responsible for here. This will be processed when we - // return to the top of the readLoop. - c.addToPCD(client) - - if client.trace { - client.traceOutOp(bytesToString(mh[:len(mh)-LEN_CR_LF]), nil) - } - - client.mu.Unlock() - - return true -} - -// Add the given sub's client to the list of clients that need flushing. -// This must be invoked from `c`'s readLoop. No lock for c is required, -// however, `client` lock must be held on entry. This holds true even -// if `client` is same than `c`. -func (c *client) addToPCD(client *client) { - if _, ok := c.pcd[client]; !ok { - client.out.fsp++ - c.pcd[client] = needFlush - } -} - -// This will track a remote reply for an exported service that has requested -// latency tracking. -// Lock assumed to be held. -func (c *client) trackRemoteReply(subject, reply string) { - a := c.acc - if a == nil { - return - } - - var lrt time.Duration - var respThresh time.Duration - - a.mu.RLock() - se := a.getServiceExport(subject) - if se != nil { - lrt = a.lowestServiceExportResponseTime() - respThresh = se.respThresh - } - a.mu.RUnlock() - - if se == nil { - return - } - - if c.rrTracking == nil { - c.rrTracking = &rrTracking{ - rmap: make(map[string]*remoteLatency), - ptmr: time.AfterFunc(lrt, c.pruneRemoteTracking), - lrt: lrt, - } - } - rl := remoteLatency{ - Account: a.Name, - ReqId: reply, - respThresh: respThresh, - } - rl.M2.RequestStart = time.Now().UTC() - c.rrTracking.rmap[reply] = &rl -} - -// pruneRemoteTracking will prune any remote tracking objects -// that are too old. These are orphaned when a service is not -// sending reponses etc. -// Lock should be held upon entry. -func (c *client) pruneRemoteTracking() { - c.mu.Lock() - if c.rrTracking == nil { - c.mu.Unlock() - return - } - now := time.Now() - for subject, rl := range c.rrTracking.rmap { - if now.After(rl.M2.RequestStart.Add(rl.respThresh)) { - delete(c.rrTracking.rmap, subject) - } - } - if len(c.rrTracking.rmap) > 0 { - t := c.rrTracking.ptmr - t.Stop() - t.Reset(c.rrTracking.lrt) - } else { - c.rrTracking.ptmr.Stop() - c.rrTracking = nil - } - c.mu.Unlock() -} - -// pruneReplyPerms will remove any stale or expired entries -// in our reply cache. We make sure to not check too often. -func (c *client) pruneReplyPerms() { - // Make sure we do not check too often. - if c.perms.resp == nil { - return - } - - mm := c.perms.resp.MaxMsgs - ttl := c.perms.resp.Expires - now := time.Now() - - for k, resp := range c.replies { - if mm > 0 && resp.n >= mm { - delete(c.replies, k) - } else if ttl > 0 && now.Sub(resp.t) > ttl { - delete(c.replies, k) - } - } - - c.repliesSincePrune = 0 - c.lastReplyPrune = now -} - -// pruneDenyCache will prune the deny cache via randomly -// deleting items. Doing so pruneSize items at a time. -// Lock must be held for this one since it is shared under -// deliverMsg. -func (c *client) pruneDenyCache() { - r := 0 - for subject := range c.mperms.dcache { - delete(c.mperms.dcache, subject) - if r++; r > pruneSize { - break - } - } -} - -// prunePubPermsCache will prune the cache via randomly -// deleting items. Doing so pruneSize items at a time. -func (c *client) prunePubPermsCache() { - // With parallel additions to the cache, it is possible that this function - // would not be able to reduce the cache to its max size in one go. We - // will try a few times but will release/reacquire the "lock" at each - // attempt to give a chance to another go routine to take over and not - // have this go routine do too many attempts. - for i := 0; i < 5; i++ { - // There is a case where we can invoke this from multiple go routines, - // (in deliverMsg() if sub.client is a LEAF), so we make sure to prune - // from only one go routine at a time. - if !atomic.CompareAndSwapInt32(&c.perms.prun, 0, 1) { - return - } - const maxPruneAtOnce = 1000 - r := 0 - c.perms.pcache.Range(func(k, _ any) bool { - c.perms.pcache.Delete(k) - if r++; (r > pruneSize && atomic.LoadInt32(&c.perms.pcsz) < int32(maxPermCacheSize)) || - (r > maxPruneAtOnce) { - return false - } - return true - }) - n := atomic.AddInt32(&c.perms.pcsz, -int32(r)) - atomic.StoreInt32(&c.perms.prun, 0) - if n <= int32(maxPermCacheSize) { - return - } - } -} - -// pubAllowed checks on publish permissioning. -// Lock should not be held. -func (c *client) pubAllowed(subject string) bool { - return c.pubAllowedFullCheck(subject, true, false) -} - -// pubAllowedFullCheck checks on all publish permissioning depending -// on the flag for dynamic reply permissions. -func (c *client) pubAllowedFullCheck(subject string, fullCheck, hasLock bool) bool { - if c.perms == nil || (c.perms.pub.allow == nil && c.perms.pub.deny == nil) { - return true - } - // Check if published subject is allowed if we have permissions in place. - v, ok := c.perms.pcache.Load(subject) - if ok { - return v.(bool) - } - allowed := true - // Cache miss, check allow then deny as needed. - if c.perms.pub.allow != nil { - np, _ := c.perms.pub.allow.NumInterest(subject) - allowed = np != 0 - } - // If we have a deny list and are currently allowed, check that as well. - if allowed && c.perms.pub.deny != nil { - np, _ := c.perms.pub.deny.NumInterest(subject) - allowed = np == 0 - } - - // If we are tracking reply subjects - // dynamically, check to see if we are allowed here but avoid pcache. - // We need to acquire the lock though. - if !allowed && fullCheck && c.perms.resp != nil { - if !hasLock { - c.mu.Lock() - } - if resp := c.replies[subject]; resp != nil { - resp.n++ - // Check if we have sent too many responses. - if c.perms.resp.MaxMsgs > 0 && resp.n > c.perms.resp.MaxMsgs { - delete(c.replies, subject) - } else if c.perms.resp.Expires > 0 && time.Since(resp.t) > c.perms.resp.Expires { - delete(c.replies, subject) - } else { - allowed = true - } - } - if !hasLock { - c.mu.Unlock() - } - } else { - // Update our cache here. - c.perms.pcache.Store(subject, allowed) - if n := atomic.AddInt32(&c.perms.pcsz, 1); n > maxPermCacheSize { - c.prunePubPermsCache() - } - } - return allowed -} - -// Test whether a reply subject is a service import reply. -func isServiceReply(reply []byte) bool { - // This function is inlined and checking this way is actually faster - // than byte-by-byte comparison. - return len(reply) > 3 && bytesToString(reply[:4]) == replyPrefix -} - -// Test whether a reply subject is a service import or a gateway routed reply. -func isReservedReply(reply []byte) bool { - if isServiceReply(reply) { - return true - } - rLen := len(reply) - // Faster to check with string([:]) than byte-by-byte - if rLen > jsAckPreLen && bytesToString(reply[:jsAckPreLen]) == jsAckPre { - return true - } else if rLen > gwReplyPrefixLen && bytesToString(reply[:gwReplyPrefixLen]) == gwReplyPrefix { - return true - } - return false -} - -// This will decide to call the client code or router code. -func (c *client) processInboundMsg(msg []byte) { - switch c.kind { - case CLIENT: - c.processInboundClientMsg(msg) - case ROUTER: - c.processInboundRoutedMsg(msg) - case GATEWAY: - c.processInboundGatewayMsg(msg) - case LEAF: - c.processInboundLeafMsg(msg) - } -} - -// selectMappedSubject will choose the mapped subject based on the client's inbound subject. -func (c *client) selectMappedSubject() bool { - nsubj, changed := c.acc.selectMappedSubject(bytesToString(c.pa.subject)) - if changed { - c.pa.mapped = c.pa.subject - c.pa.subject = []byte(nsubj) - } - return changed -} - -// clientNRGPrefix is used in processInboundClientMsg to detect if publishes -// are being made from normal clients to NRG subjects. -var clientNRGPrefix = []byte("$NRG.") - -// processInboundClientMsg is called to process an inbound msg from a client. -// Return if the message was delivered, and if the message was not delivered -// due to a permission issue. -func (c *client) processInboundClientMsg(msg []byte) (bool, bool) { - // Update statistics - // The msg includes the CR_LF, so pull back out for accounting. - c.in.msgs++ - c.in.bytes += int32(len(msg) - LEN_CR_LF) - - // Check that client (could be here with SYSTEM) is not publishing on reserved "$GNR" prefix. - if c.kind == CLIENT && hasGWRoutedReplyPrefix(c.pa.subject) { - c.pubPermissionViolation(c.pa.subject) - return false, true - } - - // Mostly under testing scenarios. - c.mu.Lock() - if c.srv == nil || c.acc == nil { - c.mu.Unlock() - return false, false - } - acc := c.acc - genidAddr := &acc.sl.genid - - // Check pub permissions - if c.perms != nil && (c.perms.pub.allow != nil || c.perms.pub.deny != nil) && !c.pubAllowedFullCheck(string(c.pa.subject), true, true) { - c.mu.Unlock() - c.pubPermissionViolation(c.pa.subject) - return false, true - } - c.mu.Unlock() - - // Check if the client is trying to publish to reserved NRG subjects. - // Doesn't apply to NRGs themselves as they use SYSTEM-kind clients instead. - if c.kind == CLIENT && bytes.HasPrefix(c.pa.subject, clientNRGPrefix) && acc != c.srv.SystemAccount() { - c.pubPermissionViolation(c.pa.subject) - return false, true - } - - // Now check for reserved replies. These are used for service imports. - if c.kind == CLIENT && len(c.pa.reply) > 0 && isReservedReply(c.pa.reply) { - c.replySubjectViolation(c.pa.reply) - return false, true - } - - if c.opts.Verbose { - c.sendOK() - } - - // If MQTT client, check for retain flag now that we have passed permissions check - if c.isMqtt() { - c.mqttHandlePubRetain() - } - - // Doing this inline as opposed to create a function (which otherwise has a measured - // performance impact reported in our bench) - var isGWRouted bool - if c.kind != CLIENT { - if atomic.LoadInt32(&acc.gwReplyMapping.check) > 0 { - acc.mu.RLock() - c.pa.subject, isGWRouted = acc.gwReplyMapping.get(c.pa.subject) - acc.mu.RUnlock() - } - } else if atomic.LoadInt32(&c.gwReplyMapping.check) > 0 { - c.mu.Lock() - c.pa.subject, isGWRouted = c.gwReplyMapping.get(c.pa.subject) - c.mu.Unlock() - } - - // If we have an exported service and we are doing remote tracking, check this subject - // to see if we need to report the latency. - if c.rrTracking != nil { - c.mu.Lock() - rl := c.rrTracking.rmap[string(c.pa.subject)] - if rl != nil { - delete(c.rrTracking.rmap, bytesToString(c.pa.subject)) - } - c.mu.Unlock() - - if rl != nil { - sl := &rl.M2 - // Fill this in and send it off to the other side. - sl.Status = 200 - sl.Responder = c.getClientInfo(true) - sl.ServiceLatency = time.Since(sl.RequestStart) - sl.Responder.RTT - sl.TotalLatency = sl.ServiceLatency + sl.Responder.RTT - sanitizeLatencyMetric(sl) - lsub := remoteLatencySubjectForResponse(c.pa.subject) - c.srv.sendInternalAccountMsg(nil, lsub, rl) // Send to SYS account - } - } - - // If the subject was converted to the gateway routed subject, then handle it now - // and be done with the rest of this function. - if isGWRouted { - c.handleGWReplyMap(msg) - return true, false - } - - // Match the subscriptions. We will use our own L1 map if - // it's still valid, avoiding contention on the shared sublist. - var r *SublistResult - var ok bool - - genid := atomic.LoadUint64(genidAddr) - if genid == c.in.genid && c.in.results != nil { - r, ok = c.in.results[string(c.pa.subject)] - } else { - // Reset our L1 completely. - c.in.results = make(map[string]*SublistResult) - c.in.genid = genid - } - - // Go back to the sublist data structure. - if !ok { - // Match may use the subject here to populate a cache, so can not use bytesToString here. - r = acc.sl.Match(string(c.pa.subject)) - if len(r.psubs)+len(r.qsubs) > 0 { - // Prune the results cache. Keeps us from unbounded growth. Random delete. - if len(c.in.results) >= maxResultCacheSize { - n := 0 - for subject := range c.in.results { - delete(c.in.results, subject) - if n++; n > pruneSize { - break - } - } - } - // Then add the new cache entry. - c.in.results[string(c.pa.subject)] = r - } - } - - // Indication if we attempted to deliver the message to anyone. - var didDeliver bool - var qnames [][]byte - - // Check for no interest, short circuit if so. - // This is the fanout scale. - if len(r.psubs)+len(r.qsubs) > 0 { - flag := pmrNoFlag - // If there are matching queue subs and we are in gateway mode, - // we need to keep track of the queue names the messages are - // delivered to. When sending to the GWs, the RMSG will include - // those names so that the remote clusters do not deliver messages - // to their queue subs of the same names. - if len(r.qsubs) > 0 && c.srv.gateway.enabled && - atomic.LoadInt64(&c.srv.gateway.totalQSubs) > 0 { - flag |= pmrCollectQueueNames - } - didDeliver, qnames = c.processMsgResults(acc, r, msg, c.pa.deliver, c.pa.subject, c.pa.reply, flag) - } - - // Now deal with gateways - if c.srv.gateway.enabled { - reply := c.pa.reply - if len(c.pa.deliver) > 0 && c.kind == JETSTREAM && len(c.pa.reply) > 0 { - reply = append(reply, '@') - reply = append(reply, c.pa.deliver...) - } - didDeliver = c.sendMsgToGateways(acc, msg, c.pa.subject, reply, qnames, false) || didDeliver - } - - // Check to see if we did not deliver to anyone and the client has a reply subject set - // and wants notification of no_responders. - if !didDeliver && len(c.pa.reply) > 0 { - c.mu.Lock() - if c.opts.NoResponders { - if sub := c.subForReply(c.pa.reply); sub != nil { - proto := fmt.Sprintf("HMSG %s %s 16 16\r\nNATS/1.0 503\r\n\r\n\r\n", c.pa.reply, sub.sid) - c.queueOutbound([]byte(proto)) - c.addToPCD(c) - } - } - c.mu.Unlock() - } - - return didDeliver, false -} - -// Return the subscription for this reply subject. Only look at normal subs for this client. -func (c *client) subForReply(reply []byte) *subscription { - r := c.acc.sl.Match(string(reply)) - for _, sub := range r.psubs { - if sub.client == c { - return sub - } - } - return nil -} - -// This is invoked knowing that c.pa.subject has been set to the gateway routed subject. -// This function will send the message to possibly LEAFs and directly back to the origin -// gateway. -func (c *client) handleGWReplyMap(msg []byte) bool { - // Check for leaf nodes - if c.srv.gwLeafSubs.Count() > 0 { - if r := c.srv.gwLeafSubs.MatchBytes(c.pa.subject); len(r.psubs) > 0 { - c.processMsgResults(c.acc, r, msg, c.pa.deliver, c.pa.subject, c.pa.reply, pmrNoFlag) - } - } - if c.srv.gateway.enabled { - reply := c.pa.reply - if len(c.pa.deliver) > 0 && c.kind == JETSTREAM && len(c.pa.reply) > 0 { - reply = append(reply, '@') - reply = append(reply, c.pa.deliver...) - } - c.sendMsgToGateways(c.acc, msg, c.pa.subject, reply, nil, false) - } - return true -} - -// Used to setup the response map for a service import request that has a reply subject. -func (c *client) setupResponseServiceImport(acc *Account, si *serviceImport, tracking bool, header http.Header) *serviceImport { - rsi := si.acc.addRespServiceImport(acc, string(c.pa.reply), si, tracking, header) - if si.latency != nil { - if c.rtt == 0 { - // We have a service import that we are tracking but have not established RTT. - c.sendRTTPing() - } - si.acc.mu.Lock() - rsi.rc = c - si.acc.mu.Unlock() - } - return rsi -} - -// Will remove a header if present. -func removeHeaderIfPresent(hdr []byte, key string) []byte { - start := bytes.Index(hdr, []byte(key)) - // key can't be first and we want to check that it is preceded by a '\n' - if start < 1 || hdr[start-1] != '\n' { - return hdr - } - index := start + len(key) - if index >= len(hdr) || hdr[index] != ':' { - return hdr - } - end := bytes.Index(hdr[start:], []byte(_CRLF_)) - if end < 0 { - return hdr - } - hdr = append(hdr[:start], hdr[start+end+len(_CRLF_):]...) - if len(hdr) <= len(emptyHdrLine) { - return nil - } - return hdr -} - -func removeHeaderIfPrefixPresent(hdr []byte, prefix string) []byte { - var index int - for { - if index >= len(hdr) { - return hdr - } - - start := bytes.Index(hdr[index:], []byte(prefix)) - if start < 0 { - return hdr - } - index += start - if index < 1 || hdr[index-1] != '\n' { - return hdr - } - - end := bytes.Index(hdr[index+len(prefix):], []byte(_CRLF_)) - if end < 0 { - return hdr - } - - hdr = append(hdr[:index], hdr[index+end+len(prefix)+len(_CRLF_):]...) - if len(hdr) <= len(emptyHdrLine) { - return nil - } - } -} - -// Generate a new header based on optional original header and key value. -// More used in JetStream layers. -func genHeader(hdr []byte, key, value string) []byte { - var bb bytes.Buffer - if len(hdr) > LEN_CR_LF { - bb.Write(hdr[:len(hdr)-LEN_CR_LF]) - } else { - bb.WriteString(hdrLine) - } - http.Header{key: []string{value}}.Write(&bb) - bb.WriteString(CR_LF) - return bb.Bytes() -} - -// This will set a header for the message. -// Lock does not need to be held but this should only be called -// from the inbound go routine. We will update the pubArgs. -// This will replace any previously set header and not add to it per normal spec. -func (c *client) setHeader(key, value string, msg []byte) []byte { - var bb bytes.Buffer - var omi int - // Write original header if present. - if c.pa.hdr > LEN_CR_LF { - omi = c.pa.hdr - hdr := removeHeaderIfPresent(msg[:c.pa.hdr-LEN_CR_LF], key) - if len(hdr) == 0 { - bb.WriteString(hdrLine) - } else { - bb.Write(hdr) - } - } else { - bb.WriteString(hdrLine) - } - http.Header{key: []string{value}}.Write(&bb) - bb.WriteString(CR_LF) - nhdr := bb.Len() - // Put the original message back. - // FIXME(dlc) - This is inefficient. - bb.Write(msg[omi:]) - nsize := bb.Len() - LEN_CR_LF - // MQTT producers don't have CRLF, so add it back. - if c.isMqtt() { - nsize += LEN_CR_LF - } - // Update pubArgs - // If others will use this later we need to save and restore original. - c.pa.hdr = nhdr - c.pa.size = nsize - c.pa.hdb = []byte(strconv.Itoa(nhdr)) - c.pa.szb = []byte(strconv.Itoa(nsize)) - return bb.Bytes() -} - -// Will return a copy of the value for the header denoted by key or nil if it does not exist. -// If you know that it is safe to refer to the underlying hdr slice for the period that the -// return value is used, then sliceHeader() will be faster. -func getHeader(key string, hdr []byte) []byte { - v := sliceHeader(key, hdr) - if v == nil { - return nil - } - return append(make([]byte, 0, len(v)), v...) -} - -// Will return the sliced value for the header denoted by key or nil if it does not exists. -// This function ignores errors and tries to achieve speed and no additional allocations. -func sliceHeader(key string, hdr []byte) []byte { - if len(hdr) == 0 { - return nil - } - index := bytes.Index(hdr, []byte(key)) - hdrLen := len(hdr) - // Check that we have enough characters, this will handle the -1 case of the key not - // being found and will also handle not having enough characters for trailing CRLF. - if index < 2 { - return nil - } - // There should be a terminating CRLF. - if index >= hdrLen-1 || hdr[index-1] != '\n' || hdr[index-2] != '\r' { - return nil - } - // The key should be immediately followed by a : separator. - index += len(key) + 1 - if index >= hdrLen || hdr[index-1] != ':' { - return nil - } - // Skip over whitespace before the value. - for index < hdrLen && hdr[index] == ' ' { - index++ - } - // Collect together the rest of the value until we hit a CRLF. - start := index - for index < hdrLen { - if hdr[index] == '\r' && index < hdrLen-1 && hdr[index+1] == '\n' { - break - } - index++ - } - return hdr[start:index:index] -} - -// For bytes.HasPrefix below. -var ( - jsRequestNextPreB = []byte(jsRequestNextPre) - jsDirectGetPreB = []byte(jsDirectGetPre) -) - -// processServiceImport is an internal callback when a subscription matches an imported service -// from another account. This includes response mappings as well. -func (c *client) processServiceImport(si *serviceImport, acc *Account, msg []byte) bool { - // If we are a GW and this is not a direct serviceImport ignore. - isResponse := si.isRespServiceImport() - if (c.kind == GATEWAY || c.kind == ROUTER) && !isResponse { - return false - } - // Detect cycles and ignore (return) when we detect one. - if len(c.pa.psi) > 0 { - for i := len(c.pa.psi) - 1; i >= 0; i-- { - if psi := c.pa.psi[i]; psi.se == si.se { - return false - } - } - } - - acc.mu.RLock() - var checkJS bool - shouldReturn := si.invalid || acc.sl == nil - if !shouldReturn && !isResponse && si.to == jsAllAPI { - if bytes.HasPrefix(c.pa.subject, jsDirectGetPreB) || bytes.HasPrefix(c.pa.subject, jsRequestNextPreB) { - checkJS = true - } - } - siAcc := si.acc - allowTrace := si.atrc - acc.mu.RUnlock() - - // We have a special case where JetStream pulls in all service imports through one export. - // However the GetNext for consumers and DirectGet for streams are a no-op and causes buildups of service imports, - // response service imports and rrMap entries which all will need to simply expire. - // TODO(dlc) - Come up with something better. - if shouldReturn || (checkJS && si.se != nil && si.se.acc == c.srv.SystemAccount()) { - return false - } - - mt, traceOnly := c.isMsgTraceEnabled() - - var nrr []byte - var rsi *serviceImport - - // Check if there is a reply present and set up a response. - tracking, headers := shouldSample(si.latency, c) - if len(c.pa.reply) > 0 { - // Special case for now, need to formalize. - // TODO(dlc) - Formalize as a service import option for reply rewrite. - // For now we can't do $JS.ACK since that breaks pull consumers across accounts. - if !bytes.HasPrefix(c.pa.reply, []byte(jsAckPre)) { - if rsi = c.setupResponseServiceImport(acc, si, tracking, headers); rsi != nil { - nrr = []byte(rsi.from) - } - } else { - // This only happens when we do a pull subscriber that trampolines through another account. - // Normally this code is not called. - nrr = c.pa.reply - } - } else if !isResponse && si.latency != nil && tracking { - // Check to see if this was a bad request with no reply and we were supposed to be tracking. - siAcc.sendBadRequestTrackingLatency(si, c, headers) - } - - // Send tracking info here if we are tracking this response. - // This is always a response. - var didSendTL bool - if si.tracking && !si.didDeliver { - // Stamp that we attempted delivery. - si.didDeliver = true - didSendTL = acc.sendTrackingLatency(si, c) - } - - // Pick correct "to" subject. If we matched on a wildcard use the literal publish subject. - to, subject := si.to, string(c.pa.subject) - - if si.tr != nil { - // FIXME(dlc) - This could be slow, may want to look at adding cache to bare transforms? - to = si.tr.TransformSubject(subject) - } else if si.usePub { - to = subject - } - - // Copy our pubArg since this gets modified as we process the service import itself. - pacopy := c.pa - - // Now check to see if this account has mappings that could affect the service import. - // Can't use non-locked trick like in processInboundClientMsg, so just call into selectMappedSubject - // so we only lock once. - nsubj, changed := siAcc.selectMappedSubject(to) - if changed { - c.pa.mapped = []byte(to) - to = nsubj - } - - // Set previous service import to detect chaining. - lpsi := len(c.pa.psi) - hadPrevSi, share := lpsi > 0, si.share - if hadPrevSi { - share = c.pa.psi[lpsi-1].share - } - c.pa.psi = append(c.pa.psi, si) - - // Place our client info for the request in the original message. - // This will survive going across routes, etc. - if !isResponse { - isSysImport := siAcc == c.srv.SystemAccount() - var ci *ClientInfo - if hadPrevSi && c.pa.hdr >= 0 { - var cis ClientInfo - if err := json.Unmarshal(sliceHeader(ClientInfoHdr, msg[:c.pa.hdr]), &cis); err == nil { - ci = &cis - ci.Service = acc.Name - // Check if we are moving into a share details account from a non-shared - // and add in server and cluster details. - if !share && (si.share || isSysImport) { - c.addServerAndClusterInfo(ci) - } - } - } else if c.kind != LEAF || c.pa.hdr < 0 || len(sliceHeader(ClientInfoHdr, msg[:c.pa.hdr])) == 0 { - ci = c.getClientInfo(share) - // If we did not share but the imports destination is the system account add in the server and cluster info. - if !share && isSysImport { - c.addServerAndClusterInfo(ci) - } - } else if c.kind == LEAF && (si.share || isSysImport) { - // We have a leaf header here for ci, augment as above. - ci = c.getClientInfo(si.share) - if !si.share && isSysImport { - c.addServerAndClusterInfo(ci) - } - } - // Set clientInfo if present. - if ci != nil { - if b, _ := json.Marshal(ci); b != nil { - msg = c.setHeader(ClientInfoHdr, bytesToString(b), msg) - } - } - } - - // Set our optional subject(to) and reply. - if !isResponse && to != subject { - c.pa.subject = []byte(to) - } - c.pa.reply = nrr - - if changed && c.isMqtt() && c.pa.hdr > 0 { - c.srv.mqttStoreQoSMsgForAccountOnNewSubject(c.pa.hdr, msg, siAcc.GetName(), to) - } - - // FIXME(dlc) - Do L1 cache trick like normal client? - rr := siAcc.sl.Match(to) - - // If we are a route or gateway or leafnode and this message is flipped to a queue subscriber we - // need to handle that since the processMsgResults will want a queue filter. - flags := pmrMsgImportedFromService - if c.kind == GATEWAY || c.kind == ROUTER || c.kind == LEAF { - flags |= pmrIgnoreEmptyQueueFilter - } - - // We will be calling back into processMsgResults since we are now being called as a normal sub. - // We need to take care of the c.in.rts, so save off what is there and use a local version. We - // will put back what was there after. - - orts := c.in.rts - - var lrts [routeTargetInit]routeTarget - c.in.rts = lrts[:0] - - var skipProcessing bool - // If message tracing enabled, add the service import trace. - if mt != nil { - mt.addServiceImportEvent(siAcc.GetName(), string(pacopy.subject), to) - // If we are not allowing tracing and doing trace only, we stop at this level. - if !allowTrace { - if traceOnly { - skipProcessing = true - } else { - // We are going to do normal processing, and possibly chainning - // with other server imports, but the rest won't be traced. - // We do so by setting the c.pa.trace to nil (it will be restored - // with c.pa = pacopy). - c.pa.trace = nil - // We also need to disable the message trace headers so that - // if the message is routed, it does not initialize tracing in the - // remote. - positions := disableTraceHeaders(c, msg) - defer enableTraceHeaders(msg, positions) - } - } - } - - var didDeliver bool - - if !skipProcessing { - // If this is not a gateway connection but gateway is enabled, - // try to send this converted message to all gateways. - if c.srv.gateway.enabled { - flags |= pmrCollectQueueNames - var queues [][]byte - didDeliver, queues = c.processMsgResults(siAcc, rr, msg, c.pa.deliver, []byte(to), nrr, flags) - didDeliver = c.sendMsgToGateways(siAcc, msg, []byte(to), nrr, queues, false) || didDeliver - } else { - didDeliver, _ = c.processMsgResults(siAcc, rr, msg, c.pa.deliver, []byte(to), nrr, flags) - } - } - - // Restore to original values. - c.in.rts = orts - c.pa = pacopy - - // Before we undo didDeliver based on tracing and last mile, mark in the c.pa which informs us of no responders status. - // If we override due to tracing and traceOnly we do not want to send back a no responders. - c.pa.delivered = didDeliver - - // If this was a message trace but we skip last-mile delivery, we need to - // do the remove, so: - if mt != nil && traceOnly && didDeliver { - didDeliver = false - } - - // Determine if we should remove this service import. This is for response service imports. - // We will remove if we did not deliver, or if we are a response service import and we are - // a singleton, or we have an EOF message. - shouldRemove := !didDeliver || (isResponse && (si.rt == Singleton || len(msg) == LEN_CR_LF)) - // If we are tracking and we did not actually send the latency info we need to suppress the removal. - if si.tracking && !didSendTL { - shouldRemove = false - } - // If we are streamed or chunked we need to update our timestamp to avoid cleanup. - if si.rt != Singleton && didDeliver { - acc.mu.Lock() - si.ts = time.Now().UnixNano() - acc.mu.Unlock() - } - - // Cleanup of a response service import - if shouldRemove { - reason := rsiOk - if !didDeliver { - reason = rsiNoDelivery - } - if isResponse { - acc.removeRespServiceImport(si, reason) - } else { - // This is a main import and since we could not even deliver to the exporting account - // go ahead and remove the respServiceImport we created above. - siAcc.removeRespServiceImport(rsi, reason) - } - } - - return didDeliver -} - -func (c *client) addSubToRouteTargets(sub *subscription) { - if c.in.rts == nil { - c.in.rts = make([]routeTarget, 0, routeTargetInit) - } - - for i := range c.in.rts { - rt := &c.in.rts[i] - if rt.sub.client == sub.client { - if sub.queue != nil { - rt.qs = append(rt.qs, sub.queue...) - rt.qs = append(rt.qs, ' ') - } - return - } - } - - var rt *routeTarget - lrts := len(c.in.rts) - - // If we are here we do not have the sub yet in our list - // If we have to grow do so here. - if lrts == cap(c.in.rts) { - c.in.rts = append(c.in.rts, routeTarget{}) - } - - c.in.rts = c.in.rts[:lrts+1] - rt = &c.in.rts[lrts] - rt.sub = sub - rt.qs = rt._qs[:0] - if sub.queue != nil { - rt.qs = append(rt.qs, sub.queue...) - rt.qs = append(rt.qs, ' ') - } -} - -// This processes the sublist results for a given message. -// Returns if the message was delivered to at least target and queue filters. -func (c *client) processMsgResults(acc *Account, r *SublistResult, msg, deliver, subject, reply []byte, flags int) (bool, [][]byte) { - // For sending messages across routes and leafnodes. - // Reset if we have one since we reuse this data structure. - if c.in.rts != nil { - c.in.rts = c.in.rts[:0] - } - - var rplyHasGWPrefix bool - var creply = reply - - // If the reply subject is a GW routed reply, we will perform some - // tracking in deliverMsg(). We also want to send to the user the - // reply without the prefix. `creply` will be set to that and be - // used to create the message header for client connections. - if rplyHasGWPrefix = isGWRoutedReply(reply); rplyHasGWPrefix { - creply = reply[gwSubjectOffset:] - } - - // With JetStream we now have times where we want to match a subscription - // on one subject, but deliver it with another. e.g. JetStream deliverables. - // This only works for last mile, meaning to a client. For other types we need - // to use the original subject. - subj := subject - if len(deliver) > 0 { - subj = deliver - } - - // Check for JetStream encoded reply subjects. - // For now these will only be on $JS.ACK prefixed reply subjects. - var remapped bool - if len(creply) > 0 && - c.kind != CLIENT && c.kind != SYSTEM && c.kind != JETSTREAM && c.kind != ACCOUNT && - bytes.HasPrefix(creply, []byte(jsAckPre)) { - // We need to rewrite the subject and the reply. - if li := bytes.LastIndex(creply, []byte("@")); li != -1 && li < len(creply)-1 { - remapped = true - subj, creply = creply[li+1:], creply[:li] - } - } - - var didDeliver bool - - // delivery subject for clients - var dsubj []byte - // Used as scratch if mapping - var _dsubj [128]byte - - // For stats, we will keep track of the number of messages that have been - // delivered and then multiply by the size of that message and update - // server and account stats in a "single" operation (instead of per-sub). - // However, we account for situations where the message is possibly changed - // by having an extra size - var dlvMsgs int64 - var dlvExtraSize int64 - - // We need to know if this is a MQTT producer because they send messages - // without CR_LF (we otherwise remove the size of CR_LF from message size). - prodIsMQTT := c.isMqtt() - - updateStats := func() { - if dlvMsgs == 0 { - return - } - totalBytes := dlvMsgs*int64(len(msg)) + dlvExtraSize - // For non MQTT producers, remove the CR_LF * number of messages - if !prodIsMQTT { - totalBytes -= dlvMsgs * int64(LEN_CR_LF) - } - if acc != nil { - atomic.AddInt64(&acc.outMsgs, dlvMsgs) - atomic.AddInt64(&acc.outBytes, totalBytes) - } - if srv := c.srv; srv != nil { - atomic.AddInt64(&srv.outMsgs, dlvMsgs) - atomic.AddInt64(&srv.outBytes, totalBytes) - } - } - - mt, traceOnly := c.isMsgTraceEnabled() - - // Loop over all normal subscriptions that match. - for _, sub := range r.psubs { - // Check if this is a send to a ROUTER. We now process - // these after everything else. - switch sub.client.kind { - case ROUTER: - if (c.kind != ROUTER && !c.isSpokeLeafNode()) || (flags&pmrAllowSendFromRouteToRoute != 0) { - c.addSubToRouteTargets(sub) - } - continue - case GATEWAY: - // Never send to gateway from here. - continue - case LEAF: - // We handle similarly to routes and use the same data structures. - // Leaf node delivery audience is different however. - // Also leaf nodes are always no echo, so we make sure we are not - // going to send back to ourselves here. For messages from routes we want - // to suppress in general unless we know from the hub or its a service reply. - if c != sub.client && (c.kind != ROUTER || sub.client.isHubLeafNode() || isServiceReply(c.pa.subject)) { - c.addSubToRouteTargets(sub) - } - continue - } - - // Assume delivery subject is the normal subject to this point. - dsubj = subj - - // We may need to disable tracing, by setting c.pa.trace to `nil` - // before the call to deliverMsg, if so, this will indicate that - // we need to put it back. - var restorePaTrace bool - - // Check for stream import mapped subs (shadow subs). These apply to local subs only. - if sub.im != nil { - // If this message was a service import do not re-export to an exported stream. - if flags&pmrMsgImportedFromService != 0 { - continue - } - if sub.im.tr != nil { - to := sub.im.tr.TransformSubject(bytesToString(subject)) - dsubj = append(_dsubj[:0], to...) - } else if sub.im.usePub { - dsubj = append(_dsubj[:0], subj...) - } else { - dsubj = append(_dsubj[:0], sub.im.to...) - } - - if mt != nil { - mt.addStreamExportEvent(sub.client, dsubj) - // If allow_trace is false... - if !sub.im.atrc { - // If we are doing only message tracing, we can move to the - // next sub. - if traceOnly { - // Although the message was not delivered, for the purpose - // of didDeliver, we need to set to true (to avoid possible - // no responders). - didDeliver = true - continue - } - // If we are delivering the message, we need to disable tracing - // before calling deliverMsg(). - c.pa.trace, restorePaTrace = nil, true - } - } - - // Make sure deliver is set if inbound from a route. - if remapped && (c.kind == GATEWAY || c.kind == ROUTER || c.kind == LEAF) { - deliver = subj - } - // If we are mapping for a deliver subject we will reverse roles. - // The original subj we set from above is correct for the msg header, - // but we need to transform the deliver subject to properly route. - if len(deliver) > 0 { - dsubj, subj = subj, dsubj - } - } - - // Remap to the original subject if internal. - if sub.icb != nil && sub.rsi { - dsubj = subject - } - - // Normal delivery - mh := c.msgHeader(dsubj, creply, sub) - if c.deliverMsg(prodIsMQTT, sub, acc, dsubj, creply, mh, msg, rplyHasGWPrefix) { - // We don't count internal deliveries, so do only when sub.icb is nil. - if sub.icb == nil { - dlvMsgs++ - } - didDeliver = true - } - if restorePaTrace { - c.pa.trace = mt - } - } - - // Set these up to optionally filter based on the queue lists. - // This is for messages received from routes which will have directed - // guidance on which queue groups we should deliver to. - qf := c.pa.queues - - // Declared here because of goto. - var queues [][]byte - - var leafOrigin string - switch c.kind { - case ROUTER: - if len(c.pa.origin) > 0 { - // Picture a message sent from a leafnode to a server that then routes - // this message: CluserA -leaf-> HUB1 -route-> HUB2 - // Here we are in HUB2, so c.kind is a ROUTER, but the message will - // contain a c.pa.origin set to "ClusterA" to indicate that this message - // originated from that leafnode cluster. - leafOrigin = bytesToString(c.pa.origin) - } - case LEAF: - leafOrigin = c.remoteCluster() - } - - // For all routes/leaf/gateway connections, we may still want to send messages to - // leaf nodes or routes even if there are no queue filters since we collect - // them above and do not process inline like normal clients. - // However, do select queue subs if asked to ignore empty queue filter. - if (c.kind == LEAF || c.kind == ROUTER || c.kind == GATEWAY) && len(qf) == 0 && flags&pmrIgnoreEmptyQueueFilter == 0 { - goto sendToRoutesOrLeafs - } - - // Process queue subs - for i := 0; i < len(r.qsubs); i++ { - qsubs := r.qsubs[i] - // If we have a filter check that here. We could make this a map or someting more - // complex but linear search since we expect queues to be small. Should be faster - // and more cache friendly. - if qf != nil && len(qsubs) > 0 { - tqn := qsubs[0].queue - for _, qn := range qf { - if bytes.Equal(qn, tqn) { - goto selectQSub - } - } - continue - } - - selectQSub: - // We will hold onto remote or lead qsubs when we are coming from - // a route or a leaf node just in case we can no longer do local delivery. - var rsub, sub *subscription - var _ql [32]*subscription - - src := c.kind - // If we just came from a route we want to prefer local subs. - // So only select from local subs but remember the first rsub - // in case all else fails. - if src == ROUTER { - ql := _ql[:0] - for i := 0; i < len(qsubs); i++ { - sub = qsubs[i] - if dst := sub.client.kind; dst == LEAF || dst == ROUTER { - // If the destination is a LEAF, we first need to make sure - // that we would not pick one that was the origin of this - // message. - if dst == LEAF && leafOrigin != _EMPTY_ && leafOrigin == sub.client.remoteCluster() { - continue - } - // If we have assigned a ROUTER rsub already, replace if - // the destination is a LEAF since we want to favor that. - if rsub == nil || (rsub.client.kind == ROUTER && dst == LEAF) { - rsub = sub - } else if dst == LEAF { - // We already have a LEAF and this is another one. - // Flip a coin to see if we swap it or not. - // See https://github.com/nats-io/nats-server/issues/6040 - if fastrand.Uint32()%2 == 1 { - rsub = sub - } - } - } else { - ql = append(ql, sub) - } - } - qsubs = ql - } - - sindex := 0 - lqs := len(qsubs) - if lqs > 1 { - sindex = int(fastrand.Uint32() % uint32(lqs)) - } - - // Find a subscription that is able to deliver this message starting at a random index. - // Note that if the message came from a ROUTER, we will only have CLIENT or LEAF - // queue subs here, otherwise we can have all types. - for i := 0; i < lqs; i++ { - if sindex+i < lqs { - sub = qsubs[sindex+i] - } else { - sub = qsubs[(sindex+i)%lqs] - } - if sub == nil { - continue - } - - // If we are a spoke leaf node make sure to not forward across routes. - // This mimics same behavior for normal subs above. - if c.kind == LEAF && c.isSpokeLeafNode() && sub.client.kind == ROUTER { - continue - } - - // We have taken care of preferring local subs for a message from a route above. - // Here we just care about a client or leaf and skipping a leaf and preferring locals. - if dst := sub.client.kind; dst == ROUTER || dst == LEAF { - if (src == LEAF || src == CLIENT) && dst == LEAF { - // If we come from a LEAF and are about to pick a LEAF connection, - // make sure this is not the same leaf cluster. - if src == LEAF && leafOrigin != _EMPTY_ && leafOrigin == sub.client.remoteCluster() { - continue - } - // Remember that leaf in case we don't find any other candidate. - // We already start randomly in lqs slice, so we don't need - // to do a random swap if we already have an rsub like we do - // when src == ROUTER above. - if rsub == nil { - rsub = sub - } - continue - } else { - // We want to favor qsubs in our own cluster. If the routed - // qsub has an origin, it means that is on behalf of a leaf. - // We need to treat it differently. - if len(sub.origin) > 0 { - // If we already have an rsub, nothing to do. Also, do - // not pick a routed qsub for a LEAF origin cluster - // that is the same than where the message comes from. - if rsub == nil && (leafOrigin == _EMPTY_ || leafOrigin != bytesToString(sub.origin)) { - rsub = sub - } - continue - } - // This is a qsub that is local on the remote server (or - // we are connected to an older server and we don't know). - // Pick this one and be done. - rsub = sub - break - } - } - - // Assume delivery subject is normal subject to this point. - dsubj = subj - - // We may need to disable tracing, by setting c.pa.trace to `nil` - // before the call to deliverMsg, if so, this will indicate that - // we need to put it back. - var restorePaTrace bool - var skipDelivery bool - - // Check for stream import mapped subs. These apply to local subs only. - if sub.im != nil { - // If this message was a service import do not re-export to an exported stream. - if flags&pmrMsgImportedFromService != 0 { - continue - } - if sub.im.tr != nil { - to := sub.im.tr.TransformSubject(bytesToString(subject)) - dsubj = append(_dsubj[:0], to...) - } else if sub.im.usePub { - dsubj = append(_dsubj[:0], subj...) - } else { - dsubj = append(_dsubj[:0], sub.im.to...) - } - - if mt != nil { - mt.addStreamExportEvent(sub.client, dsubj) - // If allow_trace is false... - if !sub.im.atrc { - // If we are doing only message tracing, we are done - // with this queue group. - if traceOnly { - skipDelivery = true - } else { - // If we are delivering, we need to disable tracing - // before the call to deliverMsg() - c.pa.trace, restorePaTrace = nil, true - } - } - } - - // Make sure deliver is set if inbound from a route. - if remapped && (c.kind == GATEWAY || c.kind == ROUTER || c.kind == LEAF) { - deliver = subj - } - // If we are mapping for a deliver subject we will reverse roles. - // The original subj we set from above is correct for the msg header, - // but we need to transform the deliver subject to properly route. - if len(deliver) > 0 { - dsubj, subj = subj, dsubj - } - } - - var delivered bool - if !skipDelivery { - mh := c.msgHeader(dsubj, creply, sub) - delivered = c.deliverMsg(prodIsMQTT, sub, acc, subject, creply, mh, msg, rplyHasGWPrefix) - if restorePaTrace { - c.pa.trace = mt - } - } - if skipDelivery || delivered { - // Update only if not skipped. - if !skipDelivery && sub.icb == nil { - dlvMsgs++ - } - // Do the rest even when message delivery was skipped. - didDeliver = true - // Clear rsub - rsub = nil - if flags&pmrCollectQueueNames != 0 { - queues = append(queues, sub.queue) - } - break - } - } - - if rsub != nil { - // We are here if we have selected a leaf or route as the destination, - // or if we tried to deliver to a local qsub but failed. - c.addSubToRouteTargets(rsub) - if flags&pmrCollectQueueNames != 0 { - queues = append(queues, rsub.queue) - } - } - } - -sendToRoutesOrLeafs: - - // If no messages for routes or leafnodes return here. - if len(c.in.rts) == 0 { - updateStats() - return didDeliver, queues - } - - // If we do have a deliver subject we need to do something with it. - // Again this is when JetStream (but possibly others) wants the system - // to rewrite the delivered subject. The way we will do that is place it - // at the end of the reply subject if it exists. - if len(deliver) > 0 && len(reply) > 0 { - reply = append(reply, '@') - reply = append(reply, deliver...) - } - - // Copy off original pa in case it changes. - pa := c.pa - - if mt != nil { - // We are going to replace "pa" with our copy of c.pa, but to restore - // to the original copy of c.pa, we need to save it again. - cpa := pa - msg = mt.setOriginAccountHeaderIfNeeded(c, acc, msg) - defer func() { c.pa = cpa }() - // Update pa with our current c.pa state. - pa = c.pa - } - - // We address by index to avoid struct copy. - // We have inline structs for memory layout and cache coherency. - for i := range c.in.rts { - rt := &c.in.rts[i] - dc := rt.sub.client - dmsg, hset := msg, false - - // Check if we have an origin cluster set from a leafnode message. - // If so make sure we do not send it back to the same cluster for a different - // leafnode. Cluster wide no echo. - if dc.kind == LEAF { - // Check two scenarios. One is inbound from a route (c.pa.origin), - // and the other is leaf to leaf. In both case, leafOrigin is the one - // to use for the comparison. - if leafOrigin != _EMPTY_ && leafOrigin == dc.remoteCluster() { - continue - } - - // We need to check if this is a request that has a stamped client information header. - // This will contain an account but will represent the account from the leafnode. If - // they are not named the same this would cause an account lookup failure trying to - // process the request for something like JetStream or other system services that rely - // on the client info header. We can just check for reply and the presence of a header - // to avoid slow downs for all traffic. - if len(c.pa.reply) > 0 && c.pa.hdr >= 0 { - dmsg, hset = c.checkLeafClientInfoHeader(msg) - } - } - - if mt != nil { - dmsg = mt.setHopHeader(c, dmsg) - hset = true - } - - mh := c.msgHeaderForRouteOrLeaf(subject, reply, rt, acc) - if c.deliverMsg(prodIsMQTT, rt.sub, acc, subject, reply, mh, dmsg, false) { - if rt.sub.icb == nil { - dlvMsgs++ - dlvExtraSize += int64(len(dmsg) - len(msg)) - } - didDeliver = true - } - - // If we set the header reset the origin pub args. - if hset { - c.pa = pa - } - } - updateStats() - return didDeliver, queues -} - -// Check and swap accounts on a client info header destined across a leafnode. -func (c *client) checkLeafClientInfoHeader(msg []byte) (dmsg []byte, setHdr bool) { - if c.pa.hdr < 0 || len(msg) < c.pa.hdr { - return msg, false - } - cir := sliceHeader(ClientInfoHdr, msg[:c.pa.hdr]) - if len(cir) == 0 { - return msg, false - } - - dmsg = msg - - var ci ClientInfo - if err := json.Unmarshal(cir, &ci); err == nil { - if v, _ := c.srv.leafRemoteAccounts.Load(ci.Account); v != nil { - remoteAcc := v.(string) - if ci.Account != remoteAcc { - ci.Account = remoteAcc - if b, _ := json.Marshal(ci); b != nil { - dmsg, setHdr = c.setHeader(ClientInfoHdr, bytesToString(b), msg), true - } - } - } - } - return dmsg, setHdr -} - -func (c *client) pubPermissionViolation(subject []byte) { - errTxt := fmt.Sprintf("Permissions Violation for Publish to %q", subject) - if mt, _ := c.isMsgTraceEnabled(); mt != nil { - mt.setIngressError(errTxt) - } - c.sendErr(errTxt) - c.Errorf("Publish Violation - %s, Subject %q", c.getAuthUser(), subject) -} - -func (c *client) subPermissionViolation(sub *subscription) { - errTxt := fmt.Sprintf("Permissions Violation for Subscription to %q", sub.subject) - logTxt := fmt.Sprintf("Subscription Violation - %s, Subject %q, SID %s", - c.getAuthUser(), sub.subject, sub.sid) - - if sub.queue != nil { - errTxt = fmt.Sprintf("Permissions Violation for Subscription to %q using queue %q", sub.subject, sub.queue) - logTxt = fmt.Sprintf("Subscription Violation - %s, Subject %q, Queue: %q, SID %s", - c.getAuthUser(), sub.subject, sub.queue, sub.sid) - } - - c.sendErr(errTxt) - c.Errorf(logTxt) -} - -func (c *client) replySubjectViolation(reply []byte) { - errTxt := fmt.Sprintf("Permissions Violation for Publish with Reply of %q", reply) - if mt, _ := c.isMsgTraceEnabled(); mt != nil { - mt.setIngressError(errTxt) - } - c.sendErr(errTxt) - c.Errorf("Publish Violation - %s, Reply %q", c.getAuthUser(), reply) -} - -func (c *client) maxTokensViolation(sub *subscription) { - errTxt := fmt.Sprintf("Permissions Violation for Subscription to %q, too many tokens", sub.subject) - logTxt := fmt.Sprintf("Subscription Violation Too Many Tokens - %s, Subject %q, SID %s", - c.getAuthUser(), sub.subject, sub.sid) - c.sendErr(errTxt) - c.Errorf(logTxt) -} - -func (c *client) processPingTimer() { - c.mu.Lock() - c.ping.tmr = nil - // Check if connection is still opened - if c.isClosed() { - c.mu.Unlock() - return - } - - c.Debugf("%s Ping Timer", c.kindString()) - - var sendPing bool - - opts := c.srv.getOpts() - pingInterval := opts.PingInterval - if c.kind == ROUTER && opts.Cluster.PingInterval > 0 { - pingInterval = opts.Cluster.PingInterval - } - pingInterval = adjustPingInterval(c.kind, pingInterval) - now := time.Now() - needRTT := c.rtt == 0 || now.Sub(c.rttStart) > DEFAULT_RTT_MEASUREMENT_INTERVAL - - // Do not delay PINGs for ROUTER, GATEWAY or spoke LEAF connections. - if c.kind == ROUTER || c.kind == GATEWAY || c.isSpokeLeafNode() { - sendPing = true - } else { - // If we received client data or a ping from the other side within the PingInterval, - // then there is no need to send a ping. - if delta := now.Sub(c.lastIn); delta < pingInterval && !needRTT { - c.Debugf("Delaying PING due to remote client data or ping %v ago", delta.Round(time.Second)) - } else { - sendPing = true - } - } - - if sendPing { - // Check for violation - maxPingsOut := opts.MaxPingsOut - if c.kind == ROUTER && opts.Cluster.MaxPingsOut > 0 { - maxPingsOut = opts.Cluster.MaxPingsOut - } - if c.ping.out+1 > maxPingsOut { - c.Debugf("Stale Client Connection - Closing") - c.enqueueProto([]byte(fmt.Sprintf(errProto, "Stale Connection"))) - c.mu.Unlock() - c.closeConnection(StaleConnection) - return - } - // Send PING - c.sendPing() - } - - // Reset to fire again. - c.setPingTimer() - c.mu.Unlock() -} - -// Returns the smallest value between the given `d` and some max value -// based on the connection kind. -func adjustPingInterval(kind int, d time.Duration) time.Duration { - switch kind { - case ROUTER: - if d > routeMaxPingInterval { - return routeMaxPingInterval - } - case GATEWAY: - if d > gatewayMaxPingInterval { - return gatewayMaxPingInterval - } - } - return d -} - -// This is used when a connection cannot yet start to send PINGs because -// the remote would not be able to handle them (case of compression, -// or outbound gateway, etc...), but we still want to close the connection -// if the timer has not been reset by the time we reach the time equivalent -// to have sent the max number of pings. -// -// Lock should be held -func (c *client) watchForStaleConnection(pingInterval time.Duration, pingMax int) { - c.ping.tmr = time.AfterFunc(pingInterval*time.Duration(pingMax+1), func() { - c.mu.Lock() - c.Debugf("Stale Client Connection - Closing") - c.enqueueProto([]byte(fmt.Sprintf(errProto, "Stale Connection"))) - c.mu.Unlock() - c.closeConnection(StaleConnection) - }) -} - -// Lock should be held -func (c *client) setPingTimer() { - if c.srv == nil { - return - } - opts := c.srv.getOpts() - d := opts.PingInterval - if c.kind == ROUTER && opts.Cluster.PingInterval > 0 { - d = opts.Cluster.PingInterval - } - d = adjustPingInterval(c.kind, d) - c.ping.tmr = time.AfterFunc(d, c.processPingTimer) -} - -// Lock should be held -func (c *client) clearPingTimer() { - if c.ping.tmr == nil { - return - } - c.ping.tmr.Stop() - c.ping.tmr = nil -} - -func (c *client) clearTlsToTimer() { - if c.tlsTo == nil { - return - } - c.tlsTo.Stop() - c.tlsTo = nil -} - -// Lock should be held -func (c *client) setAuthTimer(d time.Duration) { - c.atmr = time.AfterFunc(d, c.authTimeout) -} - -// Lock should be held -func (c *client) clearAuthTimer() bool { - if c.atmr == nil { - return true - } - stopped := c.atmr.Stop() - c.atmr = nil - return stopped -} - -// We may reuse atmr for expiring user jwts, -// so check connectReceived. -// Lock assume held on entry. -func (c *client) awaitingAuth() bool { - return !c.flags.isSet(connectReceived) && c.atmr != nil -} - -// This will set the atmr for the JWT expiration time. -// We will lock on entry. -func (c *client) setExpirationTimer(d time.Duration) { - c.mu.Lock() - c.setExpirationTimerUnlocked(d) - c.mu.Unlock() -} - -// This will set the atmr for the JWT expiration time. client lock should be held before call -func (c *client) setExpirationTimerUnlocked(d time.Duration) { - c.atmr = time.AfterFunc(d, c.authExpired) - // This is an JWT expiration. - if c.flags.isSet(connectReceived) { - c.expires = time.Now().Add(d).Truncate(time.Second) - } -} - -// Return when this client expires via a claim, or 0 if not set. -func (c *client) claimExpiration() time.Duration { - c.mu.Lock() - defer c.mu.Unlock() - if c.expires.IsZero() { - return 0 - } - return time.Until(c.expires).Truncate(time.Second) -} - -// Possibly flush the connection and then close the low level connection. -// The boolean `minimalFlush` indicates if the flush operation should have a -// minimal write deadline. -// Lock is held on entry. -func (c *client) flushAndClose(minimalFlush bool) { - if !c.flags.isSet(skipFlushOnClose) && c.out.pb > 0 { - if minimalFlush { - const lowWriteDeadline = 100 * time.Millisecond - - // Reduce the write deadline if needed. - if c.out.wdl > lowWriteDeadline { - c.out.wdl = lowWriteDeadline - } - } - c.flushOutbound() - } - for i := range c.out.nb { - nbPoolPut(c.out.nb[i]) - } - c.out.nb = nil - // We can't touch c.out.wnb when a flushOutbound is in progress since it - // is accessed outside the lock there. If in progress, the cleanup will be - // done in flushOutbound when detecting that connection is closed. - if !c.flags.isSet(flushOutbound) { - for i := range c.out.wnb { - nbPoolPut(c.out.wnb[i]) - } - c.out.wnb = nil - } - // This seem to be important (from experimentation) for the GC to release - // the connection. - c.out.sg = nil - - // Close the low level connection. - if c.nc != nil { - // Starting with Go 1.16, the low level close will set its own deadline - // of 5 seconds, so setting our own deadline does not work. Instead, - // we will close the TLS connection in separate go routine. - nc := c.nc - c.nc = nil - if _, ok := nc.(*tls.Conn); ok { - go func() { nc.Close() }() - } else { - nc.Close() - } - } -} - -var kindStringMap = map[int]string{ - CLIENT: "Client", - ROUTER: "Router", - GATEWAY: "Gateway", - LEAF: "Leafnode", - JETSTREAM: "JetStream", - ACCOUNT: "Account", - SYSTEM: "System", -} - -func (c *client) kindString() string { - if kindStringVal, ok := kindStringMap[c.kind]; ok { - return kindStringVal - } - return "Unknown Type" -} - -// swapAccountAfterReload will check to make sure the bound account for this client -// is current. Under certain circumstances after a reload we could be pointing to -// an older one. -func (c *client) swapAccountAfterReload() { - c.mu.Lock() - srv := c.srv - an := c.acc.GetName() - c.mu.Unlock() - if srv == nil { - return - } - if acc, _ := srv.LookupAccount(an); acc != nil { - c.mu.Lock() - if c.acc != acc { - c.acc = acc - } - c.mu.Unlock() - } -} - -// processSubsOnConfigReload removes any subscriptions the client has that are no -// longer authorized, and checks for imports (accounts) due to a config reload. -func (c *client) processSubsOnConfigReload(awcsti map[string]struct{}) { - c.mu.Lock() - var ( - checkPerms = c.perms != nil - checkAcc = c.acc != nil - acc = c.acc - ) - if !checkPerms && !checkAcc { - c.mu.Unlock() - return - } - var ( - _subs [32]*subscription - subs = _subs[:0] - _removed [32]*subscription - removed = _removed[:0] - srv = c.srv - ) - if checkAcc { - // We actually only want to check if stream imports have changed. - if _, ok := awcsti[acc.Name]; !ok { - checkAcc = false - } - } - // We will clear any mperms we have here. It will rebuild on the fly with canSubscribe, - // so we do that here as we collect them. We will check result down below. - c.mperms = nil - // Collect client's subs under the lock - for _, sub := range c.subs { - // Just checking to rebuild mperms under the lock, will collect removed though here. - // Only collect under subs array of canSubscribe and checkAcc true. - canSub := c.canSubscribe(string(sub.subject)) - canQSub := sub.queue != nil && c.canSubscribe(string(sub.subject), string(sub.queue)) - - if !canSub && !canQSub { - removed = append(removed, sub) - } else if checkAcc { - subs = append(subs, sub) - } - } - c.mu.Unlock() - - // This list is all subs who are allowed and we need to check accounts. - for _, sub := range subs { - c.mu.Lock() - oldShadows := sub.shadow - sub.shadow = nil - c.mu.Unlock() - c.addShadowSubscriptions(acc, sub, true) - for _, nsub := range oldShadows { - nsub.im.acc.sl.Remove(nsub) - } - } - - // Unsubscribe all that need to be removed and report back to client and logs. - for _, sub := range removed { - c.unsubscribe(acc, sub, true, true) - c.sendErr(fmt.Sprintf("Permissions Violation for Subscription to %q (sid %q)", - sub.subject, sub.sid)) - srv.Noticef("Removed sub %q (sid %q) for %s - not authorized", - sub.subject, sub.sid, c.getAuthUser()) - } -} - -// Allows us to count up all the queue subscribers during close. -type qsub struct { - sub *subscription - n int32 -} - -func (c *client) closeConnection(reason ClosedState) { - c.mu.Lock() - if c.flags.isSet(closeConnection) { - c.mu.Unlock() - return - } - // Note that we may have markConnAsClosed() invoked before closeConnection(), - // so don't set this to 1, instead bump the count. - c.rref++ - c.flags.set(closeConnection) - c.clearAuthTimer() - c.clearPingTimer() - c.clearTlsToTimer() - c.markConnAsClosed(reason) - - // Unblock anyone who is potentially stalled waiting on us. - if c.out.stc != nil { - close(c.out.stc) - c.out.stc = nil - } - - // If we have remote latency tracking running shut that down. - if c.rrTracking != nil { - c.rrTracking.ptmr.Stop() - c.rrTracking = nil - } - - // If we are shutting down, no need to do all the accounting on subs, etc. - if reason == ServerShutdown { - s := c.srv - c.mu.Unlock() - if s != nil { - // Unregister - s.removeClient(c) - } - return - } - - var ( - kind = c.kind - srv = c.srv - noReconnect = c.flags.isSet(noReconnect) - acc = c.acc - spoke bool - ) - - // Snapshot for use if we are a client connection. - // FIXME(dlc) - we can just stub in a new one for client - // and reference existing one. - var subs []*subscription - if kind == CLIENT || kind == LEAF || kind == JETSTREAM { - var _subs [32]*subscription - subs = _subs[:0] - // Do not set c.subs to nil or delete the sub from c.subs here because - // it will be needed in saveClosedClient (which has been started as a - // go routine in markConnAsClosed). Cleanup will be done there. - for _, sub := range c.subs { - // Auto-unsubscribe subscriptions must be unsubscribed forcibly. - sub.max = 0 - sub.close() - subs = append(subs, sub) - } - spoke = c.isSpokeLeafNode() - } - - c.mu.Unlock() - - // Remove client's or leaf node or jetstream subscriptions. - if acc != nil && (kind == CLIENT || kind == LEAF || kind == JETSTREAM) { - acc.sl.RemoveBatch(subs) - } else if kind == ROUTER { - c.removeRemoteSubs() - } - - if srv != nil { - // Unregister - srv.removeClient(c) - - if acc != nil { - // Update remote subscriptions. - if kind == CLIENT || kind == LEAF || kind == JETSTREAM { - qsubs := map[string]*qsub{} - for _, sub := range subs { - // Call unsubscribe here to cleanup shadow subscriptions and such. - c.unsubscribe(acc, sub, true, false) - // Update route as normal for a normal subscriber. - if sub.queue == nil { - if !spoke { - srv.updateRouteSubscriptionMap(acc, sub, -1) - if srv.gateway.enabled { - srv.gatewayUpdateSubInterest(acc.Name, sub, -1) - } - } - acc.updateLeafNodes(sub, -1) - } else { - // We handle queue subscribers special in case we - // have a bunch we can just send one update to the - // connected routes. - num := int32(1) - if kind == LEAF { - num = sub.qw - } - key := keyFromSub(sub) - if esub, ok := qsubs[key]; ok { - esub.n += num - } else { - qsubs[key] = &qsub{sub, num} - } - } - } - // Process any qsubs here. - for _, esub := range qsubs { - if !spoke { - srv.updateRouteSubscriptionMap(acc, esub.sub, -(esub.n)) - if srv.gateway.enabled { - srv.gatewayUpdateSubInterest(acc.Name, esub.sub, -(esub.n)) - } - } - acc.updateLeafNodes(esub.sub, -(esub.n)) - } - } - // Always remove from the account, otherwise we can leak clients. - // Note that SYSTEM and ACCOUNT types from above cleanup their own subs. - if prev := acc.removeClient(c); prev == 1 { - srv.decActiveAccounts() - } - } - } - - // Now that we are done with subscriptions, clear the field so that the - // connection can be released and gc'ed. - if kind == CLIENT || kind == LEAF { - c.mu.Lock() - c.subs = nil - c.mu.Unlock() - } - - // Don't reconnect connections that have been marked with - // the no reconnect flag. - if noReconnect { - return - } - - c.reconnect() -} - -// Depending on the kind of connections, this may attempt to recreate a connection. -// The actual reconnect attempt will be started in a go routine. -func (c *client) reconnect() { - var ( - retryImplicit bool - gwName string - gwIsOutbound bool - gwCfg *gatewayCfg - leafCfg *leafNodeCfg - ) - - c.mu.Lock() - // Decrease the ref count and perform the reconnect only if == 0. - c.rref-- - if c.flags.isSet(noReconnect) || c.rref > 0 { - c.mu.Unlock() - return - } - if c.route != nil { - // A route is marked as solicited if it was given an URL to connect to, - // which would be the case even with implicit (due to gossip), so mark this - // as a retry for a route that is solicited and not explicit. - retryImplicit = c.route.retry || (c.route.didSolicit && c.route.routeType == Implicit) - } - kind := c.kind - switch kind { - case GATEWAY: - gwName = c.gw.name - gwIsOutbound = c.gw.outbound - gwCfg = c.gw.cfg - case LEAF: - if c.isSolicitedLeafNode() { - leafCfg = c.leaf.remote - } - } - srv := c.srv - c.mu.Unlock() - - // Check for a solicited route. If it was, start up a reconnect unless - // we are already connected to the other end. - if didSolicit := c.isSolicitedRoute(); didSolicit || retryImplicit { - srv.mu.Lock() - defer srv.mu.Unlock() - - // Capture these under lock - c.mu.Lock() - rid := c.route.remoteID - rtype := c.route.routeType - rurl := c.route.url - accName := string(c.route.accName) - checkRID := accName == _EMPTY_ && srv.getOpts().Cluster.PoolSize < 1 && rid != _EMPTY_ - c.mu.Unlock() - - // It is possible that the server is being shutdown. - // If so, don't try to reconnect - if !srv.isRunning() { - return - } - - if checkRID && srv.routes[rid] != nil { - // This is the case of "no pool". Make sure that the registered one - // is upgraded to solicited if the connection trying to reconnect - // was a solicited one. - if didSolicit { - if remote := srv.routes[rid][0]; remote != nil { - upgradeRouteToSolicited(remote, rurl, rtype) - } - } - srv.Debugf("Not attempting reconnect for solicited route, already connected to %q", rid) - return - } else if rid == srv.info.ID { - srv.Debugf("Detected route to self, ignoring %q", rurl.Redacted()) - return - } else if rtype != Implicit || retryImplicit { - srv.Debugf("Attempting reconnect for solicited route %q", rurl.Redacted()) - // Keep track of this go-routine so we can wait for it on - // server shutdown. - srv.startGoRoutine(func() { srv.reConnectToRoute(rurl, rtype, accName) }) - } - } else if srv != nil && kind == GATEWAY && gwIsOutbound { - if gwCfg != nil { - srv.Debugf("Attempting reconnect for gateway %q", gwName) - // Run this as a go routine since we may be called within - // the solicitGateway itself if there was an error during - // the creation of the gateway connection. - srv.startGoRoutine(func() { srv.reconnectGateway(gwCfg) }) - } else { - srv.Debugf("Gateway %q not in configuration, not attempting reconnect", gwName) - } - } else if leafCfg != nil { - // Check if this is a solicited leaf node. Start up a reconnect. - srv.startGoRoutine(func() { srv.reConnectToRemoteLeafNode(leafCfg) }) - } -} - -// Set the noReconnect flag. This is used before a call to closeConnection() -// to prevent the connection to reconnect (routes, gateways). -func (c *client) setNoReconnect() { - c.mu.Lock() - c.flags.set(noReconnect) - c.mu.Unlock() -} - -// Returns the client's RTT value with the protection of the client's lock. -func (c *client) getRTTValue() time.Duration { - c.mu.Lock() - rtt := c.rtt - c.mu.Unlock() - return rtt -} - -// This function is used by ROUTER and GATEWAY connections to -// look for a subject on a given account (since these type of -// connections are not bound to a specific account). -// If the c.pa.subject is found in the cache, the cached result -// is returned, otherwse, we match the account's sublist and update -// the cache. The cache is pruned if reaching a certain size. -func (c *client) getAccAndResultFromCache() (*Account, *SublistResult) { - var ( - acc *Account - pac *perAccountCache - r *SublistResult - ok bool - ) - // Check our cache. - if pac, ok = c.in.pacache[string(c.pa.pacache)]; ok { - // Check the genid to see if it's still valid. - // Since v2.10.0, the config reload of accounts has been fixed - // and an account's sublist pointer should not change, so no need to - // lock to access it. - sl := pac.acc.sl - - if genid := atomic.LoadUint64(&sl.genid); genid != pac.genid { - ok = false - c.in.pacache = make(map[string]*perAccountCache) - } else { - acc = pac.acc - r = pac.results - } - } - - if !ok { - if c.kind == ROUTER && len(c.route.accName) > 0 { - if acc = c.acc; acc == nil { - return nil, nil - } - } else { - // Match correct account and sublist. - if acc, _ = c.srv.LookupAccount(bytesToString(c.pa.account)); acc == nil { - return nil, nil - } - } - sl := acc.sl - - // Match against the account sublist. - r = sl.MatchBytes(c.pa.subject) - - // Check if we need to prune. - if len(c.in.pacache) >= maxPerAccountCacheSize { - c.prunePerAccountCache() - } - - // Store in our cache,make sure to do so after we prune. - c.in.pacache[string(c.pa.pacache)] = &perAccountCache{acc, r, atomic.LoadUint64(&sl.genid)} - } - return acc, r -} - -// Account will return the associated account for this client. -func (c *client) Account() *Account { - if c == nil { - return nil - } - c.mu.Lock() - acc := c.acc - c.mu.Unlock() - return acc -} - -// prunePerAccountCache will prune off a random number of cache entries. -func (c *client) prunePerAccountCache() { - n := 0 - for cacheKey := range c.in.pacache { - delete(c.in.pacache, cacheKey) - if n++; n > prunePerAccountCacheSize { - break - } - } -} - -// pruneClosedSubFromPerAccountCache remove entries that contain subscriptions -// that have been closed. -func (c *client) pruneClosedSubFromPerAccountCache() { - for cacheKey, pac := range c.in.pacache { - for _, sub := range pac.results.psubs { - if sub.isClosed() { - goto REMOVE - } - } - for _, qsub := range pac.results.qsubs { - for _, sub := range qsub { - if sub.isClosed() { - goto REMOVE - } - } - } - continue - REMOVE: - delete(c.in.pacache, cacheKey) - } -} - -// Returns our service account for this request. -func (ci *ClientInfo) serviceAccount() string { - if ci == nil { - return _EMPTY_ - } - if ci.Service != _EMPTY_ { - return ci.Service - } - return ci.Account -} - -// Add in our server and cluster information to this client info. -func (c *client) addServerAndClusterInfo(ci *ClientInfo) { - if ci == nil { - return - } - // Server - if c.kind != LEAF { - ci.Server = c.srv.Name() - } else if c.kind == LEAF { - ci.Server = c.leaf.remoteServer - } - // Cluster - ci.Cluster = c.srv.cachedClusterName() - // If we have gateways fill in cluster alternates. - // These will be in RTT asc order. - if c.srv.gateway.enabled { - var gws []*client - c.srv.getOutboundGatewayConnections(&gws) - for _, c := range gws { - c.mu.Lock() - cn := c.gw.name - c.mu.Unlock() - ci.Alternates = append(ci.Alternates, cn) - } - } -} - -// Grabs the information for this client. -func (c *client) getClientInfo(detailed bool) *ClientInfo { - if c == nil || (c.kind != CLIENT && c.kind != LEAF && c.kind != JETSTREAM && c.kind != ACCOUNT) { - return nil - } - - // Result - var ci ClientInfo - - if detailed { - c.addServerAndClusterInfo(&ci) - } - - c.mu.Lock() - // RTT and Account are always added. - ci.Account = accForClient(c) - ci.RTT = c.rtt - // Detailed signals additional opt in. - if detailed { - ci.Start = &c.start - ci.Host = c.host - ci.ID = c.cid - ci.Name = c.opts.Name - ci.User = c.getRawAuthUser() - ci.Lang = c.opts.Lang - ci.Version = c.opts.Version - ci.Jwt = c.opts.JWT - ci.IssuerKey = issuerForClient(c) - ci.NameTag = c.nameTag - ci.Tags = c.tags - ci.Kind = c.kindString() - ci.ClientType = c.clientTypeString() - } - c.mu.Unlock() - return &ci -} - -func (c *client) doTLSServerHandshake(typ string, tlsConfig *tls.Config, timeout float64, pCerts PinnedCertSet) error { - _, err := c.doTLSHandshake(typ, false, nil, tlsConfig, _EMPTY_, timeout, pCerts) - return err -} - -func (c *client) doTLSClientHandshake(typ string, url *url.URL, tlsConfig *tls.Config, tlsName string, timeout float64, pCerts PinnedCertSet) (bool, error) { - return c.doTLSHandshake(typ, true, url, tlsConfig, tlsName, timeout, pCerts) -} - -// Performs either server or client side (if solicit is true) TLS Handshake. -// On error, the TLS handshake error has been logged and the connection -// has been closed. -// -// Lock is held on entry. -func (c *client) doTLSHandshake(typ string, solicit bool, url *url.URL, tlsConfig *tls.Config, tlsName string, timeout float64, pCerts PinnedCertSet) (bool, error) { - var host string - var resetTLSName bool - var err error - - // Capture kind for some debug/error statements. - kind := c.kind - - // If we solicited, we will act like the client, otherwise the server. - if solicit { - c.Debugf("Starting TLS %s client handshake", typ) - if tlsConfig.ServerName == _EMPTY_ { - // If the given url is a hostname, use this hostname for the - // ServerName. If it is an IP, use the cfg's tlsName. If none - // is available, resort to current IP. - host = url.Hostname() - if tlsName != _EMPTY_ && net.ParseIP(host) != nil { - host = tlsName - } - tlsConfig.ServerName = host - } - c.nc = tls.Client(c.nc, tlsConfig) - } else { - if kind == CLIENT { - c.Debugf("Starting TLS client connection handshake") - } else { - c.Debugf("Starting TLS %s server handshake", typ) - } - c.nc = tls.Server(c.nc, tlsConfig) - } - - conn := c.nc.(*tls.Conn) - - // Setup the timeout - ttl := secondsToDuration(timeout) - c.tlsTo = time.AfterFunc(ttl, func() { tlsTimeout(c, conn) }) - conn.SetReadDeadline(time.Now().Add(ttl)) - - c.mu.Unlock() - if err = conn.Handshake(); err != nil { - if solicit { - // Based on type of error, possibly clear the saved tlsName - // See: https://github.com/nats-io/nats-server/issues/1256 - // NOTE: As of Go 1.20, the HostnameError is wrapped so cannot - // type assert to check directly. - var hostnameErr x509.HostnameError - if errors.As(err, &hostnameErr) { - if host == tlsName { - resetTLSName = true - } - } - } - } else if !c.matchesPinnedCert(pCerts) { - err = ErrCertNotPinned - } - - if err != nil { - if kind == CLIENT { - c.Errorf("TLS handshake error: %v", err) - } else { - c.Errorf("TLS %s handshake error: %v", typ, err) - } - c.closeConnection(TLSHandshakeError) - - // Grab the lock before returning since the caller was holding the lock on entry - c.mu.Lock() - // Returning any error is fine. Since the connection is closed ErrConnectionClosed - // is appropriate. - return resetTLSName, ErrConnectionClosed - } - - // Reset the read deadline - conn.SetReadDeadline(time.Time{}) - - // Re-Grab lock - c.mu.Lock() - - // To be consistent with client, set this flag to indicate that handshake is done - c.flags.set(handshakeComplete) - - // The connection still may have been closed on success handshake due - // to a race with tls timeout. If that the case, return error indicating - // that the connection is closed. - if c.isClosed() { - err = ErrConnectionClosed - } - - return false, err -} - -// getRawAuthUserLock returns the raw auth user for the client. -// Will acquire the client lock. -func (c *client) getRawAuthUserLock() string { - c.mu.Lock() - defer c.mu.Unlock() - return c.getRawAuthUser() -} - -// getRawAuthUser returns the raw auth user for the client. -// Lock should be held. -func (c *client) getRawAuthUser() string { - switch { - case c.opts.Nkey != _EMPTY_: - return c.opts.Nkey - case c.opts.Username != _EMPTY_: - return c.opts.Username - case c.opts.JWT != _EMPTY_: - return c.pubKey - case c.opts.Token != _EMPTY_: - return c.opts.Token - default: - return _EMPTY_ - } -} - -// getAuthUser returns the auth user for the client. -// Lock should be held. -func (c *client) getAuthUser() string { - switch { - case c.opts.Nkey != _EMPTY_: - return fmt.Sprintf("Nkey %q", c.opts.Nkey) - case c.opts.Username != _EMPTY_: - return fmt.Sprintf("User %q", c.opts.Username) - case c.opts.JWT != _EMPTY_: - return fmt.Sprintf("JWT User %q", c.pubKey) - case c.opts.Token != _EMPTY_: - return fmt.Sprintf("Token %q", c.opts.Token) - default: - return `User "N/A"` - } -} - -// Given an array of strings, this function converts it to a map as long -// as all the content (converted to upper-case) matches some constants. - -// Converts the given array of strings to a map of string. -// The strings are converted to upper-case and added to the map only -// if the server recognize them as valid connection types. -// If there are unknown connection types, the map of valid ones is returned -// along with an error that contains the name of the unknown. -func convertAllowedConnectionTypes(cts []string) (map[string]struct{}, error) { - var unknown []string - m := make(map[string]struct{}, len(cts)) - for _, i := range cts { - i = strings.ToUpper(i) - switch i { - case jwt.ConnectionTypeStandard, jwt.ConnectionTypeWebsocket, - jwt.ConnectionTypeLeafnode, jwt.ConnectionTypeLeafnodeWS, - jwt.ConnectionTypeMqtt, jwt.ConnectionTypeMqttWS, - jwt.ConnectionTypeInProcess: - m[i] = struct{}{} - default: - unknown = append(unknown, i) - } - } - var err error - // We will still return the map of valid ones. - if len(unknown) != 0 { - err = fmt.Errorf("invalid connection types %q", unknown) - } - return m, err -} - -// This will return true if the connection is of a type present in the given `acts` map. -// Note that so far this is used only for CLIENT or LEAF connections. -// But a CLIENT can be standard or websocket (and other types in the future). -func (c *client) connectionTypeAllowed(acts map[string]struct{}) bool { - // Empty means all type of clients are allowed - if len(acts) == 0 { - return true - } - var want string - switch c.kind { - case CLIENT: - switch c.clientType() { - case NATS: - if c.iproc { - want = jwt.ConnectionTypeInProcess - } else { - want = jwt.ConnectionTypeStandard - } - case WS: - want = jwt.ConnectionTypeWebsocket - case MQTT: - if c.isWebsocket() { - want = jwt.ConnectionTypeMqttWS - } else { - want = jwt.ConnectionTypeMqtt - } - } - case LEAF: - if c.isWebsocket() { - want = jwt.ConnectionTypeLeafnodeWS - } else { - want = jwt.ConnectionTypeLeafnode - } - } - _, ok := acts[want] - return ok -} - -// isClosed returns true if either closeConnection or connMarkedClosed -// flag have been set, or if `nc` is nil, which may happen in tests. -func (c *client) isClosed() bool { - return c.flags.isSet(closeConnection) || c.flags.isSet(connMarkedClosed) || c.nc == nil -} - -// Logging functionality scoped to a client or route. -func (c *client) Error(err error) { - c.srv.Errors(c, err) -} - -func (c *client) Errorf(format string, v ...any) { - format = fmt.Sprintf("%s - %s", c, format) - c.srv.Errorf(format, v...) -} - -func (c *client) Debugf(format string, v ...any) { - format = fmt.Sprintf("%s - %s", c, format) - c.srv.Debugf(format, v...) -} - -func (c *client) Noticef(format string, v ...any) { - format = fmt.Sprintf("%s - %s", c, format) - c.srv.Noticef(format, v...) -} - -func (c *client) Tracef(format string, v ...any) { - format = fmt.Sprintf("%s - %s", c, format) - c.srv.Tracef(format, v...) -} - -func (c *client) Warnf(format string, v ...any) { - format = fmt.Sprintf("%s - %s", c, format) - c.srv.Warnf(format, v...) -} - -func (c *client) rateLimitFormatWarnf(format string, v ...any) { - if _, loaded := c.srv.rateLimitLogging.LoadOrStore(format, time.Now()); loaded { - return - } - statement := fmt.Sprintf(format, v...) - c.Warnf("%s", statement) -} - -func (c *client) RateLimitWarnf(format string, v ...any) { - // Do the check before adding the client info to the format... - statement := fmt.Sprintf(format, v...) - if _, loaded := c.srv.rateLimitLogging.LoadOrStore(statement, time.Now()); loaded { - return - } - c.Warnf("%s", statement) -} - -// Set the very first PING to a lower interval to capture the initial RTT. -// After that the PING interval will be set to the user defined value. -// Client lock should be held. -func (c *client) setFirstPingTimer() { - s := c.srv - if s == nil { - return - } - opts := s.getOpts() - d := opts.PingInterval - - if c.kind == ROUTER && opts.Cluster.PingInterval > 0 { - d = opts.Cluster.PingInterval - } - if !opts.DisableShortFirstPing { - if c.kind != CLIENT { - if d > firstPingInterval { - d = firstPingInterval - } - d = adjustPingInterval(c.kind, d) - } else if d > firstClientPingInterval { - d = firstClientPingInterval - } - } - // We randomize the first one by an offset up to 20%, e.g. 2m ~= max 24s. - addDelay := rand.Int63n(int64(d / 5)) - d += time.Duration(addDelay) - // In the case of ROUTER/LEAF and when compression is configured, it is possible - // that this timer was already set, but just to detect a stale connection - // since we have to delay the first PING after compression negotiation - // occurred. - if c.ping.tmr != nil { - c.ping.tmr.Stop() - } - c.ping.tmr = time.AfterFunc(d, c.processPingTimer) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/const.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/const.go deleted file mode 100644 index ff78445b..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/const.go +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright 2012-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "regexp" - "runtime/debug" - "time" -) - -// Command is a signal used to control a running nats-server process. -type Command string - -// Valid Command values. -const ( - CommandStop = Command("stop") - CommandQuit = Command("quit") - CommandReopen = Command("reopen") - CommandReload = Command("reload") - - // private for now - commandLDMode = Command("ldm") - commandTerm = Command("term") -) - -var ( - // gitCommit and serverVersion injected at build. - gitCommit, serverVersion string - // trustedKeys is a whitespace separated array of trusted operator's public nkeys. - trustedKeys string - // SemVer regexp to validate the VERSION. - semVerRe = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) -) - -func init() { - // Use build info if present, it would be if building using 'go build .' - // or when using a release. - if info, ok := debug.ReadBuildInfo(); ok { - for _, setting := range info.Settings { - switch setting.Key { - case "vcs.revision": - gitCommit = setting.Value[:7] - } - } - } -} - -const ( - // VERSION is the current version for the server. - VERSION = "2.11.0" - - // PROTO is the currently supported protocol. - // 0 was the original - // 1 maintains proto 0, adds echo abilities for CONNECT from the client. Clients - // should not send echo unless proto in INFO is >= 1. - PROTO = 1 - - // DEFAULT_PORT is the default port for client connections. - DEFAULT_PORT = 4222 - - // RANDOM_PORT is the value for port that, when supplied, will cause the - // server to listen on a randomly-chosen available port. The resolved port - // is available via the Addr() method. - RANDOM_PORT = -1 - - // DEFAULT_HOST defaults to all interfaces. - DEFAULT_HOST = "0.0.0.0" - - // MAX_CONTROL_LINE_SIZE is the maximum allowed protocol control line size. - // 4k should be plenty since payloads sans connect/info string are separate. - MAX_CONTROL_LINE_SIZE = 4096 - - // MAX_PAYLOAD_SIZE is the maximum allowed payload size. Should be using - // something different if > 1MB payloads are needed. - MAX_PAYLOAD_SIZE = (1024 * 1024) - - // MAX_PAYLOAD_MAX_SIZE is the size at which the server will warn about - // max_payload being too high. In the future, the server may enforce/reject - // max_payload above this value. - MAX_PAYLOAD_MAX_SIZE = (8 * 1024 * 1024) - - // MAX_PENDING_SIZE is the maximum outbound pending bytes per client. - MAX_PENDING_SIZE = (64 * 1024 * 1024) - - // DEFAULT_MAX_CONNECTIONS is the default maximum connections allowed. - DEFAULT_MAX_CONNECTIONS = (64 * 1024) - - // TLS_TIMEOUT is the TLS wait time. - TLS_TIMEOUT = 2 * time.Second - - // DEFAULT_TLS_HANDSHAKE_FIRST_FALLBACK_DELAY is the default amount of - // time for the server to wait for the TLS handshake with a client to - // be initiated before falling back to sending the INFO protocol first. - // See TLSHandshakeFirst and TLSHandshakeFirstFallback options. - DEFAULT_TLS_HANDSHAKE_FIRST_FALLBACK_DELAY = 50 * time.Millisecond - - // AUTH_TIMEOUT is the authorization wait time. - AUTH_TIMEOUT = 2 * time.Second - - // DEFAULT_PING_INTERVAL is how often pings are sent to clients, etc... - DEFAULT_PING_INTERVAL = 2 * time.Minute - - // DEFAULT_PING_MAX_OUT is maximum allowed pings outstanding before disconnect. - DEFAULT_PING_MAX_OUT = 2 - - // CR_LF string - CR_LF = "\r\n" - - // LEN_CR_LF hold onto the computed size. - LEN_CR_LF = len(CR_LF) - - // DEFAULT_FLUSH_DEADLINE is the write/flush deadlines. - DEFAULT_FLUSH_DEADLINE = 10 * time.Second - - // DEFAULT_HTTP_PORT is the default monitoring port. - DEFAULT_HTTP_PORT = 8222 - - // DEFAULT_HTTP_BASE_PATH is the default base path for monitoring. - DEFAULT_HTTP_BASE_PATH = "/" - - // ACCEPT_MIN_SLEEP is the minimum acceptable sleep times on temporary errors. - ACCEPT_MIN_SLEEP = 10 * time.Millisecond - - // ACCEPT_MAX_SLEEP is the maximum acceptable sleep times on temporary errors - ACCEPT_MAX_SLEEP = 1 * time.Second - - // DEFAULT_ROUTE_CONNECT Route solicitation intervals. - DEFAULT_ROUTE_CONNECT = 1 * time.Second - - // DEFAULT_ROUTE_RECONNECT Route reconnect intervals. - DEFAULT_ROUTE_RECONNECT = 1 * time.Second - - // DEFAULT_ROUTE_DIAL Route dial timeout. - DEFAULT_ROUTE_DIAL = 1 * time.Second - - // DEFAULT_ROUTE_POOL_SIZE Route default pool size - DEFAULT_ROUTE_POOL_SIZE = 3 - - // DEFAULT_LEAF_NODE_RECONNECT LeafNode reconnect interval. - DEFAULT_LEAF_NODE_RECONNECT = time.Second - - // DEFAULT_LEAF_TLS_TIMEOUT TLS timeout for LeafNodes - DEFAULT_LEAF_TLS_TIMEOUT = 2 * time.Second - - // PROTO_SNIPPET_SIZE is the default size of proto to print on parse errors. - PROTO_SNIPPET_SIZE = 32 - - // MAX_CONTROL_LINE_SNIPPET_SIZE is the default size of proto to print on max control line errors. - MAX_CONTROL_LINE_SNIPPET_SIZE = 128 - - // MAX_MSG_ARGS Maximum possible number of arguments from MSG proto. - MAX_MSG_ARGS = 4 - - // MAX_RMSG_ARGS Maximum possible number of arguments from RMSG proto. - MAX_RMSG_ARGS = 6 - - // MAX_HMSG_ARGS Maximum possible number of arguments from HMSG proto. - MAX_HMSG_ARGS = 7 - - // MAX_PUB_ARGS Maximum possible number of arguments from PUB proto. - MAX_PUB_ARGS = 3 - - // MAX_HPUB_ARGS Maximum possible number of arguments from HPUB proto. - MAX_HPUB_ARGS = 4 - - // MAX_RSUB_ARGS Maximum possible number of arguments from a RS+/LS+ proto. - MAX_RSUB_ARGS = 6 - - // DEFAULT_MAX_CLOSED_CLIENTS is the maximum number of closed connections we hold onto. - DEFAULT_MAX_CLOSED_CLIENTS = 10000 - - // DEFAULT_LAME_DUCK_DURATION is the time in which the server spreads - // the closing of clients when signaled to go in lame duck mode. - DEFAULT_LAME_DUCK_DURATION = 2 * time.Minute - - // DEFAULT_LAME_DUCK_GRACE_PERIOD is the duration the server waits, after entering - // lame duck mode, before starting closing client connections. - DEFAULT_LAME_DUCK_GRACE_PERIOD = 10 * time.Second - - // DEFAULT_LEAFNODE_INFO_WAIT Route dial timeout. - DEFAULT_LEAFNODE_INFO_WAIT = 1 * time.Second - - // DEFAULT_LEAFNODE_PORT is the default port for remote leafnode connections. - DEFAULT_LEAFNODE_PORT = 7422 - - // DEFAULT_CONNECT_ERROR_REPORTS is the number of attempts at which a - // repeated failed route, gateway or leaf node connection is reported. - // This is used for initial connection, that is, when the server has - // never had a connection to the given endpoint. Once connected, and - // if a disconnect occurs, DEFAULT_RECONNECT_ERROR_REPORTS is used - // instead. - // The default is to report every 3600 attempts (roughly every hour). - DEFAULT_CONNECT_ERROR_REPORTS = 3600 - - // DEFAULT_RECONNECT_ERROR_REPORTS is the default number of failed - // attempt to reconnect a route, gateway or leaf node connection. - // The default is to report every attempt. - DEFAULT_RECONNECT_ERROR_REPORTS = 1 - - // DEFAULT_RTT_MEASUREMENT_INTERVAL is how often we want to measure RTT from - // this server to clients, routes, gateways or leafnode connections. - DEFAULT_RTT_MEASUREMENT_INTERVAL = time.Hour - - // DEFAULT_ALLOW_RESPONSE_MAX_MSGS is the default number of responses allowed - // for a reply subject. - DEFAULT_ALLOW_RESPONSE_MAX_MSGS = 1 - - // DEFAULT_ALLOW_RESPONSE_EXPIRATION is the default time allowed for a given - // dynamic response permission. - DEFAULT_ALLOW_RESPONSE_EXPIRATION = 2 * time.Minute - - // DEFAULT_SERVICE_EXPORT_RESPONSE_THRESHOLD is the default time that the system will - // expect a service export response to be delivered. This is used in corner cases for - // time based cleanup of reverse mapping structures. - DEFAULT_SERVICE_EXPORT_RESPONSE_THRESHOLD = 2 * time.Minute - - // DEFAULT_SERVICE_LATENCY_SAMPLING is the default sampling rate for service - // latency metrics - DEFAULT_SERVICE_LATENCY_SAMPLING = 100 - - // DEFAULT_SYSTEM_ACCOUNT - DEFAULT_SYSTEM_ACCOUNT = "$SYS" - - // DEFAULT GLOBAL_ACCOUNT - DEFAULT_GLOBAL_ACCOUNT = "$G" - - // DEFAULT_FETCH_TIMEOUT is the default time that the system will wait for an account fetch to return. - DEFAULT_ACCOUNT_FETCH_TIMEOUT = 1900 * time.Millisecond -) diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/consumer.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/consumer.go deleted file mode 100644 index 74080619..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/consumer.go +++ /dev/null @@ -1,6118 +0,0 @@ -// Copyright 2019-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "encoding/binary" - "encoding/json" - "errors" - "fmt" - "math/rand" - "reflect" - "regexp" - "slices" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/nats-io/nats-server/v2/server/avl" - "github.com/nats-io/nuid" - "golang.org/x/time/rate" -) - -// Headers sent with Request Timeout -const ( - JSPullRequestPendingMsgs = "Nats-Pending-Messages" - JSPullRequestPendingBytes = "Nats-Pending-Bytes" - JSPullRequestWrongPinID = "NATS/1.0 423 Nats-Wrong-Pin-Id\r\n\r\n" - JSPullRequestNatsPinId = "Nats-Pin-Id" -) - -var ( - validGroupName = regexp.MustCompile(`^[a-zA-Z0-9/_=-]{1,16}$`) -) - -// Headers sent when batch size was completed, but there were remaining bytes. -const JsPullRequestRemainingBytesT = "NATS/1.0 409 Batch Completed\r\n%s: %d\r\n%s: %d\r\n\r\n" - -type ConsumerInfo struct { - Stream string `json:"stream_name"` - Name string `json:"name"` - Created time.Time `json:"created"` - Config *ConsumerConfig `json:"config,omitempty"` - Delivered SequenceInfo `json:"delivered"` - AckFloor SequenceInfo `json:"ack_floor"` - NumAckPending int `json:"num_ack_pending"` - NumRedelivered int `json:"num_redelivered"` - NumWaiting int `json:"num_waiting"` - NumPending uint64 `json:"num_pending"` - Cluster *ClusterInfo `json:"cluster,omitempty"` - PushBound bool `json:"push_bound,omitempty"` - Paused bool `json:"paused,omitempty"` - PauseRemaining time.Duration `json:"pause_remaining,omitempty"` - // TimeStamp indicates when the info was gathered - TimeStamp time.Time `json:"ts"` - PriorityGroups []PriorityGroupState `json:"priority_groups,omitempty"` -} - -type PriorityGroupState struct { - Group string `json:"group"` - PinnedClientID string `json:"pinned_client_id,omitempty"` - PinnedTS time.Time `json:"pinned_ts,omitempty"` -} - -type ConsumerConfig struct { - Durable string `json:"durable_name,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - DeliverPolicy DeliverPolicy `json:"deliver_policy"` - OptStartSeq uint64 `json:"opt_start_seq,omitempty"` - OptStartTime *time.Time `json:"opt_start_time,omitempty"` - AckPolicy AckPolicy `json:"ack_policy"` - AckWait time.Duration `json:"ack_wait,omitempty"` - MaxDeliver int `json:"max_deliver,omitempty"` - BackOff []time.Duration `json:"backoff,omitempty"` - FilterSubject string `json:"filter_subject,omitempty"` - FilterSubjects []string `json:"filter_subjects,omitempty"` - ReplayPolicy ReplayPolicy `json:"replay_policy"` - RateLimit uint64 `json:"rate_limit_bps,omitempty"` // Bits per sec - SampleFrequency string `json:"sample_freq,omitempty"` - MaxWaiting int `json:"max_waiting,omitempty"` - MaxAckPending int `json:"max_ack_pending,omitempty"` - FlowControl bool `json:"flow_control,omitempty"` - HeadersOnly bool `json:"headers_only,omitempty"` - - // Pull based options. - MaxRequestBatch int `json:"max_batch,omitempty"` - MaxRequestExpires time.Duration `json:"max_expires,omitempty"` - MaxRequestMaxBytes int `json:"max_bytes,omitempty"` - - // Push based consumers. - DeliverSubject string `json:"deliver_subject,omitempty"` - DeliverGroup string `json:"deliver_group,omitempty"` - Heartbeat time.Duration `json:"idle_heartbeat,omitempty"` - - // Ephemeral inactivity threshold. - InactiveThreshold time.Duration `json:"inactive_threshold,omitempty"` - - // Generally inherited by parent stream and other markers, now can be configured directly. - Replicas int `json:"num_replicas"` - // Force memory storage. - MemoryStorage bool `json:"mem_storage,omitempty"` - - // Don't add to general clients. - Direct bool `json:"direct,omitempty"` - - // Metadata is additional metadata for the Consumer. - Metadata map[string]string `json:"metadata,omitempty"` - - // PauseUntil is for suspending the consumer until the deadline. - PauseUntil *time.Time `json:"pause_until,omitempty"` - - // Priority groups - PriorityGroups []string `json:"priority_groups,omitempty"` - PriorityPolicy PriorityPolicy `json:"priority_policy,omitempty"` - PinnedTTL time.Duration `json:"priority_timeout,omitempty"` -} - -// SequenceInfo has both the consumer and the stream sequence and last activity. -type SequenceInfo struct { - Consumer uint64 `json:"consumer_seq"` - Stream uint64 `json:"stream_seq"` - Last *time.Time `json:"last_active,omitempty"` -} - -type CreateConsumerRequest struct { - Stream string `json:"stream_name"` - Config ConsumerConfig `json:"config"` - Action ConsumerAction `json:"action"` - Pedantic bool `json:"pedantic,omitempty"` -} - -type ConsumerAction int - -const ( - ActionCreateOrUpdate ConsumerAction = iota - ActionUpdate - ActionCreate -) - -const ( - actionUpdateJSONString = `"update"` - actionCreateJSONString = `"create"` - actionCreateOrUpdateJSONString = `""` -) - -var ( - actionUpdateJSONBytes = []byte(actionUpdateJSONString) - actionCreateJSONBytes = []byte(actionCreateJSONString) - actionCreateOrUpdateJSONBytes = []byte(actionCreateOrUpdateJSONString) -) - -func (a ConsumerAction) String() string { - switch a { - case ActionCreateOrUpdate: - return actionCreateOrUpdateJSONString - case ActionCreate: - return actionCreateJSONString - case ActionUpdate: - return actionUpdateJSONString - } - return actionCreateOrUpdateJSONString -} - -func (a ConsumerAction) MarshalJSON() ([]byte, error) { - switch a { - case ActionCreate: - return actionCreateJSONBytes, nil - case ActionUpdate: - return actionUpdateJSONBytes, nil - case ActionCreateOrUpdate: - return actionCreateOrUpdateJSONBytes, nil - default: - return nil, fmt.Errorf("can not marshal %v", a) - } -} - -func (a *ConsumerAction) UnmarshalJSON(data []byte) error { - switch string(data) { - case actionCreateJSONString: - *a = ActionCreate - case actionUpdateJSONString: - *a = ActionUpdate - case actionCreateOrUpdateJSONString: - *a = ActionCreateOrUpdate - default: - return fmt.Errorf("unknown consumer action: %v", string(data)) - } - return nil -} - -// ConsumerNakOptions is for optional NAK values, e.g. delay. -type ConsumerNakOptions struct { - Delay time.Duration `json:"delay"` -} - -// PriorityPolicy determines policy for selecting messages based on priority. -type PriorityPolicy int - -const ( - // No priority policy. - PriorityNone PriorityPolicy = iota - // Clients will get the messages only if certain criteria are specified. - PriorityOverflow - // Single client takes over handling of the messages, while others are on standby. - PriorityPinnedClient -) - -const ( - PriorityNoneJSONString = `"none"` - PriorityOverflowJSONString = `"overflow"` - PriorityPinnedClientJSONString = `"pinned_client"` -) - -var ( - PriorityNoneJSONBytes = []byte(PriorityNoneJSONString) - PriorityOverflowJSONBytes = []byte(PriorityOverflowJSONString) - PriorityPinnedClientJSONBytes = []byte(PriorityPinnedClientJSONString) -) - -func (pp PriorityPolicy) String() string { - switch pp { - case PriorityOverflow: - return PriorityOverflowJSONString - case PriorityPinnedClient: - return PriorityPinnedClientJSONString - default: - return PriorityNoneJSONString - } -} - -func (pp PriorityPolicy) MarshalJSON() ([]byte, error) { - switch pp { - case PriorityOverflow: - return PriorityOverflowJSONBytes, nil - case PriorityPinnedClient: - return PriorityPinnedClientJSONBytes, nil - case PriorityNone: - return PriorityNoneJSONBytes, nil - default: - return nil, fmt.Errorf("unknown priority policy: %v", pp) - } -} - -func (pp *PriorityPolicy) UnmarshalJSON(data []byte) error { - switch string(data) { - case PriorityOverflowJSONString: - *pp = PriorityOverflow - case PriorityPinnedClientJSONString: - *pp = PriorityPinnedClient - case PriorityNoneJSONString: - *pp = PriorityNone - default: - return fmt.Errorf("unknown priority policy: %v", string(data)) - } - return nil -} - -// DeliverPolicy determines how the consumer should select the first message to deliver. -type DeliverPolicy int - -const ( - // DeliverAll will be the default so can be omitted from the request. - DeliverAll DeliverPolicy = iota - // DeliverLast will start the consumer with the last sequence received. - DeliverLast - // DeliverNew will only deliver new messages that are sent after the consumer is created. - DeliverNew - // DeliverByStartSequence will look for a defined starting sequence to start. - DeliverByStartSequence - // DeliverByStartTime will select the first messsage with a timestamp >= to StartTime. - DeliverByStartTime - // DeliverLastPerSubject will start the consumer with the last message for all subjects received. - DeliverLastPerSubject -) - -func (dp DeliverPolicy) String() string { - switch dp { - case DeliverAll: - return "all" - case DeliverLast: - return "last" - case DeliverNew: - return "new" - case DeliverByStartSequence: - return "by_start_sequence" - case DeliverByStartTime: - return "by_start_time" - case DeliverLastPerSubject: - return "last_per_subject" - default: - return "undefined" - } -} - -// AckPolicy determines how the consumer should acknowledge delivered messages. -type AckPolicy int - -const ( - // AckNone requires no acks for delivered messages. - AckNone AckPolicy = iota - // AckAll when acking a sequence number, this implicitly acks all sequences below this one as well. - AckAll - // AckExplicit requires ack or nack for all messages. - AckExplicit -) - -func (a AckPolicy) String() string { - switch a { - case AckNone: - return "none" - case AckAll: - return "all" - default: - return "explicit" - } -} - -// ReplayPolicy determines how the consumer should replay messages it already has queued in the stream. -type ReplayPolicy int - -const ( - // ReplayInstant will replay messages as fast as possible. - ReplayInstant ReplayPolicy = iota - // ReplayOriginal will maintain the same timing as the messages were received. - ReplayOriginal -) - -func (r ReplayPolicy) String() string { - switch r { - case ReplayInstant: - return replayInstantPolicyJSONString - default: - return replayOriginalPolicyJSONString - } -} - -// OK -const OK = "+OK" - -// Ack responses. Note that a nil or no payload is same as AckAck -var ( - // Ack - AckAck = []byte("+ACK") // nil or no payload to ack subject also means ACK - AckOK = []byte(OK) // deprecated but +OK meant ack as well. - - // Nack - AckNak = []byte("-NAK") - // Progress indicator - AckProgress = []byte("+WPI") - // Ack + Deliver the next message(s). - AckNext = []byte("+NXT") - // Terminate delivery of the message. - AckTerm = []byte("+TERM") -) - -const ( - // reasons to supply when terminating messages using limits - ackTermLimitsReason = "Message deleted by stream limits" - ackTermUnackedLimitsReason = "Unacknowledged message was deleted" -) - -// Calculate accurate replicas for the consumer config with the parent stream config. -func (consCfg ConsumerConfig) replicas(strCfg *StreamConfig) int { - if consCfg.Replicas == 0 || consCfg.Replicas > strCfg.Replicas { - if !isDurableConsumer(&consCfg) && strCfg.Retention == LimitsPolicy && consCfg.Replicas == 0 { - // Matches old-school ephemerals only, where the replica count is 0. - return 1 - } - return strCfg.Replicas - } - return consCfg.Replicas -} - -// Consumer is a jetstream consumer. -type consumer struct { - // Atomic used to notify that we want to process an ack. - // This will be checked in checkPending to abort processing - // and let ack be processed in priority. - awl int64 - leader atomic.Bool - mu sync.RWMutex - js *jetStream - mset *stream - acc *Account - srv *Server - client *client - sysc *client - sid int - name string - stream string - sseq uint64 // next stream sequence - subjf subjectFilters // subject filters and their sequences - filters *Sublist // When we have multiple filters we will use LoadNextMsgMulti and pass this in. - dseq uint64 // delivered consumer sequence - adflr uint64 // ack delivery floor - asflr uint64 // ack store floor - chkflr uint64 // our check floor, interest streams only. - npc int64 // Num Pending Count - npf uint64 // Num Pending Floor Sequence - dsubj string - qgroup string - lss *lastSeqSkipList - rlimit *rate.Limiter - reqSub *subscription - ackSub *subscription - ackReplyT string - ackSubj string - nextMsgSubj string - nextMsgReqs *ipQueue[*nextMsgReq] - maxp int - pblimit int - maxpb int - pbytes int - fcsz int - fcid string - fcSub *subscription - outq *jsOutQ - pending map[uint64]*Pending - ptmr *time.Timer - ptmrEnd time.Time - rdq []uint64 - rdqi avl.SequenceSet - rdc map[uint64]uint64 - replies map[uint64]string - maxdc uint64 - waiting *waitQueue - cfg ConsumerConfig - ici *ConsumerInfo - store ConsumerStore - active bool - replay bool - dtmr *time.Timer - uptmr *time.Timer // Unpause timer - gwdtmr *time.Timer - dthresh time.Duration - mch chan struct{} // Message channel - qch chan struct{} // Quit channel - inch chan bool // Interest change channel - sfreq int32 - ackEventT string - nakEventT string - deliveryExcEventT string - created time.Time - ldt time.Time - lat time.Time - lwqic time.Time - closed bool - - // Clustered. - ca *consumerAssignment - node RaftNode - infoSub *subscription - lqsent time.Time - prm map[string]struct{} - prOk bool - uch chan struct{} - retention RetentionPolicy - - monitorWg sync.WaitGroup - inMonitor bool - - // R>1 proposals - pch chan struct{} - phead *proposal - ptail *proposal - - // Ack queue - ackMsgs *ipQueue[*jsAckMsg] - - // for stream signaling when multiple filters are set. - sigSubs []string - - // Priority groups - // Details described in ADR-42. - - // currentPinId is the current nuid for the pinned consumer. - // If the Consumer is running in `PriorityPinnedClient` mode, server will - // pick up a new nuid and assign it to first pending pull request. - currentPinId string - /// pinnedTtl is the remaining time before the current PinId expires. - pinnedTtl *time.Timer - pinnedTS time.Time -} - -// A single subject filter. -type subjectFilter struct { - subject string - tokenizedSubject []string - hasWildcard bool -} - -type subjectFilters []*subjectFilter - -// subjects is a helper function used for updating consumers. -// It is not used and should not be used in hotpath. -func (s subjectFilters) subjects() []string { - subjects := make([]string, 0, len(s)) - for _, filter := range s { - subjects = append(subjects, filter.subject) - } - return subjects -} - -type proposal struct { - data []byte - next *proposal -} - -const ( - // JsAckWaitDefault is the default AckWait, only applicable on explicit ack policy consumers. - JsAckWaitDefault = 30 * time.Second - // JsDeleteWaitTimeDefault is the default amount of time we will wait for non-durable - // consumers to be in an inactive state before deleting them. - JsDeleteWaitTimeDefault = 5 * time.Second - // JsFlowControlMaxPending specifies default pending bytes during flow control that can be outstanding. - JsFlowControlMaxPending = 32 * 1024 * 1024 - // JsDefaultMaxAckPending is set for consumers with explicit ack that do not set the max ack pending. - JsDefaultMaxAckPending = 1000 - // JsDefaultPinnedTTL is the default grace period for the pinned consumer to send a new request before a new pin - // is picked by a server. - JsDefaultPinnedTTL = 2 * time.Minute -) - -// Helper function to set consumer config defaults from above. -func setConsumerConfigDefaults(config *ConsumerConfig, streamCfg *StreamConfig, lim *JSLimitOpts, accLim *JetStreamAccountLimits, pedantic bool) *ApiError { - // Set to default if not specified. - if config.DeliverSubject == _EMPTY_ && config.MaxWaiting == 0 { - config.MaxWaiting = JSWaitQueueDefaultMax - } - // Setup proper default for ack wait if we are in explicit ack mode. - if config.AckWait == 0 && (config.AckPolicy == AckExplicit || config.AckPolicy == AckAll) { - config.AckWait = JsAckWaitDefault - } - // Setup default of -1, meaning no limit for MaxDeliver. - if config.MaxDeliver == 0 { - config.MaxDeliver = -1 - } - // If BackOff was specified that will override the AckWait and the MaxDeliver. - if len(config.BackOff) > 0 { - if pedantic && config.AckWait != config.BackOff[0] { - return NewJSPedanticError(errors.New("first backoff value has to equal batch AckWait")) - } - config.AckWait = config.BackOff[0] - } - if config.MaxAckPending == 0 { - if pedantic && streamCfg.ConsumerLimits.MaxAckPending > 0 { - return NewJSPedanticError(errors.New("max_ack_pending must be set if it's configured in stream limits")) - } - config.MaxAckPending = streamCfg.ConsumerLimits.MaxAckPending - } - if config.InactiveThreshold == 0 { - if pedantic && streamCfg.ConsumerLimits.InactiveThreshold > 0 { - return NewJSPedanticError(errors.New("inactive_threshold must be set if it's configured in stream limits")) - } - config.InactiveThreshold = streamCfg.ConsumerLimits.InactiveThreshold - } - // Set proper default for max ack pending if we are ack explicit and none has been set. - if (config.AckPolicy == AckExplicit || config.AckPolicy == AckAll) && config.MaxAckPending == 0 { - accPending := JsDefaultMaxAckPending - if lim.MaxAckPending > 0 && lim.MaxAckPending < accPending { - accPending = lim.MaxAckPending - } - if accLim.MaxAckPending > 0 && accLim.MaxAckPending < accPending { - accPending = accLim.MaxAckPending - } - config.MaxAckPending = accPending - } - // if applicable set max request batch size - if config.DeliverSubject == _EMPTY_ && config.MaxRequestBatch == 0 && lim.MaxRequestBatch > 0 { - if pedantic { - return NewJSPedanticError(errors.New("max_request_batch must be set if it's JetStream limits are set")) - } - config.MaxRequestBatch = lim.MaxRequestBatch - } - - // set the default value only if pinned policy is used. - if config.PriorityPolicy == PriorityPinnedClient && config.PinnedTTL == 0 { - config.PinnedTTL = JsDefaultPinnedTTL - } - return nil -} - -// Check the consumer config. If we are recovering don't check filter subjects. -func checkConsumerCfg( - config *ConsumerConfig, - srvLim *JSLimitOpts, - cfg *StreamConfig, - _ *Account, - accLim *JetStreamAccountLimits, - isRecovering bool, -) *ApiError { - - // Check if replicas is defined but exceeds parent stream. - if config.Replicas > 0 && config.Replicas > cfg.Replicas { - return NewJSConsumerReplicasExceedsStreamError() - } - // Check that it is not negative - if config.Replicas < 0 { - return NewJSReplicasCountCannotBeNegativeError() - } - // If the stream is interest or workqueue retention make sure the replicas - // match that of the stream. This is REQUIRED for now. - if cfg.Retention == InterestPolicy || cfg.Retention == WorkQueuePolicy { - // Only error here if not recovering. - // We handle recovering in a different spot to allow consumer to come up - // if previous version allowed it to be created. We do not want it to not come up. - if !isRecovering && config.Replicas != 0 && config.Replicas != cfg.Replicas { - return NewJSConsumerReplicasShouldMatchStreamError() - } - } - - // Check if we have a BackOff defined that MaxDeliver is within range etc. - if lbo := len(config.BackOff); lbo > 0 && config.MaxDeliver != -1 && lbo > config.MaxDeliver { - return NewJSConsumerMaxDeliverBackoffError() - } - - if len(config.Description) > JSMaxDescriptionLen { - return NewJSConsumerDescriptionTooLongError(JSMaxDescriptionLen) - } - - // For now expect a literal subject if its not empty. Empty means work queue mode (pull mode). - if config.DeliverSubject != _EMPTY_ { - if !subjectIsLiteral(config.DeliverSubject) { - return NewJSConsumerDeliverToWildcardsError() - } - if !IsValidSubject(config.DeliverSubject) { - return NewJSConsumerInvalidDeliverSubjectError() - } - if deliveryFormsCycle(cfg, config.DeliverSubject) { - return NewJSConsumerDeliverCycleError() - } - if config.MaxWaiting != 0 { - return NewJSConsumerPushMaxWaitingError() - } - if config.MaxAckPending > 0 && config.AckPolicy == AckNone { - return NewJSConsumerMaxPendingAckPolicyRequiredError() - } - if config.Heartbeat > 0 && config.Heartbeat < 100*time.Millisecond { - return NewJSConsumerSmallHeartbeatError() - } - } else { - // Pull mode with work queue retention from the stream requires an explicit ack. - if config.AckPolicy == AckNone && cfg.Retention == WorkQueuePolicy { - return NewJSConsumerPullRequiresAckError() - } - if config.RateLimit > 0 { - return NewJSConsumerPullWithRateLimitError() - } - if config.MaxWaiting < 0 { - return NewJSConsumerMaxWaitingNegativeError() - } - if config.Heartbeat > 0 { - return NewJSConsumerHBRequiresPushError() - } - if config.FlowControl { - return NewJSConsumerFCRequiresPushError() - } - if config.MaxRequestBatch < 0 { - return NewJSConsumerMaxRequestBatchNegativeError() - } - if config.MaxRequestExpires != 0 && config.MaxRequestExpires < time.Millisecond { - return NewJSConsumerMaxRequestExpiresToSmallError() - } - if srvLim.MaxRequestBatch > 0 && config.MaxRequestBatch > srvLim.MaxRequestBatch { - return NewJSConsumerMaxRequestBatchExceededError(srvLim.MaxRequestBatch) - } - } - if srvLim.MaxAckPending > 0 && config.MaxAckPending > srvLim.MaxAckPending { - return NewJSConsumerMaxPendingAckExcessError(srvLim.MaxAckPending) - } - if accLim.MaxAckPending > 0 && config.MaxAckPending > accLim.MaxAckPending { - return NewJSConsumerMaxPendingAckExcessError(accLim.MaxAckPending) - } - if cfg.ConsumerLimits.MaxAckPending > 0 && config.MaxAckPending > cfg.ConsumerLimits.MaxAckPending { - return NewJSConsumerMaxPendingAckExcessError(cfg.ConsumerLimits.MaxAckPending) - } - if cfg.ConsumerLimits.InactiveThreshold > 0 && config.InactiveThreshold > cfg.ConsumerLimits.InactiveThreshold { - return NewJSConsumerInactiveThresholdExcessError(cfg.ConsumerLimits.InactiveThreshold) - } - - // Direct need to be non-mapped ephemerals. - if config.Direct { - if config.DeliverSubject == _EMPTY_ { - return NewJSConsumerDirectRequiresPushError() - } - if isDurableConsumer(config) { - return NewJSConsumerDirectRequiresEphemeralError() - } - } - - // Do not allow specifying both FilterSubject and FilterSubjects, - // as that's probably unintentional without any difference from passing - // all filters in FilterSubjects. - if config.FilterSubject != _EMPTY_ && len(config.FilterSubjects) > 0 { - return NewJSConsumerDuplicateFilterSubjectsError() - } - - if config.FilterSubject != _EMPTY_ && !IsValidSubject(config.FilterSubject) { - return NewJSStreamInvalidConfigError(ErrBadSubject) - } - - // We treat FilterSubjects: []string{""} as a misconfig, so we validate against it. - for _, filter := range config.FilterSubjects { - if filter == _EMPTY_ { - return NewJSConsumerEmptyFilterError() - } - } - subjectFilters := gatherSubjectFilters(config.FilterSubject, config.FilterSubjects) - - // Check subject filters do not overlap. - for outer, subject := range subjectFilters { - if !IsValidSubject(subject) { - return NewJSStreamInvalidConfigError(ErrBadSubject) - } - for inner, ssubject := range subjectFilters { - if inner != outer && SubjectsCollide(subject, ssubject) { - return NewJSConsumerOverlappingSubjectFiltersError() - } - } - } - - // Helper function to formulate similar errors. - badStart := func(dp, start string) error { - return fmt.Errorf("consumer delivery policy is deliver %s, but optional start %s is also set", dp, start) - } - notSet := func(dp, notSet string) error { - return fmt.Errorf("consumer delivery policy is deliver %s, but optional %s is not set", dp, notSet) - } - - // Check on start position conflicts. - switch config.DeliverPolicy { - case DeliverAll: - if config.OptStartSeq > 0 { - return NewJSConsumerInvalidPolicyError(badStart("all", "sequence")) - } - if config.OptStartTime != nil { - return NewJSConsumerInvalidPolicyError(badStart("all", "time")) - } - case DeliverLast: - if config.OptStartSeq > 0 { - return NewJSConsumerInvalidPolicyError(badStart("last", "sequence")) - } - if config.OptStartTime != nil { - return NewJSConsumerInvalidPolicyError(badStart("last", "time")) - } - case DeliverLastPerSubject: - if config.OptStartSeq > 0 { - return NewJSConsumerInvalidPolicyError(badStart("last per subject", "sequence")) - } - if config.OptStartTime != nil { - return NewJSConsumerInvalidPolicyError(badStart("last per subject", "time")) - } - if config.FilterSubject == _EMPTY_ && len(config.FilterSubjects) == 0 { - return NewJSConsumerInvalidPolicyError(notSet("last per subject", "filter subject")) - } - case DeliverNew: - if config.OptStartSeq > 0 { - return NewJSConsumerInvalidPolicyError(badStart("new", "sequence")) - } - if config.OptStartTime != nil { - return NewJSConsumerInvalidPolicyError(badStart("new", "time")) - } - case DeliverByStartSequence: - if config.OptStartSeq == 0 { - return NewJSConsumerInvalidPolicyError(notSet("by start sequence", "start sequence")) - } - if config.OptStartTime != nil { - return NewJSConsumerInvalidPolicyError(badStart("by start sequence", "time")) - } - case DeliverByStartTime: - if config.OptStartTime == nil { - return NewJSConsumerInvalidPolicyError(notSet("by start time", "start time")) - } - if config.OptStartSeq != 0 { - return NewJSConsumerInvalidPolicyError(badStart("by start time", "start sequence")) - } - } - - if config.SampleFrequency != _EMPTY_ { - s := strings.TrimSuffix(config.SampleFrequency, "%") - if sampleFreq, err := strconv.Atoi(s); err != nil || sampleFreq < 0 { - return NewJSConsumerInvalidSamplingError(err) - } - } - - // We reject if flow control is set without heartbeats. - if config.FlowControl && config.Heartbeat == 0 { - return NewJSConsumerWithFlowControlNeedsHeartbeatsError() - } - - if config.Durable != _EMPTY_ && config.Name != _EMPTY_ { - if config.Name != config.Durable { - return NewJSConsumerCreateDurableAndNameMismatchError() - } - } - - var metadataLen int - for k, v := range config.Metadata { - metadataLen += len(k) + len(v) - } - if metadataLen > JSMaxMetadataLen { - return NewJSConsumerMetadataLengthError(fmt.Sprintf("%dKB", JSMaxMetadataLen/1024)) - } - - if config.PriorityPolicy != PriorityNone { - if len(config.PriorityGroups) == 0 { - return NewJSConsumerPriorityPolicyWithoutGroupError() - } - - for _, group := range config.PriorityGroups { - if group == _EMPTY_ { - return NewJSConsumerEmptyGroupNameError() - } - if !validGroupName.MatchString(group) { - return NewJSConsumerInvalidGroupNameError() - } - } - } - - // For now don't allow preferred server in placement. - if cfg.Placement != nil && cfg.Placement.Preferred != _EMPTY_ { - return NewJSStreamInvalidConfigError(fmt.Errorf("preferred server not permitted in placement")) - } - - return nil -} - -func (mset *stream) addConsumerWithAction(config *ConsumerConfig, action ConsumerAction, pedantic bool) (*consumer, error) { - return mset.addConsumerWithAssignment(config, _EMPTY_, nil, false, action, pedantic) -} - -func (mset *stream) addConsumer(config *ConsumerConfig) (*consumer, error) { - return mset.addConsumerWithAction(config, ActionCreateOrUpdate, false) -} - -func (mset *stream) addConsumerWithAssignment(config *ConsumerConfig, oname string, ca *consumerAssignment, isRecovering bool, action ConsumerAction, pedantic bool) (*consumer, error) { - // Check if this stream has closed. - if mset.closed.Load() { - return nil, NewJSStreamInvalidError() - } - - mset.mu.RLock() - s, jsa, cfg, acc := mset.srv, mset.jsa, mset.cfg, mset.acc - mset.mu.RUnlock() - - // If we do not have the consumer currently assigned to us in cluster mode we will proceed but warn. - // This can happen on startup with restored state where on meta replay we still do not have - // the assignment. Running in single server mode this always returns true. - if oname != _EMPTY_ && !jsa.consumerAssigned(mset.name(), oname) { - s.Debugf("Consumer %q > %q does not seem to be assigned to this server", mset.name(), oname) - } - - if config == nil { - return nil, NewJSConsumerConfigRequiredError() - } - - selectedLimits, _, _, _ := acc.selectLimits(config.replicas(&cfg)) - if selectedLimits == nil { - return nil, NewJSNoLimitsError() - } - - srvLim := &s.getOpts().JetStreamLimits - // Make sure we have sane defaults. Do so with the JS lock, otherwise a - // badly timed meta snapshot can result in a race condition. - mset.js.mu.Lock() - err := setConsumerConfigDefaults(config, &cfg, srvLim, selectedLimits, pedantic) - mset.js.mu.Unlock() - if err != nil { - return nil, err - } - - if err := checkConsumerCfg(config, srvLim, &cfg, acc, selectedLimits, isRecovering); err != nil { - return nil, err - } - sampleFreq := 0 - if config.SampleFrequency != _EMPTY_ { - // Can't fail as checkConsumerCfg checks correct format - sampleFreq, _ = strconv.Atoi(strings.TrimSuffix(config.SampleFrequency, "%")) - } - - // Grab the client, account and server reference. - c := mset.client - if c == nil { - return nil, NewJSStreamInvalidError() - } - var accName string - c.mu.Lock() - s, a := c.srv, c.acc - if a != nil { - accName = a.Name - } - c.mu.Unlock() - - // Hold mset lock here. - mset.mu.Lock() - if mset.client == nil || mset.store == nil || mset.consumers == nil { - mset.mu.Unlock() - return nil, NewJSStreamInvalidError() - } - - // If this one is durable and already exists, we let that be ok as long as only updating what should be allowed. - var cName string - if isDurableConsumer(config) { - cName = config.Durable - } else if config.Name != _EMPTY_ { - cName = config.Name - } - if cName != _EMPTY_ { - if eo, ok := mset.consumers[cName]; ok { - mset.mu.Unlock() - if action == ActionCreate && !reflect.DeepEqual(*config, eo.config()) { - return nil, NewJSConsumerAlreadyExistsError() - } - // Check for overlapping subjects if we are a workqueue - if cfg.Retention == WorkQueuePolicy { - subjects := gatherSubjectFilters(config.FilterSubject, config.FilterSubjects) - if !mset.partitionUnique(cName, subjects) { - return nil, NewJSConsumerWQConsumerNotUniqueError() - } - } - err := eo.updateConfig(config) - if err == nil { - return eo, nil - } - return nil, NewJSConsumerCreateError(err, Unless(err)) - } - } - if action == ActionUpdate { - mset.mu.Unlock() - return nil, NewJSConsumerDoesNotExistError() - } - - // Check for any limits, if the config for the consumer sets a limit we check against that - // but if not we use the value from account limits, if account limits is more restrictive - // than stream config we prefer the account limits to handle cases where account limits are - // updated during the lifecycle of the stream - maxc := cfg.MaxConsumers - if maxc <= 0 || (selectedLimits.MaxConsumers > 0 && selectedLimits.MaxConsumers < maxc) { - maxc = selectedLimits.MaxConsumers - } - if maxc > 0 && mset.numPublicConsumers() >= maxc { - mset.mu.Unlock() - return nil, NewJSMaximumConsumersLimitError() - } - - // Check on stream type conflicts with WorkQueues. - if cfg.Retention == WorkQueuePolicy && !config.Direct { - // Force explicit acks here. - if config.AckPolicy != AckExplicit { - mset.mu.Unlock() - return nil, NewJSConsumerWQRequiresExplicitAckError() - } - - if len(mset.consumers) > 0 { - subjects := gatherSubjectFilters(config.FilterSubject, config.FilterSubjects) - if len(subjects) == 0 { - mset.mu.Unlock() - return nil, NewJSConsumerWQMultipleUnfilteredError() - } else if !mset.partitionUnique(cName, subjects) { - // Prior to v2.9.7, on a stream with WorkQueue policy, the servers - // were not catching the error of having multiple consumers with - // overlapping filter subjects depending on the scope, for instance - // creating "foo.*.bar" and then "foo.>" was not detected, while - // "foo.>" and then "foo.*.bar" would have been. Failing here - // in recovery mode would leave the rejected consumer in a bad state, - // so we will simply warn here, asking the user to remove this - // consumer administratively. Otherwise, if this is the creation - // of a new consumer, we will return the error. - if isRecovering { - s.Warnf("Consumer %q > %q has a filter subject that overlaps "+ - "with other consumers, which is not allowed for a stream "+ - "with WorkQueue policy, it should be administratively deleted", - cfg.Name, cName) - } else { - // We have a partition but it is not unique amongst the others. - mset.mu.Unlock() - return nil, NewJSConsumerWQConsumerNotUniqueError() - } - } - } - if config.DeliverPolicy != DeliverAll { - mset.mu.Unlock() - return nil, NewJSConsumerWQConsumerNotDeliverAllError() - } - } - - // Set name, which will be durable name if set, otherwise we create one at random. - o := &consumer{ - mset: mset, - js: s.getJetStream(), - acc: a, - srv: s, - client: s.createInternalJetStreamClient(), - sysc: s.createInternalJetStreamClient(), - cfg: *config, - dsubj: config.DeliverSubject, - outq: mset.outq, - active: true, - qch: make(chan struct{}), - uch: make(chan struct{}, 1), - mch: make(chan struct{}, 1), - sfreq: int32(sampleFreq), - maxdc: uint64(config.MaxDeliver), - maxp: config.MaxAckPending, - retention: cfg.Retention, - created: time.Now().UTC(), - } - - // Bind internal client to the user account. - o.client.registerWithAccount(a) - // Bind to the system account. - o.sysc.registerWithAccount(s.SystemAccount()) - - if isDurableConsumer(config) { - if len(config.Durable) > JSMaxNameLen { - mset.mu.Unlock() - o.deleteWithoutAdvisory() - return nil, NewJSConsumerNameTooLongError(JSMaxNameLen) - } - o.name = config.Durable - } else if oname != _EMPTY_ { - o.name = oname - } else { - if config.Name != _EMPTY_ { - o.name = config.Name - } else { - // Legacy ephemeral auto-generated. - for { - o.name = createConsumerName() - if _, ok := mset.consumers[o.name]; !ok { - break - } - } - config.Name = o.name - } - } - // Create ackMsgs queue now that we have a consumer name - o.ackMsgs = newIPQueue[*jsAckMsg](s, fmt.Sprintf("[ACC:%s] consumer '%s' on stream '%s' ackMsgs", accName, o.name, cfg.Name)) - - // Create our request waiting queue. - if o.isPullMode() { - o.waiting = newWaitQueue(config.MaxWaiting) - // Create our internal queue for next msg requests. - o.nextMsgReqs = newIPQueue[*nextMsgReq](s, fmt.Sprintf("[ACC:%s] consumer '%s' on stream '%s' pull requests", accName, o.name, cfg.Name)) - } - - // already under lock, mset.Name() would deadlock - o.stream = cfg.Name - o.ackEventT = JSMetricConsumerAckPre + "." + o.stream + "." + o.name - o.nakEventT = JSAdvisoryConsumerMsgNakPre + "." + o.stream + "." + o.name - o.deliveryExcEventT = JSAdvisoryConsumerMaxDeliveryExceedPre + "." + o.stream + "." + o.name - - if !isValidName(o.name) { - mset.mu.Unlock() - o.deleteWithoutAdvisory() - return nil, NewJSConsumerBadDurableNameError() - } - - // Setup our storage if not a direct consumer. - if !config.Direct { - store, err := mset.store.ConsumerStore(o.name, config) - if err != nil { - mset.mu.Unlock() - o.deleteWithoutAdvisory() - return nil, NewJSConsumerStoreFailedError(err) - } - o.store = store - } - - for _, filter := range gatherSubjectFilters(o.cfg.FilterSubject, o.cfg.FilterSubjects) { - sub := &subjectFilter{ - subject: filter, - hasWildcard: subjectHasWildcard(filter), - tokenizedSubject: tokenizeSubjectIntoSlice(nil, filter), - } - o.subjf = append(o.subjf, sub) - } - - // If we have multiple filter subjects, create a sublist which we will use - // in calling store.LoadNextMsgMulti. - if len(o.cfg.FilterSubjects) > 0 { - o.filters = NewSublistNoCache() - for _, filter := range o.cfg.FilterSubjects { - o.filters.Insert(&subscription{subject: []byte(filter)}) - } - } else { - // Make sure this is nil otherwise. - o.filters = nil - } - - if o.store != nil && o.store.HasState() { - // Restore our saved state. - o.mu.Lock() - o.readStoredState(0) - o.mu.Unlock() - } else { - // Select starting sequence number - o.selectStartingSeqNo() - } - - // Now register with mset and create the ack subscription. - // Check if we already have this one registered. - if eo, ok := mset.consumers[o.name]; ok { - mset.mu.Unlock() - if !o.isDurable() || !o.isPushMode() { - o.name = _EMPTY_ // Prevent removal since same name. - o.deleteWithoutAdvisory() - return nil, NewJSConsumerNameExistError() - } - // If we are here we have already registered this durable. If it is still active that is an error. - if eo.isActive() { - o.name = _EMPTY_ // Prevent removal since same name. - o.deleteWithoutAdvisory() - return nil, NewJSConsumerExistingActiveError() - } - // Since we are here this means we have a potentially new durable so we should update here. - // Check that configs are the same. - if !configsEqualSansDelivery(o.cfg, eo.cfg) { - o.name = _EMPTY_ // Prevent removal since same name. - o.deleteWithoutAdvisory() - return nil, NewJSConsumerReplacementWithDifferentNameError() - } - // Once we are here we have a replacement push-based durable. - eo.updateDeliverSubject(o.cfg.DeliverSubject) - return eo, nil - } - - // Set up the ack subscription for this consumer. Will use wildcard for all acks. - // We will remember the template to generate replies with sequence numbers and use - // that to scanf them back in. - // Escape '%' in consumer and stream names, as `pre` is used as a template later - // in consumer.ackReply(), resulting in erroneous formatting of the ack subject. - mn := strings.ReplaceAll(cfg.Name, "%", "%%") - pre := fmt.Sprintf(jsAckT, mn, strings.ReplaceAll(o.name, "%", "%%")) - o.ackReplyT = fmt.Sprintf("%s.%%d.%%d.%%d.%%d.%%d", pre) - o.ackSubj = fmt.Sprintf("%s.*.*.*.*.*", pre) - o.nextMsgSubj = fmt.Sprintf(JSApiRequestNextT, mn, o.name) - - // Check/update the inactive threshold - o.updateInactiveThreshold(&o.cfg) - - if o.isPushMode() { - // Check if we are running only 1 replica and that the delivery subject has interest. - // Check in place here for interest. Will setup properly in setLeader. - if config.replicas(&cfg) == 1 { - interest := o.acc.sl.HasInterest(o.cfg.DeliverSubject) - if !o.hasDeliveryInterest(interest) { - // Let the interest come to us eventually, but setup delete timer. - o.updateDeliveryInterest(false) - } - } - } - - // Set our ca. - if ca != nil { - o.setConsumerAssignment(ca) - } - - // Check if we have a rate limit set. - if config.RateLimit != 0 { - o.setRateLimit(config.RateLimit) - } - - mset.setConsumer(o) - mset.mu.Unlock() - - if config.Direct || (!s.JetStreamIsClustered() && s.standAloneMode()) { - o.setLeader(true) - } - - // This is always true in single server mode. - if o.IsLeader() { - // Send advisory. - var suppress bool - if !s.standAloneMode() && ca == nil { - suppress = true - } else if ca != nil { - suppress = ca.responded - } - if !suppress { - o.sendCreateAdvisory() - } - } - - return o, nil -} - -// Updates the consumer `dthresh` delete timer duration and set -// cfg.InactiveThreshold to JsDeleteWaitTimeDefault for ephemerals -// if not explicitly already specified by the user. -// Lock should be held. -func (o *consumer) updateInactiveThreshold(cfg *ConsumerConfig) { - // Ephemerals will always have inactive thresholds. - if !o.isDurable() && cfg.InactiveThreshold <= 0 { - // Add in 1 sec of jitter above and beyond the default of 5s. - o.dthresh = JsDeleteWaitTimeDefault + 100*time.Millisecond + time.Duration(rand.Int63n(900))*time.Millisecond - // Only stamp config with default sans jitter. - cfg.InactiveThreshold = JsDeleteWaitTimeDefault - } else if cfg.InactiveThreshold > 0 { - // Add in up to 1 sec of jitter if pull mode. - if o.isPullMode() { - o.dthresh = cfg.InactiveThreshold + 100*time.Millisecond + time.Duration(rand.Int63n(900))*time.Millisecond - } else { - o.dthresh = cfg.InactiveThreshold - } - } else if cfg.InactiveThreshold <= 0 { - // We accept InactiveThreshold be set to 0 (for durables) - o.dthresh = 0 - } -} - -// Updates the paused state. If we are the leader and the pause deadline -// hasn't passed yet then we will start a timer to kick the consumer once -// that deadline is reached. Lock should be held. -func (o *consumer) updatePauseState(cfg *ConsumerConfig) { - if o.uptmr != nil { - stopAndClearTimer(&o.uptmr) - } - if !o.isLeader() { - // Only the leader will run the timer as only the leader will run - // loopAndGatherMsgs. - return - } - if cfg.PauseUntil == nil || cfg.PauseUntil.IsZero() || cfg.PauseUntil.Before(time.Now()) { - // Either the PauseUntil is unset (is effectively zero) or the - // deadline has already passed, in which case there is nothing - // to do. - return - } - o.uptmr = time.AfterFunc(time.Until(*cfg.PauseUntil), func() { - o.mu.Lock() - defer o.mu.Unlock() - - stopAndClearTimer(&o.uptmr) - o.sendPauseAdvisoryLocked(&o.cfg) - o.signalNewMessages() - }) -} - -func (o *consumer) consumerAssignment() *consumerAssignment { - o.mu.RLock() - defer o.mu.RUnlock() - return o.ca -} - -func (o *consumer) setConsumerAssignment(ca *consumerAssignment) { - o.mu.Lock() - defer o.mu.Unlock() - - o.ca = ca - if ca == nil { - return - } - // Set our node. - o.node = ca.Group.node - - // Trigger update chan. - select { - case o.uch <- struct{}{}: - default: - } -} - -func (o *consumer) updateC() <-chan struct{} { - o.mu.RLock() - defer o.mu.RUnlock() - return o.uch -} - -// checkQueueInterest will check on our interest's queue group status. -// Lock should be held. -func (o *consumer) checkQueueInterest() { - if !o.active || o.cfg.DeliverSubject == _EMPTY_ { - return - } - subj := o.dsubj - if subj == _EMPTY_ { - subj = o.cfg.DeliverSubject - } - - if rr := o.acc.sl.Match(subj); len(rr.qsubs) > 0 { - // Just grab first - if qsubs := rr.qsubs[0]; len(qsubs) > 0 { - if sub := rr.qsubs[0][0]; len(sub.queue) > 0 { - o.qgroup = string(sub.queue) - } - } - } -} - -// clears our node if we have one. When we scale down to 1. -func (o *consumer) clearNode() { - o.mu.Lock() - defer o.mu.Unlock() - if o.node != nil { - o.node.Delete() - o.node = nil - } -} - -// IsLeader will return if we are the current leader. -func (o *consumer) IsLeader() bool { - return o.isLeader() -} - -// Lock should be held. -func (o *consumer) isLeader() bool { - return o.leader.Load() -} - -func (o *consumer) setLeader(isLeader bool) { - o.mu.RLock() - mset, closed := o.mset, o.closed - movingToClustered := o.node != nil && o.pch == nil - movingToNonClustered := o.node == nil && o.pch != nil - wasLeader := o.leader.Swap(isLeader) - o.mu.RUnlock() - - // If we are here we have a change in leader status. - if isLeader { - if closed || mset == nil { - return - } - - if wasLeader { - // If we detect we are scaling up, make sure to create clustered routines and channels. - if movingToClustered { - o.mu.Lock() - // We are moving from R1 to clustered. - o.pch = make(chan struct{}, 1) - go o.loopAndForwardProposals(o.qch) - if o.phead != nil { - select { - case o.pch <- struct{}{}: - default: - } - } - o.mu.Unlock() - } else if movingToNonClustered { - // We are moving from clustered to non-clustered now. - // Set pch to nil so if we scale back up we will recreate the loopAndForward from above. - o.mu.Lock() - pch := o.pch - o.pch = nil - select { - case pch <- struct{}{}: - default: - } - o.mu.Unlock() - } - return - } - - mset.mu.RLock() - s, jsa, stream, lseq := mset.srv, mset.jsa, mset.getCfgName(), mset.lseq - mset.mu.RUnlock() - - o.mu.Lock() - o.rdq = nil - o.rdqi.Empty() - - // Restore our saved state. - // During non-leader status we just update our underlying store when not clustered. - // If clustered we need to propose our initial (possibly skipped ahead) o.sseq to the group. - if o.node == nil || o.dseq > 1 || (o.store != nil && o.store.HasState()) { - o.readStoredState(lseq) - } else if o.node != nil && o.sseq >= 1 { - o.updateSkipped(o.sseq) - } - - // Setup initial num pending. - o.streamNumPending() - - // Cleanup lss when we take over in clustered mode. - if o.hasSkipListPending() && o.sseq >= o.lss.resume { - o.lss = nil - } - - // Do info sub. - if o.infoSub == nil && jsa != nil { - isubj := fmt.Sprintf(clusterConsumerInfoT, jsa.acc(), stream, o.name) - // Note below the way we subscribe here is so that we can send requests to ourselves. - o.infoSub, _ = s.systemSubscribe(isubj, _EMPTY_, false, o.sysc, o.handleClusterConsumerInfoRequest) - } - - var err error - if o.cfg.AckPolicy != AckNone { - if o.ackSub, err = o.subscribeInternal(o.ackSubj, o.pushAck); err != nil { - o.mu.Unlock() - o.deleteWithoutAdvisory() - return - } - } - - // Setup the internal sub for next message requests regardless. - // Will error if wrong mode to provide feedback to users. - if o.reqSub, err = o.subscribeInternal(o.nextMsgSubj, o.processNextMsgReq); err != nil { - o.mu.Unlock() - o.deleteWithoutAdvisory() - return - } - - // Check on flow control settings. - if o.cfg.FlowControl { - o.setMaxPendingBytes(JsFlowControlMaxPending) - fcsubj := fmt.Sprintf(jsFlowControl, stream, o.name) - if o.fcSub, err = o.subscribeInternal(fcsubj, o.processFlowControl); err != nil { - o.mu.Unlock() - o.deleteWithoutAdvisory() - return - } - } - - // If push mode, register for notifications on interest. - if o.isPushMode() { - o.inch = make(chan bool, 8) - o.acc.sl.registerNotification(o.cfg.DeliverSubject, o.cfg.DeliverGroup, o.inch) - if o.active = <-o.inch; o.active { - o.checkQueueInterest() - } - - // Check gateways in case they are enabled. - if s.gateway.enabled { - if !o.active { - o.active = s.hasGatewayInterest(o.acc.Name, o.cfg.DeliverSubject) - } - stopAndClearTimer(&o.gwdtmr) - o.gwdtmr = time.AfterFunc(time.Second, func() { o.watchGWinterest() }) - } - } - - if o.dthresh > 0 && (o.isPullMode() || !o.active) { - // Pull consumer. We run the dtmr all the time for this one. - stopAndClearTimer(&o.dtmr) - o.dtmr = time.AfterFunc(o.dthresh, o.deleteNotActive) - } - - // Update the consumer pause tracking. - o.updatePauseState(&o.cfg) - - // If we are not in ReplayInstant mode mark us as in replay state until resolved. - if o.cfg.ReplayPolicy != ReplayInstant { - o.replay = true - } - - // Recreate quit channel. - o.qch = make(chan struct{}) - qch := o.qch - node := o.node - if node != nil && o.pch == nil { - o.pch = make(chan struct{}, 1) - } - pullMode := o.isPullMode() - o.mu.Unlock() - - // Check if there are any pending we might need to clean up etc. - o.checkPending() - - // Snapshot initial info. - o.infoWithSnap(true) - - // These are the labels we will use to annotate our goroutines. - labels := pprofLabels{ - "type": "consumer", - "account": mset.accName(), - "stream": mset.name(), - "consumer": o.name, - } - - // Now start up Go routine to deliver msgs. - go func() { - setGoRoutineLabels(labels) - o.loopAndGatherMsgs(qch) - }() - - // Now start up Go routine to process acks. - go func() { - setGoRoutineLabels(labels) - o.processInboundAcks(qch) - }() - - if pullMode { - // Now start up Go routine to process inbound next message requests. - go func() { - setGoRoutineLabels(labels) - o.processInboundNextMsgReqs(qch) - }() - } - - // If we are R>1 spin up our proposal loop. - if node != nil { - // Determine if we can send pending requests info to the group. - // They must be on server versions >= 2.7.1 - o.checkAndSetPendingRequestsOk() - o.checkPendingRequests() - go func() { - setGoRoutineLabels(labels) - o.loopAndForwardProposals(qch) - }() - } - - } else { - // Shutdown the go routines and the subscriptions. - o.mu.Lock() - if o.qch != nil { - close(o.qch) - o.qch = nil - } - // Stop any inactivity timers. Should only be running on leaders. - stopAndClearTimer(&o.dtmr) - // Stop any unpause timers. Should only be running on leaders. - stopAndClearTimer(&o.uptmr) - // Make sure to clear out any re-deliver queues - o.stopAndClearPtmr() - o.rdq = nil - o.rdqi.Empty() - o.pending = nil - // ok if they are nil, we protect inside unsubscribe() - o.unsubscribe(o.ackSub) - o.unsubscribe(o.reqSub) - o.unsubscribe(o.fcSub) - o.ackSub, o.reqSub, o.fcSub = nil, nil, nil - if o.infoSub != nil { - o.srv.sysUnsubscribe(o.infoSub) - o.infoSub = nil - } - // Reset waiting if we are in pull mode. - if o.isPullMode() { - o.waiting = newWaitQueue(o.cfg.MaxWaiting) - o.nextMsgReqs.drain() - } else if o.srv.gateway.enabled { - stopAndClearTimer(&o.gwdtmr) - } - // If we were the leader make sure to drain queued up acks. - if wasLeader { - o.ackMsgs.drain() - // Reset amount of acks that need to be processed. - atomic.StoreInt64(&o.awl, 0) - // Also remove any pending replies since we should not be the one to respond at this point. - o.replies = nil - } - o.mu.Unlock() - } -} - -// This is coming on the wire so do not block here. -func (o *consumer) handleClusterConsumerInfoRequest(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) { - go o.infoWithSnapAndReply(false, reply) -} - -// Lock should be held. -func (o *consumer) subscribeInternal(subject string, cb msgHandler) (*subscription, error) { - c := o.client - if c == nil { - return nil, fmt.Errorf("invalid consumer") - } - if !c.srv.EventsEnabled() { - return nil, ErrNoSysAccount - } - if cb == nil { - return nil, fmt.Errorf("undefined message handler") - } - - o.sid++ - - // Now create the subscription - return c.processSub([]byte(subject), nil, []byte(strconv.Itoa(o.sid)), cb, false) -} - -// Unsubscribe from our subscription. -// Lock should be held. -func (o *consumer) unsubscribe(sub *subscription) { - if sub == nil || o.client == nil { - return - } - o.client.processUnsub(sub.sid) -} - -// We need to make sure we protect access to the outq. -// Do all advisory sends here. -func (o *consumer) sendAdvisory(subject string, e any) { - if o.acc == nil { - return - } - - // If there is no one listening for this advisory then save ourselves the effort - // and don't bother encoding the JSON or sending it. - if sl := o.acc.sl; (sl != nil && !sl.HasInterest(subject)) && !o.srv.hasGatewayInterest(o.acc.Name, subject) { - return - } - - j, err := json.Marshal(e) - if err != nil { - return - } - - o.outq.sendMsg(subject, j) -} - -func (o *consumer) sendDeleteAdvisoryLocked() { - e := JSConsumerActionAdvisory{ - TypedEvent: TypedEvent{ - Type: JSConsumerActionAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Stream: o.stream, - Consumer: o.name, - Action: DeleteEvent, - Domain: o.srv.getOpts().JetStreamDomain, - } - - subj := JSAdvisoryConsumerDeletedPre + "." + o.stream + "." + o.name - o.sendAdvisory(subj, e) -} - -func (o *consumer) sendPinnedAdvisoryLocked(group string) { - e := JSConsumerGroupPinnedAdvisory{ - TypedEvent: TypedEvent{ - Type: JSConsumerGroupPinnedAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Account: o.acc.Name, - Stream: o.stream, - Consumer: o.name, - Domain: o.srv.getOpts().JetStreamDomain, - PinnedClientId: o.currentPinId, - Group: group, - } - - subj := JSAdvisoryConsumerPinnedPre + "." + o.stream + "." + o.name - o.sendAdvisory(subj, e) - -} -func (o *consumer) sendUnpinnedAdvisoryLocked(group string, reason string) { - e := JSConsumerGroupUnpinnedAdvisory{ - TypedEvent: TypedEvent{ - Type: JSConsumerGroupUnpinnedAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Account: o.acc.Name, - Stream: o.stream, - Consumer: o.name, - Domain: o.srv.getOpts().JetStreamDomain, - Group: group, - Reason: reason, - } - - subj := JSAdvisoryConsumerUnpinnedPre + "." + o.stream + "." + o.name - o.sendAdvisory(subj, e) - -} - -func (o *consumer) sendCreateAdvisory() { - o.mu.Lock() - defer o.mu.Unlock() - - e := JSConsumerActionAdvisory{ - TypedEvent: TypedEvent{ - Type: JSConsumerActionAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Stream: o.stream, - Consumer: o.name, - Action: CreateEvent, - Domain: o.srv.getOpts().JetStreamDomain, - } - - subj := JSAdvisoryConsumerCreatedPre + "." + o.stream + "." + o.name - o.sendAdvisory(subj, e) -} - -func (o *consumer) sendPauseAdvisoryLocked(cfg *ConsumerConfig) { - e := JSConsumerPauseAdvisory{ - TypedEvent: TypedEvent{ - Type: JSConsumerPauseAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Stream: o.stream, - Consumer: o.name, - Domain: o.srv.getOpts().JetStreamDomain, - } - - if cfg.PauseUntil != nil { - e.PauseUntil = *cfg.PauseUntil - e.Paused = time.Now().Before(e.PauseUntil) - } - - subj := JSAdvisoryConsumerPausePre + "." + o.stream + "." + o.name - o.sendAdvisory(subj, e) -} - -// Created returns created time. -func (o *consumer) createdTime() time.Time { - o.mu.Lock() - created := o.created - o.mu.Unlock() - return created -} - -// Internal to allow creation time to be restored. -func (o *consumer) setCreatedTime(created time.Time) { - o.mu.Lock() - o.created = created - o.mu.Unlock() -} - -// This will check for extended interest in a subject. If we have local interest we just return -// that, but in the absence of local interest and presence of gateways or service imports we need -// to check those as well. -func (o *consumer) hasDeliveryInterest(localInterest bool) bool { - o.mu.RLock() - mset := o.mset - if mset == nil { - o.mu.RUnlock() - return false - } - acc := o.acc - deliver := o.cfg.DeliverSubject - o.mu.RUnlock() - - if localInterest { - return true - } - - // If we are here check gateways. - if s := acc.srv; s != nil && s.hasGatewayInterest(acc.Name, deliver) { - return true - } - return false -} - -func (s *Server) hasGatewayInterest(account, subject string) bool { - gw := s.gateway - if !gw.enabled { - return false - } - gw.RLock() - defer gw.RUnlock() - for _, gwc := range gw.outo { - psi, qr := gwc.gatewayInterest(account, stringToBytes(subject)) - if psi || qr != nil { - return true - } - } - return false -} - -// This processes an update to the local interest for a deliver subject. -func (o *consumer) updateDeliveryInterest(localInterest bool) bool { - interest := o.hasDeliveryInterest(localInterest) - - o.mu.Lock() - defer o.mu.Unlock() - - mset := o.mset - if mset == nil || o.isPullMode() { - return false - } - - if interest && !o.active { - o.signalNewMessages() - } - // Update active status, if not active clear any queue group we captured. - if o.active = interest; !o.active { - o.qgroup = _EMPTY_ - } else { - o.checkQueueInterest() - } - - // If the delete timer has already been set do not clear here and return. - // Note that durable can now have an inactive threshold, so don't check - // for durable status, instead check for dthresh > 0. - if o.dtmr != nil && o.dthresh > 0 && !interest { - return true - } - - // Stop and clear the delete timer always. - stopAndClearTimer(&o.dtmr) - - // If we do not have interest anymore and have a delete threshold set, then set - // a timer to delete us. We wait for a bit in case of server reconnect. - if !interest && o.dthresh > 0 { - o.dtmr = time.AfterFunc(o.dthresh, o.deleteNotActive) - return true - } - return false -} - -const ( - defaultConsumerNotActiveStartInterval = 30 * time.Second - defaultConsumerNotActiveMaxInterval = 5 * time.Minute -) - -var ( - consumerNotActiveStartInterval = defaultConsumerNotActiveStartInterval - consumerNotActiveMaxInterval = defaultConsumerNotActiveMaxInterval -) - -// deleteNotActive must only be called from time.AfterFunc or in its own -// goroutine, as it can block on clean-up. -func (o *consumer) deleteNotActive() { - // Take a copy of these when the goroutine starts, mostly it avoids a - // race condition with tests that modify these consts, such as - // TestJetStreamClusterGhostEphemeralsAfterRestart. - cnaMax := consumerNotActiveMaxInterval - cnaStart := consumerNotActiveStartInterval - - o.mu.Lock() - if o.mset == nil { - o.mu.Unlock() - return - } - // Push mode just look at active. - if o.isPushMode() { - // If we are active simply return. - if o.active { - o.mu.Unlock() - return - } - } else { - // Pull mode. - elapsed := time.Since(o.waiting.last) - if elapsed <= o.cfg.InactiveThreshold { - // These need to keep firing so reset but use delta. - if o.dtmr != nil { - o.dtmr.Reset(o.dthresh - elapsed) - } else { - o.dtmr = time.AfterFunc(o.dthresh-elapsed, o.deleteNotActive) - } - o.mu.Unlock() - return - } - // Check if we still have valid requests waiting. - if o.checkWaitingForInterest() { - if o.dtmr != nil { - o.dtmr.Reset(o.dthresh) - } else { - o.dtmr = time.AfterFunc(o.dthresh, o.deleteNotActive) - } - o.mu.Unlock() - return - } - } - - s, js := o.mset.srv, o.srv.js.Load() - acc, stream, name, isDirect := o.acc.Name, o.stream, o.name, o.cfg.Direct - var qch, cqch chan struct{} - if o.srv != nil { - qch = o.srv.quitCh - } - o.mu.Unlock() - if js != nil { - cqch = js.clusterQuitC() - } - - // Useful for pprof. - setGoRoutineLabels(pprofLabels{ - "account": acc, - "stream": stream, - "consumer": name, - }) - - // We will delete locally regardless. - defer o.delete() - - // If we are clustered, check if we still have this consumer assigned. - // If we do forward a proposal to delete ourselves to the metacontroller leader. - if !isDirect && s.JetStreamIsClustered() { - js.mu.RLock() - var ( - cca consumerAssignment - meta RaftNode - removeEntry []byte - ) - ca, cc := js.consumerAssignment(acc, stream, name), js.cluster - if ca != nil && cc != nil { - meta = cc.meta - cca = *ca - cca.Reply = _EMPTY_ - removeEntry = encodeDeleteConsumerAssignment(&cca) - meta.ForwardProposal(removeEntry) - } - js.mu.RUnlock() - - if ca != nil && cc != nil { - // Check to make sure we went away. - // Don't think this needs to be a monitored go routine. - jitter := time.Duration(rand.Int63n(int64(cnaStart))) - interval := cnaStart + jitter - ticker := time.NewTicker(interval) - defer ticker.Stop() - for { - select { - case <-ticker.C: - case <-qch: - return - case <-cqch: - return - } - js.mu.RLock() - if js.shuttingDown { - js.mu.RUnlock() - return - } - nca := js.consumerAssignment(acc, stream, name) - js.mu.RUnlock() - // Make sure this is the same consumer assignment, and not a new consumer with the same name. - if nca != nil && reflect.DeepEqual(nca, ca) { - s.Warnf("Consumer assignment for '%s > %s > %s' not cleaned up, retrying", acc, stream, name) - meta.ForwardProposal(removeEntry) - if interval < cnaMax { - interval *= 2 - ticker.Reset(interval) - } - continue - } - // We saw that consumer has been removed, all done. - return - } - } - } -} - -func (o *consumer) watchGWinterest() { - pa := o.isActive() - // If there is no local interest... - if o.hasNoLocalInterest() { - o.updateDeliveryInterest(false) - if !pa && o.isActive() { - o.signalNewMessages() - } - } - - // We want this to always be running so we can also pick up on interest returning. - o.mu.Lock() - if o.gwdtmr != nil { - o.gwdtmr.Reset(time.Second) - } else { - stopAndClearTimer(&o.gwdtmr) - o.gwdtmr = time.AfterFunc(time.Second, func() { o.watchGWinterest() }) - } - o.mu.Unlock() -} - -// Config returns the consumer's configuration. -func (o *consumer) config() ConsumerConfig { - o.mu.Lock() - defer o.mu.Unlock() - return o.cfg -} - -// Check if we have hit max deliveries. If so do notification and cleanup. -// Return whether or not the max was hit. -// Lock should be held. -func (o *consumer) hasMaxDeliveries(seq uint64) bool { - if o.maxdc == 0 { - return false - } - if dc := o.deliveryCount(seq); dc >= o.maxdc { - // We have hit our max deliveries for this sequence. - // Only send the advisory once. - if dc == o.maxdc { - o.notifyDeliveryExceeded(seq, dc) - } - // Determine if we signal to start flow of messages again. - if o.maxp > 0 && len(o.pending) >= o.maxp { - o.signalNewMessages() - } - // Make sure to remove from pending. - if p, ok := o.pending[seq]; ok && p != nil { - delete(o.pending, seq) - o.updateDelivered(p.Sequence, seq, dc, p.Timestamp) - } - // Ensure redelivered state is set, if not already. - if o.rdc == nil { - o.rdc = make(map[uint64]uint64) - } - o.rdc[seq] = dc - return true - } - return false -} - -// Force expiration of all pending. -// Lock should be held. -func (o *consumer) forceExpirePending() { - var expired []uint64 - for seq := range o.pending { - if !o.onRedeliverQueue(seq) && !o.hasMaxDeliveries(seq) { - expired = append(expired, seq) - } - } - if len(expired) > 0 { - slices.Sort(expired) - o.addToRedeliverQueue(expired...) - // Now we should update the timestamp here since we are redelivering. - // We will use an incrementing time to preserve order for any other redelivery. - off := time.Now().UnixNano() - o.pending[expired[0]].Timestamp - for _, seq := range expired { - if p, ok := o.pending[seq]; ok && p != nil { - p.Timestamp += off - } - } - o.resetPtmr(o.ackWait(0)) - } - o.signalNewMessages() -} - -// Acquire proper locks and update rate limit. -// Will use what is in config. -func (o *consumer) setRateLimitNeedsLocks() { - o.mu.RLock() - mset := o.mset - o.mu.RUnlock() - - if mset == nil { - return - } - - mset.mu.RLock() - o.mu.Lock() - o.setRateLimit(o.cfg.RateLimit) - o.mu.Unlock() - mset.mu.RUnlock() -} - -// Set the rate limiter -// Both mset and consumer lock should be held. -func (o *consumer) setRateLimit(bps uint64) { - if bps == 0 { - o.rlimit = nil - return - } - - // TODO(dlc) - Make sane values or error if not sane? - // We are configured in bits per sec so adjust to bytes. - rl := rate.Limit(bps / 8) - mset := o.mset - - // Burst should be set to maximum msg size for this account, etc. - var burst int - // We don't need to get cfgMu's rlock here since this function - // is already invoked under mset.mu.RLock(), which superseeds cfgMu. - if mset.cfg.MaxMsgSize > 0 { - burst = int(mset.cfg.MaxMsgSize) - } else if mset.jsa.account.limits.mpay > 0 { - burst = int(mset.jsa.account.limits.mpay) - } else { - s := mset.jsa.account.srv - burst = int(s.getOpts().MaxPayload) - } - - o.rlimit = rate.NewLimiter(rl, burst) -} - -// Check if new consumer config allowed vs old. -func (acc *Account) checkNewConsumerConfig(cfg, ncfg *ConsumerConfig) error { - if reflect.DeepEqual(cfg, ncfg) { - return nil - } - // Something different, so check since we only allow certain things to be updated. - if cfg.DeliverPolicy != ncfg.DeliverPolicy { - return errors.New("deliver policy can not be updated") - } - if cfg.OptStartSeq != ncfg.OptStartSeq { - return errors.New("start sequence can not be updated") - } - if cfg.OptStartTime != nil && ncfg.OptStartTime != nil { - // Both have start times set, compare them directly: - if !cfg.OptStartTime.Equal(*ncfg.OptStartTime) { - return errors.New("start time can not be updated") - } - } else if cfg.OptStartTime != nil || ncfg.OptStartTime != nil { - // At least one start time is set and the other is not - return errors.New("start time can not be updated") - } - if cfg.AckPolicy != ncfg.AckPolicy { - return errors.New("ack policy can not be updated") - } - if cfg.ReplayPolicy != ncfg.ReplayPolicy { - return errors.New("replay policy can not be updated") - } - if cfg.Heartbeat != ncfg.Heartbeat { - return errors.New("heart beats can not be updated") - } - if cfg.FlowControl != ncfg.FlowControl { - return errors.New("flow control can not be updated") - } - - // Deliver Subject is conditional on if its bound. - if cfg.DeliverSubject != ncfg.DeliverSubject { - if cfg.DeliverSubject == _EMPTY_ { - return errors.New("can not update pull consumer to push based") - } - if ncfg.DeliverSubject == _EMPTY_ { - return errors.New("can not update push consumer to pull based") - } - if acc.sl.HasInterest(cfg.DeliverSubject) { - return NewJSConsumerNameExistError() - } - } - - if cfg.MaxWaiting != ncfg.MaxWaiting { - return errors.New("max waiting can not be updated") - } - - // Check if BackOff is defined, MaxDeliver is within range. - if lbo := len(ncfg.BackOff); lbo > 0 && ncfg.MaxDeliver != -1 && lbo > ncfg.MaxDeliver { - return NewJSConsumerMaxDeliverBackoffError() - } - - return nil -} - -// Update the config based on the new config, or error if update not allowed. -func (o *consumer) updateConfig(cfg *ConsumerConfig) error { - o.mu.Lock() - defer o.mu.Unlock() - - if o.closed || o.mset == nil { - return NewJSConsumerDoesNotExistError() - } - - if err := o.acc.checkNewConsumerConfig(&o.cfg, cfg); err != nil { - return err - } - - // Make sure we always store PauseUntil in UTC. - if cfg.PauseUntil != nil { - utc := (*cfg.PauseUntil).UTC() - cfg.PauseUntil = &utc - } - - if o.store != nil { - // Update local state always. - if err := o.store.UpdateConfig(cfg); err != nil { - return err - } - } - - // DeliverSubject - if cfg.DeliverSubject != o.cfg.DeliverSubject { - o.updateDeliverSubjectLocked(cfg.DeliverSubject) - } - - // MaxAckPending - if cfg.MaxAckPending != o.cfg.MaxAckPending { - o.maxp = cfg.MaxAckPending - o.signalNewMessages() - } - // AckWait - if cfg.AckWait != o.cfg.AckWait { - if o.ptmr != nil { - o.resetPtmr(100 * time.Millisecond) - } - } - // Rate Limit - if cfg.RateLimit != o.cfg.RateLimit { - // We need both locks here so do in Go routine. - go o.setRateLimitNeedsLocks() - } - if cfg.SampleFrequency != o.cfg.SampleFrequency { - s := strings.TrimSuffix(cfg.SampleFrequency, "%") - // String has been already verified for validity up in the stack, so no - // need to check for error here. - sampleFreq, _ := strconv.Atoi(s) - o.sfreq = int32(sampleFreq) - } - // Set MaxDeliver if changed - if cfg.MaxDeliver != o.cfg.MaxDeliver { - o.maxdc = uint64(cfg.MaxDeliver) - } - // Set InactiveThreshold if changed. - if val := cfg.InactiveThreshold; val != o.cfg.InactiveThreshold { - o.updateInactiveThreshold(cfg) - stopAndClearTimer(&o.dtmr) - // Restart timer only if we are the leader. - if o.isLeader() && o.dthresh > 0 { - o.dtmr = time.AfterFunc(o.dthresh, o.deleteNotActive) - } - } - // Check whether the pause has changed - { - var old, new time.Time - if o.cfg.PauseUntil != nil { - old = *o.cfg.PauseUntil - } - if cfg.PauseUntil != nil { - new = *cfg.PauseUntil - } - if !old.Equal(new) { - o.updatePauseState(cfg) - if o.isLeader() { - o.sendPauseAdvisoryLocked(cfg) - } - } - } - - // Check for Subject Filters update. - newSubjects := gatherSubjectFilters(cfg.FilterSubject, cfg.FilterSubjects) - if !subjectSliceEqual(newSubjects, o.subjf.subjects()) { - newSubjf := make(subjectFilters, 0, len(newSubjects)) - for _, newFilter := range newSubjects { - fs := &subjectFilter{ - subject: newFilter, - hasWildcard: subjectHasWildcard(newFilter), - tokenizedSubject: tokenizeSubjectIntoSlice(nil, newFilter), - } - newSubjf = append(newSubjf, fs) - } - // Make sure we have correct signaling setup. - // Consumer lock can not be held. - mset := o.mset - o.mu.Unlock() - mset.swapSigSubs(o, newSubjf.subjects()) - o.mu.Lock() - - // When we're done with signaling, we can replace the subjects. - // If filters were removed, set `o.subjf` to nil. - if len(newSubjf) == 0 { - o.subjf = nil - o.filters = nil - } else { - o.subjf = newSubjf - if len(o.subjf) == 1 { - o.filters = nil - } else { - o.filters = NewSublistNoCache() - for _, filter := range o.subjf { - o.filters.Insert(&subscription{subject: []byte(filter.subject)}) - } - } - } - } - - // Record new config for others that do not need special handling. - // Allowed but considered no-op, [Description, SampleFrequency, MaxWaiting, HeadersOnly] - o.cfg = *cfg - - // Cleanup messages that lost interest. - if o.retention == InterestPolicy { - o.mu.Unlock() - o.cleanupNoInterestMessages(o.mset, false) - o.mu.Lock() - } - - // Re-calculate num pending on update. - o.streamNumPending() - - return nil -} - -// This is a config change for the delivery subject for a -// push based consumer. -func (o *consumer) updateDeliverSubject(newDeliver string) { - // Update the config and the dsubj - o.mu.Lock() - defer o.mu.Unlock() - o.updateDeliverSubjectLocked(newDeliver) -} - -// This is a config change for the delivery subject for a -// push based consumer. -func (o *consumer) updateDeliverSubjectLocked(newDeliver string) { - if o.closed || o.isPullMode() || o.cfg.DeliverSubject == newDeliver { - return - } - - // Force redeliver of all pending on change of delivery subject. - if len(o.pending) > 0 { - o.forceExpirePending() - } - - o.acc.sl.clearNotification(o.dsubj, o.cfg.DeliverGroup, o.inch) - o.dsubj, o.cfg.DeliverSubject = newDeliver, newDeliver - // When we register new one it will deliver to update state loop. - o.acc.sl.registerNotification(newDeliver, o.cfg.DeliverGroup, o.inch) -} - -// Check that configs are equal but allow delivery subjects to be different. -func configsEqualSansDelivery(a, b ConsumerConfig) bool { - // These were copied in so can set Delivery here. - a.DeliverSubject, b.DeliverSubject = _EMPTY_, _EMPTY_ - return reflect.DeepEqual(a, b) -} - -// Helper to send a reply to an ack. -func (o *consumer) sendAckReply(subj string) { - o.mu.RLock() - defer o.mu.RUnlock() - o.outq.sendMsg(subj, nil) -} - -type jsAckMsg struct { - subject string - reply string - hdr int - msg []byte -} - -var jsAckMsgPool sync.Pool - -func newJSAckMsg(subj, reply string, hdr int, msg []byte) *jsAckMsg { - var m *jsAckMsg - am := jsAckMsgPool.Get() - if am != nil { - m = am.(*jsAckMsg) - } else { - m = &jsAckMsg{} - } - // When getting something from a pool it is critical that all fields are - // initialized. Doing this way guarantees that if someone adds a field to - // the structure, the compiler will fail the build if this line is not updated. - (*m) = jsAckMsg{subj, reply, hdr, msg} - return m -} - -func (am *jsAckMsg) returnToPool() { - if am == nil { - return - } - am.subject, am.reply, am.hdr, am.msg = _EMPTY_, _EMPTY_, -1, nil - jsAckMsgPool.Put(am) -} - -// Push the ack message to the consumer's ackMsgs queue -func (o *consumer) pushAck(_ *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - atomic.AddInt64(&o.awl, 1) - o.ackMsgs.push(newJSAckMsg(subject, reply, c.pa.hdr, copyBytes(rmsg))) -} - -// Processes a message for the ack reply subject delivered with a message. -func (o *consumer) processAck(subject, reply string, hdr int, rmsg []byte) { - defer atomic.AddInt64(&o.awl, -1) - - var msg []byte - if hdr > 0 { - msg = rmsg[hdr:] - } else { - msg = rmsg - } - - sseq, dseq, dc := ackReplyInfo(subject) - - skipAckReply := sseq == 0 - - switch { - case len(msg) == 0, bytes.Equal(msg, AckAck), bytes.Equal(msg, AckOK): - if !o.processAckMsg(sseq, dseq, dc, reply, true) { - // We handle replies for acks in updateAcks - skipAckReply = true - } - case bytes.HasPrefix(msg, AckNext): - o.processAckMsg(sseq, dseq, dc, _EMPTY_, true) - o.processNextMsgRequest(reply, msg[len(AckNext):]) - skipAckReply = true - case bytes.HasPrefix(msg, AckNak): - o.processNak(sseq, dseq, dc, msg) - case bytes.Equal(msg, AckProgress): - o.progressUpdate(sseq) - case bytes.HasPrefix(msg, AckTerm): - var reason string - if buf := msg[len(AckTerm):]; len(buf) > 0 { - reason = string(bytes.TrimSpace(buf)) - } - if !o.processTerm(sseq, dseq, dc, reason, reply) { - // We handle replies for acks in updateAcks - skipAckReply = true - } - } - - // Ack the ack if requested. - if len(reply) > 0 && !skipAckReply { - o.sendAckReply(reply) - } -} - -// Used to process a working update to delay redelivery. -func (o *consumer) progressUpdate(seq uint64) { - o.mu.Lock() - defer o.mu.Unlock() - - if p, ok := o.pending[seq]; ok { - p.Timestamp = time.Now().UnixNano() - // Update store system. - o.updateDelivered(p.Sequence, seq, 1, p.Timestamp) - } -} - -// Lock should be held. -func (o *consumer) updateSkipped(seq uint64) { - // Clustered mode and R>1 only. - if o.node == nil || !o.isLeader() { - return - } - var b [1 + 8]byte - b[0] = byte(updateSkipOp) - var le = binary.LittleEndian - le.PutUint64(b[1:], seq) - o.propose(b[:]) -} - -func (o *consumer) loopAndForwardProposals(qch chan struct{}) { - // On exit make sure we nil out pch. - defer func() { - o.mu.Lock() - o.pch = nil - o.mu.Unlock() - }() - - o.mu.RLock() - node, pch := o.node, o.pch - o.mu.RUnlock() - - if node == nil || pch == nil { - return - } - - forwardProposals := func() error { - o.mu.Lock() - if o.node == nil || !o.node.Leader() { - o.mu.Unlock() - return errors.New("no longer leader") - } - proposal := o.phead - o.phead, o.ptail = nil, nil - o.mu.Unlock() - // 256k max for now per batch. - const maxBatch = 256 * 1024 - var entries []*Entry - for sz := 0; proposal != nil; proposal = proposal.next { - entries = append(entries, newEntry(EntryNormal, proposal.data)) - sz += len(proposal.data) - if sz > maxBatch { - node.ProposeMulti(entries) - // We need to re-create `entries` because there is a reference - // to it in the node's pae map. - sz, entries = 0, nil - } - } - if len(entries) > 0 { - node.ProposeMulti(entries) - } - return nil - } - - // In case we have anything pending on entry. - forwardProposals() - - for { - select { - case <-qch: - forwardProposals() - return - case <-pch: - if err := forwardProposals(); err != nil { - return - } - } - } -} - -// Lock should be held. -func (o *consumer) propose(entry []byte) { - p := &proposal{data: entry} - if o.phead == nil { - o.phead = p - } else { - o.ptail.next = p - } - o.ptail = p - - // Kick our looper routine. - select { - case o.pch <- struct{}{}: - default: - } -} - -// Lock should be held. -func (o *consumer) updateDelivered(dseq, sseq, dc uint64, ts int64) { - // Clustered mode and R>1. - if o.node != nil { - // Inline for now, use variable compression. - var b [4*binary.MaxVarintLen64 + 1]byte - b[0] = byte(updateDeliveredOp) - n := 1 - n += binary.PutUvarint(b[n:], dseq) - n += binary.PutUvarint(b[n:], sseq) - n += binary.PutUvarint(b[n:], dc) - n += binary.PutVarint(b[n:], ts) - o.propose(b[:n]) - } else if o.store != nil { - o.store.UpdateDelivered(dseq, sseq, dc, ts) - } - // Update activity. - o.ldt = time.Now() -} - -// Used to remember a pending ack reply in a replicated consumer. -// Lock should be held. -func (o *consumer) addAckReply(sseq uint64, reply string) { - if o.replies == nil { - o.replies = make(map[uint64]string) - } - o.replies[sseq] = reply -} - -// Lock should be held. -func (o *consumer) updateAcks(dseq, sseq uint64, reply string) { - if o.node != nil { - // Inline for now, use variable compression. - var b [2*binary.MaxVarintLen64 + 1]byte - b[0] = byte(updateAcksOp) - n := 1 - n += binary.PutUvarint(b[n:], dseq) - n += binary.PutUvarint(b[n:], sseq) - o.propose(b[:n]) - if reply != _EMPTY_ { - o.addAckReply(sseq, reply) - } - } else if o.store != nil { - o.store.UpdateAcks(dseq, sseq) - if reply != _EMPTY_ { - // Already locked so send direct. - o.outq.sendMsg(reply, nil) - } - } - // Update activity. - o.lat = time.Now() -} - -// Communicate to the cluster an addition of a pending request. -// Lock should be held. -func (o *consumer) addClusterPendingRequest(reply string) { - if o.node == nil || !o.pendingRequestsOk() { - return - } - b := make([]byte, len(reply)+1) - b[0] = byte(addPendingRequest) - copy(b[1:], reply) - o.propose(b) -} - -// Communicate to the cluster a removal of a pending request. -// Lock should be held. -func (o *consumer) removeClusterPendingRequest(reply string) { - if o.node == nil || !o.pendingRequestsOk() { - return - } - b := make([]byte, len(reply)+1) - b[0] = byte(removePendingRequest) - copy(b[1:], reply) - o.propose(b) -} - -// Set whether or not we can send pending requests to followers. -func (o *consumer) setPendingRequestsOk(ok bool) { - o.mu.Lock() - o.prOk = ok - o.mu.Unlock() -} - -// Lock should be held. -func (o *consumer) pendingRequestsOk() bool { - return o.prOk -} - -// Set whether or not we can send info about pending pull requests to our group. -// Will require all peers have a minimum version. -func (o *consumer) checkAndSetPendingRequestsOk() { - o.mu.RLock() - s, isValid := o.srv, o.mset != nil - o.mu.RUnlock() - if !isValid { - return - } - - if ca := o.consumerAssignment(); ca != nil && len(ca.Group.Peers) > 1 { - for _, pn := range ca.Group.Peers { - if si, ok := s.nodeToInfo.Load(pn); ok { - if !versionAtLeast(si.(nodeInfo).version, 2, 7, 1) { - // We expect all of our peers to eventually be up to date. - // So check again in awhile. - time.AfterFunc(eventsHBInterval, func() { o.checkAndSetPendingRequestsOk() }) - o.setPendingRequestsOk(false) - return - } - } - } - } - o.setPendingRequestsOk(true) -} - -// On leadership change make sure we alert the pending requests that they are no longer valid. -func (o *consumer) checkPendingRequests() { - o.mu.Lock() - defer o.mu.Unlock() - if o.mset == nil || o.outq == nil { - return - } - hdr := []byte("NATS/1.0 409 Leadership Change\r\n\r\n") - for reply := range o.prm { - o.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - } - o.prm = nil -} - -// This will release any pending pull requests if applicable. -// Should be called only by the leader being deleted or stopped. -// Lock should be held. -func (o *consumer) releaseAnyPendingRequests(isAssigned bool) { - if o.mset == nil || o.outq == nil || o.waiting.len() == 0 { - return - } - var hdr []byte - if !isAssigned { - hdr = []byte("NATS/1.0 409 Consumer Deleted\r\n\r\n") - } - - wq := o.waiting - for wr := wq.head; wr != nil; { - if hdr != nil { - o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - } - next := wr.next - wr.recycle() - wr = next - } - // Nil out old queue. - o.waiting = nil -} - -// Process a NAK. -func (o *consumer) processNak(sseq, dseq, dc uint64, nak []byte) { - o.mu.Lock() - defer o.mu.Unlock() - - // Check for out of range. - if dseq <= o.adflr || dseq > o.dseq { - return - } - // If we are explicit ack make sure this is still on our pending list. - if _, ok := o.pending[sseq]; !ok { - return - } - - // Deliver an advisory - e := JSConsumerDeliveryNakAdvisory{ - TypedEvent: TypedEvent{ - Type: JSConsumerDeliveryNakAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Stream: o.stream, - Consumer: o.name, - ConsumerSeq: dseq, - StreamSeq: sseq, - Deliveries: dc, - Domain: o.srv.getOpts().JetStreamDomain, - } - - o.sendAdvisory(o.nakEventT, e) - - // Check to see if we have delays attached. - if len(nak) > len(AckNak) { - arg := bytes.TrimSpace(nak[len(AckNak):]) - if len(arg) > 0 { - var d time.Duration - var err error - if arg[0] == '{' { - var nd ConsumerNakOptions - if err = json.Unmarshal(arg, &nd); err == nil { - d = nd.Delay - } - } else { - d, err = time.ParseDuration(string(arg)) - } - if err != nil { - // Treat this as normal NAK. - o.srv.Warnf("JetStream consumer '%s > %s > %s' bad NAK delay value: %q", o.acc.Name, o.stream, o.name, arg) - } else { - // We have a parsed duration that the user wants us to wait before retrying. - // Make sure we are not on the rdq. - o.removeFromRedeliverQueue(sseq) - if p, ok := o.pending[sseq]; ok { - // now - ackWait is expired now, so offset from there. - p.Timestamp = time.Now().Add(-o.cfg.AckWait).Add(d).UnixNano() - // Update store system which will update followers as well. - o.updateDelivered(p.Sequence, sseq, dc, p.Timestamp) - if o.ptmr != nil { - // Want checkPending to run and figure out the next timer ttl. - // TODO(dlc) - We could optimize this maybe a bit more and track when we expect the timer to fire. - o.resetPtmr(10 * time.Millisecond) - } - } - // Nothing else for use to do now so return. - return - } - } - } - - // If already queued up also ignore. - if !o.onRedeliverQueue(sseq) { - o.addToRedeliverQueue(sseq) - } - - o.signalNewMessages() -} - -// Process a TERM -// Returns `true` if the ack was processed in place and the sender can now respond -// to the client, or `false` if there was an error or the ack is replicated (in which -// case the reply will be sent later). -func (o *consumer) processTerm(sseq, dseq, dc uint64, reason, reply string) bool { - // Treat like an ack to suppress redelivery. - ackedInPlace := o.processAckMsg(sseq, dseq, dc, reply, false) - - o.mu.Lock() - defer o.mu.Unlock() - - // Deliver an advisory - e := JSConsumerDeliveryTerminatedAdvisory{ - TypedEvent: TypedEvent{ - Type: JSConsumerDeliveryTerminatedAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Stream: o.stream, - Consumer: o.name, - ConsumerSeq: dseq, - StreamSeq: sseq, - Deliveries: dc, - Reason: reason, - Domain: o.srv.getOpts().JetStreamDomain, - } - - subj := JSAdvisoryConsumerMsgTerminatedPre + "." + o.stream + "." + o.name - o.sendAdvisory(subj, e) - return ackedInPlace -} - -// Introduce a small delay in when timer fires to check pending. -// Allows bursts to be treated in same time frame. -const ackWaitDelay = time.Millisecond - -// ackWait returns how long to wait to fire the pending timer. -func (o *consumer) ackWait(next time.Duration) time.Duration { - if next > 0 { - return next + ackWaitDelay - } - return o.cfg.AckWait + ackWaitDelay -} - -// Due to bug in calculation of sequences on restoring redelivered let's do quick sanity check. -// Lock should be held. -func (o *consumer) checkRedelivered(slseq uint64) { - var lseq uint64 - if mset := o.mset; mset != nil { - lseq = slseq - } - var shouldUpdateState bool - for sseq := range o.rdc { - if sseq <= o.asflr || (lseq > 0 && sseq > lseq) { - delete(o.rdc, sseq) - o.removeFromRedeliverQueue(sseq) - shouldUpdateState = true - } - } - if shouldUpdateState { - if err := o.writeStoreStateUnlocked(); err != nil && o.srv != nil && o.mset != nil && !o.closed { - s, acc, mset, name := o.srv, o.acc, o.mset, o.name - s.Warnf("Consumer '%s > %s > %s' error on write store state from check redelivered: %v", acc, mset.getCfgName(), name, err) - } - } -} - -// This will restore the state from disk. -// Lock should be held. -func (o *consumer) readStoredState(slseq uint64) error { - if o.store == nil { - return nil - } - state, err := o.store.State() - if err == nil { - o.applyState(state) - if len(o.rdc) > 0 { - o.checkRedelivered(slseq) - } - } - return err -} - -// Apply the consumer stored state. -// Lock should be held. -func (o *consumer) applyState(state *ConsumerState) { - if state == nil { - return - } - - o.sseq = state.Delivered.Stream + 1 - o.dseq = state.Delivered.Consumer + 1 - o.adflr = state.AckFloor.Consumer - o.asflr = state.AckFloor.Stream - o.pending = state.Pending - o.rdc = state.Redelivered - - // Setup tracking timer if we have restored pending. - if o.isLeader() && len(o.pending) > 0 { - // This is on startup or leader change. We want to check pending - // sooner in case there are inconsistencies etc. Pick between 500ms - 1.5s - delay := 500*time.Millisecond + time.Duration(rand.Int63n(1000))*time.Millisecond - - // If normal is lower than this just use that. - if o.cfg.AckWait < delay { - delay = o.ackWait(0) - } - o.resetPtmr(delay) - } -} - -// Sets our store state from another source. Used in clustered mode on snapshot restore. -// Lock should be held. -func (o *consumer) setStoreState(state *ConsumerState) error { - if state == nil || o.store == nil { - return nil - } - err := o.store.Update(state) - if err == nil { - o.applyState(state) - } - return err -} - -// Update our state to the store. -func (o *consumer) writeStoreState() error { - o.mu.Lock() - defer o.mu.Unlock() - return o.writeStoreStateUnlocked() -} - -// Update our state to the store. -// Lock should be held. -func (o *consumer) writeStoreStateUnlocked() error { - if o.store == nil { - return nil - } - state := ConsumerState{ - Delivered: SequencePair{ - Consumer: o.dseq - 1, - Stream: o.sseq - 1, - }, - AckFloor: SequencePair{ - Consumer: o.adflr, - Stream: o.asflr, - }, - Pending: o.pending, - Redelivered: o.rdc, - } - return o.store.Update(&state) -} - -// Returns an initial info. Only applicable for non-clustered consumers. -// We will clear after we return it, so one shot. -func (o *consumer) initialInfo() *ConsumerInfo { - o.mu.Lock() - ici := o.ici - o.ici = nil // gc friendly - o.mu.Unlock() - if ici == nil { - ici = o.info() - } - return ici -} - -// Clears our initial info. -// Used when we have a leader change in cluster mode but do not send a response. -func (o *consumer) clearInitialInfo() { - o.mu.Lock() - o.ici = nil // gc friendly - o.mu.Unlock() -} - -// Info returns our current consumer state. -func (o *consumer) info() *ConsumerInfo { - return o.infoWithSnap(false) -} - -func (o *consumer) infoWithSnap(snap bool) *ConsumerInfo { - return o.infoWithSnapAndReply(snap, _EMPTY_) -} - -func (o *consumer) infoWithSnapAndReply(snap bool, reply string) *ConsumerInfo { - o.mu.Lock() - mset := o.mset - if o.closed || mset == nil || mset.srv == nil { - o.mu.Unlock() - return nil - } - js := o.js - if js == nil { - o.mu.Unlock() - return nil - } - - // Capture raftGroup. - var rg *raftGroup - if o.ca != nil { - rg = o.ca.Group - } - - priorityGroups := []PriorityGroupState{} - // TODO(jrm): when we introduce supporting many priority groups, we need to update assigning `o.currentNuid` for each group. - if len(o.cfg.PriorityGroups) > 0 { - priorityGroups = append(priorityGroups, PriorityGroupState{ - Group: o.cfg.PriorityGroups[0], - PinnedClientID: o.currentPinId, - PinnedTS: o.pinnedTS, - }) - } - - cfg := o.cfg - info := &ConsumerInfo{ - Stream: o.stream, - Name: o.name, - Created: o.created, - Config: &cfg, - Delivered: SequenceInfo{ - Consumer: o.dseq - 1, - Stream: o.sseq - 1, - }, - AckFloor: SequenceInfo{ - Consumer: o.adflr, - Stream: o.asflr, - }, - NumAckPending: len(o.pending), - NumRedelivered: len(o.rdc), - NumPending: o.checkNumPending(), - PushBound: o.isPushMode() && o.active, - TimeStamp: time.Now().UTC(), - PriorityGroups: priorityGroups, - } - if o.cfg.PauseUntil != nil { - p := *o.cfg.PauseUntil - if info.Paused = time.Now().Before(p); info.Paused { - info.PauseRemaining = time.Until(p) - } - } - - // If we are replicated, we need to pull certain data from our store. - if rg != nil && rg.node != nil && o.store != nil { - state, err := o.store.BorrowState() - if err != nil { - o.mu.Unlock() - return nil - } - // If we are the leader we could have o.sseq that is skipped ahead. - // To maintain consistency in reporting (e.g. jsz) we always take the state for our delivered/ackfloor stream sequence. - // Only use skipped ahead o.sseq if we're a new consumer and have not yet replicated this state yet. - leader := o.isLeader() - if !leader || o.store.HasState() { - info.Delivered.Consumer, info.Delivered.Stream = state.Delivered.Consumer, state.Delivered.Stream - } - info.AckFloor.Consumer, info.AckFloor.Stream = state.AckFloor.Consumer, state.AckFloor.Stream - if !leader { - info.NumAckPending = len(state.Pending) - info.NumRedelivered = len(state.Redelivered) - } - } - - // Adjust active based on non-zero etc. Also make UTC here. - if !o.ldt.IsZero() { - ldt := o.ldt.UTC() // This copies as well. - info.Delivered.Last = &ldt - } - if !o.lat.IsZero() { - lat := o.lat.UTC() // This copies as well. - info.AckFloor.Last = &lat - } - - // If we are a pull mode consumer, report on number of waiting requests. - if o.isPullMode() { - o.processWaiting(false) - info.NumWaiting = o.waiting.len() - } - // If we were asked to snapshot do so here. - if snap { - o.ici = info - } - sysc := o.sysc - o.mu.Unlock() - - // Do cluster. - if rg != nil { - info.Cluster = js.clusterInfo(rg) - } - - // If we have a reply subject send the response here. - if reply != _EMPTY_ && sysc != nil { - sysc.sendInternalMsg(reply, _EMPTY_, nil, info) - } - - return info -} - -// Will signal us that new messages are available. Will break out of waiting. -func (o *consumer) signalNewMessages() { - // Kick our new message channel - select { - case o.mch <- struct{}{}: - default: - } -} - -// shouldSample lets us know if we are sampling metrics on acks. -func (o *consumer) shouldSample() bool { - switch { - case o.sfreq <= 0: - return false - case o.sfreq >= 100: - return true - } - - // TODO(ripienaar) this is a tad slow so we need to rethink here, however this will only - // hit for those with sampling enabled and its not the default - return rand.Int31n(100) <= o.sfreq -} - -func (o *consumer) sampleAck(sseq, dseq, dc uint64) { - if !o.shouldSample() { - return - } - - now := time.Now().UTC() - unow := now.UnixNano() - - e := JSConsumerAckMetric{ - TypedEvent: TypedEvent{ - Type: JSConsumerAckMetricType, - ID: nuid.Next(), - Time: now, - }, - Stream: o.stream, - Consumer: o.name, - ConsumerSeq: dseq, - StreamSeq: sseq, - Delay: unow - o.pending[sseq].Timestamp, - Deliveries: dc, - Domain: o.srv.getOpts().JetStreamDomain, - } - - o.sendAdvisory(o.ackEventT, e) -} - -// Process an ACK. -// Returns `true` if the ack was processed in place and the sender can now respond -// to the client, or `false` if there was an error or the ack is replicated (in which -// case the reply will be sent later). -func (o *consumer) processAckMsg(sseq, dseq, dc uint64, reply string, doSample bool) bool { - o.mu.Lock() - if o.closed { - o.mu.Unlock() - return false - } - - mset := o.mset - if mset == nil || mset.closed.Load() { - o.mu.Unlock() - return false - } - - // Check if this ack is above the current pointer to our next to deliver. - // This could happen on a cooperative takeover with high speed deliveries. - if sseq >= o.sseq { - // Let's make sure this is valid. - // This is only received on the consumer leader, so should never be higher - // than the last stream sequence. But could happen if we've just become - // consumer leader, and we are not up-to-date on the stream yet. - var ss StreamState - mset.store.FastState(&ss) - if sseq > ss.LastSeq { - o.srv.Warnf("JetStream consumer '%s > %s > %s' ACK sequence %d past last stream sequence of %d", - o.acc.Name, o.stream, o.name, sseq, ss.LastSeq) - // FIXME(dlc) - For 2.11 onwards should we return an error here to the caller? - } - // Even though another leader must have delivered a message with this sequence, we must not adjust - // the current pointer. This could otherwise result in a stuck consumer, where messages below this - // sequence can't be redelivered, and we'll have incorrect pending state and ack floors. - o.mu.Unlock() - return false - } - - // Let the owning stream know if we are interest or workqueue retention based. - // If this consumer is clustered (o.node != nil) this will be handled by - // processReplicatedAck after the ack has propagated. - ackInPlace := o.node == nil && o.retention != LimitsPolicy - - var sgap, floor uint64 - var needSignal bool - - switch o.cfg.AckPolicy { - case AckExplicit: - if p, ok := o.pending[sseq]; ok { - if doSample { - o.sampleAck(sseq, dseq, dc) - } - if o.maxp > 0 && len(o.pending) >= o.maxp { - needSignal = true - } - delete(o.pending, sseq) - // Use the original deliver sequence from our pending record. - dseq = p.Sequence - - // Only move floors if we matched an existing pending. - if len(o.pending) == 0 { - o.adflr = o.dseq - 1 - o.asflr = o.sseq - 1 - } else if dseq == o.adflr+1 { - o.adflr, o.asflr = dseq, sseq - for ss := sseq + 1; ss < o.sseq; ss++ { - if p, ok := o.pending[ss]; ok { - if p.Sequence > 0 { - o.adflr, o.asflr = p.Sequence-1, ss-1 - } - break - } - } - } - } - delete(o.rdc, sseq) - o.removeFromRedeliverQueue(sseq) - case AckAll: - // no-op - if dseq <= o.adflr || sseq <= o.asflr { - o.mu.Unlock() - // Return true to let caller respond back to the client. - return true - } - if o.maxp > 0 && len(o.pending) >= o.maxp { - needSignal = true - } - sgap = sseq - o.asflr - floor = sgap // start at same and set lower as we go. - o.adflr, o.asflr = dseq, sseq - - remove := func(seq uint64) { - delete(o.pending, seq) - delete(o.rdc, seq) - o.removeFromRedeliverQueue(seq) - if seq < floor { - floor = seq - } - } - // Determine if smarter to walk all of pending vs the sequence range. - if sgap > uint64(len(o.pending)) { - for seq := range o.pending { - if seq <= sseq { - remove(seq) - } - } - } else { - for seq := sseq; seq > sseq-sgap && len(o.pending) > 0; seq-- { - remove(seq) - } - } - case AckNone: - // FIXME(dlc) - This is error but do we care? - o.mu.Unlock() - return ackInPlace - } - - // No ack replication, so we set reply to "" so that updateAcks does not - // send the reply. The caller will. - if ackInPlace { - reply = _EMPTY_ - } - // Update underlying store. - o.updateAcks(dseq, sseq, reply) - o.mu.Unlock() - - if ackInPlace { - if sgap > 1 { - // FIXME(dlc) - This can very inefficient, will need to fix. - for seq := sseq; seq >= floor; seq-- { - mset.ackMsg(o, seq) - } - } else { - mset.ackMsg(o, sseq) - } - } - - // If we had max ack pending set and were at limit we need to unblock ourselves. - if needSignal { - o.signalNewMessages() - } - return ackInPlace -} - -// Determine if this is a truly filtered consumer. Modern clients will place filtered subjects -// even if the stream only has a single non-wildcard subject designation. -// Read lock should be held. -func (o *consumer) isFiltered() bool { - if o.subjf == nil { - return false - } - // If we are here we want to check if the filtered subject is - // a direct match for our only listed subject. - mset := o.mset - if mset == nil { - return true - } - - // Protect access to mset.cfg with the cfgMu mutex. - mset.cfgMu.RLock() - msetSubjects := mset.cfg.Subjects - mset.cfgMu.RUnlock() - - // `isFiltered` need to be performant, so we do - // as any checks as possible to avoid unnecessary work. - // Here we avoid iteration over slices if there is only one subject in stream - // and one filter for the consumer. - if len(msetSubjects) == 1 && len(o.subjf) == 1 { - return msetSubjects[0] != o.subjf[0].subject - } - - // if the list is not equal length, we can return early, as this is filtered. - if len(msetSubjects) != len(o.subjf) { - return true - } - - // if in rare case scenario that user passed all stream subjects as consumer filters, - // we need to do a more expensive operation. - // reflect.DeepEqual would return false if the filters are the same, but in different order - // so it can't be used here. - cfilters := make(map[string]struct{}, len(o.subjf)) - for _, val := range o.subjf { - cfilters[val.subject] = struct{}{} - } - for _, val := range msetSubjects { - if _, ok := cfilters[val]; !ok { - return true - } - } - return false -} - -// Check if we need an ack for this store seq. -// This is called for interest based retention streams to remove messages. -func (o *consumer) needAck(sseq uint64, subj string) bool { - var needAck bool - var asflr, osseq uint64 - var pending map[uint64]*Pending - var rdc map[uint64]uint64 - - o.mu.RLock() - defer o.mu.RUnlock() - - isFiltered := o.isFiltered() - if isFiltered && o.mset == nil { - return false - } - - // Check if we are filtered, and if so check if this is even applicable to us. - if isFiltered { - if subj == _EMPTY_ { - var svp StoreMsg - if _, err := o.mset.store.LoadMsg(sseq, &svp); err != nil { - return false - } - subj = svp.subj - } - if !o.isFilteredMatch(subj) { - return false - } - } - if o.isLeader() { - asflr, osseq = o.asflr, o.sseq - pending, rdc = o.pending, o.rdc - } else { - if o.store == nil { - return false - } - state, err := o.store.BorrowState() - if err != nil || state == nil { - // Fall back to what we track internally for now. - return sseq > o.asflr && !o.isFiltered() - } - // If loading state as here, the osseq is +1. - asflr, osseq, pending, rdc = state.AckFloor.Stream, state.Delivered.Stream+1, state.Pending, state.Redelivered - } - - switch o.cfg.AckPolicy { - case AckNone, AckAll: - needAck = sseq > asflr - case AckExplicit: - if sseq > asflr { - if sseq >= osseq { - needAck = true - } else { - _, needAck = pending[sseq] - } - } - } - - // Finally check if redelivery of this message is tracked. - // If the message is not pending, it should be preserved if it reached max delivery. - if !needAck { - _, needAck = rdc[sseq] - } - - return needAck -} - -type PriorityGroup struct { - Group string `json:"group,omitempty"` - MinPending int64 `json:"min_pending,omitempty"` - MinAckPending int64 `json:"min_ack_pending,omitempty"` - Id string `json:"id,omitempty"` -} - -// Used in nextReqFromMsg, since the json.Unmarshal causes the request -// struct to escape to the heap always. This should reduce GC pressure. -var jsGetNextPool = sync.Pool{ - New: func() any { - return &JSApiConsumerGetNextRequest{} - }, -} - -// Helper for the next message requests. -func nextReqFromMsg(msg []byte) (time.Time, int, int, bool, time.Duration, time.Time, *PriorityGroup, error) { - req := bytes.TrimSpace(msg) - - switch { - case len(req) == 0: - return time.Time{}, 1, 0, false, 0, time.Time{}, nil, nil - - case req[0] == '{': - cr := jsGetNextPool.Get().(*JSApiConsumerGetNextRequest) - defer func() { - *cr = JSApiConsumerGetNextRequest{} - jsGetNextPool.Put(cr) - }() - if err := json.Unmarshal(req, &cr); err != nil { - return time.Time{}, -1, 0, false, 0, time.Time{}, nil, err - } - var hbt time.Time - if cr.Heartbeat > 0 { - if cr.Heartbeat*2 > cr.Expires { - return time.Time{}, 1, 0, false, 0, time.Time{}, nil, errors.New("heartbeat value too large") - } - hbt = time.Now().Add(cr.Heartbeat) - } - priorityGroup := cr.PriorityGroup - if cr.Expires == time.Duration(0) { - return time.Time{}, cr.Batch, cr.MaxBytes, cr.NoWait, cr.Heartbeat, hbt, &priorityGroup, nil - } - return time.Now().Add(cr.Expires), cr.Batch, cr.MaxBytes, cr.NoWait, cr.Heartbeat, hbt, &priorityGroup, nil - default: - if n, err := strconv.Atoi(string(req)); err == nil { - return time.Time{}, n, 0, false, 0, time.Time{}, nil, nil - } - } - - return time.Time{}, 1, 0, false, 0, time.Time{}, nil, nil -} - -// Represents a request that is on the internal waiting queue -type waitingRequest struct { - next *waitingRequest - acc *Account - interest string - reply string - n int // For batching - d int // num delivered - b int // For max bytes tracking - expires time.Time - received time.Time - hb time.Duration - hbt time.Time - noWait bool - priorityGroup *PriorityGroup -} - -// sync.Pool for waiting requests. -var wrPool = sync.Pool{ - New: func() any { - return new(waitingRequest) - }, -} - -// Recycle this request. This request can not be accessed after this call. -func (wr *waitingRequest) recycleIfDone() bool { - if wr != nil && wr.n <= 0 { - wr.recycle() - return true - } - return false -} - -// Force a recycle. -func (wr *waitingRequest) recycle() { - if wr != nil { - wr.next, wr.acc, wr.interest, wr.reply = nil, nil, _EMPTY_, _EMPTY_ - wrPool.Put(wr) - } -} - -// waiting queue for requests that are waiting for new messages to arrive. -type waitQueue struct { - n, max int - last time.Time - head *waitingRequest - tail *waitingRequest -} - -// Create a new ring buffer with at most max items. -func newWaitQueue(max int) *waitQueue { - return &waitQueue{max: max} -} - -var ( - errWaitQueueFull = errors.New("wait queue is full") - errWaitQueueNil = errors.New("wait queue is nil") -) - -// Adds in a new request. -func (wq *waitQueue) add(wr *waitingRequest) error { - if wq == nil { - return errWaitQueueNil - } - if wq.isFull() { - return errWaitQueueFull - } - if wq.head == nil { - wq.head = wr - } else { - wq.tail.next = wr - } - // Always set tail. - wq.tail = wr - // Make sure nil - wr.next = nil - - // Track last active via when we receive a request. - wq.last = wr.received - wq.n++ - return nil -} - -func (wq *waitQueue) isFull() bool { - if wq == nil { - return false - } - return wq.n == wq.max -} - -func (wq *waitQueue) isEmpty() bool { - if wq == nil { - return true - } - return wq.n == 0 -} - -func (wq *waitQueue) len() int { - if wq == nil { - return 0 - } - return wq.n -} - -// Peek will return the next request waiting or nil if empty. -func (wq *waitQueue) peek() *waitingRequest { - if wq == nil { - return nil - } - return wq.head -} - -func (wq *waitQueue) cycle() { - wr := wq.peek() - if wr != nil { - // Always remove current now on a pop, and move to end if still valid. - // If we were the only one don't need to remove since this can be a no-op. - wq.removeCurrent() - wq.add(wr) - } -} - -// pop will return the next request and move the read cursor. -// This will now place a request that still has pending items at the ends of the list. -func (wq *waitQueue) pop() *waitingRequest { - wr := wq.peek() - if wr != nil { - wr.d++ - wr.n-- - // Always remove current now on a pop, and move to end if still valid. - // If we were the only one don't need to remove since this can be a no-op. - if wr.n > 0 && wq.n > 1 { - wq.removeCurrent() - wq.add(wr) - } else if wr.n <= 0 { - wq.removeCurrent() - } - } - return wr -} - -// Removes the current read pointer (head FIFO) entry. -func (wq *waitQueue) removeCurrent() { - wq.remove(nil, wq.head) -} - -// Remove the wr element from the wait queue. -func (wq *waitQueue) remove(pre, wr *waitingRequest) { - if wr == nil { - return - } - if pre != nil { - pre.next = wr.next - } else if wr == wq.head { - // We are removing head here. - wq.head = wr.next - } - // Check if wr was our tail. - if wr == wq.tail { - // Check if we need to assign to pre. - if wr.next == nil { - wq.tail = pre - } else { - wq.tail = wr.next - } - } - wq.n-- -} - -// Return the map of pending requests keyed by the reply subject. -// No-op if push consumer or invalid etc. -func (o *consumer) pendingRequests() map[string]*waitingRequest { - if o.waiting == nil { - return nil - } - wq, m := o.waiting, make(map[string]*waitingRequest) - for wr := wq.head; wr != nil; wr = wr.next { - m[wr.reply] = wr - } - - return m -} - -func (o *consumer) setPinnedTimer(priorityGroup string) { - if o.pinnedTtl != nil { - o.pinnedTtl.Reset(o.cfg.PinnedTTL) - } else { - o.pinnedTtl = time.AfterFunc(o.cfg.PinnedTTL, func() { - o.mu.Lock() - o.currentPinId = _EMPTY_ - o.sendUnpinnedAdvisoryLocked(priorityGroup, "timeout") - o.mu.Unlock() - o.signalNewMessages() - }) - } -} - -// Return next waiting request. This will check for expirations but not noWait or interest. -// That will be handled by processWaiting. -// Lock should be held. -func (o *consumer) nextWaiting(sz int) *waitingRequest { - if o.waiting == nil || o.waiting.isEmpty() { - return nil - } - - // Check if server needs to assign a new pin id. - needNewPin := o.currentPinId == _EMPTY_ && o.cfg.PriorityPolicy == PriorityPinnedClient - // As long as we support only one priority group, we can capture that group here and reuse it. - var priorityGroup string - if len(o.cfg.PriorityGroups) > 0 { - priorityGroup = o.cfg.PriorityGroups[0] - } - - lastRequest := o.waiting.tail - for wr := o.waiting.peek(); !o.waiting.isEmpty(); wr = o.waiting.peek() { - if wr == nil { - break - } - // Check if we have max bytes set. - if wr.b > 0 { - if sz <= wr.b { - wr.b -= sz - // If we are right now at zero, set batch to 1 to deliver this one but stop after. - if wr.b == 0 { - wr.n = 1 - } - } else { - // Since we can't send that message to the requestor, we need to - // notify that we are closing the request. - const maxBytesT = "NATS/1.0 409 Message Size Exceeds MaxBytes\r\n%s: %d\r\n%s: %d\r\n\r\n" - hdr := fmt.Appendf(nil, maxBytesT, JSPullRequestPendingMsgs, wr.n, JSPullRequestPendingBytes, wr.b) - o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - // Remove the current one, no longer valid due to max bytes limit. - o.waiting.removeCurrent() - if o.node != nil { - o.removeClusterPendingRequest(wr.reply) - } - wr.recycle() - continue - } - } - - if wr.expires.IsZero() || time.Now().Before(wr.expires) { - if needNewPin { - if wr.priorityGroup.Id == _EMPTY_ { - o.currentPinId = nuid.Next() - o.pinnedTS = time.Now().UTC() - wr.priorityGroup.Id = o.currentPinId - o.setPinnedTimer(priorityGroup) - - } else { - // There is pin id set, but not a matching one. Send a notification to the client and remove the request. - // Probably this is the old pin id. - o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, []byte(JSPullRequestWrongPinID), nil, nil, 0)) - o.waiting.removeCurrent() - if o.node != nil { - o.removeClusterPendingRequest(wr.reply) - } - wr.recycle() - continue - } - } else if o.currentPinId != _EMPTY_ { - // Check if we have a match on the currentNuid - if wr.priorityGroup != nil && wr.priorityGroup.Id == o.currentPinId { - // If we have a match, we do nothing here and will deliver the message later down the code path. - } else if wr.priorityGroup.Id == _EMPTY_ { - o.waiting.cycle() - if wr == lastRequest { - return nil - } - continue - } else { - // There is pin id set, but not a matching one. Send a notification to the client and remove the request. - o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, []byte(JSPullRequestWrongPinID), nil, nil, 0)) - o.waiting.removeCurrent() - if o.node != nil { - o.removeClusterPendingRequest(wr.reply) - } - wr.recycle() - continue - } - } - - if o.cfg.PriorityPolicy == PriorityOverflow { - if wr.priorityGroup != nil && - // We need to check o.npc+1, because before calling nextWaiting, we do o.npc-- - (wr.priorityGroup.MinPending > 0 && wr.priorityGroup.MinPending > o.npc+1 || - wr.priorityGroup.MinAckPending > 0 && wr.priorityGroup.MinAckPending > int64(len(o.pending))) { - o.waiting.cycle() - // We're done cycling through the requests. - if wr == lastRequest { - return nil - } - continue - } - } - if wr.acc.sl.HasInterest(wr.interest) { - if needNewPin { - o.sendPinnedAdvisoryLocked(priorityGroup) - } - return o.waiting.pop() - } else if time.Since(wr.received) < defaultGatewayRecentSubExpiration && (o.srv.leafNodeEnabled || o.srv.gateway.enabled) { - if needNewPin { - o.sendPinnedAdvisoryLocked(priorityGroup) - } - return o.waiting.pop() - } else if o.srv.gateway.enabled && o.srv.hasGatewayInterest(wr.acc.Name, wr.interest) { - if needNewPin { - o.sendPinnedAdvisoryLocked(priorityGroup) - } - return o.waiting.pop() - } - } else { - // We do check for expiration in `processWaiting`, but it is possible to hit the expiry here, and not there. - hdr := fmt.Appendf(nil, "NATS/1.0 408 Request Timeout\r\n%s: %d\r\n%s: %d\r\n\r\n", JSPullRequestPendingMsgs, wr.n, JSPullRequestPendingBytes, wr.b) - o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - o.waiting.removeCurrent() - if o.node != nil { - o.removeClusterPendingRequest(wr.reply) - } - wr.recycle() - continue - - } - if wr.interest != wr.reply { - const intExpT = "NATS/1.0 408 Interest Expired\r\n%s: %d\r\n%s: %d\r\n\r\n" - hdr := fmt.Appendf(nil, intExpT, JSPullRequestPendingMsgs, wr.n, JSPullRequestPendingBytes, wr.b) - o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - } - // Remove the current one, no longer valid. - o.waiting.removeCurrent() - if o.node != nil { - o.removeClusterPendingRequest(wr.reply) - } - wr.recycle() - } - - return nil -} - -// Next message request. -type nextMsgReq struct { - reply string - msg []byte -} - -var nextMsgReqPool sync.Pool - -func newNextMsgReq(reply string, msg []byte) *nextMsgReq { - var nmr *nextMsgReq - m := nextMsgReqPool.Get() - if m != nil { - nmr = m.(*nextMsgReq) - } else { - nmr = &nextMsgReq{} - } - // When getting something from a pool it is critical that all fields are - // initialized. Doing this way guarantees that if someone adds a field to - // the structure, the compiler will fail the build if this line is not updated. - (*nmr) = nextMsgReq{reply, msg} - return nmr -} - -func (nmr *nextMsgReq) returnToPool() { - if nmr == nil { - return - } - nmr.reply, nmr.msg = _EMPTY_, nil - nextMsgReqPool.Put(nmr) -} - -// processNextMsgReq will process a request for the next message available. A nil message payload means deliver -// a single message. If the payload is a formal request or a number parseable with Atoi(), then we will send a -// batch of messages without requiring another request to this endpoint, or an ACK. -func (o *consumer) processNextMsgReq(_ *subscription, c *client, _ *Account, _, reply string, msg []byte) { - if reply == _EMPTY_ { - return - } - - // Short circuit error here. - if o.nextMsgReqs == nil { - hdr := []byte("NATS/1.0 409 Consumer is push based\r\n\r\n") - o.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - return - } - - _, msg = c.msgParts(msg) - o.nextMsgReqs.push(newNextMsgReq(reply, copyBytes(msg))) -} - -func (o *consumer) processNextMsgRequest(reply string, msg []byte) { - o.mu.Lock() - defer o.mu.Unlock() - - mset := o.mset - if mset == nil { - return - } - - sendErr := func(status int, description string) { - hdr := fmt.Appendf(nil, "NATS/1.0 %d %s\r\n\r\n", status, description) - o.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - } - - if o.isPushMode() || o.waiting == nil { - sendErr(409, "Consumer is push based") - return - } - - // Check payload here to see if they sent in batch size or a formal request. - expires, batchSize, maxBytes, noWait, hb, hbt, priorityGroup, err := nextReqFromMsg(msg) - if err != nil { - sendErr(400, fmt.Sprintf("Bad Request - %v", err)) - return - } - - // Check for request limits - if o.cfg.MaxRequestBatch > 0 && batchSize > o.cfg.MaxRequestBatch { - sendErr(409, fmt.Sprintf("Exceeded MaxRequestBatch of %d", o.cfg.MaxRequestBatch)) - return - } - - if !expires.IsZero() && o.cfg.MaxRequestExpires > 0 && expires.After(time.Now().Add(o.cfg.MaxRequestExpires)) { - sendErr(409, fmt.Sprintf("Exceeded MaxRequestExpires of %v", o.cfg.MaxRequestExpires)) - return - } - - if maxBytes > 0 && o.cfg.MaxRequestMaxBytes > 0 && maxBytes > o.cfg.MaxRequestMaxBytes { - sendErr(409, fmt.Sprintf("Exceeded MaxRequestMaxBytes of %v", o.cfg.MaxRequestMaxBytes)) - return - } - - if priorityGroup != nil { - if (priorityGroup.MinPending != 0 || priorityGroup.MinAckPending != 0) && o.cfg.PriorityPolicy != PriorityOverflow { - sendErr(400, "Bad Request - Not a Overflow Priority consumer") - } - - if priorityGroup.Id != _EMPTY_ && o.cfg.PriorityPolicy != PriorityPinnedClient { - sendErr(400, "Bad Request - Not a Pinned Client Priority consumer") - } - } - - if priorityGroup != nil && o.cfg.PriorityPolicy != PriorityNone { - if priorityGroup.Group == _EMPTY_ { - sendErr(400, "Bad Request - Priority Group missing") - return - } - - found := false - for _, group := range o.cfg.PriorityGroups { - if group == priorityGroup.Group { - found = true - break - } - } - if !found { - sendErr(400, "Bad Request - Invalid Priority Group") - return - } - - if o.currentPinId != _EMPTY_ { - if priorityGroup.Id == o.currentPinId { - o.setPinnedTimer(priorityGroup.Group) - } else if priorityGroup.Id != _EMPTY_ { - sendErr(423, "Nats-Pin-Id mismatch") - return - } - } - } - - // If we have the max number of requests already pending try to expire. - if o.waiting.isFull() { - // Try to expire some of the requests. - // We do not want to push too hard here so at maximum process once per sec. - if time.Since(o.lwqic) > time.Second { - o.processWaiting(false) - } - } - - // If the request is for noWait and we have pending requests already, check if we have room. - if noWait { - msgsPending := o.numPending() + uint64(len(o.rdq)) - // If no pending at all, decide what to do with request. - // If no expires was set then fail. - if msgsPending == 0 && expires.IsZero() { - o.waiting.last = time.Now() - sendErr(404, "No Messages") - return - } - if msgsPending > 0 { - _, _, batchPending, _ := o.processWaiting(false) - if msgsPending < uint64(batchPending) { - o.waiting.last = time.Now() - sendErr(408, "Requests Pending") - return - } - } - // If we are here this should be considered a one-shot situation. - // We will wait for expires but will return as soon as we have any messages. - } - - // If we receive this request though an account export, we need to track that interest subject and account. - acc, interest := trackDownAccountAndInterest(o.acc, reply) - - // Create a waiting request. - wr := wrPool.Get().(*waitingRequest) - wr.acc, wr.interest, wr.reply, wr.n, wr.d, wr.noWait, wr.expires, wr.hb, wr.hbt, wr.priorityGroup = acc, interest, reply, batchSize, 0, noWait, expires, hb, hbt, priorityGroup - wr.b = maxBytes - wr.received = time.Now() - - if err := o.waiting.add(wr); err != nil { - sendErr(409, "Exceeded MaxWaiting") - wr.recycle() - return - } - o.signalNewMessages() - // If we are clustered update our followers about this request. - if o.node != nil { - o.addClusterPendingRequest(wr.reply) - } -} - -func trackDownAccountAndInterest(acc *Account, interest string) (*Account, string) { - for strings.HasPrefix(interest, replyPrefix) { - oa := acc - oa.mu.RLock() - if oa.exports.responses == nil { - oa.mu.RUnlock() - break - } - si := oa.exports.responses[interest] - if si == nil { - oa.mu.RUnlock() - break - } - acc, interest = si.acc, si.to - oa.mu.RUnlock() - } - return acc, interest -} - -// Return current delivery count for a given sequence. -func (o *consumer) deliveryCount(seq uint64) uint64 { - if o.rdc == nil { - return 1 - } - if dc := o.rdc[seq]; dc >= 1 { - return dc - } - return 1 -} - -// Increase the delivery count for this message. -// ONLY used on redelivery semantics. -// Lock should be held. -func (o *consumer) incDeliveryCount(sseq uint64) uint64 { - if o.rdc == nil { - o.rdc = make(map[uint64]uint64) - } - o.rdc[sseq] += 1 - return o.rdc[sseq] + 1 -} - -// Used if we have to adjust on failed delivery or bad lookups. -// Those failed attempts should not increase deliver count. -// Lock should be held. -func (o *consumer) decDeliveryCount(sseq uint64) { - if o.rdc == nil { - o.rdc = make(map[uint64]uint64) - } - o.rdc[sseq] -= 1 -} - -// send a delivery exceeded advisory. -func (o *consumer) notifyDeliveryExceeded(sseq, dc uint64) { - e := JSConsumerDeliveryExceededAdvisory{ - TypedEvent: TypedEvent{ - Type: JSConsumerDeliveryExceededAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Stream: o.stream, - Consumer: o.name, - StreamSeq: sseq, - Deliveries: dc, - Domain: o.srv.getOpts().JetStreamDomain, - } - - o.sendAdvisory(o.deliveryExcEventT, e) -} - -// Check if the candidate subject matches a filter if its present. -// Lock should be held. -func (o *consumer) isFilteredMatch(subj string) bool { - // No filter is automatic match. - if o.subjf == nil { - return true - } - for _, filter := range o.subjf { - if !filter.hasWildcard && subj == filter.subject { - return true - } - } - // It's quicker to first check for non-wildcard filters, then - // iterate again to check for subset match. - tsa := [32]string{} - tts := tokenizeSubjectIntoSlice(tsa[:0], subj) - for _, filter := range o.subjf { - if isSubsetMatchTokenized(tts, filter.tokenizedSubject) { - return true - } - } - return false -} - -// Check if the candidate filter subject is equal to or a subset match -// of one of the filter subjects. -// Lock should be held. -func (o *consumer) isEqualOrSubsetMatch(subj string) bool { - for _, filter := range o.subjf { - if !filter.hasWildcard && subj == filter.subject { - return true - } - } - tsa := [32]string{} - tts := tokenizeSubjectIntoSlice(tsa[:0], subj) - for _, filter := range o.subjf { - if isSubsetMatchTokenized(filter.tokenizedSubject, tts) { - return true - } - } - return false -} - -var ( - errMaxAckPending = errors.New("max ack pending reached") - errBadConsumer = errors.New("consumer not valid") - errNoInterest = errors.New("consumer requires interest for delivery subject when ephemeral") -) - -// Get next available message from underlying store. -// Is partition aware and redeliver aware. -// Lock should be held. -func (o *consumer) getNextMsg() (*jsPubMsg, uint64, error) { - if o.mset == nil || o.mset.store == nil { - return nil, 0, errBadConsumer - } - // Process redelivered messages before looking at possibly "skip list" (deliver last per subject) - if o.hasRedeliveries() { - var seq, dc uint64 - for seq = o.getNextToRedeliver(); seq > 0; seq = o.getNextToRedeliver() { - dc = o.incDeliveryCount(seq) - if o.maxdc > 0 && dc > o.maxdc { - // Only send once - if dc == o.maxdc+1 { - o.notifyDeliveryExceeded(seq, dc-1) - } - // Make sure to remove from pending. - if p, ok := o.pending[seq]; ok && p != nil { - delete(o.pending, seq) - o.updateDelivered(p.Sequence, seq, dc, p.Timestamp) - } - continue - } - pmsg := getJSPubMsgFromPool() - sm, err := o.mset.store.LoadMsg(seq, &pmsg.StoreMsg) - if sm == nil || err != nil { - pmsg.returnToPool() - pmsg, dc = nil, 0 - // Adjust back deliver count. - o.decDeliveryCount(seq) - } - // Message was scheduled for redelivery but was removed in the meantime. - if err == ErrStoreMsgNotFound || err == errDeletedMsg { - // This is a race condition where the message is still in o.pending and - // scheduled for redelivery, but it has been removed from the stream. - // o.processTerm is called in a goroutine so could run after we get here. - // That will correct the pending state and delivery/ack floors, so just skip here. - continue - } - return pmsg, dc, err - } - } - - // Check if we have max pending. - if o.maxp > 0 && len(o.pending) >= o.maxp { - // maxp only set when ack policy != AckNone and user set MaxAckPending - // Stall if we have hit max pending. - return nil, 0, errMaxAckPending - } - - if o.hasSkipListPending() { - seq := o.lss.seqs[0] - if len(o.lss.seqs) == 1 { - o.sseq = o.lss.resume - o.lss = nil - o.updateSkipped(o.sseq) - } else { - o.lss.seqs = o.lss.seqs[1:] - } - pmsg := getJSPubMsgFromPool() - sm, err := o.mset.store.LoadMsg(seq, &pmsg.StoreMsg) - if sm == nil || err != nil { - pmsg.returnToPool() - } - o.sseq++ - return pmsg, 1, err - } - - // Hold onto this since we release the lock. - store := o.mset.store - - var sseq uint64 - var err error - var sm *StoreMsg - var pmsg = getJSPubMsgFromPool() - - // Grab next message applicable to us. - filters, subjf, fseq := o.filters, o.subjf, o.sseq - // Check if we are multi-filtered or not. - if filters != nil { - sm, sseq, err = store.LoadNextMsgMulti(filters, fseq, &pmsg.StoreMsg) - } else if len(subjf) > 0 { // Means single filtered subject since o.filters means > 1. - filter, wc := subjf[0].subject, subjf[0].hasWildcard - sm, sseq, err = store.LoadNextMsg(filter, wc, fseq, &pmsg.StoreMsg) - } else { - // No filter here. - sm, sseq, err = store.LoadNextMsg(_EMPTY_, false, fseq, &pmsg.StoreMsg) - } - if sm == nil { - pmsg.returnToPool() - pmsg = nil - } - // Check if we should move our o.sseq. - if sseq >= o.sseq { - // If we are moving step by step then sseq == o.sseq. - // If we have jumped we should update skipped for other replicas. - if sseq != o.sseq && err == ErrStoreEOF { - o.updateSkipped(sseq + 1) - } - o.sseq = sseq + 1 - } - return pmsg, 1, err -} - -// Will check for expiration and lack of interest on waiting requests. -// Will also do any heartbeats and return the next expiration or HB interval. -func (o *consumer) processWaiting(eos bool) (int, int, int, time.Time) { - var fexp time.Time - if o.srv == nil || o.waiting.isEmpty() { - return 0, 0, 0, fexp - } - // Mark our last check time. - o.lwqic = time.Now() - - var expired, brp int - s, now := o.srv, time.Now() - - wq := o.waiting - remove := func(pre, wr *waitingRequest) *waitingRequest { - expired++ - if o.node != nil { - o.removeClusterPendingRequest(wr.reply) - } - next := wr.next - wq.remove(pre, wr) - wr.recycle() - return next - } - - var pre *waitingRequest - for wr := wq.head; wr != nil; { - // Check expiration. - if (eos && wr.noWait && wr.d > 0) || (!wr.expires.IsZero() && now.After(wr.expires)) { - hdr := fmt.Appendf(nil, "NATS/1.0 408 Request Timeout\r\n%s: %d\r\n%s: %d\r\n\r\n", JSPullRequestPendingMsgs, wr.n, JSPullRequestPendingBytes, wr.b) - o.outq.send(newJSPubMsg(wr.reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - wr = remove(pre, wr) - continue - } - // Now check interest. - interest := wr.acc.sl.HasInterest(wr.interest) - if !interest && (s.leafNodeEnabled || s.gateway.enabled) { - // If we are here check on gateways and leaf nodes (as they can mask gateways on the other end). - // If we have interest or the request is too young break and do not expire. - if time.Since(wr.received) < defaultGatewayRecentSubExpiration { - interest = true - } else if s.gateway.enabled && s.hasGatewayInterest(wr.acc.Name, wr.interest) { - interest = true - } - } - // Check if we have interest. - if !interest { - // No more interest here so go ahead and remove this one from our list. - wr = remove(pre, wr) - continue - } - - // If interest, update batch pending requests counter and update fexp timer. - brp += wr.n - if !wr.hbt.IsZero() { - if now.After(wr.hbt) { - // Fire off a heartbeat here. - o.sendIdleHeartbeat(wr.reply) - // Update next HB. - wr.hbt = now.Add(wr.hb) - } - if fexp.IsZero() || wr.hbt.Before(fexp) { - fexp = wr.hbt - } - } - if !wr.expires.IsZero() && (fexp.IsZero() || wr.expires.Before(fexp)) { - fexp = wr.expires - } - // Update pre and wr here. - pre = wr - wr = wr.next - } - - return expired, wq.len(), brp, fexp -} - -// Will check to make sure those waiting still have registered interest. -func (o *consumer) checkWaitingForInterest() bool { - o.processWaiting(true) - return o.waiting.len() > 0 -} - -// Lock should be held. -func (o *consumer) hbTimer() (time.Duration, *time.Timer) { - if o.cfg.Heartbeat == 0 { - return 0, nil - } - return o.cfg.Heartbeat, time.NewTimer(o.cfg.Heartbeat) -} - -// Check here for conditions when our ack floor may have drifted below the streams first sequence. -// In general this is accounted for in normal operations, but if the consumer misses the signal from -// the stream it will not clear the message and move the ack state. -// Should only be called from consumer leader. -func (o *consumer) checkAckFloor() { - o.mu.RLock() - mset, closed, asflr, numPending := o.mset, o.closed, o.asflr, len(o.pending) - o.mu.RUnlock() - - if asflr == 0 || closed || mset == nil { - return - } - - var ss StreamState - mset.store.FastState(&ss) - - // If our floor is equal or greater that is normal and nothing for us to do. - if ss.FirstSeq == 0 || asflr >= ss.FirstSeq-1 { - return - } - - // Check which linear space is less to walk. - if ss.FirstSeq-asflr-1 < uint64(numPending) { - // Process all messages that no longer exist. - for seq := asflr + 1; seq < ss.FirstSeq; seq++ { - // Check if this message was pending. - o.mu.RLock() - p, isPending := o.pending[seq] - rdc := o.deliveryCount(seq) - o.mu.RUnlock() - // If it was pending for us, get rid of it. - if isPending { - o.processTerm(seq, p.Sequence, rdc, ackTermLimitsReason, _EMPTY_) - } - } - } else if numPending > 0 { - // here it is shorter to walk pending. - // toTerm is seq, dseq, rcd for each entry. - toTerm := make([]uint64, 0, numPending*3) - o.mu.RLock() - for seq, p := range o.pending { - if seq < ss.FirstSeq { - var dseq uint64 = 1 - if p != nil { - dseq = p.Sequence - } - rdc := o.deliveryCount(seq) - toTerm = append(toTerm, seq, dseq, rdc) - } - } - o.mu.RUnlock() - - for i := 0; i < len(toTerm); i += 3 { - seq, dseq, rdc := toTerm[i], toTerm[i+1], toTerm[i+2] - o.processTerm(seq, dseq, rdc, ackTermLimitsReason, _EMPTY_) - } - } - - // Do one final check here. - o.mu.Lock() - defer o.mu.Unlock() - - // If we are closed do not change anything and simply return. - if o.closed { - return - } - - // If we are here, and this should be rare, we still are off with our ack floor. - // We will make sure we are not doing un-necessary work here if only off by a bit - // since this could be normal for a high activity wq or stream. - // We will set it explicitly to 1 behind our current lowest in pending, or if - // pending is empty, to our current delivered -1. - const minOffThreshold = 50 - if ss.FirstSeq >= minOffThreshold && o.asflr < ss.FirstSeq-minOffThreshold { - var psseq, pdseq uint64 - for seq, p := range o.pending { - if psseq == 0 || seq < psseq { - psseq, pdseq = seq, p.Sequence - } - } - // If we still have none, set to current delivered -1. - if psseq == 0 { - psseq, pdseq = o.sseq-1, o.dseq-1 - // If still not adjusted. - if psseq < ss.FirstSeq-1 { - psseq = ss.FirstSeq - 1 - } - } else { - // Since this was set via the pending, we should not include - // it directly but set floors to -1. - psseq, pdseq = psseq-1, pdseq-1 - } - o.asflr, o.adflr = psseq, pdseq - } -} - -func (o *consumer) processInboundAcks(qch chan struct{}) { - // Grab the server lock to watch for server quit. - o.mu.RLock() - s, mset := o.srv, o.mset - hasInactiveThresh := o.cfg.InactiveThreshold > 0 - - o.mu.RUnlock() - - if s == nil || mset == nil { - return - } - - // We will check this on entry and periodically. - o.checkAckFloor() - - // How often we will check for ack floor drift. - // Spread these out for large numbers on a server restart. - delta := time.Duration(rand.Int63n(int64(time.Minute))) - ticker := time.NewTicker(time.Minute + delta) - defer ticker.Stop() - - for { - select { - case <-o.ackMsgs.ch: - acks := o.ackMsgs.pop() - for _, ack := range acks { - o.processAck(ack.subject, ack.reply, ack.hdr, ack.msg) - ack.returnToPool() - } - o.ackMsgs.recycle(&acks) - // If we have an inactiveThreshold set, mark our activity. - if hasInactiveThresh { - o.suppressDeletion() - } - case <-ticker.C: - o.checkAckFloor() - case <-qch: - return - case <-s.quitCh: - return - } - } -} - -// Process inbound next message requests. -func (o *consumer) processInboundNextMsgReqs(qch chan struct{}) { - // Grab the server lock to watch for server quit. - o.mu.RLock() - s := o.srv - o.mu.RUnlock() - - for { - select { - case <-o.nextMsgReqs.ch: - reqs := o.nextMsgReqs.pop() - for _, req := range reqs { - o.processNextMsgRequest(req.reply, req.msg) - req.returnToPool() - } - o.nextMsgReqs.recycle(&reqs) - case <-qch: - return - case <-s.quitCh: - return - } - } -} - -// Suppress auto cleanup on ack activity of any kind. -func (o *consumer) suppressDeletion() { - o.mu.Lock() - defer o.mu.Unlock() - - if o.closed { - return - } - - if o.isPushMode() && o.dtmr != nil { - // if dtmr is not nil we have started the countdown, simply reset to threshold. - o.dtmr.Reset(o.dthresh) - } else if o.isPullMode() && o.waiting != nil { - // Pull mode always has timer running, just update last on waiting queue. - o.waiting.last = time.Now() - } -} - -// loopAndGatherMsgs waits for messages for the consumer. qch is the quit channel, -// upch is the unpause channel which fires when the PauseUntil deadline is reached. -func (o *consumer) loopAndGatherMsgs(qch chan struct{}) { - // On startup check to see if we are in a reply situation where replay policy is not instant. - var ( - lts int64 // last time stamp seen, used for replay. - lseq uint64 - ) - - o.mu.RLock() - mset := o.mset - getLSeq := o.replay - o.mu.RUnlock() - // consumer is closed when mset is set to nil. - if mset == nil { - return - } - if getLSeq { - lseq = mset.state().LastSeq - } - - o.mu.Lock() - s := o.srv - // need to check again if consumer is closed - if o.mset == nil { - o.mu.Unlock() - return - } - // For idle heartbeat support. - var hbc <-chan time.Time - hbd, hb := o.hbTimer() - if hb != nil { - hbc = hb.C - } - // Interest changes. - inch := o.inch - o.mu.Unlock() - - // Grab the stream's retention policy and name - mset.cfgMu.RLock() - stream, rp := mset.cfg.Name, mset.cfg.Retention - mset.cfgMu.RUnlock() - - var err error - - // Deliver all the msgs we have now, once done or on a condition, we wait for new ones. - for { - var ( - pmsg *jsPubMsg - dc uint64 - dsubj string - ackReply string - delay time.Duration - sz int - wrn, wrb int - ) - - o.mu.Lock() - - // consumer is closed when mset is set to nil. - if o.closed || o.mset == nil { - o.mu.Unlock() - return - } - - // Clear last error. - err = nil - - // If the consumer is paused then stop sending. - if o.cfg.PauseUntil != nil && !o.cfg.PauseUntil.IsZero() && time.Now().Before(*o.cfg.PauseUntil) { - // If the consumer is paused and we haven't reached the deadline yet then - // go back to waiting. - goto waitForMsgs - } - - // If we are in push mode and not active or under flowcontrol let's stop sending. - if o.isPushMode() { - if !o.active || (o.maxpb > 0 && o.pbytes > o.maxpb) { - goto waitForMsgs - } - } else if o.waiting.isEmpty() { - // If we are in pull mode and no one is waiting already break and wait. - goto waitForMsgs - } - - // Grab our next msg. - pmsg, dc, err = o.getNextMsg() - - // We can release the lock now under getNextMsg so need to check this condition again here. - if o.closed || o.mset == nil { - o.mu.Unlock() - return - } - - // On error either wait or return. - if err != nil || pmsg == nil { - // On EOF we can optionally fast sync num pending state. - if err == ErrStoreEOF { - o.checkNumPendingOnEOF() - } - if err == ErrStoreMsgNotFound || err == errDeletedMsg || err == ErrStoreEOF || err == errMaxAckPending { - goto waitForMsgs - } else if err == errPartialCache { - s.Warnf("Unexpected partial cache error looking up message for consumer '%s > %s > %s'", - o.mset.acc, stream, o.cfg.Name) - goto waitForMsgs - - } else { - s.Errorf("Received an error looking up message for consumer '%s > %s > %s': %v", - o.mset.acc, stream, o.cfg.Name, err) - goto waitForMsgs - } - } - - // Update our cached num pending here first. - if dc == 1 { - o.npc-- - } - // Pre-calculate ackReply - ackReply = o.ackReply(pmsg.seq, o.dseq, dc, pmsg.ts, o.numPending()) - - // If headers only do not send msg payload. - // Add in msg size itself as header. - if o.cfg.HeadersOnly { - convertToHeadersOnly(pmsg) - } - // Calculate payload size. This can be calculated on client side. - // We do not include transport subject here since not generally known on client. - sz = len(pmsg.subj) + len(ackReply) + len(pmsg.hdr) + len(pmsg.msg) - - if o.isPushMode() { - dsubj = o.dsubj - } else if wr := o.nextWaiting(sz); wr != nil { - wrn, wrb = wr.n, wr.b - dsubj = wr.reply - if o.cfg.PriorityPolicy == PriorityPinnedClient { - // FIXME(jrm): Can we make this prettier? - if len(pmsg.hdr) == 0 { - pmsg.hdr = genHeader(pmsg.hdr, JSPullRequestNatsPinId, o.currentPinId) - pmsg.buf = append(pmsg.hdr, pmsg.msg...) - } else { - pmsg.hdr = genHeader(pmsg.hdr, JSPullRequestNatsPinId, o.currentPinId) - bufLen := len(pmsg.hdr) + len(pmsg.msg) - pmsg.buf = make([]byte, bufLen) - pmsg.buf = append(pmsg.hdr, pmsg.msg...) - } - - sz = len(pmsg.subj) + len(ackReply) + len(pmsg.hdr) + len(pmsg.msg) - - } - if done := wr.recycleIfDone(); done && o.node != nil { - o.removeClusterPendingRequest(dsubj) - } else if !done && wr.hb > 0 { - wr.hbt = time.Now().Add(wr.hb) - } - } else { - // We will redo this one as long as this is not a redelivery. - // Need to also test that this is not going backwards since if - // we fail to deliver we can end up here from rdq but we do not - // want to decrement o.sseq if that is the case. - if dc == 1 && pmsg.seq == o.sseq-1 { - o.sseq-- - o.npc++ - } else if !o.onRedeliverQueue(pmsg.seq) { - // We are not on the rdq so decrement the delivery count - // and add it back. - o.decDeliveryCount(pmsg.seq) - o.addToRedeliverQueue(pmsg.seq) - } - pmsg.returnToPool() - goto waitForMsgs - } - - // If we are in a replay scenario and have not caught up check if we need to delay here. - if o.replay && lts > 0 { - if delay = time.Duration(pmsg.ts - lts); delay > time.Millisecond { - o.mu.Unlock() - select { - case <-qch: - pmsg.returnToPool() - return - case <-time.After(delay): - } - o.mu.Lock() - } - } - - // Track this regardless. - lts = pmsg.ts - - // If we have a rate limit set make sure we check that here. - if o.rlimit != nil { - now := time.Now() - r := o.rlimit.ReserveN(now, sz) - delay := r.DelayFrom(now) - if delay > 0 { - o.mu.Unlock() - select { - case <-qch: - pmsg.returnToPool() - return - case <-time.After(delay): - } - o.mu.Lock() - } - } - - // Do actual delivery. - o.deliverMsg(dsubj, ackReply, pmsg, dc, rp) - - // If given request fulfilled batch size, but there are still pending bytes, send information about it. - if wrn <= 0 && wrb > 0 { - msg := fmt.Appendf(nil, JsPullRequestRemainingBytesT, JSPullRequestPendingMsgs, wrn, JSPullRequestPendingBytes, wrb) - o.outq.send(newJSPubMsg(dsubj, _EMPTY_, _EMPTY_, msg, nil, nil, 0)) - } - // Reset our idle heartbeat timer if set. - if hb != nil { - hb.Reset(hbd) - } - - o.mu.Unlock() - continue - - waitForMsgs: - // If we were in a replay state check to see if we are caught up. If so clear. - if o.replay && o.sseq > lseq { - o.replay = false - } - - // Make sure to process any expired requests that are pending. - var wrExp <-chan time.Time - if o.isPullMode() { - // Dont expire oneshots if we are here because of max ack pending limit. - _, _, _, fexp := o.processWaiting(err != errMaxAckPending) - if !fexp.IsZero() { - expires := time.Until(fexp) - if expires <= 0 { - expires = time.Millisecond - } - wrExp = time.NewTimer(expires).C - } - } - - // We will wait here for new messages to arrive. - mch, odsubj := o.mch, o.cfg.DeliverSubject - o.mu.Unlock() - - select { - case <-mch: - // Messages are waiting. - case interest := <-inch: - // inch can be nil on pull-based, but then this will - // just block and not fire. - o.updateDeliveryInterest(interest) - case <-qch: - return - case <-wrExp: - o.mu.Lock() - o.processWaiting(true) - o.mu.Unlock() - case <-hbc: - if o.isActive() { - o.mu.RLock() - o.sendIdleHeartbeat(odsubj) - o.mu.RUnlock() - } - // Reset our idle heartbeat timer. - hb.Reset(hbd) - } - } -} - -// Lock should be held. -func (o *consumer) sendIdleHeartbeat(subj string) { - const t = "NATS/1.0 100 Idle Heartbeat\r\n%s: %d\r\n%s: %d\r\n\r\n" - sseq, dseq := o.sseq-1, o.dseq-1 - hdr := fmt.Appendf(nil, t, JSLastConsumerSeq, dseq, JSLastStreamSeq, sseq) - if fcp := o.fcid; fcp != _EMPTY_ { - // Add in that we are stalled on flow control here. - addOn := fmt.Appendf(nil, "%s: %s\r\n\r\n", JSConsumerStalled, fcp) - hdr = append(hdr[:len(hdr)-LEN_CR_LF], []byte(addOn)...) - } - o.outq.send(newJSPubMsg(subj, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) -} - -func (o *consumer) ackReply(sseq, dseq, dc uint64, ts int64, pending uint64) string { - return fmt.Sprintf(o.ackReplyT, dc, sseq, dseq, ts, pending) -} - -// Used mostly for testing. Sets max pending bytes for flow control setups. -func (o *consumer) setMaxPendingBytes(limit int) { - o.pblimit = limit - o.maxpb = limit / 16 - if o.maxpb == 0 { - o.maxpb = 1 - } -} - -// Does some sanity checks to see if we should re-calculate. -// Since there is a race when decrementing when there is contention at the beginning of the stream. -// The race is a getNextMsg skips a deleted msg, and then the decStreamPending call fires. -// This does some quick sanity checks to see if we should re-calculate num pending. -// Lock should be held. -func (o *consumer) checkNumPending() uint64 { - if o.mset != nil { - var state StreamState - o.mset.store.FastState(&state) - npc := o.numPending() - if o.sseq > state.LastSeq && npc > 0 || npc > state.Msgs { - // Re-calculate. - o.streamNumPending() - } - } - return o.numPending() -} - -// Lock should be held. -func (o *consumer) numPending() uint64 { - if o.npc < 0 { - return 0 - } - return uint64(o.npc) -} - -// This will do a quick sanity check on num pending when we encounter -// and EOF in the loop and gather. -// Lock should be held. -func (o *consumer) checkNumPendingOnEOF() { - if o.mset == nil { - return - } - var state StreamState - o.mset.store.FastState(&state) - if o.sseq > state.LastSeq && o.npc != 0 { - // We know here we can reset our running state for num pending. - o.npc, o.npf = 0, state.LastSeq - } -} - -// Call into streamNumPending after acquiring the consumer lock. -func (o *consumer) streamNumPendingLocked() uint64 { - o.mu.Lock() - defer o.mu.Unlock() - return o.streamNumPending() -} - -// Will force a set from the stream store of num pending. -// Depends on delivery policy, for last per subject we calculate differently. -// Lock should be held. -func (o *consumer) streamNumPending() uint64 { - if o.mset == nil || o.mset.store == nil { - o.npc, o.npf = 0, 0 - return 0 - } - npc, npf := o.calculateNumPending() - o.npc, o.npf = int64(npc), npf - return o.numPending() -} - -// Will calculate num pending but only requires a read lock. -// Depends on delivery policy, for last per subject we calculate differently. -// At least RLock should be held. -func (o *consumer) calculateNumPending() (npc, npf uint64) { - if o.mset == nil || o.mset.store == nil { - return 0, 0 - } - - isLastPerSubject := o.cfg.DeliverPolicy == DeliverLastPerSubject - filters, subjf := o.filters, o.subjf - - if filters != nil { - return o.mset.store.NumPendingMulti(o.sseq, filters, isLastPerSubject) - } else if len(subjf) > 0 { - filter := subjf[0].subject - return o.mset.store.NumPending(o.sseq, filter, isLastPerSubject) - } - return o.mset.store.NumPending(o.sseq, _EMPTY_, isLastPerSubject) -} - -func convertToHeadersOnly(pmsg *jsPubMsg) { - // If headers only do not send msg payload. - // Add in msg size itself as header. - hdr, msg := pmsg.hdr, pmsg.msg - var bb bytes.Buffer - if len(hdr) == 0 { - bb.WriteString(hdrLine) - } else { - bb.Write(hdr) - bb.Truncate(len(hdr) - LEN_CR_LF) - } - bb.WriteString(JSMsgSize) - bb.WriteString(": ") - bb.WriteString(strconv.FormatInt(int64(len(msg)), 10)) - bb.WriteString(CR_LF) - bb.WriteString(CR_LF) - // Replace underlying buf which we can use directly when we send. - // TODO(dlc) - Probably just use directly when forming bytes.Buffer? - pmsg.buf = pmsg.buf[:0] - pmsg.buf = append(pmsg.buf, bb.Bytes()...) - // Replace with new header. - pmsg.hdr = pmsg.buf - // Cancel msg payload - pmsg.msg = nil -} - -// Deliver a msg to the consumer. -// Lock should be held and o.mset validated to be non-nil. -func (o *consumer) deliverMsg(dsubj, ackReply string, pmsg *jsPubMsg, dc uint64, rp RetentionPolicy) { - if o.mset == nil { - pmsg.returnToPool() - return - } - - dseq := o.dseq - o.dseq++ - - pmsg.dsubj, pmsg.reply, pmsg.o = dsubj, ackReply, o - psz := pmsg.size() - - if o.maxpb > 0 { - o.pbytes += psz - } - - mset := o.mset - ap := o.cfg.AckPolicy - - // Cant touch pmsg after this sending so capture what we need. - seq, ts := pmsg.seq, pmsg.ts - - // Update delivered first. - o.updateDelivered(dseq, seq, dc, ts) - - if ap == AckExplicit || ap == AckAll { - o.trackPending(seq, dseq) - } else if ap == AckNone { - o.adflr = dseq - o.asflr = seq - } - - // Send message. - o.outq.send(pmsg) - - // Flow control. - if o.maxpb > 0 && o.needFlowControl(psz) { - o.sendFlowControl() - } - - // If pull mode and we have inactivity threshold, signaled by dthresh, update last activity. - if o.isPullMode() && o.dthresh > 0 { - o.waiting.last = time.Now() - } - - // If we are ack none and mset is interest only we should make sure stream removes interest. - if ap == AckNone && rp != LimitsPolicy { - if mset != nil && mset.ackq != nil && (o.node == nil || o.cfg.Direct) { - mset.ackq.push(seq) - } else { - o.updateAcks(dseq, seq, _EMPTY_) - } - } -} - -func (o *consumer) needFlowControl(sz int) bool { - if o.maxpb == 0 { - return false - } - // Decide whether to send a flow control message which we will need the user to respond. - // We send when we are over 50% of our current window limit. - if o.fcid == _EMPTY_ && o.pbytes > o.maxpb/2 { - return true - } - // If we have an existing outstanding FC, check to see if we need to expand the o.fcsz - if o.fcid != _EMPTY_ && (o.pbytes-o.fcsz) >= o.maxpb { - o.fcsz += sz - } - return false -} - -func (o *consumer) processFlowControl(_ *subscription, c *client, _ *Account, subj, _ string, _ []byte) { - o.mu.Lock() - defer o.mu.Unlock() - - // Ignore if not the latest we have sent out. - if subj != o.fcid { - return - } - - // For slow starts and ramping up. - if o.maxpb < o.pblimit { - o.maxpb *= 2 - if o.maxpb > o.pblimit { - o.maxpb = o.pblimit - } - } - - // Update accounting. - o.pbytes -= o.fcsz - if o.pbytes < 0 { - o.pbytes = 0 - } - o.fcid, o.fcsz = _EMPTY_, 0 - - o.signalNewMessages() -} - -// Lock should be held. -func (o *consumer) fcReply() string { - var sb strings.Builder - sb.WriteString(jsFlowControlPre) - sb.WriteString(o.stream) - sb.WriteByte(btsep) - sb.WriteString(o.name) - sb.WriteByte(btsep) - var b [4]byte - rn := rand.Int63() - for i, l := 0, rn; i < len(b); i++ { - b[i] = digits[l%base] - l /= base - } - sb.Write(b[:]) - return sb.String() -} - -// sendFlowControl will send a flow control packet to the consumer. -// Lock should be held. -func (o *consumer) sendFlowControl() { - if !o.isPushMode() { - return - } - subj, rply := o.cfg.DeliverSubject, o.fcReply() - o.fcsz, o.fcid = o.pbytes, rply - hdr := []byte("NATS/1.0 100 FlowControl Request\r\n\r\n") - o.outq.send(newJSPubMsg(subj, _EMPTY_, rply, hdr, nil, nil, 0)) -} - -// Tracks our outstanding pending acks. Only applicable to AckExplicit mode. -// Lock should be held. -func (o *consumer) trackPending(sseq, dseq uint64) { - if o.pending == nil { - o.pending = make(map[uint64]*Pending) - } - - // We could have a backoff that set a timer higher than what we need for this message. - // In that case, reset to lowest backoff required for a message redelivery. - minDelay := o.ackWait(0) - if l := len(o.cfg.BackOff); l > 0 { - bi := int(o.rdc[sseq]) - if bi < 0 { - bi = 0 - } else if bi >= l { - bi = l - 1 - } - minDelay = o.ackWait(o.cfg.BackOff[bi]) - } - minDeadline := time.Now().Add(minDelay) - if o.ptmr == nil || o.ptmrEnd.After(minDeadline) { - o.resetPtmr(minDelay) - } - - if p, ok := o.pending[sseq]; ok { - // Update timestamp but keep original consumer delivery sequence. - // So do not update p.Sequence. - p.Timestamp = time.Now().UnixNano() - } else { - o.pending[sseq] = &Pending{dseq, time.Now().UnixNano()} - } -} - -// Credit back a failed delivery. -// lock should be held. -func (o *consumer) creditWaitingRequest(reply string) { - wq := o.waiting - for wr := wq.head; wr != nil; wr = wr.next { - if wr.reply == reply { - wr.n++ - wr.d-- - return - } - } -} - -// didNotDeliver is called when a delivery for a consumer message failed. -// Depending on our state, we will process the failure. -func (o *consumer) didNotDeliver(seq uint64, subj string) { - o.mu.Lock() - mset := o.mset - if mset == nil { - o.mu.Unlock() - return - } - // Adjust back deliver count. - o.decDeliveryCount(seq) - - var checkDeliveryInterest bool - if o.isPushMode() { - o.active = false - checkDeliveryInterest = true - } else if o.pending != nil { - // Good chance we did not deliver because no interest so force a check. - o.processWaiting(false) - // If it is still there credit it back. - o.creditWaitingRequest(subj) - // pull mode and we have pending. - if _, ok := o.pending[seq]; ok { - // We found this messsage on pending, we need - // to queue it up for immediate redelivery since - // we know it was not delivered - if !o.onRedeliverQueue(seq) { - o.addToRedeliverQueue(seq) - if !o.waiting.isEmpty() { - o.signalNewMessages() - } - } - } - } - o.mu.Unlock() - - // If we do not have interest update that here. - if checkDeliveryInterest && o.hasNoLocalInterest() { - o.updateDeliveryInterest(false) - } -} - -// Lock should be held. -func (o *consumer) addToRedeliverQueue(seqs ...uint64) { - o.rdq = append(o.rdq, seqs...) - for _, seq := range seqs { - o.rdqi.Insert(seq) - } -} - -// Lock should be held. -func (o *consumer) hasRedeliveries() bool { - return len(o.rdq) > 0 -} - -func (o *consumer) getNextToRedeliver() uint64 { - if len(o.rdq) == 0 { - return 0 - } - seq := o.rdq[0] - if len(o.rdq) == 1 { - o.rdq = nil - o.rdqi.Empty() - } else { - o.rdq = append(o.rdq[:0], o.rdq[1:]...) - o.rdqi.Delete(seq) - } - return seq -} - -// This checks if we already have this sequence queued for redelivery. -// FIXME(dlc) - This is O(n) but should be fast with small redeliver size. -// Lock should be held. -func (o *consumer) onRedeliverQueue(seq uint64) bool { - return o.rdqi.Exists(seq) -} - -// Remove a sequence from the redelivery queue. -// Lock should be held. -func (o *consumer) removeFromRedeliverQueue(seq uint64) bool { - if !o.onRedeliverQueue(seq) { - return false - } - for i, rseq := range o.rdq { - if rseq == seq { - if len(o.rdq) == 1 { - o.rdq = nil - o.rdqi.Empty() - } else { - o.rdq = append(o.rdq[:i], o.rdq[i+1:]...) - o.rdqi.Delete(seq) - } - return true - } - } - return false -} - -// Checks the pending messages. -func (o *consumer) checkPending() { - o.mu.Lock() - defer o.mu.Unlock() - - mset := o.mset - // On stop, mset and timer will be nil. - if o.closed || mset == nil || o.ptmr == nil { - o.stopAndClearPtmr() - return - } - - var shouldUpdateState bool - var state StreamState - mset.store.FastState(&state) - fseq := state.FirstSeq - - now := time.Now().UnixNano() - ttl := int64(o.cfg.AckWait) - next := int64(o.ackWait(0)) - // However, if there is backoff, initializes with the largest backoff. - // It will be adjusted as needed. - if l := len(o.cfg.BackOff); l > 0 { - next = int64(o.cfg.BackOff[l-1]) - } - - // Since we can update timestamps, we have to review all pending. - // We will now bail if we see an ack pending inbound to us via o.awl. - var expired []uint64 - check := len(o.pending) > 1024 - for seq, p := range o.pending { - if check && atomic.LoadInt64(&o.awl) > 0 { - o.resetPtmr(100 * time.Millisecond) - return - } - // Check if these are no longer valid. - if seq < fseq || seq <= o.asflr { - delete(o.pending, seq) - delete(o.rdc, seq) - o.removeFromRedeliverQueue(seq) - shouldUpdateState = true - // Check if we need to move ack floors. - if seq > o.asflr { - o.asflr = seq - } - if p.Sequence > o.adflr { - o.adflr = p.Sequence - } - continue - } - elapsed, deadline := now-p.Timestamp, ttl - if len(o.cfg.BackOff) > 0 { - // This is ok even if o.rdc is nil, we would get dc == 0, which is what we want. - dc := int(o.rdc[seq]) - if dc < 0 { - // Prevent consumer backoff from going backwards. - dc = 0 - } - // This will be the index for the next backoff, will set to last element if needed. - nbi := dc + 1 - if dc+1 >= len(o.cfg.BackOff) { - dc = len(o.cfg.BackOff) - 1 - nbi = dc - } - deadline = int64(o.cfg.BackOff[dc]) - // Set `next` to the next backoff (if smaller than current `next` value). - if nextBackoff := int64(o.cfg.BackOff[nbi]); nextBackoff < next { - next = nextBackoff - } - } - if elapsed >= deadline { - // We will check if we have hit our max deliveries. Previously we would do this on getNextMsg() which - // worked well for push consumers, but with pull based consumers would require a new pull request to be - // present to process and redelivered could be reported incorrectly. - if !o.onRedeliverQueue(seq) && !o.hasMaxDeliveries(seq) { - expired = append(expired, seq) - } - } else if deadline-elapsed < next { - // Update when we should fire next. - next = deadline - elapsed - } - } - - if len(expired) > 0 { - // We need to sort. - slices.Sort(expired) - o.addToRedeliverQueue(expired...) - // Now we should update the timestamp here since we are redelivering. - // We will use an incrementing time to preserve order for any other redelivery. - off := now - o.pending[expired[0]].Timestamp - for _, seq := range expired { - if p, ok := o.pending[seq]; ok { - p.Timestamp += off - } - } - o.signalNewMessages() - } - - if len(o.pending) > 0 { - o.resetPtmr(time.Duration(next)) - } else { - // Make sure to stop timer and clear out any re delivery queues - o.stopAndClearPtmr() - o.rdq = nil - o.rdqi.Empty() - o.pending = nil - // Mimic behavior in processAckMsg when pending is empty. - o.adflr, o.asflr = o.dseq-1, o.sseq-1 - } - - // Update our state if needed. - if shouldUpdateState { - if err := o.writeStoreStateUnlocked(); err != nil && o.srv != nil && o.mset != nil && !o.closed { - s, acc, mset, name := o.srv, o.acc, o.mset, o.name - s.Warnf("Consumer '%s > %s > %s' error on write store state from check pending: %v", acc, mset.getCfgName(), name, err) - } - } -} - -// SeqFromReply will extract a sequence number from a reply subject. -func (o *consumer) seqFromReply(reply string) uint64 { - _, dseq, _ := ackReplyInfo(reply) - return dseq -} - -// StreamSeqFromReply will extract the stream sequence from the reply subject. -func (o *consumer) streamSeqFromReply(reply string) uint64 { - sseq, _, _ := ackReplyInfo(reply) - return sseq -} - -// Quick parser for positive numbers in ack reply encoding. -func parseAckReplyNum(d string) (n int64) { - if len(d) == 0 { - return -1 - } - for _, dec := range d { - if dec < asciiZero || dec > asciiNine { - return -1 - } - n = n*10 + (int64(dec) - asciiZero) - } - return n -} - -const expectedNumReplyTokens = 9 - -// Grab encoded information in the reply subject for a delivered message. -func replyInfo(subject string) (sseq, dseq, dc uint64, ts int64, pending uint64) { - tsa := [expectedNumReplyTokens]string{} - start, tokens := 0, tsa[:0] - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - tokens = append(tokens, subject[start:i]) - start = i + 1 - } - } - tokens = append(tokens, subject[start:]) - if len(tokens) != expectedNumReplyTokens || tokens[0] != "$JS" || tokens[1] != "ACK" { - return 0, 0, 0, 0, 0 - } - // TODO(dlc) - Should we error if we do not match consumer name? - // stream is tokens[2], consumer is 3. - dc = uint64(parseAckReplyNum(tokens[4])) - sseq, dseq = uint64(parseAckReplyNum(tokens[5])), uint64(parseAckReplyNum(tokens[6])) - ts = parseAckReplyNum(tokens[7]) - pending = uint64(parseAckReplyNum(tokens[8])) - - return sseq, dseq, dc, ts, pending -} - -func ackReplyInfo(subject string) (sseq, dseq, dc uint64) { - tsa := [expectedNumReplyTokens]string{} - start, tokens := 0, tsa[:0] - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - tokens = append(tokens, subject[start:i]) - start = i + 1 - } - } - tokens = append(tokens, subject[start:]) - if len(tokens) != expectedNumReplyTokens || tokens[0] != "$JS" || tokens[1] != "ACK" { - return 0, 0, 0 - } - dc = uint64(parseAckReplyNum(tokens[4])) - sseq, dseq = uint64(parseAckReplyNum(tokens[5])), uint64(parseAckReplyNum(tokens[6])) - - return sseq, dseq, dc -} - -// NextSeq returns the next delivered sequence number for this consumer. -func (o *consumer) nextSeq() uint64 { - o.mu.RLock() - dseq := o.dseq - o.mu.RUnlock() - return dseq -} - -// Used to hold skip list when deliver policy is last per subject. -type lastSeqSkipList struct { - resume uint64 - seqs []uint64 -} - -// Let's us know we have a skip list, which is for deliver last per subject and we are just starting. -// Lock should be held. -func (o *consumer) hasSkipListPending() bool { - return o.lss != nil && len(o.lss.seqs) > 0 -} - -// Will select the starting sequence. -func (o *consumer) selectStartingSeqNo() { - if o.mset == nil || o.mset.store == nil { - o.sseq = 1 - } else { - var state StreamState - o.mset.store.FastState(&state) - if o.cfg.OptStartSeq == 0 { - if o.cfg.DeliverPolicy == DeliverAll { - o.sseq = state.FirstSeq - } else if o.cfg.DeliverPolicy == DeliverLast { - if o.subjf == nil { - o.sseq = state.LastSeq - } else { - // If we are partitioned here this will be properly set when we become leader. - for _, filter := range o.subjf { - ss := o.mset.store.FilteredState(1, filter.subject) - if ss.Last > o.sseq { - o.sseq = ss.Last - } - } - } - } else if o.cfg.DeliverPolicy == DeliverLastPerSubject { - // If our parent stream is set to max msgs per subject of 1 this is just - // a normal consumer at this point. We can avoid any heavy lifting. - o.mset.cfgMu.RLock() - mmp := o.mset.cfg.MaxMsgsPer - o.mset.cfgMu.RUnlock() - if mmp == 1 { - o.sseq = state.FirstSeq - } else { - // A threshold for when we switch from get last msg to subjects state. - const numSubjectsThresh = 256 - lss := &lastSeqSkipList{resume: state.LastSeq} - var filters []string - if o.subjf == nil { - filters = append(filters, o.cfg.FilterSubject) - } else { - for _, filter := range o.subjf { - filters = append(filters, filter.subject) - } - } - for _, filter := range filters { - if st := o.mset.store.SubjectsTotals(filter); len(st) < numSubjectsThresh { - var smv StoreMsg - for subj := range st { - if sm, err := o.mset.store.LoadLastMsg(subj, &smv); err == nil { - lss.seqs = append(lss.seqs, sm.seq) - } - } - } else if mss := o.mset.store.SubjectsState(filter); len(mss) > 0 { - for _, ss := range mss { - lss.seqs = append(lss.seqs, ss.Last) - } - } - } - // Sort the skip list if needed. - if len(lss.seqs) > 1 { - slices.Sort(lss.seqs) - } - if len(lss.seqs) == 0 { - o.sseq = state.LastSeq - } else { - o.sseq = lss.seqs[0] - } - // Assign skip list. - o.lss = lss - } - } else if o.cfg.OptStartTime != nil { - // If we are here we are time based. - // TODO(dlc) - Once clustered can't rely on this. - o.sseq = o.mset.store.GetSeqFromTime(*o.cfg.OptStartTime) - // Here we want to see if we are filtered, and if so possibly close the gap - // to the nearest first given our starting sequence from time. This is so we do - // not force the system to do a linear walk between o.sseq and the real first. - if len(o.subjf) > 0 { - nseq := state.LastSeq - for _, filter := range o.subjf { - // Use first sequence since this is more optimized atm. - ss := o.mset.store.FilteredState(state.FirstSeq, filter.subject) - if ss.First >= o.sseq && ss.First < nseq { - nseq = ss.First - } - } - // Skip ahead if possible. - if nseq > o.sseq && nseq < state.LastSeq { - o.sseq = nseq - } - } - } else { - // DeliverNew - o.sseq = state.LastSeq + 1 - } - } else { - o.sseq = o.cfg.OptStartSeq - } - - if state.FirstSeq == 0 && (o.cfg.Direct || o.cfg.OptStartSeq == 0) { - // If the stream is empty, deliver only new. - // But only if mirroring/sourcing, or start seq is unset, otherwise need to respect provided value. - o.sseq = 1 - } else if o.sseq > state.LastSeq && (o.cfg.Direct || o.cfg.OptStartSeq == 0) { - // If selected sequence is in the future, clamp back down. - // But only if mirroring/sourcing, or start seq is unset, otherwise need to respect provided value. - o.sseq = state.LastSeq + 1 - } else if o.sseq < state.FirstSeq { - // If the first sequence is further ahead than the starting sequence, - // there are no messages there anymore, so move the sequence up. - o.sseq = state.FirstSeq - } - } - - // Always set delivery sequence to 1. - o.dseq = 1 - // Set ack delivery floor to delivery-1 - o.adflr = o.dseq - 1 - // Set ack store floor to store-1 - o.asflr = o.sseq - 1 - // Set our starting sequence state. - // But only if we're not clustered, if clustered we propose upon becoming leader. - if o.store != nil && o.sseq > 0 && o.cfg.replicas(&o.mset.cfg) == 1 { - o.store.SetStarting(o.sseq - 1) - } -} - -// Test whether a config represents a durable subscriber. -func isDurableConsumer(config *ConsumerConfig) bool { - return config != nil && config.Durable != _EMPTY_ -} - -func (o *consumer) isDurable() bool { - return o.cfg.Durable != _EMPTY_ -} - -// Are we in push mode, delivery subject, etc. -func (o *consumer) isPushMode() bool { - return o.cfg.DeliverSubject != _EMPTY_ -} - -func (o *consumer) isPullMode() bool { - return o.cfg.DeliverSubject == _EMPTY_ -} - -// Name returns the name of this consumer. -func (o *consumer) String() string { - o.mu.RLock() - n := o.name - o.mu.RUnlock() - return n -} - -func createConsumerName() string { - return getHash(nuid.Next()) -} - -// deleteConsumer will delete the consumer from this stream. -func (mset *stream) deleteConsumer(o *consumer) error { - return o.delete() -} - -func (o *consumer) getStream() *stream { - o.mu.RLock() - mset := o.mset - o.mu.RUnlock() - return mset -} - -func (o *consumer) streamName() string { - o.mu.RLock() - mset := o.mset - o.mu.RUnlock() - if mset != nil { - return mset.name() - } - return _EMPTY_ -} - -// Active indicates if this consumer is still active. -func (o *consumer) isActive() bool { - o.mu.RLock() - active := o.active && o.mset != nil - o.mu.RUnlock() - return active -} - -// hasNoLocalInterest return true if we have no local interest. -func (o *consumer) hasNoLocalInterest() bool { - o.mu.RLock() - interest := o.acc.sl.HasInterest(o.cfg.DeliverSubject) - o.mu.RUnlock() - return !interest -} - -// This is when the underlying stream has been purged. -// sseq is the new first seq for the stream after purge. -// Lock should NOT be held. -func (o *consumer) purge(sseq uint64, slseq uint64, isWider bool) { - // Do not update our state unless we know we are the leader. - if !o.isLeader() { - return - } - // Signals all have been purged for this consumer. - if sseq == 0 && !isWider { - sseq = slseq + 1 - } - - var store StreamStore - if isWider { - o.mu.RLock() - if o.mset != nil { - store = o.mset.store - } - o.mu.RUnlock() - } - - o.mu.Lock() - // Do not go backwards - if o.sseq < sseq { - o.sseq = sseq - } - - if o.asflr < sseq { - o.asflr = sseq - 1 - // We need to remove those no longer relevant from pending. - for seq, p := range o.pending { - if seq <= o.asflr { - if p.Sequence > o.adflr { - o.adflr = p.Sequence - if o.adflr > o.dseq { - o.dseq = o.adflr - } - } - delete(o.pending, seq) - delete(o.rdc, seq) - // rdq handled below. - } - if isWider && store != nil { - // Our filtered subject, which could be all, is wider than the underlying purge. - // We need to check if the pending items left are still valid. - var smv StoreMsg - if _, err := store.LoadMsg(seq, &smv); err == errDeletedMsg || err == ErrStoreMsgNotFound { - if p.Sequence > o.adflr { - o.adflr = p.Sequence - if o.adflr > o.dseq { - o.dseq = o.adflr - } - } - delete(o.pending, seq) - delete(o.rdc, seq) - } - } - } - } - - // This means we can reset everything at this point. - if len(o.pending) == 0 { - o.pending, o.rdc = nil, nil - o.adflr, o.asflr = o.dseq-1, o.sseq-1 - } - - // We need to remove all those being queued for redelivery under o.rdq - if len(o.rdq) > 0 { - rdq := o.rdq - o.rdq = nil - o.rdqi.Empty() - for _, sseq := range rdq { - if sseq >= o.sseq { - o.addToRedeliverQueue(sseq) - } - } - } - // Grab some info in case of error below. - s, acc, mset, name := o.srv, o.acc, o.mset, o.name - o.mu.Unlock() - - if err := o.writeStoreState(); err != nil && s != nil && mset != nil { - s.Warnf("Consumer '%s > %s > %s' error on write store state from purge: %v", acc, mset.name(), name, err) - } -} - -func stopAndClearTimer(tp **time.Timer) { - if *tp == nil { - return - } - // Will get drained in normal course, do not try to - // drain here. - (*tp).Stop() - *tp = nil -} - -// Stop will shutdown the consumer for the associated stream. -func (o *consumer) stop() error { - return o.stopWithFlags(false, false, true, false) -} - -func (o *consumer) deleteWithoutAdvisory() error { - return o.stopWithFlags(true, false, true, false) -} - -// Delete will delete the consumer for the associated stream and send advisories. -func (o *consumer) delete() error { - return o.stopWithFlags(true, false, true, true) -} - -// To test for closed state. -func (o *consumer) isClosed() bool { - o.mu.RLock() - defer o.mu.RUnlock() - return o.closed -} - -func (o *consumer) stopWithFlags(dflag, sdflag, doSignal, advisory bool) error { - // If dflag is true determine if we are still assigned. - var isAssigned bool - if dflag { - o.mu.RLock() - acc, stream, consumer := o.acc, o.stream, o.name - isClustered := o.js != nil && o.js.isClustered() - o.mu.RUnlock() - if isClustered { - // Grab jsa to check assignment. - var jsa *jsAccount - if acc != nil { - // Need lock here to avoid data race. - acc.mu.RLock() - jsa = acc.js - acc.mu.RUnlock() - } - if jsa != nil { - isAssigned = jsa.consumerAssigned(stream, consumer) - } - } - } - - o.mu.Lock() - if o.closed { - o.mu.Unlock() - return nil - } - o.closed = true - - // Check if we are the leader and are being deleted (as a node). - if dflag && o.isLeader() { - // If we are clustered and node leader (probable from above), stepdown. - if node := o.node; node != nil { - node.StepDown() - } - - // dflag does not necessarily mean that the consumer is being deleted, - // just that the consumer node is being removed from this peer, so we - // send delete advisories only if we are no longer assigned at the meta layer, - // or we are not clustered. - if !isAssigned && advisory { - o.sendDeleteAdvisoryLocked() - } - if o.isPullMode() { - // Release any pending. - o.releaseAnyPendingRequests(isAssigned) - } - } - - if o.qch != nil { - close(o.qch) - o.qch = nil - } - - a := o.acc - store := o.store - mset := o.mset - o.mset = nil - o.active = false - o.unsubscribe(o.ackSub) - o.unsubscribe(o.reqSub) - o.unsubscribe(o.fcSub) - o.ackSub = nil - o.reqSub = nil - o.fcSub = nil - if o.infoSub != nil { - o.srv.sysUnsubscribe(o.infoSub) - o.infoSub = nil - } - c := o.client - o.client = nil - sysc := o.sysc - o.sysc = nil - o.stopAndClearPtmr() - stopAndClearTimer(&o.dtmr) - stopAndClearTimer(&o.gwdtmr) - delivery := o.cfg.DeliverSubject - o.waiting = nil - // Break us out of the readLoop. - if doSignal { - o.signalNewMessages() - } - n := o.node - qgroup := o.cfg.DeliverGroup - o.ackMsgs.unregister() - if o.nextMsgReqs != nil { - o.nextMsgReqs.unregister() - } - - // For cleaning up the node assignment. - var ca *consumerAssignment - if dflag { - ca = o.ca - } - js := o.js - o.mu.Unlock() - - if c != nil { - c.closeConnection(ClientClosed) - } - if sysc != nil { - sysc.closeConnection(ClientClosed) - } - - if delivery != _EMPTY_ { - a.sl.clearNotification(delivery, qgroup, o.inch) - } - - var rp RetentionPolicy - if mset != nil { - mset.mu.Lock() - mset.removeConsumer(o) - // No need for cfgMu's lock since mset.mu.Lock superseeds it. - rp = mset.cfg.Retention - mset.mu.Unlock() - } - - // Cleanup messages that lost interest. - if dflag && rp == InterestPolicy { - o.cleanupNoInterestMessages(mset, true) - } - - // Cluster cleanup. - if n != nil { - if dflag { - n.Delete() - } else { - n.Stop() - } - } - - if ca != nil { - js.mu.Lock() - if ca.Group != nil { - ca.Group.node = nil - } - js.mu.Unlock() - } - - // Clean up our store. - var err error - if store != nil { - if dflag { - if sdflag { - err = store.StreamDelete() - } else { - err = store.Delete() - } - } else { - err = store.Stop() - } - } - - return err -} - -// We need to optionally remove all messages since we are interest based retention. -// We will do this consistently on all replicas. Note that if in clustered mode the non-leader -// consumers will need to restore state first. -// ignoreInterest marks whether the consumer should be ignored when determining interest. -// No lock held on entry. -func (o *consumer) cleanupNoInterestMessages(mset *stream, ignoreInterest bool) { - o.mu.Lock() - if !o.isLeader() { - o.readStoredState(0) - } - start := o.asflr - o.mu.Unlock() - - // Make sure we start at worst with first sequence in the stream. - state := mset.state() - if start < state.FirstSeq { - start = state.FirstSeq - } - stop := state.LastSeq - - // Consumer's interests are ignored by default. If we should not ignore interest, unset. - co := o - if !ignoreInterest { - co = nil - } - - var rmseqs []uint64 - mset.mu.RLock() - - // If over this amount of messages to check, optimistically call to checkInterestState(). - // It will not always do the right thing in removing messages that lost interest, but ensures - // we don't degrade performance by doing a linear scan through the whole stream. - // Messages might need to expire based on limits to be cleaned up. - // TODO(dlc) - Better way? - const bailThresh = 100_000 - - // Check if we would be spending too much time here and defer to separate go routine. - if len(mset.consumers) == 0 { - mset.mu.RUnlock() - mset.mu.Lock() - defer mset.mu.Unlock() - mset.store.Purge() - var state StreamState - mset.store.FastState(&state) - mset.lseq = state.LastSeq - // Also make sure we clear any pending acks. - mset.clearAllPreAcksBelowFloor(state.FirstSeq) - return - } else if stop-start > bailThresh { - mset.mu.RUnlock() - go mset.checkInterestState() - return - } - - mset.mu.RUnlock() - mset.mu.Lock() - for seq := start; seq <= stop; seq++ { - if mset.noInterest(seq, co) { - rmseqs = append(rmseqs, seq) - } - } - mset.mu.Unlock() - - // These can be removed. - for _, seq := range rmseqs { - mset.store.RemoveMsg(seq) - } -} - -// Check that we do not form a cycle by delivering to a delivery subject -// that is part of the interest group. -func deliveryFormsCycle(cfg *StreamConfig, deliverySubject string) bool { - for _, subject := range cfg.Subjects { - if subjectIsSubsetMatch(deliverySubject, subject) { - return true - } - } - return false -} - -// switchToEphemeral is called on startup when recovering ephemerals. -func (o *consumer) switchToEphemeral() { - o.mu.Lock() - o.cfg.Durable = _EMPTY_ - store, ok := o.store.(*consumerFileStore) - interest := o.acc.sl.HasInterest(o.cfg.DeliverSubject) - // Setup dthresh. - o.updateInactiveThreshold(&o.cfg) - o.updatePauseState(&o.cfg) - o.mu.Unlock() - - // Update interest - o.updateDeliveryInterest(interest) - // Write out new config - if ok { - store.updateConfig(o.cfg) - } -} - -// RequestNextMsgSubject returns the subject to request the next message when in pull or worker mode. -// Returns empty otherwise. -func (o *consumer) requestNextMsgSubject() string { - return o.nextMsgSubj -} - -func (o *consumer) decStreamPending(sseq uint64, subj string) { - o.mu.Lock() - - // Update our cached num pending only if we think deliverMsg has not done so. - if sseq >= o.sseq && o.isFilteredMatch(subj) { - o.npc-- - } - - // Check if this message was pending. - p, wasPending := o.pending[sseq] - rdc := o.deliveryCount(sseq) - - o.mu.Unlock() - - // If it was pending process it like an ack. - if wasPending { - // We could have the lock for the stream so do this in a go routine. - // TODO(dlc) - We should do this with ipq vs naked go routines. - go o.processTerm(sseq, p.Sequence, rdc, ackTermUnackedLimitsReason, _EMPTY_) - } -} - -func (o *consumer) account() *Account { - o.mu.RLock() - a := o.acc - o.mu.RUnlock() - return a -} - -// Creates a sublist for consumer. -// All subjects share the same callback. -func (o *consumer) signalSubs() []string { - o.mu.Lock() - defer o.mu.Unlock() - - if o.sigSubs != nil { - return o.sigSubs - } - - if len(o.subjf) == 0 { - subs := []string{fwcs} - o.sigSubs = subs - return subs - } - - subs := make([]string, 0, len(o.subjf)) - for _, filter := range o.subjf { - subs = append(subs, filter.subject) - } - o.sigSubs = subs - return subs -} - -// This is what will be called when our parent stream wants to kick us regarding a new message. -// We know that this subject matches us by how the parent handles registering us with the signaling sublist, -// but we must check if we are leader. -// We do need the sequence of the message however and we use the msg as the encoded seq. -func (o *consumer) processStreamSignal(seq uint64) { - // We can get called here now when not leader, so bail fast - // and without acquiring any locks. - if !o.leader.Load() { - return - } - o.mu.Lock() - defer o.mu.Unlock() - if o.mset == nil { - return - } - if seq > o.npf { - o.npc++ - } - if seq < o.sseq { - return - } - if o.isPushMode() && o.active || o.isPullMode() && !o.waiting.isEmpty() { - o.signalNewMessages() - } -} - -// Used to compare if two multiple filtered subject lists are equal. -func subjectSliceEqual(slice1 []string, slice2 []string) bool { - if len(slice1) != len(slice2) { - return false - } - set2 := make(map[string]struct{}, len(slice2)) - for _, val := range slice2 { - set2[val] = struct{}{} - } - for _, val := range slice1 { - if _, ok := set2[val]; !ok { - return false - } - } - return true -} - -// Utility for simpler if conditions in Consumer config checks. -// In future iteration, we can immediately create `o.subjf` and -// use it to validate things. -func gatherSubjectFilters(filter string, filters []string) []string { - if filter != _EMPTY_ { - filters = append(filters, filter) - } - // list of filters should never contain non-empty filter. - return filters -} - -// shouldStartMonitor will return true if we should start a monitor -// goroutine or will return false if one is already running. -func (o *consumer) shouldStartMonitor() bool { - o.mu.Lock() - defer o.mu.Unlock() - - if o.inMonitor { - return false - } - o.monitorWg.Add(1) - o.inMonitor = true - return true -} - -// Clear the monitor running state. The monitor goroutine should -// call this in a defer to clean up on exit. -func (o *consumer) clearMonitorRunning() { - o.mu.Lock() - defer o.mu.Unlock() - - if o.inMonitor { - o.monitorWg.Done() - o.inMonitor = false - } -} - -// Test whether we are in the monitor routine. -func (o *consumer) isMonitorRunning() bool { - o.mu.RLock() - defer o.mu.RUnlock() - return o.inMonitor -} - -// If we detect that our ackfloor is higher than the stream's last sequence, return this error. -var errAckFloorHigherThanLastSeq = errors.New("consumer ack floor is higher than streams last sequence") -var errAckFloorInvalid = errors.New("consumer ack floor is invalid") - -// If we are a consumer of an interest or workqueue policy stream, process that state and make sure consistent. -func (o *consumer) checkStateForInterestStream(ss *StreamState) error { - o.mu.RLock() - // See if we need to process this update if our parent stream is not a limits policy stream. - mset := o.mset - shouldProcessState := mset != nil && o.retention != LimitsPolicy - if o.closed || !shouldProcessState || o.store == nil || ss == nil { - o.mu.RUnlock() - return nil - } - store := mset.store - state, err := o.store.State() - - filters, subjf, filter := o.filters, o.subjf, _EMPTY_ - var wc bool - if filters == nil && subjf != nil { - filter, wc = subjf[0].subject, subjf[0].hasWildcard - } - chkfloor := o.chkflr - o.mu.RUnlock() - - if err != nil { - return err - } - - asflr := state.AckFloor.Stream - // Protect ourselves against rolling backwards. - if asflr&(1<<63) != 0 { - return errAckFloorInvalid - } - - // Check if the underlying stream's last sequence is less than our floor. - // This can happen if the stream has been reset and has not caught up yet. - if asflr > ss.LastSeq { - return errAckFloorHigherThanLastSeq - } - - var smv StoreMsg - var seq, nseq uint64 - // Start at first stream seq or a previous check floor, whichever is higher. - // Note this will really help for interest retention, with WQ the loadNextMsg - // gets us a long way already since it will skip deleted msgs not for our filter. - fseq := ss.FirstSeq - if chkfloor > fseq { - fseq = chkfloor - } - - var retryAsflr uint64 - for seq = fseq; asflr > 0 && seq <= asflr; seq++ { - if filters != nil { - _, nseq, err = store.LoadNextMsgMulti(filters, seq, &smv) - } else { - _, nseq, err = store.LoadNextMsg(filter, wc, seq, &smv) - } - // if we advanced sequence update our seq. This can be on no error and EOF. - if nseq > seq { - seq = nseq - } - // Only ack though if no error and seq <= ack floor. - if err == nil && seq <= asflr { - didRemove := mset.ackMsg(o, seq) - // Removing the message could fail. For example if clustered since we need to propose it. - // Overwrite retry floor (only the first time) to allow us to check next time if the removal was successful. - if didRemove && retryAsflr == 0 { - retryAsflr = seq - } - } - } - // If retry floor was not overwritten, set to ack floor+1, we don't need to account for any retries below it. - if retryAsflr == 0 { - retryAsflr = asflr + 1 - } - - o.mu.Lock() - // Update our check floor. - // Check floor must never be greater than ack floor+1, otherwise subsequent calls to this function would skip work. - if retryAsflr > o.chkflr { - o.chkflr = retryAsflr - } - // See if we need to process this update if our parent stream is not a limits policy stream. - state, _ = o.store.State() - o.mu.Unlock() - - // If we have pending, we will need to walk through to delivered in case we missed any of those acks as well. - if state != nil && len(state.Pending) > 0 && state.AckFloor.Stream > 0 { - for seq := state.AckFloor.Stream + 1; seq <= state.Delivered.Stream; seq++ { - if _, ok := state.Pending[seq]; !ok { - // Want to call needAck since it is filter aware. - if o.needAck(seq, _EMPTY_) { - mset.ackMsg(o, seq) - } - } - } - } - return nil -} - -func (o *consumer) resetPtmr(delay time.Duration) { - if o.ptmr == nil { - o.ptmr = time.AfterFunc(delay, o.checkPending) - } else { - o.ptmr.Reset(delay) - } - o.ptmrEnd = time.Now().Add(delay) -} - -func (o *consumer) stopAndClearPtmr() { - stopAndClearTimer(&o.ptmr) - o.ptmrEnd = time.Time{} -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/dirstore.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/dirstore.go deleted file mode 100644 index 9d229bc3..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/dirstore.go +++ /dev/null @@ -1,724 +0,0 @@ -// Copyright 2012-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "container/heap" - "container/list" - "crypto/sha256" - "errors" - "fmt" - "math" - "os" - "path/filepath" - "strings" - "sync" - "time" - - "github.com/nats-io/nkeys" - - "github.com/nats-io/jwt/v2" // only used to decode, not for storage -) - -const ( - fileExtension = ".jwt" -) - -// validatePathExists checks that the provided path exists and is a dir if requested -func validatePathExists(path string, dir bool) (string, error) { - if path == _EMPTY_ { - return _EMPTY_, errors.New("path is not specified") - } - - abs, err := filepath.Abs(path) - if err != nil { - return _EMPTY_, fmt.Errorf("error parsing path [%s]: %v", abs, err) - } - - var finfo os.FileInfo - if finfo, err = os.Stat(abs); os.IsNotExist(err) { - return _EMPTY_, fmt.Errorf("the path [%s] doesn't exist", abs) - } - - mode := finfo.Mode() - if dir && mode.IsRegular() { - return _EMPTY_, fmt.Errorf("the path [%s] is not a directory", abs) - } - - if !dir && mode.IsDir() { - return _EMPTY_, fmt.Errorf("the path [%s] is not a file", abs) - } - - return abs, nil -} - -// ValidateDirPath checks that the provided path exists and is a dir -func validateDirPath(path string) (string, error) { - return validatePathExists(path, true) -} - -// JWTChanged functions are called when the store file watcher notices a JWT changed -type JWTChanged func(publicKey string) - -// DirJWTStore implements the JWT Store interface, keeping JWTs in an optionally sharded -// directory structure -type DirJWTStore struct { - sync.Mutex - directory string - shard bool - readonly bool - deleteType deleteType - operator map[string]struct{} - expiration *expirationTracker - changed JWTChanged - deleted JWTChanged -} - -func newDir(dirPath string, create bool) (string, error) { - fullPath, err := validateDirPath(dirPath) - if err != nil { - if !create { - return _EMPTY_, err - } - if err = os.MkdirAll(dirPath, defaultDirPerms); err != nil { - return _EMPTY_, err - } - if fullPath, err = validateDirPath(dirPath); err != nil { - return _EMPTY_, err - } - } - return fullPath, nil -} - -// future proofing in case new options will be added -type dirJWTStoreOption any - -// Creates a directory based jwt store. -// Reads files only, does NOT watch directories and files. -func NewImmutableDirJWTStore(dirPath string, shard bool, _ ...dirJWTStoreOption) (*DirJWTStore, error) { - theStore, err := NewDirJWTStore(dirPath, shard, false, nil) - if err != nil { - return nil, err - } - theStore.readonly = true - return theStore, nil -} - -// Creates a directory based jwt store. -// Operates on files only, does NOT watch directories and files. -func NewDirJWTStore(dirPath string, shard bool, create bool, _ ...dirJWTStoreOption) (*DirJWTStore, error) { - fullPath, err := newDir(dirPath, create) - if err != nil { - return nil, err - } - theStore := &DirJWTStore{ - directory: fullPath, - shard: shard, - } - return theStore, nil -} - -type deleteType int - -const ( - NoDelete deleteType = iota - RenameDeleted - HardDelete -) - -// Creates a directory based jwt store. -// -// When ttl is set deletion of file is based on it and not on the jwt expiration -// To completely disable expiration (including expiration in jwt) set ttl to max duration time.Duration(math.MaxInt64) -// -// limit defines how many files are allowed at any given time. Set to math.MaxInt64 to disable. -// evictOnLimit determines the behavior once limit is reached. -// * true - Evict based on lru strategy -// * false - return an error -func NewExpiringDirJWTStore(dirPath string, shard bool, create bool, delete deleteType, expireCheck time.Duration, limit int64, - evictOnLimit bool, ttl time.Duration, changeNotification JWTChanged, _ ...dirJWTStoreOption) (*DirJWTStore, error) { - fullPath, err := newDir(dirPath, create) - if err != nil { - return nil, err - } - theStore := &DirJWTStore{ - directory: fullPath, - shard: shard, - deleteType: delete, - changed: changeNotification, - } - if expireCheck <= 0 { - if ttl != 0 { - expireCheck = ttl / 2 - } - if expireCheck == 0 || expireCheck > time.Minute { - expireCheck = time.Minute - } - } - if limit <= 0 { - limit = math.MaxInt64 - } - theStore.startExpiring(expireCheck, limit, evictOnLimit, ttl) - theStore.Lock() - err = filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { - if strings.HasSuffix(path, fileExtension) { - if theJwt, err := os.ReadFile(path); err == nil { - hash := sha256.Sum256(theJwt) - _, file := filepath.Split(path) - theStore.expiration.track(strings.TrimSuffix(file, fileExtension), &hash, string(theJwt)) - } - } - return nil - }) - theStore.Unlock() - if err != nil { - theStore.Close() - return nil, err - } - return theStore, err -} - -func (store *DirJWTStore) IsReadOnly() bool { - return store.readonly -} - -func (store *DirJWTStore) LoadAcc(publicKey string) (string, error) { - return store.load(publicKey) -} - -func (store *DirJWTStore) SaveAcc(publicKey string, theJWT string) error { - return store.save(publicKey, theJWT) -} - -func (store *DirJWTStore) LoadAct(hash string) (string, error) { - return store.load(hash) -} - -func (store *DirJWTStore) SaveAct(hash string, theJWT string) error { - return store.save(hash, theJWT) -} - -func (store *DirJWTStore) Close() { - store.Lock() - defer store.Unlock() - if store.expiration != nil { - store.expiration.close() - store.expiration = nil - } -} - -// Pack up to maxJWTs into a package -func (store *DirJWTStore) Pack(maxJWTs int) (string, error) { - count := 0 - var pack []string - if maxJWTs > 0 { - pack = make([]string, 0, maxJWTs) - } else { - pack = []string{} - } - store.Lock() - err := filepath.Walk(store.directory, func(path string, info os.FileInfo, err error) error { - if !info.IsDir() && strings.HasSuffix(path, fileExtension) { // this is a JWT - if count == maxJWTs { // won't match negative - return nil - } - pubKey := strings.TrimSuffix(filepath.Base(path), fileExtension) - if store.expiration != nil { - if _, ok := store.expiration.idx[pubKey]; !ok { - return nil // only include indexed files - } - } - jwtBytes, err := os.ReadFile(path) - if err != nil { - return err - } - if store.expiration != nil { - claim, err := jwt.DecodeGeneric(string(jwtBytes)) - if err == nil && claim.Expires > 0 && claim.Expires < time.Now().Unix() { - return nil - } - } - pack = append(pack, fmt.Sprintf("%s|%s", pubKey, string(jwtBytes))) - count++ - } - return nil - }) - store.Unlock() - if err != nil { - return _EMPTY_, err - } else { - return strings.Join(pack, "\n"), nil - } -} - -// Pack up to maxJWTs into a message and invoke callback with it -func (store *DirJWTStore) PackWalk(maxJWTs int, cb func(partialPackMsg string)) error { - if maxJWTs <= 0 || cb == nil { - return errors.New("bad arguments to PackWalk") - } - var packMsg []string - store.Lock() - dir := store.directory - exp := store.expiration - store.Unlock() - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if info != nil && !info.IsDir() && strings.HasSuffix(path, fileExtension) { // this is a JWT - pubKey := strings.TrimSuffix(filepath.Base(path), fileExtension) - store.Lock() - if exp != nil { - if _, ok := exp.idx[pubKey]; !ok { - store.Unlock() - return nil // only include indexed files - } - } - store.Unlock() - jwtBytes, err := os.ReadFile(path) - if err != nil { - return err - } - if len(jwtBytes) == 0 { - // Skip if no contents in the JWT. - return nil - } - if exp != nil { - claim, err := jwt.DecodeGeneric(string(jwtBytes)) - if err == nil && claim.Expires > 0 && claim.Expires < time.Now().Unix() { - return nil - } - } - packMsg = append(packMsg, fmt.Sprintf("%s|%s", pubKey, string(jwtBytes))) - if len(packMsg) == maxJWTs { // won't match negative - cb(strings.Join(packMsg, "\n")) - packMsg = nil - } - } - return nil - }) - if packMsg != nil { - cb(strings.Join(packMsg, "\n")) - } - return err -} - -// Merge takes the JWTs from package and adds them to the store -// Merge is destructive in the sense that it doesn't check if the JWT -// is newer or anything like that. -func (store *DirJWTStore) Merge(pack string) error { - newJWTs := strings.Split(pack, "\n") - for _, line := range newJWTs { - if line == _EMPTY_ { // ignore blank lines - continue - } - split := strings.Split(line, "|") - if len(split) != 2 { - return fmt.Errorf("line in package didn't contain 2 entries: %q", line) - } - pubKey := split[0] - if !nkeys.IsValidPublicAccountKey(pubKey) { - return fmt.Errorf("key to merge is not a valid public account key") - } - if err := store.saveIfNewer(pubKey, split[1]); err != nil { - return err - } - } - return nil -} - -func (store *DirJWTStore) Reload() error { - store.Lock() - exp := store.expiration - if exp == nil || store.readonly { - store.Unlock() - return nil - } - idx := exp.idx - changed := store.changed - isCache := store.expiration.evictOnLimit - // clear out indexing data structures - exp.heap = make([]*jwtItem, 0, len(exp.heap)) - exp.idx = make(map[string]*list.Element) - exp.lru = list.New() - exp.hash = [sha256.Size]byte{} - store.Unlock() - return filepath.Walk(store.directory, func(path string, info os.FileInfo, err error) error { - if strings.HasSuffix(path, fileExtension) { - if theJwt, err := os.ReadFile(path); err == nil { - hash := sha256.Sum256(theJwt) - _, file := filepath.Split(path) - pkey := strings.TrimSuffix(file, fileExtension) - notify := isCache // for cache, issue cb even when file not present (may have been evicted) - if i, ok := idx[pkey]; ok { - notify = !bytes.Equal(i.Value.(*jwtItem).hash[:], hash[:]) - } - store.Lock() - exp.track(pkey, &hash, string(theJwt)) - store.Unlock() - if notify && changed != nil { - changed(pkey) - } - } - } - return nil - }) -} - -func (store *DirJWTStore) pathForKey(publicKey string) string { - if len(publicKey) < 2 { - return _EMPTY_ - } - if !nkeys.IsValidPublicKey(publicKey) { - return _EMPTY_ - } - fileName := fmt.Sprintf("%s%s", publicKey, fileExtension) - if store.shard { - last := publicKey[len(publicKey)-2:] - return filepath.Join(store.directory, last, fileName) - } else { - return filepath.Join(store.directory, fileName) - } -} - -// Load checks the memory store and returns the matching JWT or an error -// Assumes lock is NOT held -func (store *DirJWTStore) load(publicKey string) (string, error) { - store.Lock() - defer store.Unlock() - if path := store.pathForKey(publicKey); path == _EMPTY_ { - return _EMPTY_, fmt.Errorf("invalid public key") - } else if data, err := os.ReadFile(path); err != nil { - return _EMPTY_, err - } else { - if store.expiration != nil { - store.expiration.updateTrack(publicKey) - } - return string(data), nil - } -} - -// write that keeps hash of all jwt in sync -// Assumes the lock is held. Does return true or an error never both. -func (store *DirJWTStore) write(path string, publicKey string, theJWT string) (bool, error) { - if len(theJWT) == 0 { - return false, fmt.Errorf("invalid JWT") - } - var newHash *[sha256.Size]byte - if store.expiration != nil { - h := sha256.Sum256([]byte(theJWT)) - newHash = &h - if v, ok := store.expiration.idx[publicKey]; ok { - store.expiration.updateTrack(publicKey) - // this write is an update, move to back - it := v.Value.(*jwtItem) - oldHash := it.hash[:] - if bytes.Equal(oldHash, newHash[:]) { - return false, nil - } - } else if int64(store.expiration.Len()) >= store.expiration.limit { - if !store.expiration.evictOnLimit { - return false, errors.New("jwt store is full") - } - // this write is an add, pick the least recently used value for removal - i := store.expiration.lru.Front().Value.(*jwtItem) - if err := os.Remove(store.pathForKey(i.publicKey)); err != nil { - return false, err - } else { - store.expiration.unTrack(i.publicKey) - } - } - } - if err := os.WriteFile(path, []byte(theJWT), defaultFilePerms); err != nil { - return false, err - } else if store.expiration != nil { - store.expiration.track(publicKey, newHash, theJWT) - } - return true, nil -} - -func (store *DirJWTStore) delete(publicKey string) error { - if store.readonly { - return fmt.Errorf("store is read-only") - } else if store.deleteType == NoDelete { - return fmt.Errorf("store is not set up to for delete") - } - store.Lock() - defer store.Unlock() - name := store.pathForKey(publicKey) - if store.deleteType == RenameDeleted { - if err := os.Rename(name, name+".deleted"); err != nil { - if os.IsNotExist(err) { - return nil - } - return err - } - } else if err := os.Remove(name); err != nil { - if os.IsNotExist(err) { - return nil - } - return err - } - store.expiration.unTrack(publicKey) - store.deleted(publicKey) - return nil -} - -// Save puts the JWT in a map by public key and performs update callbacks -// Assumes lock is NOT held -func (store *DirJWTStore) save(publicKey string, theJWT string) error { - if store.readonly { - return fmt.Errorf("store is read-only") - } - store.Lock() - path := store.pathForKey(publicKey) - if path == _EMPTY_ { - store.Unlock() - return fmt.Errorf("invalid public key") - } - dirPath := filepath.Dir(path) - if _, err := validateDirPath(dirPath); err != nil { - if err := os.MkdirAll(dirPath, defaultDirPerms); err != nil { - store.Unlock() - return err - } - } - changed, err := store.write(path, publicKey, theJWT) - cb := store.changed - store.Unlock() - if changed && cb != nil { - cb(publicKey) - } - return err -} - -// Assumes the lock is NOT held, and only updates if the jwt is new, or the one on disk is older -// When changed, invokes jwt changed callback -func (store *DirJWTStore) saveIfNewer(publicKey string, theJWT string) error { - if store.readonly { - return fmt.Errorf("store is read-only") - } - path := store.pathForKey(publicKey) - if path == _EMPTY_ { - return fmt.Errorf("invalid public key") - } - dirPath := filepath.Dir(path) - if _, err := validateDirPath(dirPath); err != nil { - if err := os.MkdirAll(dirPath, defaultDirPerms); err != nil { - return err - } - } - if _, err := os.Stat(path); err == nil { - if newJWT, err := jwt.DecodeGeneric(theJWT); err != nil { - return err - } else if existing, err := os.ReadFile(path); err != nil { - return err - } else if existingJWT, err := jwt.DecodeGeneric(string(existing)); err != nil { - // skip if it can't be decoded - } else if existingJWT.ID == newJWT.ID { - return nil - } else if existingJWT.IssuedAt > newJWT.IssuedAt { - return nil - } else if newJWT.Subject != publicKey { - return fmt.Errorf("jwt subject nkey and provided nkey do not match") - } else if existingJWT.Subject != newJWT.Subject { - return fmt.Errorf("subject of existing and new jwt do not match") - } - } - store.Lock() - cb := store.changed - changed, err := store.write(path, publicKey, theJWT) - store.Unlock() - if err != nil { - return err - } else if changed && cb != nil { - cb(publicKey) - } - return nil -} - -func xorAssign(lVal *[sha256.Size]byte, rVal [sha256.Size]byte) { - for i := range rVal { - (*lVal)[i] ^= rVal[i] - } -} - -// returns a hash representing all indexed jwt -func (store *DirJWTStore) Hash() [sha256.Size]byte { - store.Lock() - defer store.Unlock() - if store.expiration == nil { - return [sha256.Size]byte{} - } else { - return store.expiration.hash - } -} - -// An jwtItem is something managed by the priority queue -type jwtItem struct { - index int - publicKey string - expiration int64 // consists of unix time of expiration (ttl when set or jwt expiration) in seconds - hash [sha256.Size]byte -} - -// A expirationTracker implements heap.Interface and holds Items. -type expirationTracker struct { - heap []*jwtItem // sorted by jwtItem.expiration - idx map[string]*list.Element - lru *list.List // keep which jwt are least used - limit int64 // limit how many jwt are being tracked - evictOnLimit bool // when limit is hit, error or evict using lru - ttl time.Duration - hash [sha256.Size]byte // xor of all jwtItem.hash in idx - quit chan struct{} - wg sync.WaitGroup -} - -func (q *expirationTracker) Len() int { return len(q.heap) } - -func (q *expirationTracker) Less(i, j int) bool { - pq := q.heap - return pq[i].expiration < pq[j].expiration -} - -func (q *expirationTracker) Swap(i, j int) { - pq := q.heap - pq[i], pq[j] = pq[j], pq[i] - pq[i].index = i - pq[j].index = j -} - -func (q *expirationTracker) Push(x any) { - n := len(q.heap) - item := x.(*jwtItem) - item.index = n - q.heap = append(q.heap, item) - q.idx[item.publicKey] = q.lru.PushBack(item) -} - -func (q *expirationTracker) Pop() any { - old := q.heap - n := len(old) - item := old[n-1] - old[n-1] = nil // avoid memory leak - item.index = -1 - q.heap = old[0 : n-1] - q.lru.Remove(q.idx[item.publicKey]) - delete(q.idx, item.publicKey) - return item -} - -func (pq *expirationTracker) updateTrack(publicKey string) { - if e, ok := pq.idx[publicKey]; ok { - i := e.Value.(*jwtItem) - if pq.ttl != 0 { - // only update expiration when set - i.expiration = time.Now().Add(pq.ttl).UnixNano() - heap.Fix(pq, i.index) - } - if pq.evictOnLimit { - pq.lru.MoveToBack(e) - } - } -} - -func (pq *expirationTracker) unTrack(publicKey string) { - if it, ok := pq.idx[publicKey]; ok { - xorAssign(&pq.hash, it.Value.(*jwtItem).hash) - heap.Remove(pq, it.Value.(*jwtItem).index) - delete(pq.idx, publicKey) - } -} - -func (pq *expirationTracker) track(publicKey string, hash *[sha256.Size]byte, theJWT string) { - var exp int64 - // prioritize ttl over expiration - if pq.ttl != 0 { - if pq.ttl == time.Duration(math.MaxInt64) { - exp = math.MaxInt64 - } else { - exp = time.Now().Add(pq.ttl).UnixNano() - } - } else { - if g, err := jwt.DecodeGeneric(theJWT); err == nil { - exp = time.Unix(g.Expires, 0).UnixNano() - } - if exp == 0 { - exp = math.MaxInt64 // default to indefinite - } - } - if e, ok := pq.idx[publicKey]; ok { - i := e.Value.(*jwtItem) - xorAssign(&pq.hash, i.hash) // remove old hash - i.expiration = exp - i.hash = *hash - heap.Fix(pq, i.index) - } else { - heap.Push(pq, &jwtItem{-1, publicKey, exp, *hash}) - } - xorAssign(&pq.hash, *hash) // add in new hash -} - -func (pq *expirationTracker) close() { - if pq == nil || pq.quit == nil { - return - } - close(pq.quit) - pq.quit = nil -} - -func (store *DirJWTStore) startExpiring(reCheck time.Duration, limit int64, evictOnLimit bool, ttl time.Duration) { - store.Lock() - defer store.Unlock() - quit := make(chan struct{}) - pq := &expirationTracker{ - make([]*jwtItem, 0, 10), - make(map[string]*list.Element), - list.New(), - limit, - evictOnLimit, - ttl, - [sha256.Size]byte{}, - quit, - sync.WaitGroup{}, - } - store.expiration = pq - pq.wg.Add(1) - go func() { - t := time.NewTicker(reCheck) - defer t.Stop() - defer pq.wg.Done() - for { - now := time.Now().UnixNano() - store.Lock() - if pq.Len() > 0 { - if it := pq.heap[0]; it.expiration <= now { - path := store.pathForKey(it.publicKey) - if err := os.Remove(path); err == nil { - heap.Pop(pq) - pq.unTrack(it.publicKey) - xorAssign(&pq.hash, it.hash) - store.Unlock() - continue // we removed an entry, check next one right away - } - } - } - store.Unlock() - select { - case <-t.C: - case <-quit: - return - } - } - }() -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail.go deleted file mode 100644 index 65e4ecb7..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2020-2022 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build !windows && !openbsd && !netbsd && !wasm - -package server - -import ( - "os" - "syscall" -) - -func diskAvailable(storeDir string) int64 { - var ba int64 - if _, err := os.Stat(storeDir); os.IsNotExist(err) { - os.MkdirAll(storeDir, defaultDirPerms) - } - var fs syscall.Statfs_t - if err := syscall.Statfs(storeDir, &fs); err == nil { - // Estimate 75% of available storage. - ba = int64(uint64(fs.Bavail) * uint64(fs.Bsize) / 4 * 3) - } else { - // Used 1TB default as a guess if all else fails. - ba = JetStreamMaxStoreDefault - } - return ba -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_netbsd.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_netbsd.go deleted file mode 100644 index 1ce39208..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_netbsd.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2022 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build netbsd - -package server - -// TODO - See if there is a version of this for NetBSD. -func diskAvailable(storeDir string) int64 { - return JetStreamMaxStoreDefault -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_openbsd.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_openbsd.go deleted file mode 100644 index 6ed468fc..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_openbsd.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2021 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build openbsd - -package server - -import ( - "os" - "syscall" -) - -func diskAvailable(storeDir string) int64 { - var ba int64 - if _, err := os.Stat(storeDir); os.IsNotExist(err) { - os.MkdirAll(storeDir, defaultDirPerms) - } - var fs syscall.Statfs_t - if err := syscall.Statfs(storeDir, &fs); err == nil { - // Estimate 75% of available storage. - ba = int64(uint64(fs.F_bavail) * uint64(fs.F_bsize) / 4 * 3) - } else { - // Used 1TB default as a guess if all else fails. - ba = JetStreamMaxStoreDefault - } - return ba -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_wasm.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_wasm.go deleted file mode 100644 index 47648834..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_wasm.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2022-2021 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build wasm - -package server - -func diskAvailable(storeDir string) int64 { - return JetStreamMaxStoreDefault -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_windows.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_windows.go deleted file mode 100644 index 9c212437..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/disk_avail_windows.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2020-2021 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build windows - -package server - -// TODO(dlc) - See if there is a version of this for windows. -func diskAvailable(storeDir string) int64 { - return JetStreamMaxStoreDefault -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/errors.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/errors.go deleted file mode 100644 index 1bd4e8f7..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/errors.go +++ /dev/null @@ -1,396 +0,0 @@ -// Copyright 2012-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "errors" - "fmt" -) - -var ( - // ErrConnectionClosed represents an error condition on a closed connection. - ErrConnectionClosed = errors.New("connection closed") - - // ErrAuthentication represents an error condition on failed authentication. - ErrAuthentication = errors.New("authentication error") - - // ErrAuthTimeout represents an error condition on failed authorization due to timeout. - ErrAuthTimeout = errors.New("authentication timeout") - - // ErrAuthExpired represents an expired authorization due to timeout. - ErrAuthExpired = errors.New("authentication expired") - - // ErrMaxPayload represents an error condition when the payload is too big. - ErrMaxPayload = errors.New("maximum payload exceeded") - - // ErrMaxControlLine represents an error condition when the control line is too big. - ErrMaxControlLine = errors.New("maximum control line exceeded") - - // ErrReservedPublishSubject represents an error condition when sending to a reserved subject, e.g. _SYS.> - ErrReservedPublishSubject = errors.New("reserved internal subject") - - // ErrBadPublishSubject represents an error condition for an invalid publish subject. - ErrBadPublishSubject = errors.New("invalid publish subject") - - // ErrBadSubject represents an error condition for an invalid subject. - ErrBadSubject = errors.New("invalid subject") - - // ErrBadQualifier is used to error on a bad qualifier for a transform. - ErrBadQualifier = errors.New("bad qualifier") - - // ErrBadClientProtocol signals a client requested an invalid client protocol. - ErrBadClientProtocol = errors.New("invalid client protocol") - - // ErrTooManyConnections signals a client that the maximum number of connections supported by the - // server has been reached. - ErrTooManyConnections = errors.New("maximum connections exceeded") - - // ErrTooManyAccountConnections signals that an account has reached its maximum number of active - // connections. - ErrTooManyAccountConnections = errors.New("maximum account active connections exceeded") - - // ErrLeafNodeLoop signals a leafnode is trying to register for a cluster we already have registered. - ErrLeafNodeLoop = errors.New("leafnode loop detected") - - // ErrTooManySubs signals a client that the maximum number of subscriptions per connection - // has been reached. - ErrTooManySubs = errors.New("maximum subscriptions exceeded") - - // ErrTooManySubTokens signals a client that the subject has too many tokens. - ErrTooManySubTokens = errors.New("subject has exceeded number of tokens limit") - - // ErrClientConnectedToRoutePort represents an error condition when a client - // attempted to connect to the route listen port. - ErrClientConnectedToRoutePort = errors.New("attempted to connect to route port") - - // ErrClientConnectedToLeafNodePort represents an error condition when a client - // attempted to connect to the leaf node listen port. - ErrClientConnectedToLeafNodePort = errors.New("attempted to connect to leaf node port") - - // ErrLeafNodeHasSameClusterName represents an error condition when a leafnode is a cluster - // and it has the same cluster name as the hub cluster. - ErrLeafNodeHasSameClusterName = errors.New("remote leafnode has same cluster name") - - // ErrLeafNodeDisabled is when we disable leafnodes. - ErrLeafNodeDisabled = errors.New("leafnodes disabled") - - // ErrConnectedToWrongPort represents an error condition when a connection is attempted - // to the wrong listen port (for instance a LeafNode to a client port, etc...) - ErrConnectedToWrongPort = errors.New("attempted to connect to wrong port") - - // ErrAccountExists is returned when an account is attempted to be registered - // but already exists. - ErrAccountExists = errors.New("account exists") - - // ErrBadAccount represents a malformed or incorrect account. - ErrBadAccount = errors.New("bad account") - - // ErrReservedAccount represents a reserved account that can not be created. - ErrReservedAccount = errors.New("reserved account") - - // ErrMissingAccount is returned when an account does not exist. - ErrMissingAccount = errors.New("account missing") - - // ErrMissingService is returned when an account does not have an exported service. - ErrMissingService = errors.New("service missing") - - // ErrBadServiceType is returned when latency tracking is being applied to non-singleton response types. - ErrBadServiceType = errors.New("bad service response type") - - // ErrBadSampling is returned when the sampling for latency tracking is not 1 >= sample <= 100. - ErrBadSampling = errors.New("bad sampling percentage, should be 1-100") - - // ErrAccountValidation is returned when an account has failed validation. - ErrAccountValidation = errors.New("account validation failed") - - // ErrAccountExpired is returned when an account has expired. - ErrAccountExpired = errors.New("account expired") - - // ErrNoAccountResolver is returned when we attempt an update but do not have an account resolver. - ErrNoAccountResolver = errors.New("account resolver missing") - - // ErrAccountResolverUpdateTooSoon is returned when we attempt an update too soon to last request. - ErrAccountResolverUpdateTooSoon = errors.New("account resolver update too soon") - - // ErrAccountResolverSameClaims is returned when same claims have been fetched. - ErrAccountResolverSameClaims = errors.New("account resolver no new claims") - - // ErrStreamImportAuthorization is returned when a stream import is not authorized. - ErrStreamImportAuthorization = errors.New("stream import not authorized") - - // ErrStreamImportBadPrefix is returned when a stream import prefix contains wildcards. - ErrStreamImportBadPrefix = errors.New("stream import prefix can not contain wildcard tokens") - - // ErrStreamImportDuplicate is returned when a stream import is a duplicate of one that already exists. - ErrStreamImportDuplicate = errors.New("stream import already exists") - - // ErrServiceImportAuthorization is returned when a service import is not authorized. - ErrServiceImportAuthorization = errors.New("service import not authorized") - - // ErrImportFormsCycle is returned when an import would form a cycle. - ErrImportFormsCycle = errors.New("import forms a cycle") - - // ErrCycleSearchDepth is returned when we have exceeded our maximum search depth.. - ErrCycleSearchDepth = errors.New("search cycle depth exhausted") - - // ErrClientOrRouteConnectedToGatewayPort represents an error condition when - // a client or route attempted to connect to the Gateway port. - ErrClientOrRouteConnectedToGatewayPort = errors.New("attempted to connect to gateway port") - - // ErrWrongGateway represents an error condition when a server receives a connect - // request from a remote Gateway with a destination name that does not match the server's - // Gateway's name. - ErrWrongGateway = errors.New("wrong gateway") - - // ErrGatewayNameHasSpaces signals that the gateway name contains spaces, which is not allowed. - ErrGatewayNameHasSpaces = errors.New("gateway name cannot contain spaces") - - // ErrNoSysAccount is returned when an attempt to publish or subscribe is made - // when there is no internal system account defined. - ErrNoSysAccount = errors.New("system account not setup") - - // ErrRevocation is returned when a credential has been revoked. - ErrRevocation = errors.New("credentials have been revoked") - - // ErrServerNotRunning is used to signal an error that a server is not running. - ErrServerNotRunning = errors.New("server is not running") - - // ErrServerNameHasSpaces signals that the server name contains spaces, which is not allowed. - ErrServerNameHasSpaces = errors.New("server name cannot contain spaces") - - // ErrBadMsgHeader signals the parser detected a bad message header - ErrBadMsgHeader = errors.New("bad message header detected") - - // ErrMsgHeadersNotSupported signals the parser detected a message header - // but they are not supported on this server. - ErrMsgHeadersNotSupported = errors.New("message headers not supported") - - // ErrNoRespondersRequiresHeaders signals that a client needs to have headers - // on if they want no responders behavior. - ErrNoRespondersRequiresHeaders = errors.New("no responders requires headers support") - - // ErrClusterNameConfigConflict signals that the options for cluster name in cluster and gateway are in conflict. - ErrClusterNameConfigConflict = errors.New("cluster name conflicts between cluster and gateway definitions") - - // ErrClusterNameRemoteConflict signals that a remote server has a different cluster name. - ErrClusterNameRemoteConflict = errors.New("cluster name from remote server conflicts") - - // ErrClusterNameHasSpaces signals that the cluster name contains spaces, which is not allowed. - ErrClusterNameHasSpaces = errors.New("cluster name cannot contain spaces") - - // ErrMalformedSubject is returned when a subscription is made with a subject that does not conform to subject rules. - ErrMalformedSubject = errors.New("malformed subject") - - // ErrSubscribePermissionViolation is returned when processing of a subscription fails due to permissions. - ErrSubscribePermissionViolation = errors.New("subscribe permission violation") - - // ErrNoTransforms signals no subject transforms are available to map this subject. - ErrNoTransforms = errors.New("no matching transforms available") - - // ErrCertNotPinned is returned when pinned certs are set and the certificate is not in it - ErrCertNotPinned = errors.New("certificate not pinned") - - // ErrDuplicateServerName is returned when processing a server remote connection and - // the server reports that this server name is already used in the cluster. - ErrDuplicateServerName = errors.New("duplicate server name") - - // ErrMinimumVersionRequired is returned when a connection is not at the minimum version required. - ErrMinimumVersionRequired = errors.New("minimum version required") - - // ErrInvalidMappingDestination is used for all subject mapping destination errors - ErrInvalidMappingDestination = errors.New("invalid mapping destination") - - // ErrInvalidMappingDestinationSubject is used to error on a bad transform destination mapping - ErrInvalidMappingDestinationSubject = fmt.Errorf("%w: invalid transform", ErrInvalidMappingDestination) - - // ErrMappingDestinationNotUsingAllWildcards is used to error on a transform destination not using all of the token wildcards - ErrMappingDestinationNotUsingAllWildcards = fmt.Errorf("%w: not using all of the token wildcard(s)", ErrInvalidMappingDestination) - - // ErrUnknownMappingDestinationFunction is returned when a subject mapping destination contains an unknown mustache-escaped mapping function. - ErrUnknownMappingDestinationFunction = fmt.Errorf("%w: unknown function", ErrInvalidMappingDestination) - - // ErrMappingDestinationIndexOutOfRange is returned when the mapping destination function is passed an out of range wildcard index value for one of it's arguments - ErrMappingDestinationIndexOutOfRange = fmt.Errorf("%w: wildcard index out of range", ErrInvalidMappingDestination) - - // ErrMappingDestinationNotEnoughArgs is returned when the mapping destination function is not passed enough arguments - ErrMappingDestinationNotEnoughArgs = fmt.Errorf("%w: not enough arguments passed to the function", ErrInvalidMappingDestination) - - // ErrMappingDestinationInvalidArg is returned when the mapping destination function is passed and invalid argument - ErrMappingDestinationInvalidArg = fmt.Errorf("%w: function argument is invalid or in the wrong format", ErrInvalidMappingDestination) - - // ErrMappingDestinationTooManyArgs is returned when the mapping destination function is passed too many arguments - ErrMappingDestinationTooManyArgs = fmt.Errorf("%w: too many arguments passed to the function", ErrInvalidMappingDestination) - - // ErrMappingDestinationNotSupportedForImport is returned when you try to use a mapping function other than wildcard in a transform that needs to be reversible (i.e. an import) - ErrMappingDestinationNotSupportedForImport = fmt.Errorf("%w: the only mapping function allowed for import transforms is {{Wildcard()}}", ErrInvalidMappingDestination) -) - -// mappingDestinationErr is a type of subject mapping destination error -type mappingDestinationErr struct { - token string - err error -} - -func (e *mappingDestinationErr) Error() string { - return fmt.Sprintf("%s in %s", e.err, e.token) -} - -func (e *mappingDestinationErr) Is(target error) bool { - return target == ErrInvalidMappingDestination -} - -// configErr is a configuration error. -type configErr struct { - token token - reason string -} - -// Source reports the location of a configuration error. -func (e *configErr) Source() string { - return fmt.Sprintf("%s:%d:%d", e.token.SourceFile(), e.token.Line(), e.token.Position()) -} - -// Error reports the location and reason from a configuration error. -func (e *configErr) Error() string { - if e.token != nil { - return fmt.Sprintf("%s: %s", e.Source(), e.reason) - } - return e.reason -} - -// unknownConfigFieldErr is an error reported in pedantic mode. -type unknownConfigFieldErr struct { - configErr - field string -} - -// Error reports that an unknown field was in the configuration. -func (e *unknownConfigFieldErr) Error() string { - return fmt.Sprintf("%s: unknown field %q", e.Source(), e.field) -} - -// configWarningErr is an error reported in pedantic mode. -type configWarningErr struct { - configErr - field string -} - -// Error reports a configuration warning. -func (e *configWarningErr) Error() string { - return fmt.Sprintf("%s: invalid use of field %q: %s", e.Source(), e.field, e.reason) -} - -// processConfigErr is the result of processing the configuration from the server. -type processConfigErr struct { - errors []error - warnings []error -} - -// Error returns the collection of errors separated by new lines, -// warnings appear first then hard errors. -func (e *processConfigErr) Error() string { - var msg string - for _, err := range e.Warnings() { - msg += err.Error() + "\n" - } - for _, err := range e.Errors() { - msg += err.Error() + "\n" - } - return msg -} - -// Warnings returns the list of warnings. -func (e *processConfigErr) Warnings() []error { - return e.warnings -} - -// Errors returns the list of errors. -func (e *processConfigErr) Errors() []error { - return e.errors -} - -// errCtx wraps an error and stores additional ctx information for tracing. -// Does not print or return it unless explicitly requested. -type errCtx struct { - error - ctx string -} - -func NewErrorCtx(err error, format string, args ...any) error { - return &errCtx{err, fmt.Sprintf(format, args...)} -} - -// Unwrap implement to work with errors.Is and errors.As -func (e *errCtx) Unwrap() error { - if e == nil { - return nil - } - return e.error -} - -// Context for error -func (e *errCtx) Context() string { - if e == nil { - return "" - } - return e.ctx -} - -// UnpackIfErrorCtx return Error or, if type is right error and context -func UnpackIfErrorCtx(err error) string { - if e, ok := err.(*errCtx); ok { - if _, ok := e.error.(*errCtx); ok { - return fmt.Sprint(UnpackIfErrorCtx(e.error), ": ", e.Context()) - } - return fmt.Sprint(e.Error(), ": ", e.Context()) - } - return err.Error() -} - -// implements: go 1.13 errors.Unwrap(err error) error -// TODO replace with native code once we no longer support go1.12 -func errorsUnwrap(err error) error { - u, ok := err.(interface { - Unwrap() error - }) - if !ok { - return nil - } - return u.Unwrap() -} - -// ErrorIs implements: go 1.13 errors.Is(err, target error) bool -// TODO replace with native code once we no longer support go1.12 -func ErrorIs(err, target error) bool { - // this is an outright copy of go 1.13 errors.Is(err, target error) bool - // removed isComparable - if err == nil || target == nil { - return err == target - } - - for { - if err == target { - return true - } - if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { - return true - } - // TODO: consider supporing target.Is(err). This would allow - // user-definable predicates, but also may allow for coping with sloppy - // APIs, thereby making it easier to get away with them. - if err = errorsUnwrap(err); err == nil { - return false - } - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/errors.json b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/errors.json deleted file mode 100644 index 7b90366a..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/errors.json +++ /dev/null @@ -1,1662 +0,0 @@ -[ - { - "constant": "JSClusterPeerNotMemberErr", - "code": 400, - "error_code": 10040, - "description": "peer not a member", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerEphemeralWithDurableInSubjectErr", - "code": 400, - "error_code": 10019, - "description": "consumer expected to be ephemeral but detected a durable name set in subject", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamExternalDelPrefixOverlapsErrF", - "code": 400, - "error_code": 10022, - "description": "stream external delivery prefix {prefix} overlaps with stream subject {subject}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSAccountResourcesExceededErr", - "code": 400, - "error_code": 10002, - "description": "resource limits exceeded for account", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSClusterNotAvailErr", - "code": 503, - "error_code": 10008, - "description": "JetStream system temporarily unavailable", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamSubjectOverlapErr", - "code": 400, - "error_code": 10065, - "description": "subjects overlap with an existing stream", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamWrongLastSequenceErrF", - "code": 400, - "error_code": 10071, - "description": "wrong last sequence: {seq}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSTemplateNameNotMatchSubjectErr", - "code": 400, - "error_code": 10073, - "description": "template name in subject does not match request", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSClusterNoPeersErrF", - "code": 400, - "error_code": 10005, - "description": "{err}", - "comment": "Error causing no peers to be available", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerEphemeralWithDurableNameErr", - "code": 400, - "error_code": 10020, - "description": "consumer expected to be ephemeral but a durable name was set in request", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSInsufficientResourcesErr", - "code": 503, - "error_code": 10023, - "description": "insufficient resources", - "comment": "", - "help": "", - "url": "", - "deprecates": "ErrJetStreamResourcesExceeded" - }, - { - "constant": "JSMirrorMaxMessageSizeTooBigErr", - "code": 400, - "error_code": 10030, - "description": "stream mirror must have max message size \u003e= source", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamTemplateDeleteErrF", - "code": 500, - "error_code": 10067, - "description": "{err}", - "comment": "Generic stream template deletion failed error string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSBadRequestErr", - "code": 400, - "error_code": 10003, - "description": "bad request", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSClusterUnSupportFeatureErr", - "code": 503, - "error_code": 10036, - "description": "not currently supported in clustered mode", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerNotFoundErr", - "code": 404, - "error_code": 10014, - "description": "consumer not found", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSSourceMaxMessageSizeTooBigErr", - "code": 400, - "error_code": 10046, - "description": "stream source must have max message size \u003e= target", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamAssignmentErrF", - "code": 500, - "error_code": 10048, - "description": "{err}", - "comment": "Generic stream assignment error string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamMessageExceedsMaximumErr", - "code": 400, - "error_code": 10054, - "description": "message size exceeds maximum allowed", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamTemplateCreateErrF", - "code": 500, - "error_code": 10066, - "description": "{err}", - "comment": "Generic template creation failed string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSInvalidJSONErr", - "code": 400, - "error_code": 10025, - "description": "invalid JSON: {err}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamInvalidExternalDeliverySubjErrF", - "code": 400, - "error_code": 10024, - "description": "stream external delivery prefix {prefix} must not contain wildcards", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamRestoreErrF", - "code": 500, - "error_code": 10062, - "description": "restore failed: {err}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSClusterIncompleteErr", - "code": 503, - "error_code": 10004, - "description": "incomplete results", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSNoAccountErr", - "code": 503, - "error_code": 10035, - "description": "account not found", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSRaftGeneralErrF", - "code": 500, - "error_code": 10041, - "description": "{err}", - "comment": "General RAFT error string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSRestoreSubscribeFailedErrF", - "code": 500, - "error_code": 10042, - "description": "JetStream unable to subscribe to restore snapshot {subject}: {err}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamDeleteErrF", - "code": 500, - "error_code": 10050, - "description": "{err}", - "comment": "General stream deletion error string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamExternalApiOverlapErrF", - "code": 400, - "error_code": 10021, - "description": "stream external api prefix {prefix} must not overlap with {subject}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSMirrorWithSubjectsErr", - "code": 400, - "error_code": 10034, - "description": "stream mirrors can not contain subjects", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSMirrorWithFirstSeqErr", - "code": 400, - "error_code": 10143, - "description": "stream mirrors can not have first sequence configured", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSNotEnabledErr", - "code": 503, - "error_code": 10076, - "description": "JetStream not enabled", - "comment": "", - "help": "This error indicates that JetStream is not enabled at a global level", - "url": "", - "deprecates": "ErrJetStreamNotEnabled" - }, - { - "constant": "JSNotEnabledForAccountErr", - "code": 503, - "error_code": 10039, - "description": "JetStream not enabled for account", - "comment": "", - "help": "This error indicates that JetStream is not enabled for an account account level", - "url": "", - "deprecates": "" - }, - { - "constant": "JSSequenceNotFoundErrF", - "code": 400, - "error_code": 10043, - "description": "sequence {seq} not found", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamMirrorNotUpdatableErr", - "code": 400, - "error_code": 10055, - "description": "stream mirror configuration can not be updated", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamSequenceNotMatchErr", - "code": 503, - "error_code": 10063, - "description": "expected stream sequence does not match", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamWrongLastMsgIDErrF", - "code": 400, - "error_code": 10070, - "description": "wrong last msg ID: {id}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSTempStorageFailedErr", - "code": 500, - "error_code": 10072, - "description": "JetStream unable to open temp storage for restore", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStorageResourcesExceededErr", - "code": 500, - "error_code": 10047, - "description": "insufficient storage resources available", - "comment": "", - "help": "", - "url": "", - "deprecates": "ErrStorageResourcesExceeded" - }, - { - "constant": "JSStreamMismatchErr", - "code": 400, - "error_code": 10056, - "description": "stream name in subject does not match request", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamNotMatchErr", - "code": 400, - "error_code": 10060, - "description": "expected stream does not match", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSMirrorConsumerSetupFailedErrF", - "code": 500, - "error_code": 10029, - "description": "{err}", - "comment": "generic mirror consumer setup failure string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSNotEmptyRequestErr", - "code": 400, - "error_code": 10038, - "description": "expected an empty request payload", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamNameExistErr", - "code": 400, - "error_code": 10058, - "description": "stream name already in use with a different configuration", - "comment": "", - "help": "", - "url": "", - "deprecates": "ErrJetStreamStreamAlreadyUsed" - }, - { - "constant": "JSClusterTagsErr", - "code": 400, - "error_code": 10011, - "description": "tags placement not supported for operation", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSMaximumConsumersLimitErr", - "code": 400, - "error_code": 10026, - "description": "maximum consumers limit reached", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSSourceConsumerSetupFailedErrF", - "code": 500, - "error_code": 10045, - "description": "{err}", - "comment": "General source consumer setup failure string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerCreateErrF", - "code": 500, - "error_code": 10012, - "description": "{err}", - "comment": "General consumer creation failure string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerDurableNameNotInSubjectErr", - "code": 400, - "error_code": 10016, - "description": "consumer expected to be durable but no durable name set in subject", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamLimitsErrF", - "code": 500, - "error_code": 10053, - "description": "{err}", - "comment": "General stream limits exceeded error string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamReplicasNotUpdatableErr", - "code": 400, - "error_code": 10061, - "description": "Replicas configuration can not be updated", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamTemplateNotFoundErr", - "code": 404, - "error_code": 10068, - "description": "template not found", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSClusterNotAssignedErr", - "code": 500, - "error_code": 10007, - "description": "JetStream cluster not assigned to this server", - "comment": "", - "help": "", - "url": "", - "deprecates": "ErrJetStreamNotAssigned" - }, - { - "constant": "JSClusterNotLeaderErr", - "code": 500, - "error_code": 10009, - "description": "JetStream cluster can not handle request", - "comment": "", - "help": "", - "url": "", - "deprecates": "ErrJetStreamNotLeader" - }, - { - "constant": "JSConsumerNameExistErr", - "code": 400, - "error_code": 10013, - "description": "consumer name already in use", - "comment": "", - "help": "", - "url": "", - "deprecates": "ErrJetStreamConsumerAlreadyUsed" - }, - { - "constant": "JSMirrorWithSourcesErr", - "code": 400, - "error_code": 10031, - "description": "stream mirrors can not also contain other sources", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamNotFoundErr", - "code": 404, - "error_code": 10059, - "description": "stream not found", - "comment": "", - "help": "", - "url": "", - "deprecates": "ErrJetStreamStreamNotFound" - }, - { - "constant": "JSClusterRequiredErr", - "code": 503, - "error_code": 10010, - "description": "JetStream clustering support required", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerDurableNameNotSetErr", - "code": 400, - "error_code": 10018, - "description": "consumer expected to be durable but a durable name was not set", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSMaximumStreamsLimitErr", - "code": 400, - "error_code": 10027, - "description": "maximum number of streams reached", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSMirrorWithStartSeqAndTimeErr", - "code": 400, - "error_code": 10032, - "description": "stream mirrors can not have both start seq and start time configured", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamSnapshotErrF", - "code": 500, - "error_code": 10064, - "description": "snapshot failed: {err}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamUpdateErrF", - "code": 500, - "error_code": 10069, - "description": "{err}", - "comment": "Generic stream update error string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSClusterNotActiveErr", - "code": 500, - "error_code": 10006, - "description": "JetStream not in clustered mode", - "comment": "", - "help": "", - "url": "", - "deprecates": "ErrJetStreamNotClustered" - }, - { - "constant": "JSConsumerDurableNameNotMatchSubjectErr", - "code": 400, - "error_code": 10017, - "description": "consumer name in subject does not match durable name in request", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSMemoryResourcesExceededErr", - "code": 500, - "error_code": 10028, - "description": "insufficient memory resources available", - "comment": "", - "help": "", - "url": "", - "deprecates": "ErrMemoryResourcesExceeded" - }, - { - "constant": "JSMirrorWithSubjectFiltersErr", - "code": 400, - "error_code": 10033, - "description": "stream mirrors can not contain filtered subjects", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamCreateErrF", - "code": 500, - "error_code": 10049, - "description": "{err}", - "comment": "Generic stream creation error string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSClusterServerNotMemberErr", - "code": 400, - "error_code": 10044, - "description": "server is not a member of the cluster", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSNoMessageFoundErr", - "code": 404, - "error_code": 10037, - "description": "no message found", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSSnapshotDeliverSubjectInvalidErr", - "code": 400, - "error_code": 10015, - "description": "deliver subject not valid", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamGeneralErrorF", - "code": 500, - "error_code": 10051, - "description": "{err}", - "comment": "General stream failure string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamInvalidConfigF", - "code": 500, - "error_code": 10052, - "description": "{err}", - "comment": "Stream configuration validation error string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamReplicasNotSupportedErr", - "code": 500, - "error_code": 10074, - "description": "replicas \u003e 1 not supported in non-clustered mode", - "comment": "", - "help": "", - "url": "", - "deprecates": "ErrReplicasNotSupported" - }, - { - "constant": "JSStreamMsgDeleteFailedF", - "code": 500, - "error_code": 10057, - "description": "{err}", - "comment": "Generic message deletion failure error string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSPeerRemapErr", - "code": 503, - "error_code": 10075, - "description": "peer remap failed", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamStoreFailedF", - "code": 503, - "error_code": 10077, - "description": "{err}", - "comment": "Generic error when storing a message failed", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerConfigRequiredErr", - "code": 400, - "error_code": 10078, - "description": "consumer config required", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerDeliverToWildcardsErr", - "code": 400, - "error_code": 10079, - "description": "consumer deliver subject has wildcards", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerPushMaxWaitingErr", - "code": 400, - "error_code": 10080, - "description": "consumer in push mode can not set max waiting", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerDeliverCycleErr", - "code": 400, - "error_code": 10081, - "description": "consumer deliver subject forms a cycle", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerMaxPendingAckPolicyRequiredErr", - "code": 400, - "error_code": 10082, - "description": "consumer requires ack policy for max ack pending", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerSmallHeartbeatErr", - "code": 400, - "error_code": 10083, - "description": "consumer idle heartbeat needs to be \u003e= 100ms", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerPullRequiresAckErr", - "code": 400, - "error_code": 10084, - "description": "consumer in pull mode requires explicit ack policy on workqueue stream", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerPullNotDurableErr", - "code": 400, - "error_code": 10085, - "description": "consumer in pull mode requires a durable name", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerPullWithRateLimitErr", - "code": 400, - "error_code": 10086, - "description": "consumer in pull mode can not have rate limit set", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerMaxWaitingNegativeErr", - "code": 400, - "error_code": 10087, - "description": "consumer max waiting needs to be positive", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerHBRequiresPushErr", - "code": 400, - "error_code": 10088, - "description": "consumer idle heartbeat requires a push based consumer", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerFCRequiresPushErr", - "code": 400, - "error_code": 10089, - "description": "consumer flow control requires a push based consumer", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerDirectRequiresPushErr", - "code": 400, - "error_code": 10090, - "description": "consumer direct requires a push based consumer", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerDirectRequiresEphemeralErr", - "code": 400, - "error_code": 10091, - "description": "consumer direct requires an ephemeral consumer", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerOnMappedErr", - "code": 400, - "error_code": 10092, - "description": "consumer direct on a mapped consumer", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerFilterNotSubsetErr", - "code": 400, - "error_code": 10093, - "description": "consumer filter subject is not a valid subset of the interest subjects", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerInvalidPolicyErrF", - "code": 400, - "error_code": 10094, - "description": "{err}", - "comment": "Generic delivery policy error", - "help": "Error returned for impossible deliver policies when combined with start sequences etc", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerInvalidSamplingErrF", - "code": 400, - "error_code": 10095, - "description": "failed to parse consumer sampling configuration: {err}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamInvalidErr", - "code": 500, - "error_code": 10096, - "description": "stream not valid", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerWQRequiresExplicitAckErr", - "code": 400, - "error_code": 10098, - "description": "workqueue stream requires explicit ack", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerWQMultipleUnfilteredErr", - "code": 400, - "error_code": 10099, - "description": "multiple non-filtered consumers not allowed on workqueue stream", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerWQConsumerNotUniqueErr", - "code": 400, - "error_code": 10100, - "description": "filtered consumer not unique on workqueue stream", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerWQConsumerNotDeliverAllErr", - "code": 400, - "error_code": 10101, - "description": "consumer must be deliver all on workqueue stream", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerNameTooLongErrF", - "code": 400, - "error_code": 10102, - "description": "consumer name is too long, maximum allowed is {max}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerBadDurableNameErr", - "code": 400, - "error_code": 10103, - "description": "durable name can not contain '.', '*', '\u003e'", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerStoreFailedErrF", - "code": 500, - "error_code": 10104, - "description": "error creating store for consumer: {err}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerExistingActiveErr", - "code": 400, - "error_code": 10105, - "description": "consumer already exists and is still active", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerReplacementWithDifferentNameErr", - "code": 400, - "error_code": 10106, - "description": "consumer replacement durable config not the same", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerDescriptionTooLongErrF", - "code": 400, - "error_code": 10107, - "description": "consumer description is too long, maximum allowed is {max}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamHeaderExceedsMaximumErr", - "code": 400, - "error_code": 10097, - "description": "header size exceeds maximum allowed of 64k", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerWithFlowControlNeedsHeartbeats", - "code": 400, - "error_code": 10108, - "description": "consumer with flow control also needs heartbeats", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamSealedErr", - "code": 400, - "error_code": 10109, - "description": "invalid operation on sealed stream", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamPurgeFailedF", - "code": 500, - "error_code": 10110, - "description": "{err}", - "comment": "Generic stream purge failure error string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamRollupFailedF", - "code": 500, - "error_code": 10111, - "description": "{err}", - "comment": "Generic stream rollup failure error string", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerInvalidDeliverSubject", - "code": 400, - "error_code": 10112, - "description": "invalid push consumer deliver subject", - "comment": "", - "help": "Returned when the delivery subject on a Push Consumer is not a valid NATS Subject", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamMaxBytesRequired", - "code": 400, - "error_code": 10113, - "description": "account requires a stream config to have max bytes set", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerMaxRequestBatchNegativeErr", - "code": 400, - "error_code": 10114, - "description": "consumer max request batch needs to be \u003e 0", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerMaxRequestExpiresToSmall", - "code": 400, - "error_code": 10115, - "description": "consumer max request expires needs to be \u003e= 1ms", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerMaxDeliverBackoffErr", - "code": 400, - "error_code": 10116, - "description": "max deliver is required to be \u003e length of backoff values", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamInfoMaxSubjectsErr", - "code": 500, - "error_code": 10117, - "description": "subject details would exceed maximum allowed", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamOfflineErr", - "code": 500, - "error_code": 10118, - "description": "stream is offline", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerOfflineErr", - "code": 500, - "error_code": 10119, - "description": "consumer is offline", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSNoLimitsErr", - "code": 400, - "error_code": 10120, - "description": "no JetStream default or applicable tiered limit present", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerMaxPendingAckExcessErrF", - "code": 400, - "error_code": 10121, - "description": "consumer max ack pending exceeds system limit of {limit}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamMaxStreamBytesExceeded", - "code": 400, - "error_code": 10122, - "description": "stream max bytes exceeds account limit max stream bytes", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamMoveAndScaleErr", - "code": 400, - "error_code": 10123, - "description": "can not move and scale a stream in a single update", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamMoveInProgressF", - "code": 400, - "error_code": 10124, - "description": "stream move already in progress: {msg}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerMaxRequestBatchExceededF", - "code": 400, - "error_code": 10125, - "description": "consumer max request batch exceeds server limit of {limit}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerReplicasExceedsStream", - "code": 400, - "error_code": 10126, - "description": "consumer config replica count exceeds parent stream", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerNameContainsPathSeparatorsErr", - "code": 400, - "error_code": 10127, - "description": "Consumer name can not contain path separators", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamNameContainsPathSeparatorsErr", - "code": 400, - "error_code": 10128, - "description": "Stream name can not contain path separators", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamMoveNotInProgress", - "code": 400, - "error_code": 10129, - "description": "stream move not in progress", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamNameExistRestoreFailedErr", - "code": 400, - "error_code": 10130, - "description": "stream name already in use, cannot restore", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerCreateFilterSubjectMismatchErr", - "code": 400, - "error_code": 10131, - "description": "Consumer create request did not match filtered subject from create subject", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerCreateDurableAndNameMismatch", - "code": 400, - "error_code": 10132, - "description": "Consumer Durable and Name have to be equal if both are provided", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSReplicasCountCannotBeNegative", - "code": 400, - "error_code": 10133, - "description": "replicas count cannot be negative", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerReplicasShouldMatchStream", - "code": 400, - "error_code": 10134, - "description": "consumer config replicas must match interest retention stream's replicas", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerMetadataLengthErrF", - "code": 400, - "error_code": 10135, - "description": "consumer metadata exceeds maximum size of {limit}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerDuplicateFilterSubjects", - "code": 400, - "error_code": 10136, - "description": "consumer cannot have both FilterSubject and FilterSubjects specified", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerMultipleFiltersNotAllowed", - "code": 400, - "error_code": 10137, - "description": "consumer with multiple subject filters cannot use subject based API", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerOverlappingSubjectFilters", - "code": 400, - "error_code": 10138, - "description": "consumer subject filters cannot overlap", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerEmptyFilter", - "code": 400, - "error_code": 10139, - "description": "consumer filter in FilterSubjects cannot be empty", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSSourceDuplicateDetected", - "code": 400, - "error_code": 10140, - "description": "duplicate source configuration detected", - "comment": "source stream, filter and transform (plus external if present) must form a unique combination", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSSourceInvalidStreamName", - "code": 400, - "error_code": 10141, - "description": "sourced stream name is invalid", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSMirrorInvalidStreamName", - "code": 400, - "error_code": 10142, - "description": "mirrored stream name is invalid", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSSourceMultipleFiltersNotAllowed", - "code": 400, - "error_code": 10144, - "description": "source with multiple subject transforms cannot also have a single subject filter", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSSourceInvalidSubjectFilter", - "code": 400, - "error_code": 10145, - "description": "source transform source: {err}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSSourceInvalidTransformDestination", - "code": 400, - "error_code": 10146, - "description": "source transform: {err}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSSourceOverlappingSubjectFilters", - "code": 400, - "error_code": 10147, - "description": "source filters can not overlap", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerAlreadyExists", - "code": 400, - "error_code": 10148, - "description": "consumer already exists", - "comment": "action CREATE is used for a existing consumer with a different config", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerDoesNotExist", - "code": 400, - "error_code": 10149, - "description": "consumer does not exist", - "comment": "action UPDATE is used for a nonexisting consumer", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSMirrorMultipleFiltersNotAllowed", - "code": 400, - "error_code": 10150, - "description": "mirror with multiple subject transforms cannot also have a single subject filter", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSMirrorInvalidSubjectFilter", - "code": 400, - "error_code": 10151, - "description": "mirror transform source: {err}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSMirrorOverlappingSubjectFilters", - "code": 400, - "error_code": 10152, - "description": "mirror subject filters can not overlap", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerInactiveThresholdExcess", - "code": 400, - "error_code": 10153, - "description": "consumer inactive threshold exceeds system limit of {limit}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSMirrorInvalidTransformDestination", - "code": 400, - "error_code": 10154, - "description": "mirror transform: {err}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamTransformInvalidSource", - "code": 400, - "error_code": 10155, - "description": "stream transform source: {err}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamTransformInvalidDestination", - "code": 400, - "error_code": 10156, - "description": "stream transform: {err}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSPedanticErrF", - "code": 400, - "error_code": 10157, - "description": "pedantic mode: {err}", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamDuplicateMessageConflict", - "code": 409, - "error_code": 10158, - "description": "duplicate message id is in process", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerPriorityPolicyWithoutGroup", - "code": 400, - "error_code": 10159, - "description": "Setting PriorityPolicy requires at least one PriorityGroup to be set", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerInvalidPriorityGroupErr", - "code": 400, - "error_code": 10160, - "description": "Provided priority group does not exist for this consumer", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerEmptyGroupName", - "code": 400, - "error_code": 10161, - "description": "Group name cannot be an empty string", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSConsumerInvalidGroupNameErr", - "code": 400, - "error_code": 10162, - "description": "Valid priority group name must match A-Z, a-z, 0-9, -_/=)+ and may not exceed 16 characters", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamExpectedLastSeqPerSubjectNotReady", - "code": 503, - "error_code": 10163, - "description": "expected last sequence per subject temporarily unavailable", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamWrongLastSequenceConstantErr", - "code": 400, - "error_code": 10164, - "description": "wrong last sequence", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSMessageTTLInvalidErr", - "code": 400, - "error_code": 10165, - "description": "invalid per-message TTL", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSMessageTTLDisabledErr", - "code": 400, - "error_code": 10166, - "description": "per-message TTL is disabled", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - }, - { - "constant": "JSStreamTooManyRequests", - "code": 429, - "error_code": 10167, - "description": "too many requests", - "comment": "", - "help": "", - "url": "", - "deprecates": "" - } -] diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/events.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/events.go deleted file mode 100644 index 533ca9fa..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/events.go +++ /dev/null @@ -1,3241 +0,0 @@ -// Copyright 2018-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "compress/gzip" - "crypto/sha256" - "crypto/x509" - "encoding/json" - "errors" - "fmt" - "math/rand" - "net/http" - "runtime" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/klauspost/compress/s2" - "github.com/nats-io/jwt/v2" - "github.com/nats-io/nats-server/v2/server/certidp" - "github.com/nats-io/nats-server/v2/server/pse" -) - -const ( - accLookupReqTokens = 6 - accLookupReqSubj = "$SYS.REQ.ACCOUNT.%s.CLAIMS.LOOKUP" - accPackReqSubj = "$SYS.REQ.CLAIMS.PACK" - accListReqSubj = "$SYS.REQ.CLAIMS.LIST" - accClaimsReqSubj = "$SYS.REQ.CLAIMS.UPDATE" - accDeleteReqSubj = "$SYS.REQ.CLAIMS.DELETE" - - connectEventSubj = "$SYS.ACCOUNT.%s.CONNECT" - disconnectEventSubj = "$SYS.ACCOUNT.%s.DISCONNECT" - accDirectReqSubj = "$SYS.REQ.ACCOUNT.%s.%s" - accPingReqSubj = "$SYS.REQ.ACCOUNT.PING.%s" // atm. only used for STATZ and CONNZ import from system account - // kept for backward compatibility when using http resolver - // this overlaps with the names for events but you'd have to have the operator private key in order to succeed. - accUpdateEventSubjOld = "$SYS.ACCOUNT.%s.CLAIMS.UPDATE" - accUpdateEventSubjNew = "$SYS.REQ.ACCOUNT.%s.CLAIMS.UPDATE" - connsRespSubj = "$SYS._INBOX_.%s" - accConnsEventSubjNew = "$SYS.ACCOUNT.%s.SERVER.CONNS" - accConnsEventSubjOld = "$SYS.SERVER.ACCOUNT.%s.CONNS" // kept for backward compatibility - lameDuckEventSubj = "$SYS.SERVER.%s.LAMEDUCK" - shutdownEventSubj = "$SYS.SERVER.%s.SHUTDOWN" - clientKickReqSubj = "$SYS.REQ.SERVER.%s.KICK" - clientLDMReqSubj = "$SYS.REQ.SERVER.%s.LDM" - authErrorEventSubj = "$SYS.SERVER.%s.CLIENT.AUTH.ERR" - authErrorAccountEventSubj = "$SYS.ACCOUNT.CLIENT.AUTH.ERR" - serverStatsSubj = "$SYS.SERVER.%s.STATSZ" - serverDirectReqSubj = "$SYS.REQ.SERVER.%s.%s" - serverPingReqSubj = "$SYS.REQ.SERVER.PING.%s" - serverStatsPingReqSubj = "$SYS.REQ.SERVER.PING" // use $SYS.REQ.SERVER.PING.STATSZ instead - serverReloadReqSubj = "$SYS.REQ.SERVER.%s.RELOAD" // with server ID - leafNodeConnectEventSubj = "$SYS.ACCOUNT.%s.LEAFNODE.CONNECT" // for internal use only - remoteLatencyEventSubj = "$SYS.LATENCY.M2.%s" - inboxRespSubj = "$SYS._INBOX.%s.%s" - - // Used to return information to a user on bound account and user permissions. - userDirectInfoSubj = "$SYS.REQ.USER.INFO" - userDirectReqSubj = "$SYS.REQ.USER.%s.INFO" - - // FIXME(dlc) - Should account scope, even with wc for now, but later on - // we can then shard as needed. - accNumSubsReqSubj = "$SYS.REQ.ACCOUNT.NSUBS" - - // These are for exported debug services. These are local to this server only. - accSubsSubj = "$SYS.DEBUG.SUBSCRIBERS" - - shutdownEventTokens = 4 - serverSubjectIndex = 2 - accUpdateTokensNew = 6 - accUpdateTokensOld = 5 - accUpdateAccIdxOld = 2 - - accReqTokens = 5 - accReqAccIndex = 3 - - ocspPeerRejectEventSubj = "$SYS.SERVER.%s.OCSP.PEER.CONN.REJECT" - ocspPeerChainlinkInvalidEventSubj = "$SYS.SERVER.%s.OCSP.PEER.LINK.INVALID" -) - -// FIXME(dlc) - make configurable. -var eventsHBInterval = 30 * time.Second -var statsHBInterval = 10 * time.Second - -// Default minimum wait time for sending statsz -const defaultStatszRateLimit = 1 * time.Second - -// Variable version so we can set in tests. -var statszRateLimit = defaultStatszRateLimit - -type sysMsgHandler func(sub *subscription, client *client, acc *Account, subject, reply string, hdr, msg []byte) - -// Used if we have to queue things internally to avoid the route/gw path. -type inSysMsg struct { - sub *subscription - c *client - acc *Account - subj string - rply string - hdr []byte - msg []byte - cb sysMsgHandler -} - -// Used to send and receive messages from inside the server. -type internal struct { - account *Account - client *client - seq uint64 - sid int - servers map[string]*serverUpdate - sweeper *time.Timer - stmr *time.Timer - replies map[string]msgHandler - sendq *ipQueue[*pubMsg] - recvq *ipQueue[*inSysMsg] - recvqp *ipQueue[*inSysMsg] // For STATSZ/Pings - resetCh chan struct{} - wg sync.WaitGroup - sq *sendq - orphMax time.Duration - chkOrph time.Duration - statsz time.Duration - cstatsz time.Duration - shash string - inboxPre string - remoteStatsSub *subscription - lastStatsz time.Time -} - -// ServerStatsMsg is sent periodically with stats updates. -type ServerStatsMsg struct { - Server ServerInfo `json:"server"` - Stats ServerStats `json:"statsz"` -} - -// ConnectEventMsg is sent when a new connection is made that is part of an account. -type ConnectEventMsg struct { - TypedEvent - Server ServerInfo `json:"server"` - Client ClientInfo `json:"client"` -} - -// ConnectEventMsgType is the schema type for ConnectEventMsg -const ConnectEventMsgType = "io.nats.server.advisory.v1.client_connect" - -// DisconnectEventMsg is sent when a new connection previously defined from a -// ConnectEventMsg is closed. -type DisconnectEventMsg struct { - TypedEvent - Server ServerInfo `json:"server"` - Client ClientInfo `json:"client"` - Sent DataStats `json:"sent"` - Received DataStats `json:"received"` - Reason string `json:"reason"` -} - -// DisconnectEventMsgType is the schema type for DisconnectEventMsg -const DisconnectEventMsgType = "io.nats.server.advisory.v1.client_disconnect" - -// OCSPPeerRejectEventMsg is sent when a peer TLS handshake is ultimately rejected due to OCSP invalidation. -// A "peer" can be an inbound client connection or a leaf connection to a remote server. Peer in event payload -// is always the peer's (TLS) leaf cert, which may or may be the invalid cert (See also OCSPPeerChainlinkInvalidEventMsg) -type OCSPPeerRejectEventMsg struct { - TypedEvent - Kind string `json:"kind"` - Peer certidp.CertInfo `json:"peer"` - Server ServerInfo `json:"server"` - Reason string `json:"reason"` -} - -// OCSPPeerRejectEventMsgType is the schema type for OCSPPeerRejectEventMsg -const OCSPPeerRejectEventMsgType = "io.nats.server.advisory.v1.ocsp_peer_reject" - -// OCSPPeerChainlinkInvalidEventMsg is sent when a certificate (link) in a valid TLS chain is found to be OCSP invalid -// during a peer TLS handshake. A "peer" can be an inbound client connection or a leaf connection to a remote server. -// Peer and Link may be the same if the invalid cert was the peer's leaf cert -type OCSPPeerChainlinkInvalidEventMsg struct { - TypedEvent - Link certidp.CertInfo `json:"link"` - Peer certidp.CertInfo `json:"peer"` - Server ServerInfo `json:"server"` - Reason string `json:"reason"` -} - -// OCSPPeerChainlinkInvalidEventMsgType is the schema type for OCSPPeerChainlinkInvalidEventMsg -const OCSPPeerChainlinkInvalidEventMsgType = "io.nats.server.advisory.v1.ocsp_peer_link_invalid" - -// AccountNumConns is an event that will be sent from a server that is tracking -// a given account when the number of connections changes. It will also HB -// updates in the absence of any changes. -type AccountNumConns struct { - TypedEvent - Server ServerInfo `json:"server"` - AccountStat -} - -// AccountStat contains the data common between AccountNumConns and AccountStatz -type AccountStat struct { - Account string `json:"acc"` - Name string `json:"name"` - Conns int `json:"conns"` - LeafNodes int `json:"leafnodes"` - TotalConns int `json:"total_conns"` - NumSubs uint32 `json:"num_subscriptions"` - Sent DataStats `json:"sent"` - Received DataStats `json:"received"` - SlowConsumers int64 `json:"slow_consumers"` -} - -const AccountNumConnsMsgType = "io.nats.server.advisory.v1.account_connections" - -// accNumConnsReq is sent when we are starting to track an account for the first -// time. We will request others send info to us about their local state. -type accNumConnsReq struct { - Server ServerInfo `json:"server"` - Account string `json:"acc"` -} - -// ServerID is basic static info for a server. -type ServerID struct { - Name string `json:"name"` - Host string `json:"host"` - ID string `json:"id"` -} - -// Type for our server capabilities. -type ServerCapability uint64 - -// ServerInfo identifies remote servers. -type ServerInfo struct { - Name string `json:"name"` - Host string `json:"host"` - ID string `json:"id"` - Cluster string `json:"cluster,omitempty"` - Domain string `json:"domain,omitempty"` - Version string `json:"ver"` - Tags []string `json:"tags,omitempty"` - // Whether JetStream is enabled (deprecated in favor of the `ServerCapability`). - JetStream bool `json:"jetstream"` - // Generic capability flags - Flags ServerCapability `json:"flags"` - // Sequence and Time from the remote server for this message. - Seq uint64 `json:"seq"` - Time time.Time `json:"time"` -} - -const ( - JetStreamEnabled ServerCapability = 1 << iota // Server had JetStream enabled. - BinaryStreamSnapshot // New stream snapshot capability. - AccountNRG // Move NRG traffic out of system account. -) - -// Set JetStream capability. -func (si *ServerInfo) SetJetStreamEnabled() { - si.Flags |= JetStreamEnabled - // Still set old version. - si.JetStream = true -} - -// JetStreamEnabled indicates whether or not we have JetStream enabled. -func (si *ServerInfo) JetStreamEnabled() bool { - // Take into account old version. - return si.Flags&JetStreamEnabled != 0 || si.JetStream -} - -// Set binary stream snapshot capability. -func (si *ServerInfo) SetBinaryStreamSnapshot() { - si.Flags |= BinaryStreamSnapshot -} - -// JetStreamEnabled indicates whether or not we have binary stream snapshot capbilities. -func (si *ServerInfo) BinaryStreamSnapshot() bool { - return si.Flags&BinaryStreamSnapshot != 0 -} - -// Set account NRG capability. -func (si *ServerInfo) SetAccountNRG() { - si.Flags |= AccountNRG -} - -// AccountNRG indicates whether or not we support moving the NRG traffic out of the -// system account and into the asset account. -func (si *ServerInfo) AccountNRG() bool { - return si.Flags&AccountNRG != 0 -} - -// ClientInfo is detailed information about the client forming a connection. -type ClientInfo struct { - Start *time.Time `json:"start,omitempty"` - Host string `json:"host,omitempty"` - ID uint64 `json:"id,omitempty"` - Account string `json:"acc,omitempty"` - Service string `json:"svc,omitempty"` - User string `json:"user,omitempty"` - Name string `json:"name,omitempty"` - Lang string `json:"lang,omitempty"` - Version string `json:"ver,omitempty"` - RTT time.Duration `json:"rtt,omitempty"` - Server string `json:"server,omitempty"` - Cluster string `json:"cluster,omitempty"` - Alternates []string `json:"alts,omitempty"` - Stop *time.Time `json:"stop,omitempty"` - Jwt string `json:"jwt,omitempty"` - IssuerKey string `json:"issuer_key,omitempty"` - NameTag string `json:"name_tag,omitempty"` - Tags jwt.TagList `json:"tags,omitempty"` - Kind string `json:"kind,omitempty"` - ClientType string `json:"client_type,omitempty"` - MQTTClient string `json:"client_id,omitempty"` // This is the MQTT client ID - Nonce string `json:"nonce,omitempty"` -} - -// forAssignmentSnap returns the minimum amount of ClientInfo we need for assignment snapshots. -func (ci *ClientInfo) forAssignmentSnap() *ClientInfo { - return &ClientInfo{ - Account: ci.Account, - Service: ci.Service, - Cluster: ci.Cluster, - } -} - -// forProposal returns the minimum amount of ClientInfo we need for assignment proposals. -func (ci *ClientInfo) forProposal() *ClientInfo { - if ci == nil { - return nil - } - cci := *ci - cci.Jwt = _EMPTY_ - cci.IssuerKey = _EMPTY_ - return &cci -} - -// forAdvisory returns the minimum amount of ClientInfo we need for JS advisory events. -func (ci *ClientInfo) forAdvisory() *ClientInfo { - if ci == nil { - return nil - } - cci := *ci - cci.Jwt = _EMPTY_ - cci.Alternates = nil - return &cci -} - -// ServerStats hold various statistics that we will periodically send out. -type ServerStats struct { - Start time.Time `json:"start"` - Mem int64 `json:"mem"` - Cores int `json:"cores"` - CPU float64 `json:"cpu"` - Connections int `json:"connections"` - TotalConnections uint64 `json:"total_connections"` - ActiveAccounts int `json:"active_accounts"` - NumSubs uint32 `json:"subscriptions"` - Sent DataStats `json:"sent"` - Received DataStats `json:"received"` - SlowConsumers int64 `json:"slow_consumers"` - SlowConsumersStats *SlowConsumersStats `json:"slow_consumer_stats,omitempty"` - Routes []*RouteStat `json:"routes,omitempty"` - Gateways []*GatewayStat `json:"gateways,omitempty"` - ActiveServers int `json:"active_servers,omitempty"` - JetStream *JetStreamVarz `json:"jetstream,omitempty"` -} - -// RouteStat holds route statistics. -type RouteStat struct { - ID uint64 `json:"rid"` - Name string `json:"name,omitempty"` - Sent DataStats `json:"sent"` - Received DataStats `json:"received"` - Pending int `json:"pending"` -} - -// GatewayStat holds gateway statistics. -type GatewayStat struct { - ID uint64 `json:"gwid"` - Name string `json:"name"` - Sent DataStats `json:"sent"` - Received DataStats `json:"received"` - NumInbound int `json:"inbound_connections"` -} - -// DataStats reports how may msg and bytes. Applicable for both sent and received. -type DataStats struct { - Msgs int64 `json:"msgs"` - Bytes int64 `json:"bytes"` -} - -// Used for internally queueing up messages that the server wants to send. -type pubMsg struct { - c *client - sub string - rply string - si *ServerInfo - hdr map[string]string - msg any - oct compressionType - echo bool - last bool -} - -var pubMsgPool sync.Pool - -func newPubMsg(c *client, sub, rply string, si *ServerInfo, hdr map[string]string, - msg any, oct compressionType, echo, last bool) *pubMsg { - - var m *pubMsg - pm := pubMsgPool.Get() - if pm != nil { - m = pm.(*pubMsg) - } else { - m = &pubMsg{} - } - // When getting something from a pool it is critical that all fields are - // initialized. Doing this way guarantees that if someone adds a field to - // the structure, the compiler will fail the build if this line is not updated. - (*m) = pubMsg{c, sub, rply, si, hdr, msg, oct, echo, last} - return m -} - -func (pm *pubMsg) returnToPool() { - if pm == nil { - return - } - pm.c, pm.sub, pm.rply, pm.si, pm.hdr, pm.msg = nil, _EMPTY_, _EMPTY_, nil, nil, nil - pubMsgPool.Put(pm) -} - -// Used to track server updates. -type serverUpdate struct { - seq uint64 - ltime time.Time -} - -// TypedEvent is a event or advisory sent by the server that has nats type hints -// typically used for events that might be consumed by 3rd party event systems -type TypedEvent struct { - Type string `json:"type"` - ID string `json:"id"` - Time time.Time `json:"timestamp"` -} - -// internalReceiveLoop will be responsible for dispatching all messages that -// a server receives and needs to internally process, e.g. internal subs. -func (s *Server) internalReceiveLoop(recvq *ipQueue[*inSysMsg]) { - for s.eventsRunning() { - select { - case <-recvq.ch: - msgs := recvq.pop() - for _, m := range msgs { - if m.cb != nil { - m.cb(m.sub, m.c, m.acc, m.subj, m.rply, m.hdr, m.msg) - } - } - recvq.recycle(&msgs) - case <-s.quitCh: - return - } - } -} - -// internalSendLoop will be responsible for serializing all messages that -// a server wants to send. -func (s *Server) internalSendLoop(wg *sync.WaitGroup) { - defer wg.Done() - -RESET: - s.mu.RLock() - if s.sys == nil || s.sys.sendq == nil { - s.mu.RUnlock() - return - } - sysc := s.sys.client - resetCh := s.sys.resetCh - sendq := s.sys.sendq - id := s.info.ID - host := s.info.Host - servername := s.info.Name - domain := s.info.Domain - seqp := &s.sys.seq - js := s.info.JetStream - cluster := s.info.Cluster - if s.gateway.enabled { - cluster = s.getGatewayName() - } - s.mu.RUnlock() - - // Grab tags. - tags := s.getOpts().Tags - - for s.eventsRunning() { - select { - case <-sendq.ch: - msgs := sendq.pop() - for _, pm := range msgs { - if si := pm.si; si != nil { - si.Name = servername - si.Domain = domain - si.Host = host - si.Cluster = cluster - si.ID = id - si.Seq = atomic.AddUint64(seqp, 1) - si.Version = VERSION - si.Time = time.Now().UTC() - si.Tags = tags - si.Flags = 0 - if js { - // New capability based flags. - si.SetJetStreamEnabled() - si.SetBinaryStreamSnapshot() - if s.accountNRGAllowed.Load() { - si.SetAccountNRG() - } - } - } - var b []byte - if pm.msg != nil { - switch v := pm.msg.(type) { - case string: - b = []byte(v) - case []byte: - b = v - default: - b, _ = json.Marshal(pm.msg) - } - } - // Setup our client. If the user wants to use a non-system account use our internal - // account scoped here so that we are not changing out accounts for the system client. - var c *client - if pm.c != nil { - c = pm.c - } else { - c = sysc - } - - // Grab client lock. - c.mu.Lock() - - // Prep internal structures needed to send message. - c.pa.subject, c.pa.reply = []byte(pm.sub), []byte(pm.rply) - c.pa.size, c.pa.szb = len(b), []byte(strconv.FormatInt(int64(len(b)), 10)) - c.pa.hdr, c.pa.hdb = -1, nil - trace := c.trace - - // Now check for optional compression. - var contentHeader string - var bb bytes.Buffer - - if len(b) > 0 { - switch pm.oct { - case gzipCompression: - zw := gzip.NewWriter(&bb) - zw.Write(b) - zw.Close() - b = bb.Bytes() - contentHeader = "gzip" - case snappyCompression: - sw := s2.NewWriter(&bb, s2.WriterSnappyCompat()) - sw.Write(b) - sw.Close() - b = bb.Bytes() - contentHeader = "snappy" - case unsupportedCompression: - contentHeader = "identity" - } - } - // Optional Echo - replaceEcho := c.echo != pm.echo - if replaceEcho { - c.echo = !c.echo - } - c.mu.Unlock() - - // Add in NL - b = append(b, _CRLF_...) - - // Check if we should set content-encoding - if contentHeader != _EMPTY_ { - b = c.setHeader(contentEncodingHeader, contentHeader, b) - } - - // Optional header processing. - if pm.hdr != nil { - for k, v := range pm.hdr { - b = c.setHeader(k, v, b) - } - } - // Tracing - if trace { - c.traceInOp(fmt.Sprintf("PUB %s %s %d", c.pa.subject, c.pa.reply, c.pa.size), nil) - c.traceMsg(b) - } - - // Process like a normal inbound msg. - c.processInboundClientMsg(b) - - // Put echo back if needed. - if replaceEcho { - c.mu.Lock() - c.echo = !c.echo - c.mu.Unlock() - } - - // See if we are doing graceful shutdown. - if !pm.last { - c.flushClients(0) // Never spend time in place. - } else { - // For the Shutdown event, we need to send in place otherwise - // there is a chance that the process will exit before the - // writeLoop has a chance to send it. - c.flushClients(time.Second) - sendq.recycle(&msgs) - return - } - pm.returnToPool() - } - sendq.recycle(&msgs) - case <-resetCh: - goto RESET - case <-s.quitCh: - return - } - } -} - -// Will send a shutdown message for lame-duck. Unlike sendShutdownEvent, this will -// not close off the send queue or reply handler, as we may still have a workload -// that needs migrating off. -// Lock should be held. -func (s *Server) sendLDMShutdownEventLocked() { - if s.sys == nil || s.sys.sendq == nil { - return - } - subj := fmt.Sprintf(lameDuckEventSubj, s.info.ID) - si := &ServerInfo{} - s.sys.sendq.push(newPubMsg(nil, subj, _EMPTY_, si, nil, si, noCompression, false, true)) -} - -// Will send a shutdown message. -func (s *Server) sendShutdownEvent() { - s.mu.Lock() - if s.sys == nil || s.sys.sendq == nil { - s.mu.Unlock() - return - } - subj := fmt.Sprintf(shutdownEventSubj, s.info.ID) - sendq := s.sys.sendq - // Stop any more messages from queueing up. - s.sys.sendq = nil - // Unhook all msgHandlers. Normal client cleanup will deal with subs, etc. - s.sys.replies = nil - // Send to the internal queue and mark as last. - si := &ServerInfo{} - sendq.push(newPubMsg(nil, subj, _EMPTY_, si, nil, si, noCompression, false, true)) - s.mu.Unlock() -} - -// Used to send an internal message to an arbitrary account. -func (s *Server) sendInternalAccountMsg(a *Account, subject string, msg any) error { - return s.sendInternalAccountMsgWithReply(a, subject, _EMPTY_, nil, msg, false) -} - -// Used to send an internal message with an optional reply to an arbitrary account. -func (s *Server) sendInternalAccountMsgWithReply(a *Account, subject, reply string, hdr map[string]string, msg any, echo bool) error { - s.mu.RLock() - if s.sys == nil || s.sys.sendq == nil { - s.mu.RUnlock() - if s.isShuttingDown() { - // Skip in case this was called at the end phase during shut down - // to avoid too many entries in the logs. - return nil - } - return ErrNoSysAccount - } - c := s.sys.client - // Replace our client with the account's internal client. - if a != nil { - a.mu.Lock() - c = a.internalClient() - a.mu.Unlock() - } - s.sys.sendq.push(newPubMsg(c, subject, reply, nil, hdr, msg, noCompression, echo, false)) - s.mu.RUnlock() - return nil -} - -// Send system style message to an account scope. -func (s *Server) sendInternalAccountSysMsg(a *Account, subj string, si *ServerInfo, msg any, ct compressionType) { - s.mu.RLock() - if s.sys == nil || s.sys.sendq == nil || a == nil { - s.mu.RUnlock() - return - } - sendq := s.sys.sendq - s.mu.RUnlock() - - a.mu.Lock() - c := a.internalClient() - a.mu.Unlock() - - sendq.push(newPubMsg(c, subj, _EMPTY_, si, nil, msg, ct, false, false)) -} - -// This will queue up a message to be sent. -// Lock should not be held. -func (s *Server) sendInternalMsgLocked(subj, rply string, si *ServerInfo, msg any) { - s.mu.RLock() - s.sendInternalMsg(subj, rply, si, msg) - s.mu.RUnlock() -} - -// This will queue up a message to be sent. -// Assumes lock is held on entry. -func (s *Server) sendInternalMsg(subj, rply string, si *ServerInfo, msg any) { - if s.sys == nil || s.sys.sendq == nil { - return - } - s.sys.sendq.push(newPubMsg(nil, subj, rply, si, nil, msg, noCompression, false, false)) -} - -// Will send an api response. -func (s *Server) sendInternalResponse(subj string, response *ServerAPIResponse) { - s.mu.RLock() - if s.sys == nil || s.sys.sendq == nil { - s.mu.RUnlock() - return - } - s.sys.sendq.push(newPubMsg(nil, subj, _EMPTY_, response.Server, nil, response, response.compress, false, false)) - s.mu.RUnlock() -} - -// Used to send internal messages from other system clients to avoid no echo issues. -func (c *client) sendInternalMsg(subj, rply string, si *ServerInfo, msg any) { - if c == nil { - return - } - s := c.srv - if s == nil { - return - } - s.mu.RLock() - if s.sys == nil || s.sys.sendq == nil { - s.mu.RUnlock() - return - } - s.sys.sendq.push(newPubMsg(c, subj, rply, si, nil, msg, noCompression, false, false)) - s.mu.RUnlock() -} - -// Locked version of checking if events system running. Also checks server. -func (s *Server) eventsRunning() bool { - if s == nil { - return false - } - s.mu.RLock() - er := s.isRunning() && s.eventsEnabled() - s.mu.RUnlock() - return er -} - -// EventsEnabled will report if the server has internal events enabled via -// a defined system account. -func (s *Server) EventsEnabled() bool { - s.mu.RLock() - defer s.mu.RUnlock() - return s.eventsEnabled() -} - -// eventsEnabled will report if events are enabled. -// Lock should be held. -func (s *Server) eventsEnabled() bool { - return s.sys != nil && s.sys.client != nil && s.sys.account != nil -} - -// TrackedRemoteServers returns how many remote servers we are tracking -// from a system events perspective. -func (s *Server) TrackedRemoteServers() int { - s.mu.RLock() - defer s.mu.RUnlock() - if !s.isRunning() || !s.eventsEnabled() { - return -1 - } - return len(s.sys.servers) -} - -// Check for orphan servers who may have gone away without notification. -// This should be wrapChk() to setup common locking. -func (s *Server) checkRemoteServers() { - now := time.Now() - for sid, su := range s.sys.servers { - if now.Sub(su.ltime) > s.sys.orphMax { - s.Debugf("Detected orphan remote server: %q", sid) - // Simulate it going away. - s.processRemoteServerShutdown(sid) - } - } - if s.sys.sweeper != nil { - s.sys.sweeper.Reset(s.sys.chkOrph) - } -} - -// Grab RSS and PCPU -// Server lock will be held but released. -func (s *Server) updateServerUsage(v *ServerStats) { - var vss int64 - pse.ProcUsage(&v.CPU, &v.Mem, &vss) - v.Cores = runtime.NumCPU() -} - -// Generate a route stat for our statz update. -func routeStat(r *client) *RouteStat { - if r == nil { - return nil - } - r.mu.Lock() - // Note: *client.out[Msgs|Bytes] are not set using atomics, - // unlike in[Msgs|Bytes]. - rs := &RouteStat{ - ID: r.cid, - Sent: DataStats{ - Msgs: r.outMsgs, - Bytes: r.outBytes, - }, - Received: DataStats{ - Msgs: atomic.LoadInt64(&r.inMsgs), - Bytes: atomic.LoadInt64(&r.inBytes), - }, - Pending: int(r.out.pb), - } - if r.route != nil { - rs.Name = r.route.remoteName - } - r.mu.Unlock() - return rs -} - -// Actual send method for statz updates. -// Lock should be held. -func (s *Server) sendStatsz(subj string) { - var m ServerStatsMsg - s.updateServerUsage(&m.Stats) - - if s.limitStatsz(subj) { - return - } - - s.mu.RLock() - defer s.mu.RUnlock() - - // Check that we have a system account, etc. - if s.sys == nil || s.sys.account == nil { - return - } - - shouldCheckInterest := func() bool { - opts := s.getOpts() - if opts.Cluster.Port != 0 || opts.Gateway.Port != 0 || opts.LeafNode.Port != 0 { - return false - } - // If we are here we have no clustering or gateways and are not a leafnode hub. - // Check for leafnode remotes that connect the system account. - if len(opts.LeafNode.Remotes) > 0 { - sysAcc := s.sys.account.GetName() - for _, r := range opts.LeafNode.Remotes { - if r.LocalAccount == sysAcc { - return false - } - } - } - return true - } - - // if we are running standalone, check for interest. - if shouldCheckInterest() { - // Check if we even have interest in this subject. - sacc := s.sys.account - rr := sacc.sl.Match(subj) - totalSubs := len(rr.psubs) + len(rr.qsubs) - if totalSubs == 0 { - return - } else if totalSubs == 1 && len(rr.psubs) == 1 { - // For the broadcast subject we listen to that ourselves with no echo for remote updates. - // If we are the only ones listening do not send either. - if rr.psubs[0] == s.sys.remoteStatsSub { - return - } - } - } - - m.Stats.Start = s.start - m.Stats.Connections = len(s.clients) - m.Stats.TotalConnections = s.totalClients - m.Stats.ActiveAccounts = int(atomic.LoadInt32(&s.activeAccounts)) - m.Stats.Received.Msgs = atomic.LoadInt64(&s.inMsgs) - m.Stats.Received.Bytes = atomic.LoadInt64(&s.inBytes) - m.Stats.Sent.Msgs = atomic.LoadInt64(&s.outMsgs) - m.Stats.Sent.Bytes = atomic.LoadInt64(&s.outBytes) - m.Stats.SlowConsumers = atomic.LoadInt64(&s.slowConsumers) - // Evaluate the slow consumer stats, but set it only if one of the value is not 0. - scs := &SlowConsumersStats{ - Clients: s.NumSlowConsumersClients(), - Routes: s.NumSlowConsumersRoutes(), - Gateways: s.NumSlowConsumersGateways(), - Leafs: s.NumSlowConsumersLeafs(), - } - if scs.Clients != 0 || scs.Routes != 0 || scs.Gateways != 0 || scs.Leafs != 0 { - m.Stats.SlowConsumersStats = scs - } - m.Stats.NumSubs = s.numSubscriptions() - // Routes - s.forEachRoute(func(r *client) { - m.Stats.Routes = append(m.Stats.Routes, routeStat(r)) - }) - // Gateways - if s.gateway.enabled { - gw := s.gateway - gw.RLock() - for name, c := range gw.out { - gs := &GatewayStat{Name: name} - c.mu.Lock() - gs.ID = c.cid - // Note that *client.out[Msgs|Bytes] are not set using atomic, - // unlike the in[Msgs|bytes]. - gs.Sent = DataStats{ - Msgs: c.outMsgs, - Bytes: c.outBytes, - } - c.mu.Unlock() - // Gather matching inbound connections - gs.Received = DataStats{} - for _, c := range gw.in { - c.mu.Lock() - if c.gw.name == name { - gs.Received.Msgs += atomic.LoadInt64(&c.inMsgs) - gs.Received.Bytes += atomic.LoadInt64(&c.inBytes) - gs.NumInbound++ - } - c.mu.Unlock() - } - m.Stats.Gateways = append(m.Stats.Gateways, gs) - } - gw.RUnlock() - } - // Active Servers - m.Stats.ActiveServers = len(s.sys.servers) + 1 - - // JetStream - if js := s.js.Load(); js != nil { - jStat := &JetStreamVarz{} - s.mu.RUnlock() - js.mu.RLock() - c := js.config - c.StoreDir = _EMPTY_ - jStat.Config = &c - js.mu.RUnlock() - jStat.Stats = js.usageStats() - // Update our own usage since we do not echo so we will not hear ourselves. - ourNode := getHash(s.serverName()) - if v, ok := s.nodeToInfo.Load(ourNode); ok && v != nil { - ni := v.(nodeInfo) - ni.stats = jStat.Stats - ni.cfg = jStat.Config - s.optsMu.RLock() - ni.tags = copyStrings(s.opts.Tags) - s.optsMu.RUnlock() - s.nodeToInfo.Store(ourNode, ni) - } - // Metagroup info. - if mg := js.getMetaGroup(); mg != nil { - if mg.Leader() { - if ci := s.raftNodeToClusterInfo(mg); ci != nil { - jStat.Meta = &MetaClusterInfo{ - Name: ci.Name, - Leader: ci.Leader, - Peer: getHash(ci.Leader), - Replicas: ci.Replicas, - Size: mg.ClusterSize(), - } - } - } else { - // non leader only include a shortened version without peers - leader := s.serverNameForNode(mg.GroupLeader()) - jStat.Meta = &MetaClusterInfo{ - Name: mg.Group(), - Leader: leader, - Peer: getHash(leader), - Size: mg.ClusterSize(), - } - } - if ipq := s.jsAPIRoutedReqs; ipq != nil && jStat.Meta != nil { - jStat.Meta.Pending = ipq.len() - } - } - jStat.Limits = &s.getOpts().JetStreamLimits - m.Stats.JetStream = jStat - s.mu.RLock() - } - // Send message. - s.sendInternalMsg(subj, _EMPTY_, &m.Server, &m) -} - -// Limit updates to the heartbeat interval, max one second by default. -func (s *Server) limitStatsz(subj string) bool { - s.mu.Lock() - defer s.mu.Unlock() - - if s.sys == nil { - return true - } - - // Only limit the normal broadcast subject. - if subj != fmt.Sprintf(serverStatsSubj, s.ID()) { - return false - } - - interval := statszRateLimit - if s.sys.cstatsz < interval { - interval = s.sys.cstatsz - } - if time.Since(s.sys.lastStatsz) < interval { - // Reschedule heartbeat for the next interval. - if s.sys.stmr != nil { - s.sys.stmr.Reset(time.Until(s.sys.lastStatsz.Add(interval))) - } - return true - } - s.sys.lastStatsz = time.Now() - return false -} - -// Send out our statz update. -// This should be wrapChk() to setup common locking. -func (s *Server) heartbeatStatsz() { - if s.sys.stmr != nil { - // Increase after startup to our max. - if s.sys.cstatsz < s.sys.statsz { - s.sys.cstatsz *= 2 - if s.sys.cstatsz > s.sys.statsz { - s.sys.cstatsz = s.sys.statsz - } - } - s.sys.stmr.Reset(s.sys.cstatsz) - } - // Do in separate Go routine. - go s.sendStatszUpdate() -} - -// Reset statsz rate limit for the next broadcast. -// This should be wrapChk() to setup common locking. -func (s *Server) resetLastStatsz() { - s.sys.lastStatsz = time.Time{} -} - -func (s *Server) sendStatszUpdate() { - s.sendStatsz(fmt.Sprintf(serverStatsSubj, s.ID())) -} - -// This should be wrapChk() to setup common locking. -func (s *Server) startStatszTimer() { - // We will start by sending out more of these and trail off to the statsz being the max. - s.sys.cstatsz = 250 * time.Millisecond - // Send out the first one quickly, we will slowly back off. - s.sys.stmr = time.AfterFunc(s.sys.cstatsz, s.wrapChk(s.heartbeatStatsz)) -} - -// Start a ticker that will fire periodically and check for orphaned servers. -// This should be wrapChk() to setup common locking. -func (s *Server) startRemoteServerSweepTimer() { - s.sys.sweeper = time.AfterFunc(s.sys.chkOrph, s.wrapChk(s.checkRemoteServers)) -} - -// Length of our system hash used for server targeted messages. -const sysHashLen = 8 - -// Computes a hash of 8 characters for the name. -func getHash(name string) string { - return getHashSize(name, sysHashLen) -} - -// Computes a hash for the given `name`. The result will be `size` characters long. -func getHashSize(name string, size int) string { - sha := sha256.New() - sha.Write([]byte(name)) - b := sha.Sum(nil) - for i := 0; i < size; i++ { - b[i] = digits[int(b[i]%base)] - } - return string(b[:size]) -} - -// Returns the node name for this server which is a hash of the server name. -func (s *Server) Node() string { - s.mu.RLock() - defer s.mu.RUnlock() - if s.sys != nil { - return s.sys.shash - } - return _EMPTY_ -} - -// This will setup our system wide tracking subs. -// For now we will setup one wildcard subscription to -// monitor all accounts for changes in number of connections. -// We can make this on a per account tracking basis if needed. -// Tradeoff is subscription and interest graph events vs connect and -// disconnect events, etc. -func (s *Server) initEventTracking() { - // Capture sys in case we are shutdown while setting up. - s.mu.RLock() - sys := s.sys - s.mu.RUnlock() - - if sys == nil || sys.client == nil || sys.account == nil { - return - } - // Create a system hash which we use for other servers to target us specifically. - sys.shash = getHash(s.info.Name) - - // This will be for all inbox responses. - subject := fmt.Sprintf(inboxRespSubj, sys.shash, "*") - if _, err := s.sysSubscribe(subject, s.inboxReply); err != nil { - s.Errorf("Error setting up internal tracking: %v", err) - return - } - sys.inboxPre = subject - // This is for remote updates for connection accounting. - subject = fmt.Sprintf(accConnsEventSubjOld, "*") - if _, err := s.sysSubscribe(subject, s.noInlineCallback(s.remoteConnsUpdate)); err != nil { - s.Errorf("Error setting up internal tracking for %s: %v", subject, err) - return - } - // This will be for responses for account info that we send out. - subject = fmt.Sprintf(connsRespSubj, s.info.ID) - if _, err := s.sysSubscribe(subject, s.noInlineCallback(s.remoteConnsUpdate)); err != nil { - s.Errorf("Error setting up internal tracking: %v", err) - return - } - // Listen for broad requests to respond with number of subscriptions for a given subject. - if _, err := s.sysSubscribe(accNumSubsReqSubj, s.noInlineCallback(s.nsubsRequest)); err != nil { - s.Errorf("Error setting up internal tracking: %v", err) - return - } - // Listen for statsz from others. - subject = fmt.Sprintf(serverStatsSubj, "*") - if sub, err := s.sysSubscribe(subject, s.noInlineCallback(s.remoteServerUpdate)); err != nil { - s.Errorf("Error setting up internal tracking: %v", err) - return - } else { - // Keep track of this one. - sys.remoteStatsSub = sub - } - - // Listen for all server shutdowns. - subject = fmt.Sprintf(shutdownEventSubj, "*") - if _, err := s.sysSubscribe(subject, s.noInlineCallback(s.remoteServerShutdown)); err != nil { - s.Errorf("Error setting up internal tracking: %v", err) - return - } - // Listen for servers entering lame-duck mode. - // NOTE: This currently is handled in the same way as a server shutdown, but has - // a different subject in case we need to handle differently in future. - subject = fmt.Sprintf(lameDuckEventSubj, "*") - if _, err := s.sysSubscribe(subject, s.noInlineCallback(s.remoteServerShutdown)); err != nil { - s.Errorf("Error setting up internal tracking: %v", err) - return - } - // Listen for account claims updates. - subscribeToUpdate := true - if s.accResolver != nil { - subscribeToUpdate = !s.accResolver.IsTrackingUpdate() - } - if subscribeToUpdate { - for _, sub := range []string{accUpdateEventSubjOld, accUpdateEventSubjNew} { - if _, err := s.sysSubscribe(fmt.Sprintf(sub, "*"), s.noInlineCallback(s.accountClaimUpdate)); err != nil { - s.Errorf("Error setting up internal tracking: %v", err) - return - } - } - } - // Listen for ping messages that will be sent to all servers for statsz. - // This subscription is kept for backwards compatibility. Got replaced by ...PING.STATZ from below - if _, err := s.sysSubscribe(serverStatsPingReqSubj, s.noInlineCallbackStatsz(s.statszReq)); err != nil { - s.Errorf("Error setting up internal tracking: %v", err) - return - } - monSrvc := map[string]sysMsgHandler{ - "IDZ": s.idzReq, - "STATSZ": s.statszReq, - "VARZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &VarzEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Varz(&optz.VarzOptions) }) - }, - "SUBSZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &SubszEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Subsz(&optz.SubszOptions) }) - }, - "CONNZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &ConnzEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Connz(&optz.ConnzOptions) }) - }, - "ROUTEZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &RoutezEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Routez(&optz.RoutezOptions) }) - }, - "GATEWAYZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &GatewayzEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Gatewayz(&optz.GatewayzOptions) }) - }, - "LEAFZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &LeafzEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Leafz(&optz.LeafzOptions) }) - }, - "ACCOUNTZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &AccountzEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Accountz(&optz.AccountzOptions) }) - }, - "JSZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &JszEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Jsz(&optz.JSzOptions) }) - }, - "HEALTHZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &HealthzEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.healthz(&optz.HealthzOptions), nil }) - }, - "PROFILEZ": nil, // Special case, see below - "EXPVARZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &ExpvarzEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.expvarz(optz), nil }) - }, - "IPQUEUESZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &IpqueueszEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Ipqueuesz(&optz.IpqueueszOptions), nil }) - }, - "RAFTZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &RaftzEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { return s.Raftz(&optz.RaftzOptions), nil }) - }, - } - profilez := func(_ *subscription, c *client, _ *Account, _, rply string, rmsg []byte) { - hdr, msg := c.msgParts(rmsg) - // Need to copy since we are passing those to the go routine below. - hdr, msg = copyBytes(hdr), copyBytes(msg) - // Execute in its own go routine because CPU profiling, for instance, - // could take several seconds to complete. - go func() { - optz := &ProfilezEventOptions{} - s.zReq(c, rply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { - return s.profilez(&optz.ProfilezOptions), nil - }) - }() - } - for name, req := range monSrvc { - var h msgHandler - switch name { - case "PROFILEZ": - h = profilez - case "STATSZ": - h = s.noInlineCallbackStatsz(req) - default: - h = s.noInlineCallback(req) - } - subject = fmt.Sprintf(serverDirectReqSubj, s.info.ID, name) - if _, err := s.sysSubscribe(subject, h); err != nil { - s.Errorf("Error setting up internal tracking: %v", err) - return - } - subject = fmt.Sprintf(serverPingReqSubj, name) - if _, err := s.sysSubscribe(subject, h); err != nil { - s.Errorf("Error setting up internal tracking: %v", err) - return - } - } - extractAccount := func(subject string) (string, error) { - if tk := strings.Split(subject, tsep); len(tk) != accReqTokens { - return _EMPTY_, fmt.Errorf("subject %q is malformed", subject) - } else { - return tk[accReqAccIndex], nil - } - } - monAccSrvc := map[string]sysMsgHandler{ - "SUBSZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &SubszEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { - if acc, err := extractAccount(subject); err != nil { - return nil, err - } else { - optz.SubszOptions.Subscriptions = true - optz.SubszOptions.Account = acc - return s.Subsz(&optz.SubszOptions) - } - }) - }, - "CONNZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &ConnzEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { - if acc, err := extractAccount(subject); err != nil { - return nil, err - } else { - optz.ConnzOptions.Account = acc - return s.Connz(&optz.ConnzOptions) - } - }) - }, - "LEAFZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &LeafzEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { - if acc, err := extractAccount(subject); err != nil { - return nil, err - } else { - optz.LeafzOptions.Account = acc - return s.Leafz(&optz.LeafzOptions) - } - }) - }, - "JSZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &JszEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { - if acc, err := extractAccount(subject); err != nil { - return nil, err - } else { - optz.Account = acc - return s.JszAccount(&optz.JSzOptions) - } - }) - }, - "INFO": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &AccInfoEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { - if acc, err := extractAccount(subject); err != nil { - return nil, err - } else { - return s.accountInfo(acc) - } - }) - }, - // STATZ is essentially a duplicate of CONNS with an envelope identical to the others. - // For historical reasons CONNS is the odd one out. - // STATZ is also less heavy weight than INFO - "STATZ": func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &AccountStatzEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { - if acc, err := extractAccount(subject); err != nil { - return nil, err - } else if acc == "PING" { // Filter PING subject. Happens for server as well. But wildcards are not used - return nil, errSkipZreq - } else { - optz.Accounts = []string{acc} - if stz, err := s.AccountStatz(&optz.AccountStatzOptions); err != nil { - return nil, err - } else if len(stz.Accounts) == 0 && !optz.IncludeUnused { - return nil, errSkipZreq - } else { - return stz, nil - } - } - }) - }, - "CONNS": s.connsRequest, - } - for name, req := range monAccSrvc { - if _, err := s.sysSubscribe(fmt.Sprintf(accDirectReqSubj, "*", name), s.noInlineCallback(req)); err != nil { - s.Errorf("Error setting up internal tracking: %v", err) - return - } - } - - // User info. - // TODO(dlc) - Can be internal and not forwarded since bound server for the client connection - // is only one that will answer. This breaks tests since we still forward on remote server connect. - if _, err := s.sysSubscribe(fmt.Sprintf(userDirectReqSubj, "*"), s.userInfoReq); err != nil { - s.Errorf("Error setting up internal tracking: %v", err) - return - } - - // For now only the STATZ subject has an account specific ping equivalent. - if _, err := s.sysSubscribe(fmt.Sprintf(accPingReqSubj, "STATZ"), - s.noInlineCallback(func(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - optz := &AccountStatzEventOptions{} - s.zReq(c, reply, hdr, msg, &optz.EventFilterOptions, optz, func() (any, error) { - if stz, err := s.AccountStatz(&optz.AccountStatzOptions); err != nil { - return nil, err - } else if len(stz.Accounts) == 0 && !optz.IncludeUnused { - return nil, errSkipZreq - } else { - return stz, nil - } - }) - })); err != nil { - s.Errorf("Error setting up internal tracking: %v", err) - return - } - - // Listen for updates when leaf nodes connect for a given account. This will - // force any gateway connections to move to `modeInterestOnly` - subject = fmt.Sprintf(leafNodeConnectEventSubj, "*") - if _, err := s.sysSubscribe(subject, s.noInlineCallback(s.leafNodeConnected)); err != nil { - s.Errorf("Error setting up internal tracking: %v", err) - return - } - // For tracking remote latency measurements. - subject = fmt.Sprintf(remoteLatencyEventSubj, sys.shash) - if _, err := s.sysSubscribe(subject, s.noInlineCallback(s.remoteLatencyUpdate)); err != nil { - s.Errorf("Error setting up internal latency tracking: %v", err) - return - } - // This is for simple debugging of number of subscribers that exist in the system. - if _, err := s.sysSubscribeInternal(accSubsSubj, s.noInlineCallback(s.debugSubscribers)); err != nil { - s.Errorf("Error setting up internal debug service for subscribers: %v", err) - return - } - - // Listen for requests to reload the server configuration. - subject = fmt.Sprintf(serverReloadReqSubj, s.info.ID) - if _, err := s.sysSubscribe(subject, s.noInlineCallback(s.reloadConfig)); err != nil { - s.Errorf("Error setting up server reload handler: %v", err) - return - } - - // Client connection kick - subject = fmt.Sprintf(clientKickReqSubj, s.info.ID) - if _, err := s.sysSubscribe(subject, s.noInlineCallback(s.kickClient)); err != nil { - s.Errorf("Error setting up client kick service: %v", err) - return - } - // Client connection LDM - subject = fmt.Sprintf(clientLDMReqSubj, s.info.ID) - if _, err := s.sysSubscribe(subject, s.noInlineCallback(s.ldmClient)); err != nil { - s.Errorf("Error setting up client LDM service: %v", err) - return - } -} - -// UserInfo returns basic information to a user about bound account and user permissions. -// For account information they will need to ping that separately, and this allows security -// controls on each subsystem if desired, e.g. account info, jetstream account info, etc. -type UserInfo struct { - UserID string `json:"user"` - Account string `json:"account"` - Permissions *Permissions `json:"permissions,omitempty"` - Expires time.Duration `json:"expires,omitempty"` -} - -// Process a user info request. -func (s *Server) userInfoReq(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) { - if !s.EventsEnabled() || reply == _EMPTY_ { - return - } - - response := &ServerAPIResponse{Server: &ServerInfo{}} - - ci, _, _, _, err := s.getRequestInfo(c, msg) - if err != nil { - response.Error = &ApiError{Code: http.StatusBadRequest} - s.sendInternalResponse(reply, response) - return - } - - response.Data = &UserInfo{ - UserID: ci.User, - Account: ci.Account, - Permissions: c.publicPermissions(), - Expires: c.claimExpiration(), - } - s.sendInternalResponse(reply, response) -} - -// register existing accounts with any system exports. -func (s *Server) registerSystemImportsForExisting() { - var accounts []*Account - - s.mu.RLock() - if s.sys == nil { - s.mu.RUnlock() - return - } - sacc := s.sys.account - s.accounts.Range(func(k, v any) bool { - a := v.(*Account) - if a != sacc { - accounts = append(accounts, a) - } - return true - }) - s.mu.RUnlock() - - for _, a := range accounts { - s.registerSystemImports(a) - } -} - -// add all exports a system account will need -func (s *Server) addSystemAccountExports(sacc *Account) { - if !s.EventsEnabled() { - return - } - accConnzSubj := fmt.Sprintf(accDirectReqSubj, "*", "CONNZ") - // prioritize not automatically added exports - if !sacc.hasServiceExportMatching(accConnzSubj) { - // pick export type that clamps importing account id into subject - if err := sacc.addServiceExportWithResponseAndAccountPos(accConnzSubj, Streamed, nil, 4); err != nil { - //if err := sacc.AddServiceExportWithResponse(accConnzSubj, Streamed, nil); err != nil { - s.Errorf("Error adding system service export for %q: %v", accConnzSubj, err) - } - } - // prioritize not automatically added exports - accStatzSubj := fmt.Sprintf(accDirectReqSubj, "*", "STATZ") - if !sacc.hasServiceExportMatching(accStatzSubj) { - // pick export type that clamps importing account id into subject - if err := sacc.addServiceExportWithResponseAndAccountPos(accStatzSubj, Streamed, nil, 4); err != nil { - s.Errorf("Error adding system service export for %q: %v", accStatzSubj, err) - } - } - // FIXME(dlc) - Old experiment, Remove? - if !sacc.hasServiceExportMatching(accSubsSubj) { - if err := sacc.AddServiceExport(accSubsSubj, nil); err != nil { - s.Errorf("Error adding system service export for %q: %v", accSubsSubj, err) - } - } - - // User info export. - userInfoSubj := fmt.Sprintf(userDirectReqSubj, "*") - if !sacc.hasServiceExportMatching(userInfoSubj) { - if err := sacc.AddServiceExport(userInfoSubj, nil); err != nil { - s.Errorf("Error adding system service export for %q: %v", userInfoSubj, err) - } - mappedSubj := fmt.Sprintf(userDirectReqSubj, sacc.GetName()) - if err := sacc.AddServiceImport(sacc, userDirectInfoSubj, mappedSubj); err != nil { - s.Errorf("Error setting up system service import %s: %v", mappedSubj, err) - } - // Make sure to share details. - sacc.setServiceImportSharing(sacc, mappedSubj, false, true) - } - - // Register any accounts that existed prior. - s.registerSystemImportsForExisting() - - // in case of a mixed mode setup, enable js exports anyway - if s.JetStreamEnabled() || !s.standAloneMode() { - s.checkJetStreamExports() - } -} - -// accountClaimUpdate will receive claim updates for accounts. -func (s *Server) accountClaimUpdate(sub *subscription, c *client, _ *Account, subject, resp string, hdr, msg []byte) { - if !s.EventsEnabled() { - return - } - var pubKey string - toks := strings.Split(subject, tsep) - if len(toks) == accUpdateTokensNew { - pubKey = toks[accReqAccIndex] - } else if len(toks) == accUpdateTokensOld { - pubKey = toks[accUpdateAccIdxOld] - } else { - s.Debugf("Received account claims update on bad subject %q", subject) - return - } - if len(msg) == 0 { - err := errors.New("request body is empty") - respondToUpdate(s, resp, pubKey, "jwt update error", err) - } else if claim, err := jwt.DecodeAccountClaims(string(msg)); err != nil { - respondToUpdate(s, resp, pubKey, "jwt update resulted in error", err) - } else if claim.Subject != pubKey { - err := errors.New("subject does not match jwt content") - respondToUpdate(s, resp, pubKey, "jwt update resulted in error", err) - } else if v, ok := s.accounts.Load(pubKey); !ok { - respondToUpdate(s, resp, pubKey, "jwt update skipped", nil) - } else if err := s.updateAccountWithClaimJWT(v.(*Account), string(msg)); err != nil { - respondToUpdate(s, resp, pubKey, "jwt update resulted in error", err) - } else { - respondToUpdate(s, resp, pubKey, "jwt updated", nil) - } -} - -// processRemoteServerShutdown will update any affected accounts. -// Will update the remote count for clients. -// Lock assume held. -func (s *Server) processRemoteServerShutdown(sid string) { - s.accounts.Range(func(k, v any) bool { - v.(*Account).removeRemoteServer(sid) - return true - }) - // Update any state in nodeInfo. - s.nodeToInfo.Range(func(k, v any) bool { - ni := v.(nodeInfo) - if ni.id == sid { - ni.offline = true - s.nodeToInfo.Store(k, ni) - return false - } - return true - }) - delete(s.sys.servers, sid) -} - -func (s *Server) sameDomain(domain string) bool { - return domain == _EMPTY_ || s.info.Domain == _EMPTY_ || domain == s.info.Domain -} - -// remoteServerShutdown is called when we get an event from another server shutting down. -func (s *Server) remoteServerShutdown(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - s.mu.Lock() - defer s.mu.Unlock() - if !s.eventsEnabled() { - return - } - toks := strings.Split(subject, tsep) - if len(toks) < shutdownEventTokens { - s.Debugf("Received remote server shutdown on bad subject %q", subject) - return - } - - if len(msg) == 0 { - s.Errorf("Remote server sent invalid (empty) shutdown message to %q", subject) - return - } - - // We have an optional serverInfo here, remove from nodeToX lookups. - var si ServerInfo - if err := json.Unmarshal(msg, &si); err != nil { - s.Debugf("Received bad server info for remote server shutdown") - return - } - - // JetStream node updates if applicable. - node := getHash(si.Name) - if v, ok := s.nodeToInfo.Load(node); ok && v != nil { - ni := v.(nodeInfo) - ni.offline = true - s.nodeToInfo.Store(node, ni) - } - - sid := toks[serverSubjectIndex] - if su := s.sys.servers[sid]; su != nil { - s.processRemoteServerShutdown(sid) - } -} - -// remoteServerUpdate listens for statsz updates from other servers. -func (s *Server) remoteServerUpdate(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - var ssm ServerStatsMsg - if len(msg) == 0 { - s.Debugf("Received empty server info for remote server update") - return - } else if err := json.Unmarshal(msg, &ssm); err != nil { - s.Debugf("Received bad server info for remote server update") - return - } - si := ssm.Server - - // Should do normal updates before bailing if wrong domain. - s.mu.Lock() - if s.isRunning() && s.eventsEnabled() && ssm.Server.ID != s.info.ID { - s.updateRemoteServer(&si) - } - s.mu.Unlock() - - // JetStream node updates. - if !s.sameDomain(si.Domain) { - return - } - - var cfg *JetStreamConfig - var stats *JetStreamStats - - if ssm.Stats.JetStream != nil { - cfg = ssm.Stats.JetStream.Config - stats = ssm.Stats.JetStream.Stats - } - - node := getHash(si.Name) - accountNRG := si.AccountNRG() - oldInfo, _ := s.nodeToInfo.Swap(node, nodeInfo{ - si.Name, - si.Version, - si.Cluster, - si.Domain, - si.ID, - si.Tags, - cfg, - stats, - false, - si.JetStreamEnabled(), - si.BinaryStreamSnapshot(), - accountNRG, - }) - if oldInfo == nil || accountNRG != oldInfo.(nodeInfo).accountNRG { - // One of the servers we received statsz from changed its mind about - // whether or not it supports in-account NRG, so update the groups - // with this information. - s.updateNRGAccountStatus() - } -} - -// updateRemoteServer is called when we have an update from a remote server. -// This allows us to track remote servers, respond to shutdown messages properly, -// make sure that messages are ordered, and allow us to prune dead servers. -// Lock should be held upon entry. -func (s *Server) updateRemoteServer(si *ServerInfo) { - su := s.sys.servers[si.ID] - if su == nil { - s.sys.servers[si.ID] = &serverUpdate{si.Seq, time.Now()} - s.processNewServer(si) - } else { - // Should always be going up. - if si.Seq <= su.seq { - s.Errorf("Received out of order remote server update from: %q", si.ID) - return - } - su.seq = si.Seq - su.ltime = time.Now() - } -} - -// processNewServer will hold any logic we want to use when we discover a new server. -// Lock should be held upon entry. -func (s *Server) processNewServer(si *ServerInfo) { - // Right now we only check if we have leafnode servers and if so send another - // connect update to make sure they switch this account to interest only mode. - s.ensureGWsInterestOnlyForLeafNodes() - - // Add to our nodeToName - if s.sameDomain(si.Domain) { - node := getHash(si.Name) - // Only update if non-existent - if _, ok := s.nodeToInfo.Load(node); !ok { - s.nodeToInfo.Store(node, nodeInfo{ - si.Name, - si.Version, - si.Cluster, - si.Domain, - si.ID, - si.Tags, - nil, - nil, - false, - si.JetStreamEnabled(), - si.BinaryStreamSnapshot(), - si.AccountNRG(), - }) - } - } - go s.updateNRGAccountStatus() - // Announce ourselves.. - // Do this in a separate Go routine. - go s.sendStatszUpdate() -} - -// Works out whether all nodes support moving the NRG traffic into -// the account and moves it appropriately. -// Server lock MUST NOT be held on entry. -func (s *Server) updateNRGAccountStatus() { - s.rnMu.RLock() - raftNodes := make([]RaftNode, 0, len(s.raftNodes)) - for _, n := range s.raftNodes { - raftNodes = append(raftNodes, n) - } - s.rnMu.RUnlock() - for _, n := range raftNodes { - // In the event that the node is happy that all nodes that - // it cares about haven't changed, this will be a no-op. - if err := n.RecreateInternalSubs(); err != nil { - n.Stop() - } - } -} - -// If GW is enabled on this server and there are any leaf node connections, -// this function will send a LeafNode connect system event to the super cluster -// to ensure that the GWs are in interest-only mode for this account. -// Lock should be held upon entry. -// TODO(dlc) - this will cause this account to be loaded on all servers. Need a better -// way with GW2. -func (s *Server) ensureGWsInterestOnlyForLeafNodes() { - if !s.gateway.enabled || len(s.leafs) == 0 { - return - } - sent := make(map[*Account]bool, len(s.leafs)) - for _, c := range s.leafs { - if !sent[c.acc] { - s.sendLeafNodeConnectMsg(c.acc.Name) - sent[c.acc] = true - } - } -} - -// shutdownEventing will clean up all eventing state. -func (s *Server) shutdownEventing() { - if !s.eventsRunning() { - return - } - - s.mu.Lock() - clearTimer(&s.sys.sweeper) - clearTimer(&s.sys.stmr) - rc := s.sys.resetCh - s.sys.resetCh = nil - wg := &s.sys.wg - s.mu.Unlock() - - // We will queue up a shutdown event and wait for the - // internal send loop to exit. - s.sendShutdownEvent() - wg.Wait() - - s.mu.Lock() - defer s.mu.Unlock() - - // Whip through all accounts. - s.accounts.Range(func(k, v any) bool { - v.(*Account).clearEventing() - return true - }) - // Turn everything off here. - s.sys = nil - // Make sure this is done after s.sys = nil, so that we don't - // get sends to closed channels on badly-timed config reloads. - close(rc) -} - -// Request for our local connection count. -func (s *Server) connsRequest(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - if !s.eventsRunning() { - return - } - tk := strings.Split(subject, tsep) - if len(tk) != accReqTokens { - s.sys.client.Errorf("Bad subject account connections request message") - return - } - a := tk[accReqAccIndex] - m := accNumConnsReq{Account: a} - if len(msg) > 0 { - if err := json.Unmarshal(msg, &m); err != nil { - s.sys.client.Errorf("Error unmarshalling account connections request message: %v", err) - return - } - } - if m.Account != a { - s.sys.client.Errorf("Error unmarshalled account does not match subject") - return - } - // Here we really only want to lookup the account if its local. We do not want to fetch this - // account if we have no interest in it. - var acc *Account - if v, ok := s.accounts.Load(m.Account); ok { - acc = v.(*Account) - } - if acc == nil { - return - } - // We know this is a local connection. - if nlc := acc.NumLocalConnections(); nlc > 0 { - s.mu.Lock() - s.sendAccConnsUpdate(acc, reply) - s.mu.Unlock() - } -} - -// leafNodeConnected is an event we will receive when a leaf node for a given account connects. -func (s *Server) leafNodeConnected(sub *subscription, _ *client, _ *Account, subject, reply string, hdr, msg []byte) { - m := accNumConnsReq{} - if err := json.Unmarshal(msg, &m); err != nil { - s.sys.client.Errorf("Error unmarshalling account connections request message: %v", err) - return - } - - s.mu.RLock() - na := m.Account == _EMPTY_ || !s.eventsEnabled() || !s.gateway.enabled - s.mu.RUnlock() - - if na { - return - } - - if acc, _ := s.lookupAccount(m.Account); acc != nil { - s.switchAccountToInterestMode(acc.Name) - } -} - -// Common filter options for system requests STATSZ VARZ SUBSZ CONNZ ROUTEZ GATEWAYZ LEAFZ -type EventFilterOptions struct { - Name string `json:"server_name,omitempty"` // filter by server name - Cluster string `json:"cluster,omitempty"` // filter by cluster name - Host string `json:"host,omitempty"` // filter by host name - Tags []string `json:"tags,omitempty"` // filter by tags (must match all tags) - Domain string `json:"domain,omitempty"` // filter by JS domain -} - -// StatszEventOptions are options passed to Statsz -type StatszEventOptions struct { - // No actual options yet - EventFilterOptions -} - -// Options for account Info -type AccInfoEventOptions struct { - // No actual options yet - EventFilterOptions -} - -// In the context of system events, ConnzEventOptions are options passed to Connz -type ConnzEventOptions struct { - ConnzOptions - EventFilterOptions -} - -// In the context of system events, RoutezEventOptions are options passed to Routez -type RoutezEventOptions struct { - RoutezOptions - EventFilterOptions -} - -// In the context of system events, SubzEventOptions are options passed to Subz -type SubszEventOptions struct { - SubszOptions - EventFilterOptions -} - -// In the context of system events, VarzEventOptions are options passed to Varz -type VarzEventOptions struct { - VarzOptions - EventFilterOptions -} - -// In the context of system events, GatewayzEventOptions are options passed to Gatewayz -type GatewayzEventOptions struct { - GatewayzOptions - EventFilterOptions -} - -// In the context of system events, LeafzEventOptions are options passed to Leafz -type LeafzEventOptions struct { - LeafzOptions - EventFilterOptions -} - -// In the context of system events, AccountzEventOptions are options passed to Accountz -type AccountzEventOptions struct { - AccountzOptions - EventFilterOptions -} - -// In the context of system events, AccountzEventOptions are options passed to Accountz -type AccountStatzEventOptions struct { - AccountStatzOptions - EventFilterOptions -} - -// In the context of system events, JszEventOptions are options passed to Jsz -type JszEventOptions struct { - JSzOptions - EventFilterOptions -} - -// In the context of system events, HealthzEventOptions are options passed to Healthz -type HealthzEventOptions struct { - HealthzOptions - EventFilterOptions -} - -// In the context of system events, ProfilezEventOptions are options passed to Profilez -type ProfilezEventOptions struct { - ProfilezOptions - EventFilterOptions -} - -// In the context of system events, ExpvarzEventOptions are options passed to Expvarz -type ExpvarzEventOptions struct { - EventFilterOptions -} - -// In the context of system events, IpqueueszEventOptions are options passed to Ipqueuesz -type IpqueueszEventOptions struct { - EventFilterOptions - IpqueueszOptions -} - -// In the context of system events, RaftzEventOptions are options passed to Raftz -type RaftzEventOptions struct { - EventFilterOptions - RaftzOptions -} - -// returns true if the request does NOT apply to this server and can be ignored. -// DO NOT hold the server lock when -func (s *Server) filterRequest(fOpts *EventFilterOptions) bool { - if fOpts.Name != _EMPTY_ && !strings.Contains(s.info.Name, fOpts.Name) { - return true - } - if fOpts.Host != _EMPTY_ && !strings.Contains(s.info.Host, fOpts.Host) { - return true - } - if fOpts.Cluster != _EMPTY_ { - if !strings.Contains(s.ClusterName(), fOpts.Cluster) { - return true - } - } - if len(fOpts.Tags) > 0 { - opts := s.getOpts() - for _, t := range fOpts.Tags { - if !opts.Tags.Contains(t) { - return true - } - } - } - if fOpts.Domain != _EMPTY_ && s.getOpts().JetStreamDomain != fOpts.Domain { - return true - } - return false -} - -// Encoding support (compression) -type compressionType int8 - -const ( - noCompression = compressionType(iota) - gzipCompression - snappyCompression - unsupportedCompression -) - -// ServerAPIResponse is the response type for the server API like varz, connz etc. -type ServerAPIResponse struct { - Server *ServerInfo `json:"server"` - Data any `json:"data,omitempty"` - Error *ApiError `json:"error,omitempty"` - - // Private to indicate compression if any. - compress compressionType -} - -// Specialized response types for unmarshalling. These structures are not -// used in the server code and only there for users of the Z endpoints to -// unmarshal the data without having to create these structs in their code - -// ServerAPIConnzResponse is the response type connz -type ServerAPIConnzResponse struct { - Server *ServerInfo `json:"server"` - Data *Connz `json:"data,omitempty"` - Error *ApiError `json:"error,omitempty"` -} - -// ServerAPIRoutezResponse is the response type for routez -type ServerAPIRoutezResponse struct { - Server *ServerInfo `json:"server"` - Data *Routez `json:"data,omitempty"` - Error *ApiError `json:"error,omitempty"` -} - -// ServerAPIGatewayzResponse is the response type for gatewayz -type ServerAPIGatewayzResponse struct { - Server *ServerInfo `json:"server"` - Data *Gatewayz `json:"data,omitempty"` - Error *ApiError `json:"error,omitempty"` -} - -// ServerAPIJszResponse is the response type for jsz -type ServerAPIJszResponse struct { - Server *ServerInfo `json:"server"` - Data *JSInfo `json:"data,omitempty"` - Error *ApiError `json:"error,omitempty"` -} - -// ServerAPIHealthzResponse is the response type for healthz -type ServerAPIHealthzResponse struct { - Server *ServerInfo `json:"server"` - Data *HealthStatus `json:"data,omitempty"` - Error *ApiError `json:"error,omitempty"` -} - -// ServerAPIVarzResponse is the response type for varz -type ServerAPIVarzResponse struct { - Server *ServerInfo `json:"server"` - Data *Varz `json:"data,omitempty"` - Error *ApiError `json:"error,omitempty"` -} - -// ServerAPISubszResponse is the response type for subsz -type ServerAPISubszResponse struct { - Server *ServerInfo `json:"server"` - Data *Subsz `json:"data,omitempty"` - Error *ApiError `json:"error,omitempty"` -} - -// ServerAPILeafzResponse is the response type for leafz -type ServerAPILeafzResponse struct { - Server *ServerInfo `json:"server"` - Data *Leafz `json:"data,omitempty"` - Error *ApiError `json:"error,omitempty"` -} - -// ServerAPIAccountzResponse is the response type for accountz -type ServerAPIAccountzResponse struct { - Server *ServerInfo `json:"server"` - Data *Accountz `json:"data,omitempty"` - Error *ApiError `json:"error,omitempty"` -} - -// ServerAPIExpvarzResponse is the response type for expvarz -type ServerAPIExpvarzResponse struct { - Server *ServerInfo `json:"server"` - Data *ExpvarzStatus `json:"data,omitempty"` - Error *ApiError `json:"error,omitempty"` -} - -// ServerAPIpqueueszResponse is the response type for ipqueuesz -type ServerAPIpqueueszResponse struct { - Server *ServerInfo `json:"server"` - Data *IpqueueszStatus `json:"data,omitempty"` - Error *ApiError `json:"error,omitempty"` -} - -// ServerAPIRaftzResponse is the response type for raftz -type ServerAPIRaftzResponse struct { - Server *ServerInfo `json:"server"` - Data *RaftzStatus `json:"data,omitempty"` - Error *ApiError `json:"error,omitempty"` -} - -// statszReq is a request for us to respond with current statsz. -func (s *Server) statszReq(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - if !s.EventsEnabled() { - return - } - - // No reply is a signal that we should use our normal broadcast subject. - if reply == _EMPTY_ { - reply = fmt.Sprintf(serverStatsSubj, s.info.ID) - s.wrapChk(s.resetLastStatsz) - } - - opts := StatszEventOptions{} - if len(msg) != 0 { - if err := json.Unmarshal(msg, &opts); err != nil { - response := &ServerAPIResponse{ - Server: &ServerInfo{}, - Error: &ApiError{Code: http.StatusBadRequest, Description: err.Error()}, - } - s.sendInternalMsgLocked(reply, _EMPTY_, response.Server, response) - return - } else if ignore := s.filterRequest(&opts.EventFilterOptions); ignore { - return - } - } - s.sendStatsz(reply) -} - -// idzReq is for a request for basic static server info. -// Try to not hold the write lock or dynamically create data. -func (s *Server) idzReq(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - s.mu.RLock() - defer s.mu.RUnlock() - id := &ServerID{ - Name: s.info.Name, - Host: s.info.Host, - ID: s.info.ID, - } - s.sendInternalMsg(reply, _EMPTY_, nil, &id) -} - -var errSkipZreq = errors.New("filtered response") - -const ( - acceptEncodingHeader = "Accept-Encoding" - contentEncodingHeader = "Content-Encoding" -) - -// This is not as formal as it could be. We see if anything has s2 or snappy first, then gzip. -func getAcceptEncoding(hdr []byte) compressionType { - ae := strings.ToLower(string(getHeader(acceptEncodingHeader, hdr))) - if ae == _EMPTY_ { - return noCompression - } - if strings.Contains(ae, "snappy") || strings.Contains(ae, "s2") { - return snappyCompression - } - if strings.Contains(ae, "gzip") { - return gzipCompression - } - return unsupportedCompression -} - -func (s *Server) zReq(_ *client, reply string, hdr, msg []byte, fOpts *EventFilterOptions, optz any, respf func() (any, error)) { - if !s.EventsEnabled() || reply == _EMPTY_ { - return - } - response := &ServerAPIResponse{Server: &ServerInfo{}} - var err error - status := 0 - if len(msg) != 0 { - if err = json.Unmarshal(msg, optz); err != nil { - status = http.StatusBadRequest // status is only included on error, so record how far execution got - } else if s.filterRequest(fOpts) { - return - } - } - if err == nil { - response.Data, err = respf() - if errors.Is(err, errSkipZreq) { - return - } else if err != nil { - status = http.StatusInternalServerError - } - } - if err != nil { - response.Error = &ApiError{Code: status, Description: err.Error()} - } else if len(hdr) > 0 { - response.compress = getAcceptEncoding(hdr) - } - s.sendInternalResponse(reply, response) -} - -// remoteConnsUpdate gets called when we receive a remote update from another server. -func (s *Server) remoteConnsUpdate(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - if !s.eventsRunning() { - return - } - var m AccountNumConns - if len(msg) == 0 { - s.sys.client.Errorf("No message body provided") - return - } else if err := json.Unmarshal(msg, &m); err != nil { - s.sys.client.Errorf("Error unmarshalling account connection event message: %v", err) - return - } - - // See if we have the account registered, if not drop it. - // Make sure this does not force us to load this account here. - var acc *Account - if v, ok := s.accounts.Load(m.Account); ok { - acc = v.(*Account) - } - // Silently ignore these if we do not have local interest in the account. - if acc == nil { - return - } - - s.mu.Lock() - - // check again here if we have been shutdown. - if !s.isRunning() || !s.eventsEnabled() { - s.mu.Unlock() - return - } - // Double check that this is not us, should never happen, so error if it does. - if m.Server.ID == s.info.ID { - s.sys.client.Errorf("Processing our own account connection event message: ignored") - s.mu.Unlock() - return - } - // If we are here we have interest in tracking this account. Update our accounting. - clients := acc.updateRemoteServer(&m) - s.updateRemoteServer(&m.Server) - s.mu.Unlock() - // Need to close clients outside of server lock - for _, c := range clients { - c.maxAccountConnExceeded() - } -} - -// This will import any system level exports. -func (s *Server) registerSystemImports(a *Account) { - if a == nil || !s.EventsEnabled() { - return - } - sacc := s.SystemAccount() - if sacc == nil || sacc == a { - return - } - // FIXME(dlc) - make a shared list between sys exports etc. - - importSrvc := func(subj, mappedSubj string) { - if !a.serviceImportExists(subj) { - if err := a.addServiceImportWithClaim(sacc, subj, mappedSubj, nil, true); err != nil { - s.Errorf("Error setting up system service import %s -> %s for account: %v", - subj, mappedSubj, err) - } - } - } - // Add in this to the account in 2 places. - // "$SYS.REQ.SERVER.PING.CONNZ" and "$SYS.REQ.ACCOUNT.PING.CONNZ" - mappedConnzSubj := fmt.Sprintf(accDirectReqSubj, a.Name, "CONNZ") - importSrvc(fmt.Sprintf(accPingReqSubj, "CONNZ"), mappedConnzSubj) - importSrvc(fmt.Sprintf(serverPingReqSubj, "CONNZ"), mappedConnzSubj) - importSrvc(fmt.Sprintf(accPingReqSubj, "STATZ"), fmt.Sprintf(accDirectReqSubj, a.Name, "STATZ")) - - // This is for user's looking up their own info. - mappedSubject := fmt.Sprintf(userDirectReqSubj, a.Name) - importSrvc(userDirectInfoSubj, mappedSubject) - // Make sure to share details. - a.setServiceImportSharing(sacc, mappedSubject, false, true) -} - -// Setup tracking for this account. This allows us to track global account activity. -// Lock should be held on entry. -func (s *Server) enableAccountTracking(a *Account) { - if a == nil || !s.eventsEnabled() { - return - } - - // TODO(ik): Generate payload although message may not be sent. - // May need to ensure we do so only if there is a known interest. - // This can get complicated with gateways. - - subj := fmt.Sprintf(accDirectReqSubj, a.Name, "CONNS") - reply := fmt.Sprintf(connsRespSubj, s.info.ID) - m := accNumConnsReq{Account: a.Name} - s.sendInternalMsg(subj, reply, &m.Server, &m) -} - -// Event on leaf node connect. -// Lock should NOT be held on entry. -func (s *Server) sendLeafNodeConnect(a *Account) { - s.mu.Lock() - // If we are not in operator mode, or do not have any gateways defined, this should also be a no-op. - if a == nil || !s.eventsEnabled() || !s.gateway.enabled { - s.mu.Unlock() - return - } - s.sendLeafNodeConnectMsg(a.Name) - s.mu.Unlock() - - s.switchAccountToInterestMode(a.Name) -} - -// Send the leafnode connect message. -// Lock should be held. -func (s *Server) sendLeafNodeConnectMsg(accName string) { - subj := fmt.Sprintf(leafNodeConnectEventSubj, accName) - m := accNumConnsReq{Account: accName} - s.sendInternalMsg(subj, _EMPTY_, &m.Server, &m) -} - -// sendAccConnsUpdate is called to send out our information on the -// account's local connections. -// Lock should be held on entry. -func (s *Server) sendAccConnsUpdate(a *Account, subj ...string) { - if !s.eventsEnabled() || a == nil { - return - } - sendQ := s.sys.sendq - if sendQ == nil { - return - } - // Build event with account name and number of local clients and leafnodes. - eid := s.nextEventID() - a.mu.Lock() - stat := a.statz() - m := AccountNumConns{ - TypedEvent: TypedEvent{ - Type: AccountNumConnsMsgType, - ID: eid, - Time: time.Now().UTC(), - }, - AccountStat: *stat, - } - // Set timer to fire again unless we are at zero. - if m.TotalConns == 0 { - clearTimer(&a.ctmr) - } else { - // Check to see if we have an HB running and update. - if a.ctmr == nil { - a.ctmr = time.AfterFunc(eventsHBInterval, func() { s.accConnsUpdate(a) }) - } else { - a.ctmr.Reset(eventsHBInterval) - } - } - for _, sub := range subj { - msg := newPubMsg(nil, sub, _EMPTY_, &m.Server, nil, &m, noCompression, false, false) - sendQ.push(msg) - } - a.mu.Unlock() -} - -// Lock should be held on entry. -func (a *Account) statz() *AccountStat { - localConns := a.numLocalConnections() - leafConns := a.numLocalLeafNodes() - return &AccountStat{ - Account: a.Name, - Name: a.getNameTagLocked(), - Conns: localConns, - LeafNodes: leafConns, - TotalConns: localConns + leafConns, - NumSubs: a.sl.Count(), - Received: DataStats{ - Msgs: atomic.LoadInt64(&a.inMsgs), - Bytes: atomic.LoadInt64(&a.inBytes), - }, - Sent: DataStats{ - Msgs: atomic.LoadInt64(&a.outMsgs), - Bytes: atomic.LoadInt64(&a.outBytes), - }, - SlowConsumers: atomic.LoadInt64(&a.slowConsumers), - } -} - -// accConnsUpdate is called whenever there is a change to the account's -// number of active connections, or during a heartbeat. -// We will not send for $G. -func (s *Server) accConnsUpdate(a *Account) { - s.mu.Lock() - defer s.mu.Unlock() - if !s.eventsEnabled() || a == nil || a == s.gacc { - return - } - s.sendAccConnsUpdate(a, fmt.Sprintf(accConnsEventSubjOld, a.Name), fmt.Sprintf(accConnsEventSubjNew, a.Name)) -} - -// server lock should be held -func (s *Server) nextEventID() string { - return s.eventIds.Next() -} - -// accountConnectEvent will send an account client connect event if there is interest. -// This is a billing event. -func (s *Server) accountConnectEvent(c *client) { - s.mu.Lock() - if !s.eventsEnabled() { - s.mu.Unlock() - return - } - gacc := s.gacc - eid := s.nextEventID() - s.mu.Unlock() - - c.mu.Lock() - // Ignore global account activity - if c.acc == nil || c.acc == gacc { - c.mu.Unlock() - return - } - - m := ConnectEventMsg{ - TypedEvent: TypedEvent{ - Type: ConnectEventMsgType, - ID: eid, - Time: time.Now().UTC(), - }, - Client: ClientInfo{ - Start: &c.start, - Host: c.host, - ID: c.cid, - Account: accForClient(c), - User: c.getRawAuthUser(), - Name: c.opts.Name, - Lang: c.opts.Lang, - Version: c.opts.Version, - Jwt: c.opts.JWT, - IssuerKey: issuerForClient(c), - Tags: c.tags, - NameTag: c.acc.getNameTag(), - Kind: c.kindString(), - ClientType: c.clientTypeString(), - MQTTClient: c.getMQTTClientID(), - }, - } - subj := fmt.Sprintf(connectEventSubj, c.acc.Name) - c.mu.Unlock() - - s.sendInternalMsgLocked(subj, _EMPTY_, &m.Server, &m) -} - -// accountDisconnectEvent will send an account client disconnect event if there is interest. -// This is a billing event. -func (s *Server) accountDisconnectEvent(c *client, now time.Time, reason string) { - s.mu.Lock() - if !s.eventsEnabled() { - s.mu.Unlock() - return - } - gacc := s.gacc - eid := s.nextEventID() - s.mu.Unlock() - - c.mu.Lock() - - // Ignore global account activity - if c.acc == nil || c.acc == gacc { - c.mu.Unlock() - return - } - - m := DisconnectEventMsg{ - TypedEvent: TypedEvent{ - Type: DisconnectEventMsgType, - ID: eid, - Time: now, - }, - Client: ClientInfo{ - Start: &c.start, - Stop: &now, - Host: c.host, - ID: c.cid, - Account: accForClient(c), - User: c.getRawAuthUser(), - Name: c.opts.Name, - Lang: c.opts.Lang, - Version: c.opts.Version, - RTT: c.getRTT(), - Jwt: c.opts.JWT, - IssuerKey: issuerForClient(c), - Tags: c.tags, - NameTag: c.acc.getNameTag(), - Kind: c.kindString(), - ClientType: c.clientTypeString(), - MQTTClient: c.getMQTTClientID(), - }, - Sent: DataStats{ - Msgs: atomic.LoadInt64(&c.inMsgs), - Bytes: atomic.LoadInt64(&c.inBytes), - }, - Received: DataStats{ - Msgs: c.outMsgs, - Bytes: c.outBytes, - }, - Reason: reason, - } - accName := c.acc.Name - c.mu.Unlock() - - subj := fmt.Sprintf(disconnectEventSubj, accName) - s.sendInternalMsgLocked(subj, _EMPTY_, &m.Server, &m) -} - -// This is the system level event sent to the system account for operators. -func (s *Server) sendAuthErrorEvent(c *client) { - s.mu.Lock() - if !s.eventsEnabled() { - s.mu.Unlock() - return - } - eid := s.nextEventID() - s.mu.Unlock() - - now := time.Now().UTC() - c.mu.Lock() - m := DisconnectEventMsg{ - TypedEvent: TypedEvent{ - Type: DisconnectEventMsgType, - ID: eid, - Time: now, - }, - Client: ClientInfo{ - Start: &c.start, - Stop: &now, - Host: c.host, - ID: c.cid, - Account: accForClient(c), - User: c.getRawAuthUser(), - Name: c.opts.Name, - Lang: c.opts.Lang, - Version: c.opts.Version, - RTT: c.getRTT(), - Jwt: c.opts.JWT, - IssuerKey: issuerForClient(c), - Tags: c.tags, - NameTag: c.acc.getNameTag(), - Kind: c.kindString(), - ClientType: c.clientTypeString(), - MQTTClient: c.getMQTTClientID(), - }, - Sent: DataStats{ - Msgs: c.inMsgs, - Bytes: c.inBytes, - }, - Received: DataStats{ - Msgs: c.outMsgs, - Bytes: c.outBytes, - }, - Reason: AuthenticationViolation.String(), - } - c.mu.Unlock() - - s.mu.Lock() - subj := fmt.Sprintf(authErrorEventSubj, s.info.ID) - s.sendInternalMsg(subj, _EMPTY_, &m.Server, &m) - s.mu.Unlock() -} - -// This is the account level event sent to the origin account for account owners. -func (s *Server) sendAccountAuthErrorEvent(c *client, acc *Account, reason string) { - if acc == nil { - return - } - s.mu.Lock() - if !s.eventsEnabled() { - s.mu.Unlock() - return - } - eid := s.nextEventID() - s.mu.Unlock() - - now := time.Now().UTC() - c.mu.Lock() - m := DisconnectEventMsg{ - TypedEvent: TypedEvent{ - Type: DisconnectEventMsgType, - ID: eid, - Time: now, - }, - Client: ClientInfo{ - Start: &c.start, - Stop: &now, - Host: c.host, - ID: c.cid, - Account: acc.Name, - User: c.getRawAuthUser(), - Name: c.opts.Name, - Lang: c.opts.Lang, - Version: c.opts.Version, - RTT: c.getRTT(), - Jwt: c.opts.JWT, - IssuerKey: issuerForClient(c), - Tags: c.tags, - NameTag: c.acc.getNameTag(), - Kind: c.kindString(), - ClientType: c.clientTypeString(), - MQTTClient: c.getMQTTClientID(), - }, - Sent: DataStats{ - Msgs: c.inMsgs, - Bytes: c.inBytes, - }, - Received: DataStats{ - Msgs: c.outMsgs, - Bytes: c.outBytes, - }, - Reason: reason, - } - c.mu.Unlock() - - s.sendInternalAccountSysMsg(acc, authErrorAccountEventSubj, &m.Server, &m, noCompression) -} - -// Internal message callback. -// If the msg is needed past the callback it is required to be copied. -// rmsg contains header and the message. use client.msgParts(rmsg) to split them apart -type msgHandler func(sub *subscription, client *client, acc *Account, subject, reply string, rmsg []byte) - -const ( - recvQMuxed = 1 - recvQStatsz = 2 -) - -// Create a wrapped callback handler for the subscription that will move it to an -// internal recvQ for processing not inline with routes etc. -func (s *Server) noInlineCallback(cb sysMsgHandler) msgHandler { - return s.noInlineCallbackRecvQSelect(cb, recvQMuxed) -} - -// Create a wrapped callback handler for the subscription that will move it to an -// internal recvQ for Statsz/Pings for processing not inline with routes etc. -func (s *Server) noInlineCallbackStatsz(cb sysMsgHandler) msgHandler { - return s.noInlineCallbackRecvQSelect(cb, recvQStatsz) -} - -// Create a wrapped callback handler for the subscription that will move it to an -// internal IPQueue for processing not inline with routes etc. -func (s *Server) noInlineCallbackRecvQSelect(cb sysMsgHandler, recvQSelect int) msgHandler { - s.mu.RLock() - if !s.eventsEnabled() { - s.mu.RUnlock() - return nil - } - // Capture here for direct reference to avoid any unnecessary blocking inline with routes, gateways etc. - var recvq *ipQueue[*inSysMsg] - switch recvQSelect { - case recvQStatsz: - recvq = s.sys.recvqp - default: - recvq = s.sys.recvq - } - s.mu.RUnlock() - - return func(sub *subscription, c *client, acc *Account, subj, rply string, rmsg []byte) { - // Need to copy and split here. - hdr, msg := c.msgParts(rmsg) - recvq.push(&inSysMsg{sub, c, acc, subj, rply, copyBytes(hdr), copyBytes(msg), cb}) - } -} - -// Create an internal subscription. sysSubscribeQ for queue groups. -func (s *Server) sysSubscribe(subject string, cb msgHandler) (*subscription, error) { - return s.systemSubscribe(subject, _EMPTY_, false, nil, cb) -} - -// Create an internal subscription with queue -func (s *Server) sysSubscribeQ(subject, queue string, cb msgHandler) (*subscription, error) { - return s.systemSubscribe(subject, queue, false, nil, cb) -} - -// Create an internal subscription but do not forward interest. -func (s *Server) sysSubscribeInternal(subject string, cb msgHandler) (*subscription, error) { - return s.systemSubscribe(subject, _EMPTY_, true, nil, cb) -} - -func (s *Server) systemSubscribe(subject, queue string, internalOnly bool, c *client, cb msgHandler) (*subscription, error) { - s.mu.Lock() - if !s.eventsEnabled() { - s.mu.Unlock() - return nil, ErrNoSysAccount - } - if cb == nil { - s.mu.Unlock() - return nil, fmt.Errorf("undefined message handler") - } - if c == nil { - c = s.sys.client - } - trace := c.trace - s.sys.sid++ - sid := strconv.Itoa(s.sys.sid) - s.mu.Unlock() - - // Now create the subscription - if trace { - c.traceInOp("SUB", []byte(subject+" "+queue+" "+sid)) - } - - var q []byte - if queue != _EMPTY_ { - q = []byte(queue) - } - - // Now create the subscription - return c.processSub([]byte(subject), q, []byte(sid), cb, internalOnly) -} - -func (s *Server) sysUnsubscribe(sub *subscription) { - if sub == nil { - return - } - s.mu.RLock() - if !s.eventsEnabled() { - s.mu.RUnlock() - return - } - c := sub.client - s.mu.RUnlock() - - if c != nil { - c.processUnsub(sub.sid) - } -} - -// This will generate the tracking subject for remote latency from the response subject. -func remoteLatencySubjectForResponse(subject []byte) string { - if !isTrackedReply(subject) { - return "" - } - toks := bytes.Split(subject, []byte(tsep)) - // FIXME(dlc) - Sprintf may become a performance concern at some point. - return fmt.Sprintf(remoteLatencyEventSubj, toks[len(toks)-2]) -} - -// remoteLatencyUpdate is used to track remote latency measurements for tracking on exported services. -func (s *Server) remoteLatencyUpdate(sub *subscription, _ *client, _ *Account, subject, _ string, hdr, msg []byte) { - if !s.eventsRunning() { - return - } - var rl remoteLatency - if err := json.Unmarshal(msg, &rl); err != nil { - s.Errorf("Error unmarshalling remote latency measurement: %v", err) - return - } - // Now we need to look up the responseServiceImport associated with this measurement. - acc, err := s.LookupAccount(rl.Account) - if err != nil { - s.Warnf("Could not lookup account %q for latency measurement", rl.Account) - return - } - // Now get the request id / reply. We need to see if we have a GW prefix and if so strip that off. - reply := rl.ReqId - if gwPrefix, old := isGWRoutedSubjectAndIsOldPrefix([]byte(reply)); gwPrefix { - reply = string(getSubjectFromGWRoutedReply([]byte(reply), old)) - } - acc.mu.RLock() - si := acc.exports.responses[reply] - if si == nil { - acc.mu.RUnlock() - return - } - lsub := si.latency.subject - acc.mu.RUnlock() - - si.acc.mu.Lock() - m1 := si.m1 - m2 := rl.M2 - - // So we have not processed the response tracking measurement yet. - if m1 == nil { - // Store our value there for them to pick up. - si.m1 = &m2 - } - si.acc.mu.Unlock() - - if m1 == nil { - return - } - - // Calculate the correct latencies given M1 and M2. - m1.merge(&m2) - - // Clear the requesting client since we send the result here. - acc.mu.Lock() - si.rc = nil - acc.mu.Unlock() - - // Make sure we remove the entry here. - acc.removeServiceImport(si.from) - // Send the metrics - s.sendInternalAccountMsg(acc, lsub, m1) -} - -// This is used for all inbox replies so that we do not send supercluster wide interest -// updates for every request. Same trick used in modern NATS clients. -func (s *Server) inboxReply(sub *subscription, c *client, acc *Account, subject, reply string, msg []byte) { - s.mu.RLock() - if !s.eventsEnabled() || s.sys.replies == nil { - s.mu.RUnlock() - return - } - cb, ok := s.sys.replies[subject] - s.mu.RUnlock() - - if ok && cb != nil { - cb(sub, c, acc, subject, reply, msg) - } -} - -// Copied from go client. -// We could use serviceReply here instead to save some code. -// I prefer these semantics for the moment, when tracing you know what this is. -const ( - InboxPrefix = "$SYS._INBOX." - inboxPrefixLen = len(InboxPrefix) - respInboxPrefixLen = inboxPrefixLen + sysHashLen + 1 - replySuffixLen = 8 // Gives us 62^8 -) - -// Creates an internal inbox used for replies that will be processed by the global wc handler. -func (s *Server) newRespInbox() string { - var b [respInboxPrefixLen + replySuffixLen]byte - pres := b[:respInboxPrefixLen] - copy(pres, s.sys.inboxPre) - rn := rand.Int63() - for i, l := respInboxPrefixLen, rn; i < len(b); i++ { - b[i] = digits[l%base] - l /= base - } - return string(b[:]) -} - -// accNumSubsReq is sent when we need to gather remote info on subs. -type accNumSubsReq struct { - Account string `json:"acc"` - Subject string `json:"subject"` - Queue []byte `json:"queue,omitempty"` -} - -// helper function to total information from results to count subs. -func totalSubs(rr *SublistResult, qg []byte) (nsubs int32) { - if rr == nil { - return - } - checkSub := func(sub *subscription) { - // TODO(dlc) - This could be smarter. - if qg != nil && !bytes.Equal(qg, sub.queue) { - return - } - if sub.client.kind == CLIENT || sub.client.isHubLeafNode() { - nsubs++ - } - } - if qg == nil { - for _, sub := range rr.psubs { - checkSub(sub) - } - } - for _, qsub := range rr.qsubs { - for _, sub := range qsub { - checkSub(sub) - } - } - return -} - -// Allows users of large systems to debug active subscribers for a given subject. -// Payload should be the subject of interest. -func (s *Server) debugSubscribers(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - // Even though this is an internal only subscription, meaning interest was not forwarded, we could - // get one here from a GW in optimistic mode. Ignore for now. - // FIXME(dlc) - Should we send no interest here back to the GW? - if c.kind != CLIENT { - return - } - - var ci ClientInfo - if len(hdr) > 0 { - if err := json.Unmarshal(getHeader(ClientInfoHdr, hdr), &ci); err != nil { - return - } - } - - var acc *Account - if ci.Service != _EMPTY_ { - acc, _ = s.LookupAccount(ci.Service) - } else if ci.Account != _EMPTY_ { - acc, _ = s.LookupAccount(ci.Account) - } else { - // Direct $SYS access. - acc = c.acc - if acc == nil { - acc = s.SystemAccount() - } - } - if acc == nil { - return - } - - // We could have a single subject or we could have a subject and a wildcard separated by whitespace. - args := strings.Split(strings.TrimSpace(string(msg)), " ") - if len(args) == 0 { - s.sendInternalAccountMsg(acc, reply, 0) - return - } - - tsubj := args[0] - var qgroup []byte - if len(args) > 1 { - qgroup = []byte(args[1]) - } - - var nsubs int32 - - if subjectIsLiteral(tsubj) { - // We will look up subscribers locally first then determine if we need to solicit other servers. - rr := acc.sl.Match(tsubj) - nsubs = totalSubs(rr, qgroup) - } else { - // We have a wildcard, so this is a bit slower path. - var _subs [32]*subscription - subs := _subs[:0] - acc.sl.All(&subs) - for _, sub := range subs { - if subjectIsSubsetMatch(string(sub.subject), tsubj) { - if qgroup != nil && !bytes.Equal(qgroup, sub.queue) { - continue - } - if sub.client.kind == CLIENT || sub.client.isHubLeafNode() { - nsubs++ - } - } - } - } - - // We should have an idea of how many responses to expect from remote servers. - var expected = acc.expectedRemoteResponses() - - // If we are only local, go ahead and return. - if expected == 0 { - s.sendInternalAccountMsg(nil, reply, nsubs) - return - } - - // We need to solicit from others. - // To track status. - responses := int32(0) - done := make(chan (bool)) - - s.mu.Lock() - // Create direct reply inbox that we multiplex under the WC replies. - replySubj := s.newRespInbox() - // Store our handler. - s.sys.replies[replySubj] = func(sub *subscription, _ *client, _ *Account, subject, _ string, msg []byte) { - if n, err := strconv.Atoi(string(msg)); err == nil { - atomic.AddInt32(&nsubs, int32(n)) - } - if atomic.AddInt32(&responses, 1) >= expected { - select { - case done <- true: - default: - } - } - } - // Send the request to the other servers. - request := &accNumSubsReq{ - Account: acc.Name, - Subject: tsubj, - Queue: qgroup, - } - s.sendInternalMsg(accNumSubsReqSubj, replySubj, nil, request) - s.mu.Unlock() - - // FIXME(dlc) - We should rate limit here instead of blind Go routine. - go func() { - select { - case <-done: - case <-time.After(500 * time.Millisecond): - } - // Cleanup the WC entry. - var sendResponse bool - s.mu.Lock() - if s.sys != nil && s.sys.replies != nil { - delete(s.sys.replies, replySubj) - sendResponse = true - } - s.mu.Unlock() - if sendResponse { - // Send the response. - s.sendInternalAccountMsg(nil, reply, atomic.LoadInt32(&nsubs)) - } - }() -} - -// Request for our local subscription count. This will come from a remote origin server -// that received the initial request. -func (s *Server) nsubsRequest(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - if !s.eventsRunning() { - return - } - m := accNumSubsReq{} - if len(msg) == 0 { - s.sys.client.Errorf("request requires a body") - return - } else if err := json.Unmarshal(msg, &m); err != nil { - s.sys.client.Errorf("Error unmarshalling account nsubs request message: %v", err) - return - } - // Grab account. - acc, _ := s.lookupAccount(m.Account) - if acc == nil || acc.numLocalAndLeafConnections() == 0 { - return - } - // We will look up subscribers locally first then determine if we need to solicit other servers. - var nsubs int32 - if subjectIsLiteral(m.Subject) { - rr := acc.sl.Match(m.Subject) - nsubs = totalSubs(rr, m.Queue) - } else { - // We have a wildcard, so this is a bit slower path. - var _subs [32]*subscription - subs := _subs[:0] - acc.sl.All(&subs) - for _, sub := range subs { - if (sub.client.kind == CLIENT || sub.client.isHubLeafNode()) && subjectIsSubsetMatch(string(sub.subject), m.Subject) { - if m.Queue != nil && !bytes.Equal(m.Queue, sub.queue) { - continue - } - nsubs++ - } - } - } - s.sendInternalMsgLocked(reply, _EMPTY_, nil, nsubs) -} - -func (s *Server) reloadConfig(sub *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - if !s.eventsRunning() { - return - } - - optz := &EventFilterOptions{} - s.zReq(c, reply, hdr, msg, optz, optz, func() (any, error) { - // Reload the server config, as requested. - return nil, s.Reload() - }) -} - -type KickClientReq struct { - CID uint64 `json:"cid"` -} - -type LDMClientReq struct { - CID uint64 `json:"cid"` -} - -func (s *Server) kickClient(_ *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - if !s.eventsRunning() { - return - } - - var req KickClientReq - if err := json.Unmarshal(msg, &req); err != nil { - s.sys.client.Errorf("Error unmarshalling kick client request: %v", err) - return - } - - optz := &EventFilterOptions{} - s.zReq(c, reply, hdr, msg, optz, optz, func() (any, error) { - return nil, s.DisconnectClientByID(req.CID) - }) - -} - -func (s *Server) ldmClient(_ *subscription, c *client, _ *Account, subject, reply string, hdr, msg []byte) { - if !s.eventsRunning() { - return - } - - var req LDMClientReq - if err := json.Unmarshal(msg, &req); err != nil { - s.sys.client.Errorf("Error unmarshalling kick client request: %v", err) - return - } - - optz := &EventFilterOptions{} - s.zReq(c, reply, hdr, msg, optz, optz, func() (any, error) { - return nil, s.LDMClientByID(req.CID) - }) -} - -// Helper to grab account name for a client. -func accForClient(c *client) string { - if c.acc != nil { - return c.acc.Name - } - return "N/A" -} - -// Helper to grab issuer for a client. -func issuerForClient(c *client) (issuerKey string) { - if c == nil || c.user == nil { - return - } - issuerKey = c.user.SigningKey - if issuerKey == _EMPTY_ && c.user.Account != nil { - issuerKey = c.user.Account.Name - } - return -} - -// Helper to clear timers. -func clearTimer(tp **time.Timer) { - if t := *tp; t != nil { - t.Stop() - *tp = nil - } -} - -// Helper function to wrap functions with common test -// to lock server and return if events not enabled. -func (s *Server) wrapChk(f func()) func() { - return func() { - s.mu.Lock() - if !s.eventsEnabled() { - s.mu.Unlock() - return - } - f() - s.mu.Unlock() - } -} - -// sendOCSPPeerRejectEvent sends a system level event to system account when a peer connection is -// rejected due to OCSP invalid status of its trust chain(s). -func (s *Server) sendOCSPPeerRejectEvent(kind string, peer *x509.Certificate, reason string) { - s.mu.Lock() - defer s.mu.Unlock() - if !s.eventsEnabled() { - return - } - if peer == nil { - s.Errorf(certidp.ErrPeerEmptyNoEvent) - return - } - eid := s.nextEventID() - now := time.Now().UTC() - m := OCSPPeerRejectEventMsg{ - TypedEvent: TypedEvent{ - Type: OCSPPeerRejectEventMsgType, - ID: eid, - Time: now, - }, - Kind: kind, - Peer: certidp.CertInfo{ - Subject: certidp.GetSubjectDNForm(peer), - Issuer: certidp.GetIssuerDNForm(peer), - Fingerprint: certidp.GenerateFingerprint(peer), - Raw: peer.Raw, - }, - Reason: reason, - } - subj := fmt.Sprintf(ocspPeerRejectEventSubj, s.info.ID) - s.sendInternalMsg(subj, _EMPTY_, &m.Server, &m) -} - -// sendOCSPPeerChainlinkInvalidEvent sends a system level event to system account when a link in a peer's trust chain -// is OCSP invalid. -func (s *Server) sendOCSPPeerChainlinkInvalidEvent(peer *x509.Certificate, link *x509.Certificate, reason string) { - s.mu.Lock() - defer s.mu.Unlock() - if !s.eventsEnabled() { - return - } - if peer == nil || link == nil { - s.Errorf(certidp.ErrPeerEmptyNoEvent) - return - } - eid := s.nextEventID() - now := time.Now().UTC() - m := OCSPPeerChainlinkInvalidEventMsg{ - TypedEvent: TypedEvent{ - Type: OCSPPeerChainlinkInvalidEventMsgType, - ID: eid, - Time: now, - }, - Link: certidp.CertInfo{ - Subject: certidp.GetSubjectDNForm(link), - Issuer: certidp.GetIssuerDNForm(link), - Fingerprint: certidp.GenerateFingerprint(link), - Raw: link.Raw, - }, - Peer: certidp.CertInfo{ - Subject: certidp.GetSubjectDNForm(peer), - Issuer: certidp.GetIssuerDNForm(peer), - Fingerprint: certidp.GenerateFingerprint(peer), - Raw: peer.Raw, - }, - Reason: reason, - } - subj := fmt.Sprintf(ocspPeerChainlinkInvalidEventSubj, s.info.ID) - s.sendInternalMsg(subj, _EMPTY_, &m.Server, &m) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/filestore.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/filestore.go deleted file mode 100644 index 528219fb..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/filestore.go +++ /dev/null @@ -1,10857 +0,0 @@ -// Copyright 2019-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "archive/tar" - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "crypto/sha256" - "encoding/binary" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "hash" - "io" - "io/fs" - "math" - mrand "math/rand" - "net" - "os" - "path/filepath" - "runtime" - "slices" - "sort" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/klauspost/compress/s2" - "github.com/minio/highwayhash" - "github.com/nats-io/nats-server/v2/server/avl" - "github.com/nats-io/nats-server/v2/server/stree" - "github.com/nats-io/nats-server/v2/server/thw" - "golang.org/x/crypto/chacha20" - "golang.org/x/crypto/chacha20poly1305" -) - -type FileStoreConfig struct { - // Where the parent directory for all storage will be located. - StoreDir string - // BlockSize is the file block size. This also represents the maximum overhead size. - BlockSize uint64 - // CacheExpire is how long with no activity until we expire the cache. - CacheExpire time.Duration - // SubjectStateExpire is how long with no activity until we expire a msg block's subject state. - SubjectStateExpire time.Duration - // SyncInterval is how often we sync to disk in the background. - SyncInterval time.Duration - // SyncAlways is when the stream should sync all data writes. - SyncAlways bool - // AsyncFlush allows async flush to batch write operations. - AsyncFlush bool - // Cipher is the cipher to use when encrypting. - Cipher StoreCipher - // Compression is the algorithm to use when compressing. - Compression StoreCompression - - // Internal reference to our server. - srv *Server -} - -// FileStreamInfo allows us to remember created time. -type FileStreamInfo struct { - Created time.Time - StreamConfig -} - -type StoreCipher int - -const ( - ChaCha StoreCipher = iota - AES - NoCipher -) - -func (cipher StoreCipher) String() string { - switch cipher { - case ChaCha: - return "ChaCha20-Poly1305" - case AES: - return "AES-GCM" - case NoCipher: - return "None" - default: - return "Unknown StoreCipher" - } -} - -type StoreCompression uint8 - -const ( - NoCompression StoreCompression = iota - S2Compression -) - -func (alg StoreCompression) String() string { - switch alg { - case NoCompression: - return "None" - case S2Compression: - return "S2" - default: - return "Unknown StoreCompression" - } -} - -func (alg StoreCompression) MarshalJSON() ([]byte, error) { - var str string - switch alg { - case S2Compression: - str = "s2" - case NoCompression: - str = "none" - default: - return nil, fmt.Errorf("unknown compression algorithm") - } - return json.Marshal(str) -} - -func (alg *StoreCompression) UnmarshalJSON(b []byte) error { - var str string - if err := json.Unmarshal(b, &str); err != nil { - return err - } - switch str { - case "s2": - *alg = S2Compression - case "none": - *alg = NoCompression - default: - return fmt.Errorf("unknown compression algorithm") - } - return nil -} - -// File ConsumerInfo is used for creating consumer stores. -type FileConsumerInfo struct { - Created time.Time - Name string - ConsumerConfig -} - -// Default file and directory permissions. -const ( - defaultDirPerms = os.FileMode(0700) - defaultFilePerms = os.FileMode(0600) -) - -type psi struct { - total uint64 - fblk uint32 - lblk uint32 -} - -type fileStore struct { - srv *Server - mu sync.RWMutex - state StreamState - tombs []uint64 - ld *LostStreamData - scb StorageUpdateHandler - sdmcb SubjectDeleteMarkerUpdateHandler - ageChk *time.Timer - syncTmr *time.Timer - cfg FileStreamInfo - fcfg FileStoreConfig - prf keyGen - oldprf keyGen - aek cipher.AEAD - lmb *msgBlock - blks []*msgBlock - bim map[uint32]*msgBlock - psim *stree.SubjectTree[psi] - tsl int - adml int - hh hash.Hash64 - qch chan struct{} - fsld chan struct{} - cmu sync.RWMutex - cfs []ConsumerStore - sips int - dirty int - closing bool - closed bool - fip bool - receivedAny bool - firstMoved bool - ttls *thw.HashWheel - ttlseq uint64 // How up-to-date is the `ttls` THW? - markers []string -} - -// Represents a message store block and its data. -type msgBlock struct { - // Here for 32bit systems and atomic. - first msgId - last msgId - mu sync.RWMutex - fs *fileStore - aek cipher.AEAD - bek cipher.Stream - seed []byte - nonce []byte - mfn string - mfd *os.File - cmp StoreCompression // Effective compression at the time of loading the block - liwsz int64 - index uint32 - bytes uint64 // User visible bytes count. - rbytes uint64 // Total bytes (raw) including deleted. Used for rolling to new blk. - cbytes uint64 // Bytes count after last compaction. 0 if no compaction happened yet. - msgs uint64 // User visible message count. - fss *stree.SubjectTree[SimpleState] - kfn string - lwts int64 - llts int64 - lrts int64 - lsts int64 - llseq uint64 - hh hash.Hash64 - cache *cache - cloads uint64 - cexp time.Duration - fexp time.Duration - ctmr *time.Timer - werr error - dmap avl.SequenceSet - fch chan struct{} - qch chan struct{} - lchk [8]byte - loading bool - flusher bool - noTrack bool - needSync bool - syncAlways bool - noCompact bool - closed bool - ttls uint64 // How many msgs have TTLs? - - // Used to mock write failures. - mockWriteErr bool -} - -// Write through caching layer that is also used on loading messages. -type cache struct { - buf []byte - off int - wp int - idx []uint32 - lrl uint32 - fseq uint64 - nra bool -} - -type msgId struct { - seq uint64 - ts int64 -} - -const ( - // Magic is used to identify the file store files. - magic = uint8(22) - // Version - version = uint8(1) - // New IndexInfo Version - newVersion = uint8(2) - // hdrLen - hdrLen = 2 - // This is where we keep the streams. - streamsDir = "streams" - // This is where we keep the message store blocks. - msgDir = "msgs" - // This is where we temporarily move the messages dir. - purgeDir = "__msgs__" - // used to scan blk file names. - blkScan = "%d.blk" - // suffix of a block file - blkSuffix = ".blk" - // used for compacted blocks that are staged. - newScan = "%d.new" - // used to scan index file names. - indexScan = "%d.idx" - // used to store our block encryption key. - keyScan = "%d.key" - // to look for orphans - keyScanAll = "*.key" - // This is where we keep state on consumers. - consumerDir = "obs" - // Index file for a consumer. - consumerState = "o.dat" - // The suffix that will be given to a new temporary block during compression. - compressTmpSuffix = ".tmp" - // This is where we keep state on templates. - tmplsDir = "templates" - // Maximum size of a write buffer we may consider for re-use. - maxBufReuse = 2 * 1024 * 1024 - // default cache buffer expiration - defaultCacheBufferExpiration = 10 * time.Second - // default sync interval - defaultSyncInterval = 2 * time.Minute - // default idle timeout to close FDs. - closeFDsIdle = 30 * time.Second - // default expiration time for mb.fss when idle. - defaultFssExpiration = 2 * time.Minute - // coalesceMinimum - coalesceMinimum = 16 * 1024 - // maxFlushWait is maximum we will wait to gather messages to flush. - maxFlushWait = 8 * time.Millisecond - - // Metafiles for streams and consumers. - JetStreamMetaFile = "meta.inf" - JetStreamMetaFileSum = "meta.sum" - JetStreamMetaFileKey = "meta.key" - - // This is the full snapshotted state for the stream. - streamStreamStateFile = "index.db" - - // This is the encoded time hash wheel for TTLs. - ttlStreamStateFile = "thw.db" - - // AEK key sizes - minMetaKeySize = 64 - minBlkKeySize = 64 - - // Default stream block size. - defaultLargeBlockSize = 8 * 1024 * 1024 // 8MB - // Default for workqueue or interest based. - defaultMediumBlockSize = 4 * 1024 * 1024 // 4MB - // For smaller reuse buffers. Usually being generated during contention on the lead write buffer. - // E.g. mirrors/sources etc. - defaultSmallBlockSize = 1 * 1024 * 1024 // 1MB - // Maximum size for the encrypted head block. - maximumEncryptedBlockSize = 2 * 1024 * 1024 // 2MB - // Default for KV based - defaultKVBlockSize = defaultMediumBlockSize - // max block size for now. - maxBlockSize = defaultLargeBlockSize - // Compact minimum threshold. - compactMinimum = 2 * 1024 * 1024 // 2MB - // FileStoreMinBlkSize is minimum size we will do for a blk size. - FileStoreMinBlkSize = 32 * 1000 // 32kib - // FileStoreMaxBlkSize is maximum size we will do for a blk size. - FileStoreMaxBlkSize = maxBlockSize - // Check for bad record length value due to corrupt data. - rlBadThresh = 32 * 1024 * 1024 - // Checksum size for hash for msg records. - recordHashSize = 8 -) - -func newFileStore(fcfg FileStoreConfig, cfg StreamConfig) (*fileStore, error) { - return newFileStoreWithCreated(fcfg, cfg, time.Now().UTC(), nil, nil) -} - -func newFileStoreWithCreated(fcfg FileStoreConfig, cfg StreamConfig, created time.Time, prf, oldprf keyGen) (*fileStore, error) { - if cfg.Name == _EMPTY_ { - return nil, fmt.Errorf("name required") - } - if cfg.Storage != FileStorage { - return nil, fmt.Errorf("fileStore requires file storage type in config") - } - // Default values. - if fcfg.BlockSize == 0 { - fcfg.BlockSize = dynBlkSize(cfg.Retention, cfg.MaxBytes, prf != nil) - } - if fcfg.BlockSize > maxBlockSize { - return nil, fmt.Errorf("filestore max block size is %s", friendlyBytes(maxBlockSize)) - } - if fcfg.CacheExpire == 0 { - fcfg.CacheExpire = defaultCacheBufferExpiration - } - if fcfg.SubjectStateExpire == 0 { - fcfg.SubjectStateExpire = defaultFssExpiration - } - if fcfg.SyncInterval == 0 { - fcfg.SyncInterval = defaultSyncInterval - } - - // Check the directory - if stat, err := os.Stat(fcfg.StoreDir); os.IsNotExist(err) { - if err := os.MkdirAll(fcfg.StoreDir, defaultDirPerms); err != nil { - return nil, fmt.Errorf("could not create storage directory - %v", err) - } - } else if stat == nil || !stat.IsDir() { - return nil, fmt.Errorf("storage directory is not a directory") - } - tmpfile, err := os.CreateTemp(fcfg.StoreDir, "_test_") - if err != nil { - return nil, fmt.Errorf("storage directory is not writable") - } - - tmpfile.Close() - <-dios - os.Remove(tmpfile.Name()) - dios <- struct{}{} - - fs := &fileStore{ - fcfg: fcfg, - psim: stree.NewSubjectTree[psi](), - bim: make(map[uint32]*msgBlock), - cfg: FileStreamInfo{Created: created, StreamConfig: cfg}, - prf: prf, - oldprf: oldprf, - qch: make(chan struct{}), - fsld: make(chan struct{}), - srv: fcfg.srv, - } - - // Only create a THW if we're going to allow TTLs. - if cfg.AllowMsgTTL { - fs.ttls = thw.NewHashWheel() - } - - // Set flush in place to AsyncFlush which by default is false. - fs.fip = !fcfg.AsyncFlush - - // Check if this is a new setup. - mdir := filepath.Join(fcfg.StoreDir, msgDir) - odir := filepath.Join(fcfg.StoreDir, consumerDir) - if err := os.MkdirAll(mdir, defaultDirPerms); err != nil { - return nil, fmt.Errorf("could not create message storage directory - %v", err) - } - if err := os.MkdirAll(odir, defaultDirPerms); err != nil { - return nil, fmt.Errorf("could not create consumer storage directory - %v", err) - } - - // Create highway hash for message blocks. Use sha256 of directory as key. - key := sha256.Sum256([]byte(cfg.Name)) - fs.hh, err = highwayhash.New64(key[:]) - if err != nil { - return nil, fmt.Errorf("could not create hash: %v", err) - } - - keyFile := filepath.Join(fs.fcfg.StoreDir, JetStreamMetaFileKey) - // Make sure we do not have an encrypted store underneath of us but no main key. - if fs.prf == nil { - if _, err := os.Stat(keyFile); err == nil { - return nil, errNoMainKey - } - } - - // Attempt to recover our state. - err = fs.recoverFullState() - if err != nil { - if !os.IsNotExist(err) { - fs.warn("Recovering stream state from index errored: %v", err) - } - // Hold onto state - prior := fs.state - // Reset anything that could have been set from above. - fs.state = StreamState{} - fs.psim, fs.tsl = fs.psim.Empty(), 0 - fs.bim = make(map[uint32]*msgBlock) - fs.blks = nil - fs.tombs = nil - - // Recover our message state the old way - if err := fs.recoverMsgs(); err != nil { - return nil, err - } - - // Check if our prior state remembers a last sequence past where we can see. - if fs.ld != nil && prior.LastSeq > fs.state.LastSeq { - fs.state.LastSeq, fs.state.LastTime = prior.LastSeq, prior.LastTime - if fs.state.Msgs == 0 { - fs.state.FirstSeq = fs.state.LastSeq + 1 - fs.state.FirstTime = time.Time{} - } - if _, err := fs.newMsgBlockForWrite(); err == nil { - if err = fs.writeTombstone(prior.LastSeq, prior.LastTime.UnixNano()); err != nil { - return nil, err - } - } else { - return nil, err - } - } - // Since we recovered here, make sure to kick ourselves to write out our stream state. - fs.dirty++ - } - - // See if we can bring back our TTL timed hash wheel state from disk. - if cfg.AllowMsgTTL { - if err = fs.recoverTTLState(); err != nil && !os.IsNotExist(err) { - fs.warn("Recovering TTL state from index errored: %v", err) - } - } - - // Also make sure we get rid of old idx and fss files on return. - // Do this in separate go routine vs inline and at end of processing. - defer func() { - go fs.cleanupOldMeta() - }() - - // Lock while we do enforcements and removals. - fs.mu.Lock() - - // Check if we have any left over tombstones to process. - if len(fs.tombs) > 0 { - for _, seq := range fs.tombs { - fs.removeMsg(seq, false, true, false) - fs.removeFromLostData(seq) - } - // Not needed after this phase. - fs.tombs = nil - } - - // Limits checks and enforcement. - fs.enforceMsgLimit() - fs.enforceBytesLimit() - - // Do age checks too, make sure to call in place. - if fs.cfg.MaxAge != 0 { - err := fs.expireMsgsOnRecover() - if isPermissionError(err) { - return nil, err - } - fs.startAgeChk() - } - - // If we have max msgs per subject make sure the is also enforced. - if fs.cfg.MaxMsgsPer > 0 { - fs.enforceMsgPerSubjectLimit(false) - } - - // Grab first sequence for check below while we have lock. - firstSeq := fs.state.FirstSeq - fs.mu.Unlock() - - // If the stream has an initial sequence number then make sure we - // have purged up until that point. We will do this only if the - // recovered first sequence number is before our configured first - // sequence. Need to do this locked as by now the age check timer - // has started. - if cfg.FirstSeq > 0 && firstSeq <= cfg.FirstSeq { - if _, err := fs.purge(cfg.FirstSeq, true); err != nil { - return nil, err - } - } - - // Write our meta data if it does not exist or is zero'd out. - meta := filepath.Join(fcfg.StoreDir, JetStreamMetaFile) - fi, err := os.Stat(meta) - if err != nil && os.IsNotExist(err) || fi != nil && fi.Size() == 0 { - if err := fs.writeStreamMeta(); err != nil { - return nil, err - } - } - - // If we expect to be encrypted check that what we are restoring is not plaintext. - // This can happen on snapshot restores or conversions. - if fs.prf != nil { - if _, err := os.Stat(keyFile); err != nil && os.IsNotExist(err) { - if err := fs.writeStreamMeta(); err != nil { - return nil, err - } - } - } - - // Setup our sync timer. - fs.setSyncTimer() - - // Spin up the go routine that will write out our full state stream index. - go fs.flushStreamStateLoop(fs.qch, fs.fsld) - - return fs, nil -} - -// Lock all existing message blocks. -// Lock held on entry. -func (fs *fileStore) lockAllMsgBlocks() { - for _, mb := range fs.blks { - mb.mu.Lock() - } -} - -// Unlock all existing message blocks. -// Lock held on entry. -func (fs *fileStore) unlockAllMsgBlocks() { - for _, mb := range fs.blks { - mb.mu.Unlock() - } -} - -func (fs *fileStore) UpdateConfig(cfg *StreamConfig) error { - start := time.Now() - defer func() { - if took := time.Since(start); took > time.Minute { - fs.warn("UpdateConfig took %v", took.Round(time.Millisecond)) - } - }() - - if fs.isClosed() { - return ErrStoreClosed - } - if cfg.Name == _EMPTY_ { - return fmt.Errorf("name required") - } - if cfg.Storage != FileStorage { - return fmt.Errorf("fileStore requires file storage type in config") - } - if cfg.MaxMsgsPer < -1 { - cfg.MaxMsgsPer = -1 - } - - fs.mu.Lock() - new_cfg := FileStreamInfo{Created: fs.cfg.Created, StreamConfig: *cfg} - old_cfg := fs.cfg - // The reference story has changed here, so this full msg block lock - // may not be needed. - fs.lockAllMsgBlocks() - fs.cfg = new_cfg - fs.unlockAllMsgBlocks() - if err := fs.writeStreamMeta(); err != nil { - fs.lockAllMsgBlocks() - fs.cfg = old_cfg - fs.unlockAllMsgBlocks() - fs.mu.Unlock() - return err - } - - // Limits checks and enforcement. - fs.enforceMsgLimit() - fs.enforceBytesLimit() - - // Do age timers. - if fs.ageChk == nil && fs.cfg.MaxAge != 0 { - fs.startAgeChk() - } - if fs.ageChk != nil && fs.cfg.MaxAge == 0 { - fs.ageChk.Stop() - fs.ageChk = nil - } - - if fs.cfg.MaxMsgsPer > 0 && (old_cfg.MaxMsgsPer == 0 || fs.cfg.MaxMsgsPer < old_cfg.MaxMsgsPer) { - fs.enforceMsgPerSubjectLimit(true) - } - fs.mu.Unlock() - - if cfg.MaxAge != 0 { - fs.expireMsgs() - } - return nil -} - -func dynBlkSize(retention RetentionPolicy, maxBytes int64, encrypted bool) uint64 { - if maxBytes > 0 { - blkSize := (maxBytes / 4) + 1 // (25% overhead) - // Round up to nearest 100 - if m := blkSize % 100; m != 0 { - blkSize += 100 - m - } - if blkSize <= FileStoreMinBlkSize { - blkSize = FileStoreMinBlkSize - } else if blkSize >= FileStoreMaxBlkSize { - blkSize = FileStoreMaxBlkSize - } else { - blkSize = defaultMediumBlockSize - } - if encrypted && blkSize > maximumEncryptedBlockSize { - // Notes on this below. - blkSize = maximumEncryptedBlockSize - } - return uint64(blkSize) - } - - switch { - case encrypted: - // In the case of encrypted stores, large blocks can result in worsened perf - // since many writes on disk involve re-encrypting the entire block. For now, - // we will enforce a cap on the block size when encryption is enabled to avoid - // this. - return maximumEncryptedBlockSize - case retention == LimitsPolicy: - // TODO(dlc) - Make the blocksize relative to this if set. - return defaultLargeBlockSize - default: - // TODO(dlc) - Make the blocksize relative to this if set. - return defaultMediumBlockSize - } -} - -func genEncryptionKey(sc StoreCipher, seed []byte) (ek cipher.AEAD, err error) { - if sc == ChaCha { - ek, err = chacha20poly1305.NewX(seed) - } else if sc == AES { - block, e := aes.NewCipher(seed) - if e != nil { - return nil, e - } - ek, err = cipher.NewGCMWithNonceSize(block, block.BlockSize()) - } else { - err = errUnknownCipher - } - return ek, err -} - -// Generate an asset encryption key from the context and server PRF. -func (fs *fileStore) genEncryptionKeys(context string) (aek cipher.AEAD, bek cipher.Stream, seed, encrypted []byte, err error) { - if fs.prf == nil { - return nil, nil, nil, nil, errNoEncryption - } - // Generate key encryption key. - rb, err := fs.prf([]byte(context)) - if err != nil { - return nil, nil, nil, nil, err - } - - sc := fs.fcfg.Cipher - - kek, err := genEncryptionKey(sc, rb) - if err != nil { - return nil, nil, nil, nil, err - } - // Generate random asset encryption key seed. - - const seedSize = 32 - seed = make([]byte, seedSize) - if n, err := rand.Read(seed); err != nil { - return nil, nil, nil, nil, err - } else if n != seedSize { - return nil, nil, nil, nil, fmt.Errorf("not enough seed bytes read (%d != %d", n, seedSize) - } - - aek, err = genEncryptionKey(sc, seed) - if err != nil { - return nil, nil, nil, nil, err - } - - // Generate our nonce. Use same buffer to hold encrypted seed. - nonce := make([]byte, kek.NonceSize(), kek.NonceSize()+len(seed)+kek.Overhead()) - if n, err := rand.Read(nonce); err != nil { - return nil, nil, nil, nil, err - } else if n != len(nonce) { - return nil, nil, nil, nil, fmt.Errorf("not enough nonce bytes read (%d != %d)", n, len(nonce)) - } - - bek, err = genBlockEncryptionKey(sc, seed[:], nonce) - if err != nil { - return nil, nil, nil, nil, err - } - - return aek, bek, seed, kek.Seal(nonce, nonce, seed, nil), nil -} - -// Will generate the block encryption key. -func genBlockEncryptionKey(sc StoreCipher, seed, nonce []byte) (cipher.Stream, error) { - if sc == ChaCha { - return chacha20.NewUnauthenticatedCipher(seed, nonce) - } else if sc == AES { - block, err := aes.NewCipher(seed) - if err != nil { - return nil, err - } - return cipher.NewCTR(block, nonce), nil - } - return nil, errUnknownCipher -} - -// Lock should be held. -func (fs *fileStore) recoverAEK() error { - if fs.prf != nil && fs.aek == nil { - ekey, err := os.ReadFile(filepath.Join(fs.fcfg.StoreDir, JetStreamMetaFileKey)) - if err != nil { - return err - } - rb, err := fs.prf([]byte(fs.cfg.Name)) - if err != nil { - return err - } - kek, err := genEncryptionKey(fs.fcfg.Cipher, rb) - if err != nil { - return err - } - ns := kek.NonceSize() - seed, err := kek.Open(nil, ekey[:ns], ekey[ns:], nil) - if err != nil { - return err - } - aek, err := genEncryptionKey(fs.fcfg.Cipher, seed) - if err != nil { - return err - } - fs.aek = aek - } - return nil -} - -// Lock should be held. -func (fs *fileStore) setupAEK() error { - if fs.prf != nil && fs.aek == nil { - key, _, _, encrypted, err := fs.genEncryptionKeys(fs.cfg.Name) - if err != nil { - return err - } - keyFile := filepath.Join(fs.fcfg.StoreDir, JetStreamMetaFileKey) - if _, err := os.Stat(keyFile); err != nil && !os.IsNotExist(err) { - return err - } - err = fs.writeFileWithOptionalSync(keyFile, encrypted, defaultFilePerms) - if err != nil { - return err - } - // Set our aek. - fs.aek = key - } - return nil -} - -// Write out meta and the checksum. -// Lock should be held. -func (fs *fileStore) writeStreamMeta() error { - if err := fs.setupAEK(); err != nil { - return err - } - - meta := filepath.Join(fs.fcfg.StoreDir, JetStreamMetaFile) - if _, err := os.Stat(meta); err != nil && !os.IsNotExist(err) { - return err - } - b, err := json.Marshal(fs.cfg) - if err != nil { - return err - } - // Encrypt if needed. - if fs.aek != nil { - nonce := make([]byte, fs.aek.NonceSize(), fs.aek.NonceSize()+len(b)+fs.aek.Overhead()) - if n, err := rand.Read(nonce); err != nil { - return err - } else if n != len(nonce) { - return fmt.Errorf("not enough nonce bytes read (%d != %d)", n, len(nonce)) - } - b = fs.aek.Seal(nonce, nonce, b, nil) - } - - err = fs.writeFileWithOptionalSync(meta, b, defaultFilePerms) - if err != nil { - return err - } - fs.hh.Reset() - fs.hh.Write(b) - checksum := hex.EncodeToString(fs.hh.Sum(nil)) - sum := filepath.Join(fs.fcfg.StoreDir, JetStreamMetaFileSum) - err = fs.writeFileWithOptionalSync(sum, []byte(checksum), defaultFilePerms) - if err != nil { - return err - } - return nil -} - -// Pools to recycle the blocks to help with memory pressure. -var blkPoolBig sync.Pool // 16MB -var blkPoolMedium sync.Pool // 8MB -var blkPoolSmall sync.Pool // 2MB - -// Get a new msg block based on sz estimate. -func getMsgBlockBuf(sz int) (buf []byte) { - var pb any - if sz <= defaultSmallBlockSize { - pb = blkPoolSmall.Get() - } else if sz <= defaultMediumBlockSize { - pb = blkPoolMedium.Get() - } else { - pb = blkPoolBig.Get() - } - if pb != nil { - buf = *(pb.(*[]byte)) - } else { - // Here we need to make a new blk. - // If small leave as is.. - if sz > defaultSmallBlockSize && sz <= defaultMediumBlockSize { - sz = defaultMediumBlockSize - } else if sz > defaultMediumBlockSize { - sz = defaultLargeBlockSize - } - buf = make([]byte, sz) - } - return buf[:0] -} - -// Recycle the msg block. -func recycleMsgBlockBuf(buf []byte) { - if buf == nil || cap(buf) < defaultSmallBlockSize { - return - } - // Make sure to reset before placing back into pool. - buf = buf[:0] - - // We need to make sure the load code gets a block that can fit the maximum for a size block. - // E.g. 8, 16 etc. otherwise we thrash and actually make things worse by pulling it out, and putting - // it right back in and making a new []byte. - // From above we know its already >= defaultSmallBlockSize - if sz := cap(buf); sz < defaultMediumBlockSize { - blkPoolSmall.Put(&buf) - } else if sz < defaultLargeBlockSize { - blkPoolMedium.Put(&buf) - } else { - blkPoolBig.Put(&buf) - } -} - -const ( - msgHdrSize = 22 - checksumSize = 8 - emptyRecordLen = msgHdrSize + checksumSize -) - -// Lock should be held. -func (fs *fileStore) noTrackSubjects() bool { - return !(fs.psim.Size() > 0 || len(fs.cfg.Subjects) > 0 || fs.cfg.Mirror != nil || len(fs.cfg.Sources) > 0) -} - -// Will init the basics for a message block. -func (fs *fileStore) initMsgBlock(index uint32) *msgBlock { - mb := &msgBlock{ - fs: fs, - index: index, - cexp: fs.fcfg.CacheExpire, - fexp: fs.fcfg.SubjectStateExpire, - noTrack: fs.noTrackSubjects(), - syncAlways: fs.fcfg.SyncAlways, - } - - mdir := filepath.Join(fs.fcfg.StoreDir, msgDir) - mb.mfn = filepath.Join(mdir, fmt.Sprintf(blkScan, index)) - - if mb.hh == nil { - key := sha256.Sum256(fs.hashKeyForBlock(index)) - mb.hh, _ = highwayhash.New64(key[:]) - } - return mb -} - -// Lock for fs should be held. -func (fs *fileStore) loadEncryptionForMsgBlock(mb *msgBlock) error { - if fs.prf == nil { - return nil - } - - var createdKeys bool - mdir := filepath.Join(fs.fcfg.StoreDir, msgDir) - ekey, err := os.ReadFile(filepath.Join(mdir, fmt.Sprintf(keyScan, mb.index))) - if err != nil { - // We do not seem to have keys even though we should. Could be a plaintext conversion. - // Create the keys and we will double check below. - if err := fs.genEncryptionKeysForBlock(mb); err != nil { - return err - } - createdKeys = true - } else { - if len(ekey) < minBlkKeySize { - return errBadKeySize - } - // Recover key encryption key. - rb, err := fs.prf([]byte(fmt.Sprintf("%s:%d", fs.cfg.Name, mb.index))) - if err != nil { - return err - } - - sc := fs.fcfg.Cipher - kek, err := genEncryptionKey(sc, rb) - if err != nil { - return err - } - ns := kek.NonceSize() - seed, err := kek.Open(nil, ekey[:ns], ekey[ns:], nil) - if err != nil { - // We may be here on a cipher conversion, so attempt to convert. - if err = mb.convertCipher(); err != nil { - return err - } - } else { - mb.seed, mb.nonce = seed, ekey[:ns] - } - mb.aek, err = genEncryptionKey(sc, mb.seed) - if err != nil { - return err - } - if mb.bek, err = genBlockEncryptionKey(sc, mb.seed, mb.nonce); err != nil { - return err - } - } - - // If we created keys here, let's check the data and if it is plaintext convert here. - if createdKeys { - if err := mb.convertToEncrypted(); err != nil { - return err - } - } - - return nil -} - -// Load a last checksum if needed from the block file. -// Lock should be held. -func (mb *msgBlock) ensureLastChecksumLoaded() { - var empty [8]byte - if mb.lchk != empty { - return - } - copy(mb.lchk[0:], mb.lastChecksum()) -} - -// Lock held on entry -func (fs *fileStore) recoverMsgBlock(index uint32) (*msgBlock, error) { - mb := fs.initMsgBlock(index) - // Open up the message file, but we will try to recover from the index file. - // We will check that the last checksums match. - file, err := mb.openBlock() - if err != nil { - return nil, err - } - defer file.Close() - - if fi, err := file.Stat(); fi != nil { - mb.rbytes = uint64(fi.Size()) - } else { - return nil, err - } - - // Make sure encryption loaded if needed. - fs.loadEncryptionForMsgBlock(mb) - - // Grab last checksum from main block file. - var lchk [8]byte - if mb.rbytes >= checksumSize { - if mb.bek != nil { - if buf, _ := mb.loadBlock(nil); len(buf) >= checksumSize { - mb.bek.XORKeyStream(buf, buf) - copy(lchk[0:], buf[len(buf)-checksumSize:]) - } - } else { - file.ReadAt(lchk[:], int64(mb.rbytes)-checksumSize) - } - } - - file.Close() - - // Read our index file. Use this as source of truth if possible. - // This not applicable in >= 2.10 servers. Here for upgrade paths from < 2.10. - if err := mb.readIndexInfo(); err == nil { - // Quick sanity check here. - // Note this only checks that the message blk file is not newer then this file, or is empty and we expect empty. - if (mb.rbytes == 0 && mb.msgs == 0) || bytes.Equal(lchk[:], mb.lchk[:]) { - if mb.msgs > 0 && !mb.noTrack && fs.psim != nil { - fs.populateGlobalPerSubjectInfo(mb) - // Try to dump any state we needed on recovery. - mb.tryForceExpireCacheLocked() - } - fs.addMsgBlock(mb) - return mb, nil - } - } - - // If we get data loss rebuilding the message block state record that with the fs itself. - ld, tombs, _ := mb.rebuildState() - if ld != nil { - fs.addLostData(ld) - } - // Collect all tombstones. - if len(tombs) > 0 { - fs.tombs = append(fs.tombs, tombs...) - } - - if mb.msgs > 0 && !mb.noTrack && fs.psim != nil { - fs.populateGlobalPerSubjectInfo(mb) - // Try to dump any state we needed on recovery. - mb.tryForceExpireCacheLocked() - } - - mb.closeFDs() - fs.addMsgBlock(mb) - - return mb, nil -} - -func (fs *fileStore) lostData() *LostStreamData { - fs.mu.RLock() - defer fs.mu.RUnlock() - if fs.ld == nil { - return nil - } - nld := *fs.ld - return &nld -} - -// Lock should be held. -func (fs *fileStore) addLostData(ld *LostStreamData) { - if ld == nil { - return - } - if fs.ld != nil { - var added bool - for _, seq := range ld.Msgs { - if _, found := fs.ld.exists(seq); !found { - fs.ld.Msgs = append(fs.ld.Msgs, seq) - added = true - } - } - if added { - msgs := fs.ld.Msgs - slices.Sort(msgs) - fs.ld.Bytes += ld.Bytes - } - } else { - fs.ld = ld - } -} - -// Helper to see if we already have this sequence reported in our lost data. -func (ld *LostStreamData) exists(seq uint64) (int, bool) { - i := slices.IndexFunc(ld.Msgs, func(i uint64) bool { - return i == seq - }) - return i, i > -1 -} - -func (fs *fileStore) removeFromLostData(seq uint64) { - if fs.ld == nil { - return - } - if i, found := fs.ld.exists(seq); found { - fs.ld.Msgs = append(fs.ld.Msgs[:i], fs.ld.Msgs[i+1:]...) - if len(fs.ld.Msgs) == 0 { - fs.ld = nil - } - } -} - -func (fs *fileStore) rebuildState(ld *LostStreamData) { - fs.mu.Lock() - defer fs.mu.Unlock() - fs.rebuildStateLocked(ld) -} - -// Lock should be held. -func (fs *fileStore) rebuildStateLocked(ld *LostStreamData) { - fs.addLostData(ld) - - fs.state.Msgs, fs.state.Bytes = 0, 0 - fs.state.FirstSeq, fs.state.LastSeq = 0, 0 - - for _, mb := range fs.blks { - mb.mu.RLock() - fs.state.Msgs += mb.msgs - fs.state.Bytes += mb.bytes - fseq := atomic.LoadUint64(&mb.first.seq) - if fs.state.FirstSeq == 0 || fseq < fs.state.FirstSeq { - fs.state.FirstSeq = fseq - fs.state.FirstTime = time.Unix(0, mb.first.ts).UTC() - } - fs.state.LastSeq = atomic.LoadUint64(&mb.last.seq) - fs.state.LastTime = time.Unix(0, mb.last.ts).UTC() - mb.mu.RUnlock() - } -} - -// Attempt to convert the cipher used for this message block. -func (mb *msgBlock) convertCipher() error { - fs := mb.fs - sc := fs.fcfg.Cipher - - var osc StoreCipher - switch sc { - case ChaCha: - osc = AES - case AES: - osc = ChaCha - } - - mdir := filepath.Join(fs.fcfg.StoreDir, msgDir) - ekey, err := os.ReadFile(filepath.Join(mdir, fmt.Sprintf(keyScan, mb.index))) - if err != nil { - return err - } - if len(ekey) < minBlkKeySize { - return errBadKeySize - } - type prfWithCipher struct { - keyGen - StoreCipher - } - var prfs []prfWithCipher - if fs.prf != nil { - prfs = append(prfs, prfWithCipher{fs.prf, sc}) - prfs = append(prfs, prfWithCipher{fs.prf, osc}) - } - if fs.oldprf != nil { - prfs = append(prfs, prfWithCipher{fs.oldprf, sc}) - prfs = append(prfs, prfWithCipher{fs.oldprf, osc}) - } - - for _, prf := range prfs { - // Recover key encryption key. - rb, err := prf.keyGen([]byte(fmt.Sprintf("%s:%d", fs.cfg.Name, mb.index))) - if err != nil { - continue - } - kek, err := genEncryptionKey(prf.StoreCipher, rb) - if err != nil { - continue - } - ns := kek.NonceSize() - seed, err := kek.Open(nil, ekey[:ns], ekey[ns:], nil) - if err != nil { - continue - } - nonce := ekey[:ns] - bek, err := genBlockEncryptionKey(prf.StoreCipher, seed, nonce) - if err != nil { - return err - } - - buf, _ := mb.loadBlock(nil) - bek.XORKeyStream(buf, buf) - // Make sure we can parse with old cipher and key file. - if err = mb.indexCacheBuf(buf); err != nil { - return err - } - // Reset the cache since we just read everything in. - mb.cache = nil - - // Generate new keys. If we error for some reason then we will put - // the old keyfile back. - if err := fs.genEncryptionKeysForBlock(mb); err != nil { - keyFile := filepath.Join(mdir, fmt.Sprintf(keyScan, mb.index)) - fs.writeFileWithOptionalSync(keyFile, ekey, defaultFilePerms) - return err - } - mb.bek.XORKeyStream(buf, buf) - <-dios - err = os.WriteFile(mb.mfn, buf, defaultFilePerms) - dios <- struct{}{} - if err != nil { - return err - } - return nil - } - return fmt.Errorf("unable to recover keys") -} - -// Convert a plaintext block to encrypted. -func (mb *msgBlock) convertToEncrypted() error { - if mb.bek == nil { - return nil - } - buf, err := mb.loadBlock(nil) - if err != nil { - return err - } - if err := mb.indexCacheBuf(buf); err != nil { - // This likely indicates this was already encrypted or corrupt. - mb.cache = nil - return err - } - // Undo cache from above for later. - mb.cache = nil - mb.bek.XORKeyStream(buf, buf) - <-dios - err = os.WriteFile(mb.mfn, buf, defaultFilePerms) - dios <- struct{}{} - if err != nil { - return err - } - return nil -} - -// Return the mb's index. -func (mb *msgBlock) getIndex() uint32 { - mb.mu.RLock() - defer mb.mu.RUnlock() - return mb.index -} - -// Rebuild the state of the blk based on what we have on disk in the N.blk file. -// We will return any lost data, and we will return any delete tombstones we encountered. -func (mb *msgBlock) rebuildState() (*LostStreamData, []uint64, error) { - mb.mu.Lock() - defer mb.mu.Unlock() - return mb.rebuildStateLocked() -} - -// Rebuild the state of the blk based on what we have on disk in the N.blk file. -// Lock should be held. -func (mb *msgBlock) rebuildStateLocked() (*LostStreamData, []uint64, error) { - startLastSeq := atomic.LoadUint64(&mb.last.seq) - - // Remove the .fss file and clear any cache we have set. - mb.clearCacheAndOffset() - - buf, err := mb.loadBlock(nil) - defer recycleMsgBlockBuf(buf) - - if err != nil || len(buf) == 0 { - var ld *LostStreamData - // No data to rebuild from here. - if mb.msgs > 0 { - // We need to declare lost data here. - ld = &LostStreamData{Msgs: make([]uint64, 0, mb.msgs), Bytes: mb.bytes} - firstSeq, lastSeq := atomic.LoadUint64(&mb.first.seq), atomic.LoadUint64(&mb.last.seq) - for seq := firstSeq; seq <= lastSeq; seq++ { - if !mb.dmap.Exists(seq) { - ld.Msgs = append(ld.Msgs, seq) - } - } - // Clear invalid state. We will let this blk be added in here. - mb.msgs, mb.bytes, mb.rbytes, mb.fss = 0, 0, 0, nil - mb.dmap.Empty() - atomic.StoreUint64(&mb.first.seq, atomic.LoadUint64(&mb.last.seq)+1) - } - return ld, nil, err - } - - // Clear state we need to rebuild. - mb.msgs, mb.bytes, mb.rbytes, mb.fss = 0, 0, 0, nil - atomic.StoreUint64(&mb.last.seq, 0) - mb.last.ts = 0 - firstNeedsSet := true - - // Check if we need to decrypt. - if mb.bek != nil && len(buf) > 0 { - // Recreate to reset counter. - mb.bek, err = genBlockEncryptionKey(mb.fs.fcfg.Cipher, mb.seed, mb.nonce) - if err != nil { - return nil, nil, err - } - mb.bek.XORKeyStream(buf, buf) - } - - // Check for compression. - if buf, err = mb.decompressIfNeeded(buf); err != nil { - return nil, nil, err - } - - mb.rbytes = uint64(len(buf)) - - addToDmap := func(seq uint64) { - if seq == 0 { - return - } - mb.dmap.Insert(seq) - } - - var le = binary.LittleEndian - - truncate := func(index uint32) { - var fd *os.File - if mb.mfd != nil { - fd = mb.mfd - } else { - <-dios - fd, err = os.OpenFile(mb.mfn, os.O_RDWR, defaultFilePerms) - dios <- struct{}{} - if err == nil { - defer fd.Close() - } - } - if fd == nil { - return - } - if err := fd.Truncate(int64(index)); err == nil { - // Update our checksum. - if index >= 8 { - var lchk [8]byte - fd.ReadAt(lchk[:], int64(index-8)) - copy(mb.lchk[0:], lchk[:]) - } - fd.Sync() - } - } - - gatherLost := func(lb uint32) *LostStreamData { - var ld LostStreamData - for seq := atomic.LoadUint64(&mb.last.seq) + 1; seq <= startLastSeq; seq++ { - ld.Msgs = append(ld.Msgs, seq) - } - ld.Bytes = uint64(lb) - return &ld - } - - // For tombstones that we find and collect. - var ( - tombstones []uint64 - minTombstoneSeq uint64 - minTombstoneTs int64 - ) - - // To detect gaps from compaction. - var last uint64 - - for index, lbuf := uint32(0), uint32(len(buf)); index < lbuf; { - if index+msgHdrSize > lbuf { - truncate(index) - return gatherLost(lbuf - index), tombstones, nil - } - - hdr := buf[index : index+msgHdrSize] - rl, slen := le.Uint32(hdr[0:]), int(le.Uint16(hdr[20:])) - - hasHeaders := rl&hbit != 0 - var ttl int64 - if mb.fs.ttls != nil && len(hdr) > 0 { - ttl, _ = getMessageTTL(hdr) - } - // Clear any headers bit that could be set. - rl &^= hbit - dlen := int(rl) - msgHdrSize - // Do some quick sanity checks here. - if dlen < 0 || slen > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh { - truncate(index) - return gatherLost(lbuf - index), tombstones, errBadMsg - } - - // Check for checksum failures before additional processing. - data := buf[index+msgHdrSize : index+rl] - if hh := mb.hh; hh != nil { - hh.Reset() - hh.Write(hdr[4:20]) - hh.Write(data[:slen]) - if hasHeaders { - hh.Write(data[slen+4 : dlen-recordHashSize]) - } else { - hh.Write(data[slen : dlen-recordHashSize]) - } - checksum := hh.Sum(nil) - if !bytes.Equal(checksum, data[len(data)-recordHashSize:]) { - truncate(index) - return gatherLost(lbuf - index), tombstones, errBadMsg - } - copy(mb.lchk[0:], checksum) - } - - // Grab our sequence and timestamp. - seq := le.Uint64(hdr[4:]) - ts := int64(le.Uint64(hdr[12:])) - - // Check if this is a delete tombstone. - if seq&tbit != 0 { - seq = seq &^ tbit - // Need to process this here and make sure we have accounted for this properly. - tombstones = append(tombstones, seq) - if minTombstoneSeq == 0 || seq < minTombstoneSeq { - minTombstoneSeq, minTombstoneTs = seq, ts - } - index += rl - continue - } - - fseq := atomic.LoadUint64(&mb.first.seq) - // This is an old erased message, or a new one that we can track. - if seq == 0 || seq&ebit != 0 || seq < fseq { - seq = seq &^ ebit - if seq >= fseq { - atomic.StoreUint64(&mb.last.seq, seq) - mb.last.ts = ts - if mb.msgs == 0 { - atomic.StoreUint64(&mb.first.seq, seq+1) - mb.first.ts = 0 - } else if seq != 0 { - // Only add to dmap if past recorded first seq and non-zero. - addToDmap(seq) - } - } - index += rl - continue - } - - // This is for when we have index info that adjusts for deleted messages - // at the head. So the first.seq will be already set here. If this is larger - // replace what we have with this seq. - if firstNeedsSet && seq >= fseq { - atomic.StoreUint64(&mb.first.seq, seq) - firstNeedsSet, mb.first.ts = false, ts - } - - if !mb.dmap.Exists(seq) { - mb.msgs++ - mb.bytes += uint64(rl) - if mb.fs.ttls != nil && ttl > 0 { - expires := time.Duration(ts) + (time.Second * time.Duration(ttl)) - mb.fs.ttls.Add(seq, int64(expires)) - mb.ttls++ - } - } - - // Check for any gaps from compaction, meaning no ebit entry. - if last > 0 && seq != last+1 { - for dseq := last + 1; dseq < seq; dseq++ { - addToDmap(dseq) - } - } - - // Always set last - last = seq - atomic.StoreUint64(&mb.last.seq, last) - mb.last.ts = ts - - // Advance to next record. - index += rl - } - - // For empty msg blocks make sure we recover last seq correctly based off of first. - // Or if we seem to have no messages but had a tombstone, which we use to remember - // sequences and timestamps now, use that to properly setup the first and last. - if mb.msgs == 0 { - fseq := atomic.LoadUint64(&mb.first.seq) - if fseq > 0 { - atomic.StoreUint64(&mb.last.seq, fseq-1) - } else if fseq == 0 && minTombstoneSeq > 0 { - atomic.StoreUint64(&mb.first.seq, minTombstoneSeq+1) - mb.first.ts = 0 - if mb.last.seq == 0 { - atomic.StoreUint64(&mb.last.seq, minTombstoneSeq) - mb.last.ts = minTombstoneTs - } - } - } - - return nil, tombstones, nil -} - -// For doing warn logging. -// Lock should be held. -func (fs *fileStore) warn(format string, args ...any) { - // No-op if no server configured. - if fs.srv == nil { - return - } - fs.srv.Warnf(fmt.Sprintf("Filestore [%s] %s", fs.cfg.Name, format), args...) -} - -// For doing debug logging. -// Lock should be held. -func (fs *fileStore) debug(format string, args ...any) { - // No-op if no server configured. - if fs.srv == nil { - return - } - fs.srv.Debugf(fmt.Sprintf("Filestore [%s] %s", fs.cfg.Name, format), args...) -} - -// Track local state but ignore timestamps here. -func updateTrackingState(state *StreamState, mb *msgBlock) { - if state.FirstSeq == 0 { - state.FirstSeq = mb.first.seq - } else if mb.first.seq < state.FirstSeq { - state.FirstSeq = mb.first.seq - } - if mb.last.seq > state.LastSeq { - state.LastSeq = mb.last.seq - } - state.Msgs += mb.msgs - state.Bytes += mb.bytes -} - -// Determine if our tracking states are the same. -func trackingStatesEqual(fs, mb *StreamState) bool { - // When a fs is brand new the fs state will have first seq of 0, but tracking mb may have 1. - // If either has a first sequence that is not 0 or 1 we will check if they are the same, otherwise skip. - if (fs.FirstSeq > 1 && mb.FirstSeq > 1) || mb.FirstSeq > 1 { - return fs.Msgs == mb.Msgs && fs.FirstSeq == mb.FirstSeq && fs.LastSeq == mb.LastSeq && fs.Bytes == mb.Bytes - } - return fs.Msgs == mb.Msgs && fs.LastSeq == mb.LastSeq && fs.Bytes == mb.Bytes -} - -// recoverFullState will attempt to receover our last full state and re-process any state changes -// that happened afterwards. -func (fs *fileStore) recoverFullState() (rerr error) { - fs.mu.Lock() - defer fs.mu.Unlock() - - // Check for any left over purged messages. - <-dios - pdir := filepath.Join(fs.fcfg.StoreDir, purgeDir) - if _, err := os.Stat(pdir); err == nil { - os.RemoveAll(pdir) - } - // Grab our stream state file and load it in. - fn := filepath.Join(fs.fcfg.StoreDir, msgDir, streamStreamStateFile) - buf, err := os.ReadFile(fn) - dios <- struct{}{} - - if err != nil { - if !os.IsNotExist(err) { - fs.warn("Could not read stream state file: %v", err) - } - return err - } - - const minLen = 32 - if len(buf) < minLen { - os.Remove(fn) - fs.warn("Stream state too short (%d bytes)", len(buf)) - return errCorruptState - } - - // The highwayhash will be on the end. Check that it still matches. - h := buf[len(buf)-highwayhash.Size64:] - buf = buf[:len(buf)-highwayhash.Size64] - fs.hh.Reset() - fs.hh.Write(buf) - if !bytes.Equal(h, fs.hh.Sum(nil)) { - os.Remove(fn) - fs.warn("Stream state checksum did not match") - return errCorruptState - } - - // Decrypt if needed. - if fs.prf != nil { - // We can be setup for encryption but if this is a snapshot restore we will be missing the keyfile - // since snapshots strip encryption. - if err := fs.recoverAEK(); err == nil { - ns := fs.aek.NonceSize() - buf, err = fs.aek.Open(nil, buf[:ns], buf[ns:], nil) - if err != nil { - fs.warn("Stream state error reading encryption key: %v", err) - return err - } - } - } - - version := buf[1] - if buf[0] != fullStateMagic || version < fullStateMinVersion || version > fullStateVersion { - os.Remove(fn) - fs.warn("Stream state magic and version mismatch") - return errCorruptState - } - - bi := hdrLen - - readU64 := func() uint64 { - if bi < 0 { - return 0 - } - v, n := binary.Uvarint(buf[bi:]) - if n <= 0 { - bi = -1 - return 0 - } - bi += n - return v - } - readI64 := func() int64 { - if bi < 0 { - return 0 - } - v, n := binary.Varint(buf[bi:]) - if n <= 0 { - bi = -1 - return -1 - } - bi += n - return v - } - - setTime := func(t *time.Time, ts int64) { - if ts == 0 { - *t = time.Time{} - } else { - *t = time.Unix(0, ts).UTC() - } - } - - var state StreamState - state.Msgs = readU64() - state.Bytes = readU64() - state.FirstSeq = readU64() - baseTime := readI64() - setTime(&state.FirstTime, baseTime) - state.LastSeq = readU64() - setTime(&state.LastTime, readI64()) - - // Check for per subject info. - if numSubjects := int(readU64()); numSubjects > 0 { - fs.psim, fs.tsl = fs.psim.Empty(), 0 - for i := 0; i < numSubjects; i++ { - if lsubj := int(readU64()); lsubj > 0 { - if bi+lsubj > len(buf) { - os.Remove(fn) - fs.warn("Stream state bad subject len (%d)", lsubj) - return errCorruptState - } - // If we have lots of subjects this will alloc for each one. - // We could reference the underlying buffer, but we could guess wrong if - // number of blocks is large and subjects is low, since we would reference buf. - subj := buf[bi : bi+lsubj] - // We had a bug that could cause memory corruption in the PSIM that could have gotten stored to disk. - // Only would affect subjects, so do quick check. - if !isValidSubject(bytesToString(subj), true) { - os.Remove(fn) - fs.warn("Stream state corrupt subject detected") - return errCorruptState - } - bi += lsubj - psi := psi{total: readU64(), fblk: uint32(readU64())} - if psi.total > 1 { - psi.lblk = uint32(readU64()) - } else { - psi.lblk = psi.fblk - } - fs.psim.Insert(subj, psi) - fs.tsl += lsubj - } - } - } - - // Track the state as represented by the blocks themselves. - var mstate StreamState - - if numBlocks := readU64(); numBlocks > 0 { - lastIndex := int(numBlocks - 1) - fs.blks = make([]*msgBlock, 0, numBlocks) - for i := 0; i < int(numBlocks); i++ { - index, nbytes, fseq, fts, lseq, lts, numDeleted := uint32(readU64()), readU64(), readU64(), readI64(), readU64(), readI64(), readU64() - var ttls uint64 - if version >= 2 { - ttls = readU64() - } - if bi < 0 { - os.Remove(fn) - return errCorruptState - } - mb := fs.initMsgBlock(index) - atomic.StoreUint64(&mb.first.seq, fseq) - atomic.StoreUint64(&mb.last.seq, lseq) - mb.msgs, mb.bytes = lseq-fseq+1, nbytes - mb.first.ts, mb.last.ts = fts+baseTime, lts+baseTime - mb.ttls = ttls - if numDeleted > 0 { - dmap, n, err := avl.Decode(buf[bi:]) - if err != nil { - os.Remove(fn) - fs.warn("Stream state error decoding avl dmap: %v", err) - return errCorruptState - } - mb.dmap = *dmap - if mb.msgs > numDeleted { - mb.msgs -= numDeleted - } else { - mb.msgs = 0 - } - bi += n - } - // Only add in if not empty or the lmb. - if mb.msgs > 0 || i == lastIndex { - fs.addMsgBlock(mb) - updateTrackingState(&mstate, mb) - } else { - // Mark dirty to cleanup. - fs.dirty++ - } - } - } - - // Pull in last block index for the block that had last checksum when we wrote the full state. - blkIndex := uint32(readU64()) - var lchk [8]byte - if bi+len(lchk) > len(buf) { - bi = -1 - } else { - copy(lchk[0:], buf[bi:bi+len(lchk)]) - } - - // Check if we had any errors. - if bi < 0 { - os.Remove(fn) - fs.warn("Stream state has no checksum present") - return errCorruptState - } - - // Move into place our state, msgBlks and subject info. - fs.state = state - - // First let's check the happy path, open the blk file that was the lmb when we created the full state. - // See if we have the last block available. - var matched bool - mb := fs.lmb - if mb == nil || mb.index != blkIndex { - os.Remove(fn) - fs.warn("Stream state block does not exist or index mismatch") - return errCorruptState - } - if _, err := os.Stat(mb.mfn); err != nil && os.IsNotExist(err) { - // If our saved state is past what we see on disk, fallback and rebuild. - if ld, _, _ := mb.rebuildState(); ld != nil { - fs.addLostData(ld) - } - fs.warn("Stream state detected prior state, could not locate msg block %d", blkIndex) - return errPriorState - } - if matched = bytes.Equal(mb.lastChecksum(), lchk[:]); !matched { - // Detected a stale index.db, we didn't write it upon shutdown so can't rely on it being correct. - fs.warn("Stream state outdated, last block has additional entries, will rebuild") - return errPriorState - } - - // We need to see if any blocks exist after our last one even though we matched the last record exactly. - mdir := filepath.Join(fs.fcfg.StoreDir, msgDir) - var dirs []os.DirEntry - - <-dios - if f, err := os.Open(mdir); err == nil { - dirs, _ = f.ReadDir(-1) - f.Close() - } - dios <- struct{}{} - - var index uint32 - for _, fi := range dirs { - // Ensure it's actually a block file, otherwise fmt.Sscanf also matches %d.blk.tmp - if !strings.HasSuffix(fi.Name(), blkSuffix) { - continue - } - if n, err := fmt.Sscanf(fi.Name(), blkScan, &index); err == nil && n == 1 { - if index > blkIndex { - fs.warn("Stream state outdated, found extra blocks, will rebuild") - return errPriorState - } - } - } - - // We check first and last seq and number of msgs and bytes. If there is a difference, - // return and error so we rebuild from the message block state on disk. - if !trackingStatesEqual(&fs.state, &mstate) { - os.Remove(fn) - fs.warn("Stream state encountered internal inconsistency on recover") - return errCorruptState - } - - return nil -} - -func (fs *fileStore) recoverTTLState() error { - // See if we have a timed hash wheel for TTLs. - <-dios - fn := filepath.Join(fs.fcfg.StoreDir, msgDir, ttlStreamStateFile) - buf, err := os.ReadFile(fn) - dios <- struct{}{} - - if err != nil && !os.IsNotExist(err) { - return err - } - - fs.ttls = thw.NewHashWheel() - - if err == nil { - fs.ttlseq, err = fs.ttls.Decode(buf) - if err != nil { - fs.warn("Error decoding TTL state: %s", err) - os.Remove(fn) - } - } - - if fs.ttlseq < fs.state.FirstSeq { - fs.ttlseq = fs.state.FirstSeq - } - - defer fs.resetAgeChk(0) - if fs.state.Msgs > 0 && fs.ttlseq <= fs.state.LastSeq { - fs.warn("TTL state is outdated; attempting to recover using linear scan (seq %d to %d)", fs.ttlseq, fs.state.LastSeq) - var sm StoreMsg - mb := fs.selectMsgBlock(fs.ttlseq) - if mb == nil { - return nil - } - mblseq := atomic.LoadUint64(&mb.last.seq) - for seq := fs.ttlseq; seq <= fs.state.LastSeq; seq++ { - retry: - if mb.ttls == 0 { - // None of the messages in the block have message TTLs so don't - // bother doing anything further with this block, skip to the end. - seq = atomic.LoadUint64(&mb.last.seq) + 1 - } - if seq > mblseq { - // We've reached the end of the loaded block, see if we can continue - // by loading the next one. - mb.tryForceExpireCache() - if mb = fs.selectMsgBlock(seq); mb == nil { - // TODO(nat): Deal with gaps properly. Right now this will be - // probably expensive on CPU. - continue - } - mblseq = atomic.LoadUint64(&mb.last.seq) - // At this point we've loaded another block, so let's go back to the - // beginning and see if we need to skip this one too. - goto retry - } - msg, _, err := mb.fetchMsg(seq, &sm) - if err != nil { - fs.warn("Error loading msg seq %d for recovering TTL: %s", seq, err) - continue - } - if len(msg.hdr) == 0 { - continue - } - if ttl, _ := getMessageTTL(msg.hdr); ttl > 0 { - expires := time.Duration(msg.ts) + (time.Second * time.Duration(ttl)) - fs.ttls.Add(seq, int64(expires)) - if seq > fs.ttlseq { - fs.ttlseq = seq - } - } - } - } - return nil -} - -// Grabs last checksum for the named block file. -// Takes into account encryption etc. -func (mb *msgBlock) lastChecksum() []byte { - f, err := mb.openBlock() - if err != nil { - return nil - } - defer f.Close() - - var lchk [8]byte - if fi, _ := f.Stat(); fi != nil { - mb.rbytes = uint64(fi.Size()) - } - if mb.rbytes < checksumSize { - return lchk[:] - } - // Encrypted? - // Check for encryption, we do not load keys on startup anymore so might need to load them here. - if mb.fs != nil && mb.fs.prf != nil && (mb.aek == nil || mb.bek == nil) { - if err := mb.fs.loadEncryptionForMsgBlock(mb); err != nil { - return nil - } - } - if mb.bek != nil { - if buf, _ := mb.loadBlock(nil); len(buf) >= checksumSize { - bek, err := genBlockEncryptionKey(mb.fs.fcfg.Cipher, mb.seed, mb.nonce) - if err != nil { - return nil - } - mb.bek = bek - mb.bek.XORKeyStream(buf, buf) - copy(lchk[0:], buf[len(buf)-checksumSize:]) - } - } else { - f.ReadAt(lchk[:], int64(mb.rbytes)-checksumSize) - } - return lchk[:] -} - -// This will make sure we clean up old idx and fss files. -func (fs *fileStore) cleanupOldMeta() { - fs.mu.RLock() - mdir := filepath.Join(fs.fcfg.StoreDir, msgDir) - fs.mu.RUnlock() - - <-dios - f, err := os.Open(mdir) - dios <- struct{}{} - if err != nil { - return - } - - dirs, _ := f.ReadDir(-1) - f.Close() - - const ( - minLen = 4 - idxSuffix = ".idx" - fssSuffix = ".fss" - ) - for _, fi := range dirs { - if name := fi.Name(); strings.HasSuffix(name, idxSuffix) || strings.HasSuffix(name, fssSuffix) { - os.Remove(filepath.Join(mdir, name)) - } - } -} - -func (fs *fileStore) recoverMsgs() error { - fs.mu.Lock() - defer fs.mu.Unlock() - - // Check for any left over purged messages. - <-dios - pdir := filepath.Join(fs.fcfg.StoreDir, purgeDir) - if _, err := os.Stat(pdir); err == nil { - os.RemoveAll(pdir) - } - mdir := filepath.Join(fs.fcfg.StoreDir, msgDir) - f, err := os.Open(mdir) - if err != nil { - dios <- struct{}{} - return errNotReadable - } - dirs, err := f.ReadDir(-1) - f.Close() - dios <- struct{}{} - - if err != nil { - return errNotReadable - } - - indices := make(sort.IntSlice, 0, len(dirs)) - var index int - for _, fi := range dirs { - // Ensure it's actually a block file, otherwise fmt.Sscanf also matches %d.blk.tmp - if !strings.HasSuffix(fi.Name(), blkSuffix) { - continue - } - if n, err := fmt.Sscanf(fi.Name(), blkScan, &index); err == nil && n == 1 { - indices = append(indices, index) - } - } - indices.Sort() - - // Recover all of the msg blocks. - // We now guarantee they are coming in order. - for _, index := range indices { - if mb, err := fs.recoverMsgBlock(uint32(index)); err == nil && mb != nil { - // This is a truncate block with possibly no index. If the OS got shutdown - // out from underneath of us this is possible. - if mb.first.seq == 0 { - mb.dirtyCloseWithRemove(true) - fs.removeMsgBlockFromList(mb) - continue - } - if fseq := atomic.LoadUint64(&mb.first.seq); fs.state.FirstSeq == 0 || fseq < fs.state.FirstSeq { - fs.state.FirstSeq = fseq - if mb.first.ts == 0 { - fs.state.FirstTime = time.Time{} - } else { - fs.state.FirstTime = time.Unix(0, mb.first.ts).UTC() - } - } - if lseq := atomic.LoadUint64(&mb.last.seq); lseq > fs.state.LastSeq { - fs.state.LastSeq = lseq - if mb.last.ts == 0 { - fs.state.LastTime = time.Time{} - } else { - fs.state.LastTime = time.Unix(0, mb.last.ts).UTC() - } - } - fs.state.Msgs += mb.msgs - fs.state.Bytes += mb.bytes - } else { - return err - } - } - - if len(fs.blks) > 0 { - fs.lmb = fs.blks[len(fs.blks)-1] - } else { - _, err = fs.newMsgBlockForWrite() - } - - // Check if we encountered any lost data. - if fs.ld != nil { - var emptyBlks []*msgBlock - for _, mb := range fs.blks { - if mb.msgs == 0 && mb.rbytes == 0 { - emptyBlks = append(emptyBlks, mb) - } - } - for _, mb := range emptyBlks { - // Need the mb lock here. - mb.mu.Lock() - fs.removeMsgBlock(mb) - mb.mu.Unlock() - } - } - - if err != nil { - return err - } - - // Check for keyfiles orphans. - if kms, err := filepath.Glob(filepath.Join(mdir, keyScanAll)); err == nil && len(kms) > 0 { - valid := make(map[uint32]bool) - for _, mb := range fs.blks { - valid[mb.index] = true - } - for _, fn := range kms { - var index uint32 - shouldRemove := true - if n, err := fmt.Sscanf(filepath.Base(fn), keyScan, &index); err == nil && n == 1 && valid[index] { - shouldRemove = false - } - if shouldRemove { - os.Remove(fn) - } - } - } - - return nil -} - -// Will expire msgs that have aged out on restart. -// We will treat this differently in case we have a recovery -// that will expire alot of messages on startup. -// Should only be called on startup. -func (fs *fileStore) expireMsgsOnRecover() error { - if fs.state.Msgs == 0 { - return nil - } - - var minAge = time.Now().UnixNano() - int64(fs.cfg.MaxAge) - var purged, bytes uint64 - var deleted int - var nts int64 - - // If we expire all make sure to write out a tombstone. Need to be done by hand here, - // usually taken care of by fs.removeMsgBlock() but we do not call that here. - var last msgId - - deleteEmptyBlock := func(mb *msgBlock) error { - // If we are the last keep state to remember first/last sequence. - // Do this part by hand since not deleting one by one. - if mb == fs.lmb { - last.seq = atomic.LoadUint64(&mb.last.seq) - last.ts = mb.last.ts - } - // Make sure we do subject cleanup as well. - mb.ensurePerSubjectInfoLoaded() - mb.fss.IterOrdered(func(bsubj []byte, ss *SimpleState) bool { - subj := bytesToString(bsubj) - for i := uint64(0); i < ss.Msgs; i++ { - fs.removePerSubject(subj, false) - } - return true - }) - err := mb.dirtyCloseWithRemove(true) - if isPermissionError(err) { - return err - } - deleted++ - return nil - } - - for _, mb := range fs.blks { - mb.mu.Lock() - if minAge < mb.first.ts { - nts = mb.first.ts - mb.mu.Unlock() - break - } - // Can we remove whole block here? - // TODO(nat): We can't do this with LimitsTTL as we have no way to know - // if we're throwing away real messages or other tombstones without - // loading them, so in this case we'll fall through to the "slow path". - // There might be a better way of handling this though. - if mb.fs.cfg.SubjectDeleteMarkerTTL <= 0 && mb.last.ts <= minAge { - purged += mb.msgs - bytes += mb.bytes - err := deleteEmptyBlock(mb) - mb.mu.Unlock() - if isPermissionError(err) { - return err - } - continue - } - - // If we are here we have to process the interior messages of this blk. - // This will load fss as well. - if err := mb.loadMsgsWithLock(); err != nil { - mb.mu.Unlock() - break - } - - var smv StoreMsg - var needNextFirst bool - - // Walk messages and remove if expired. - fseq, lseq := atomic.LoadUint64(&mb.first.seq), atomic.LoadUint64(&mb.last.seq) - for seq := fseq; seq <= lseq; seq++ { - sm, err := mb.cacheLookup(seq, &smv) - // Process interior deleted msgs. - if err == errDeletedMsg { - // Update dmap. - if mb.dmap.Exists(seq) { - mb.dmap.Delete(seq) - } - // Keep this updated just in case since we are removing dmap entries. - atomic.StoreUint64(&mb.first.seq, seq) - needNextFirst = true - continue - } - // Break on other errors. - if err != nil || sm == nil { - atomic.StoreUint64(&mb.first.seq, seq) - needNextFirst = true - break - } - - // No error and sm != nil from here onward. - - // Check for done. - if minAge < sm.ts { - atomic.StoreUint64(&mb.first.seq, sm.seq) - mb.first.ts = sm.ts - needNextFirst = false - nts = sm.ts - break - } - - // Delete the message here. - if mb.msgs > 0 { - atomic.StoreUint64(&mb.first.seq, seq) - needNextFirst = true - sz := fileStoreMsgSize(sm.subj, sm.hdr, sm.msg) - if sz > mb.bytes { - sz = mb.bytes - } - mb.bytes -= sz - bytes += sz - mb.msgs-- - purged++ - } - // Update fss - // Make sure we have fss loaded. - mb.removeSeqPerSubject(sm.subj, seq) - fs.removePerSubject(sm.subj, fs.cfg.SubjectDeleteMarkerTTL > 0 && len(getHeader(JSMarkerReason, sm.hdr)) == 0) - } - // Make sure we have a proper next first sequence. - if needNextFirst { - mb.selectNextFirst() - } - // Check if empty after processing, could happen if tail of messages are all deleted. - if mb.msgs == 0 { - deleteEmptyBlock(mb) - } - mb.mu.Unlock() - break - } - - if nts > 0 { - // Make sure to set age check based on this value. - fs.resetAgeChk(nts - minAge) - } - - if deleted > 0 { - // Update block map. - if fs.bim != nil { - for _, mb := range fs.blks[:deleted] { - delete(fs.bim, mb.index) - } - } - // Update blks slice. - fs.blks = copyMsgBlocks(fs.blks[deleted:]) - if lb := len(fs.blks); lb == 0 { - fs.lmb = nil - } else { - fs.lmb = fs.blks[lb-1] - } - } - // Update top level accounting. - if purged < fs.state.Msgs { - fs.state.Msgs -= purged - } else { - fs.state.Msgs = 0 - } - if bytes < fs.state.Bytes { - fs.state.Bytes -= bytes - } else { - fs.state.Bytes = 0 - } - // Make sure to we properly set the fs first sequence and timestamp. - fs.selectNextFirst() - - // Check if we have no messages and blocks left. - if fs.lmb == nil && last.seq != 0 { - if lmb, _ := fs.newMsgBlockForWrite(); lmb != nil { - fs.writeTombstone(last.seq, last.ts) - } - // Clear any global subject state. - fs.psim, fs.tsl = fs.psim.Empty(), 0 - } - - // If we have pending markers, then create them. - fs.subjectDeleteMarkersAfterOperation(JSMarkerReasonMaxAge) - - // If we purged anything, make sure we kick flush state loop. - if purged > 0 { - fs.dirty++ - } - return nil -} - -func copyMsgBlocks(src []*msgBlock) []*msgBlock { - if src == nil { - return nil - } - dst := make([]*msgBlock, len(src)) - copy(dst, src) - return dst -} - -// GetSeqFromTime looks for the first sequence number that has -// the message with >= timestamp. -// FIXME(dlc) - inefficient, and dumb really. Make this better. -func (fs *fileStore) GetSeqFromTime(t time.Time) uint64 { - fs.mu.RLock() - lastSeq := fs.state.LastSeq - closed := fs.closed - fs.mu.RUnlock() - - if closed { - return 0 - } - - mb := fs.selectMsgBlockForStart(t) - if mb == nil { - return lastSeq + 1 - } - - fseq := atomic.LoadUint64(&mb.first.seq) - lseq := atomic.LoadUint64(&mb.last.seq) - - var smv StoreMsg - - // Linear search, hence the dumb part.. - ts := t.UnixNano() - for seq := fseq; seq <= lseq; seq++ { - sm, _, _ := mb.fetchMsg(seq, &smv) - if sm != nil && sm.ts >= ts { - return sm.seq - } - } - return 0 -} - -// Find the first matching message against a sublist. -func (mb *msgBlock) firstMatchingMulti(sl *Sublist, start uint64, sm *StoreMsg) (*StoreMsg, bool, error) { - mb.mu.Lock() - var didLoad bool - var updateLLTS bool - defer func() { - if updateLLTS { - mb.llts = time.Now().UnixNano() - } - mb.mu.Unlock() - }() - - // Need messages loaded from here on out. - if mb.cacheNotLoaded() { - if err := mb.loadMsgsWithLock(); err != nil { - return nil, false, err - } - didLoad = true - } - - // Make sure to start at mb.first.seq if fseq < mb.first.seq - if seq := atomic.LoadUint64(&mb.first.seq); seq > start { - start = seq - } - lseq := atomic.LoadUint64(&mb.last.seq) - - if sm == nil { - sm = new(StoreMsg) - } - - // If the FSS state has fewer entries than sequences in the linear scan, - // then use intersection instead as likely going to be cheaper. This will - // often be the case with high numbers of deletes, as well as a smaller - // number of subjects in the block. - if uint64(mb.fss.Size()) < lseq-start { - // If there are no subject matches then this is effectively no-op. - hseq := uint64(math.MaxUint64) - IntersectStree(mb.fss, sl, func(subj []byte, ss *SimpleState) { - if ss.firstNeedsUpdate || ss.lastNeedsUpdate { - // mb is already loaded into the cache so should be fast-ish. - mb.recalculateForSubj(bytesToString(subj), ss) - } - first := ss.First - if start > first { - first = start - } - if first > ss.Last || first >= hseq { - // The start cutoff is after the last sequence for this subject, - // or we think we already know of a subject with an earlier msg - // than our first seq for this subject. - return - } - if first == ss.First { - // If the start floor is below where this subject starts then we can - // short-circuit, avoiding needing to scan for the next message. - if fsm, err := mb.cacheLookup(ss.First, sm); err == nil { - sm = fsm - hseq = ss.First - } - return - } - for seq := first; seq <= ss.Last; seq++ { - // Otherwise we have a start floor that intersects where this subject - // has messages in the block, so we need to walk up until we find a - // message matching the subject. - if mb.dmap.Exists(seq) { - // Optimisation to avoid calling cacheLookup which hits time.Now(). - // Instead we will update it only once in a defer. - updateLLTS = true - continue - } - llseq := mb.llseq - fsm, err := mb.cacheLookup(seq, sm) - if err != nil { - continue - } - updateLLTS = false // cacheLookup already updated it. - if sl.HasInterest(fsm.subj) { - hseq = seq - sm = fsm - break - } - // If we are here we did not match, so put the llseq back. - mb.llseq = llseq - } - }) - if hseq < uint64(math.MaxUint64) && sm != nil { - return sm, didLoad, nil - } - } else { - for seq := start; seq <= lseq; seq++ { - if mb.dmap.Exists(seq) { - // Optimisation to avoid calling cacheLookup which hits time.Now(). - // Instead we will update it only once in a defer. - updateLLTS = true - continue - } - llseq := mb.llseq - fsm, err := mb.cacheLookup(seq, sm) - if err != nil { - continue - } - expireOk := seq == lseq && mb.llseq == seq - updateLLTS = false // cacheLookup already updated it. - if sl.HasInterest(fsm.subj) { - return fsm, expireOk, nil - } - // If we are here we did not match, so put the llseq back. - mb.llseq = llseq - } - } - - return nil, didLoad, ErrStoreMsgNotFound -} - -// Find the first matching message. -// fs lock should be held. -func (mb *msgBlock) firstMatching(filter string, wc bool, start uint64, sm *StoreMsg) (*StoreMsg, bool, error) { - mb.mu.Lock() - var updateLLTS bool - defer func() { - if updateLLTS { - mb.llts = time.Now().UnixNano() - } - mb.mu.Unlock() - }() - - fseq, isAll, subs := start, filter == _EMPTY_ || filter == fwcs, []string{filter} - - var didLoad bool - if mb.fssNotLoaded() { - // Make sure we have fss loaded. - mb.loadMsgsWithLock() - didLoad = true - } - // Mark fss activity. - mb.lsts = time.Now().UnixNano() - - if filter == _EMPTY_ { - filter = fwcs - wc = true - } - - // If we only have 1 subject currently and it matches our filter we can also set isAll. - if !isAll && mb.fss.Size() == 1 { - if !wc { - _, isAll = mb.fss.Find(stringToBytes(filter)) - } else { - // Since mb.fss.Find won't work if filter is a wildcard, need to use Match instead. - mb.fss.Match(stringToBytes(filter), func(subject []byte, _ *SimpleState) { - isAll = true - }) - } - } - // Make sure to start at mb.first.seq if fseq < mb.first.seq - if seq := atomic.LoadUint64(&mb.first.seq); seq > fseq { - fseq = seq - } - lseq := atomic.LoadUint64(&mb.last.seq) - - // Optionally build the isMatch for wildcard filters. - _tsa, _fsa := [32]string{}, [32]string{} - tsa, fsa := _tsa[:0], _fsa[:0] - var isMatch func(subj string) bool - // Decide to build. - if wc { - fsa = tokenizeSubjectIntoSlice(fsa[:0], filter) - isMatch = func(subj string) bool { - tsa = tokenizeSubjectIntoSlice(tsa[:0], subj) - return isSubsetMatchTokenized(tsa, fsa) - } - } - - subjs := mb.fs.cfg.Subjects - // If isAll or our single filter matches the filter arg do linear scan. - doLinearScan := isAll || (wc && len(subjs) == 1 && subjs[0] == filter) - // If we do not think we should do a linear scan check how many fss we - // would need to scan vs the full range of the linear walk. Optimize for - // 25th quantile of a match in a linear walk. Filter should be a wildcard. - // We should consult fss if our cache is not loaded and we only have fss loaded. - if !doLinearScan && wc && mb.cacheAlreadyLoaded() { - doLinearScan = mb.fss.Size()*4 > int(lseq-fseq) - } - - if !doLinearScan { - // If we have a wildcard match against all tracked subjects we know about. - if wc { - subs = subs[:0] - mb.fss.Match(stringToBytes(filter), func(bsubj []byte, _ *SimpleState) { - subs = append(subs, string(bsubj)) - }) - // Check if we matched anything - if len(subs) == 0 { - return nil, didLoad, ErrStoreMsgNotFound - } - } - fseq = lseq + 1 - for _, subj := range subs { - ss, _ := mb.fss.Find(stringToBytes(subj)) - if ss != nil && (ss.firstNeedsUpdate || ss.lastNeedsUpdate) { - mb.recalculateForSubj(subj, ss) - } - if ss == nil || start > ss.Last || ss.First >= fseq { - continue - } - if ss.First < start { - fseq = start - } else { - fseq = ss.First - } - } - } - - if fseq > lseq { - return nil, didLoad, ErrStoreMsgNotFound - } - - // If we guess to not do a linear scan, but the above resulted in alot of subs that will - // need to be checked for every scanned message, revert. - // TODO(dlc) - we could memoize the subs across calls. - if !doLinearScan && len(subs) > int(lseq-fseq) { - doLinearScan = true - } - - // Need messages loaded from here on out. - if mb.cacheNotLoaded() { - if err := mb.loadMsgsWithLock(); err != nil { - return nil, false, err - } - didLoad = true - } - - if sm == nil { - sm = new(StoreMsg) - } - - for seq := fseq; seq <= lseq; seq++ { - if mb.dmap.Exists(seq) { - // Optimisation to avoid calling cacheLookup which hits time.Now(). - // Instead we will update it only once in a defer. - updateLLTS = true - continue - } - llseq := mb.llseq - fsm, err := mb.cacheLookup(seq, sm) - if err != nil { - if err == errPartialCache || err == errNoCache { - return nil, false, err - } - continue - } - updateLLTS = false // cacheLookup already updated it. - expireOk := seq == lseq && mb.llseq == seq - if isAll { - return fsm, expireOk, nil - } - if doLinearScan { - if wc && isMatch(sm.subj) { - return fsm, expireOk, nil - } else if !wc && fsm.subj == filter { - return fsm, expireOk, nil - } - } else { - for _, subj := range subs { - if fsm.subj == subj { - return fsm, expireOk, nil - } - } - } - // If we are here we did not match, so put the llseq back. - mb.llseq = llseq - } - - return nil, didLoad, ErrStoreMsgNotFound -} - -// This will traverse a message block and generate the filtered pending. -func (mb *msgBlock) filteredPending(subj string, wc bool, seq uint64) (total, first, last uint64) { - mb.mu.Lock() - defer mb.mu.Unlock() - return mb.filteredPendingLocked(subj, wc, seq) -} - -// This will traverse a message block and generate the filtered pending. -// Lock should be held. -func (mb *msgBlock) filteredPendingLocked(filter string, wc bool, sseq uint64) (total, first, last uint64) { - isAll := filter == _EMPTY_ || filter == fwcs - - // First check if we can optimize this part. - // This means we want all and the starting sequence was before this block. - if isAll { - if fseq := atomic.LoadUint64(&mb.first.seq); sseq <= fseq { - return mb.msgs, fseq, atomic.LoadUint64(&mb.last.seq) - } - } - - if filter == _EMPTY_ { - filter = fwcs - wc = true - } - - update := func(ss *SimpleState) { - total += ss.Msgs - if first == 0 || ss.First < first { - first = ss.First - } - if ss.Last > last { - last = ss.Last - } - } - - // Make sure we have fss loaded. - mb.ensurePerSubjectInfoLoaded() - - _tsa, _fsa := [32]string{}, [32]string{} - tsa, fsa := _tsa[:0], _fsa[:0] - fsa = tokenizeSubjectIntoSlice(fsa[:0], filter) - - // 1. See if we match any subs from fss. - // 2. If we match and the sseq is past ss.Last then we can use meta only. - // 3. If we match and we need to do a partial, break and clear any totals and do a full scan like num pending. - - isMatch := func(subj string) bool { - if !wc { - return subj == filter - } - tsa = tokenizeSubjectIntoSlice(tsa[:0], subj) - return isSubsetMatchTokenized(tsa, fsa) - } - - var havePartial bool - mb.fss.Match(stringToBytes(filter), func(bsubj []byte, ss *SimpleState) { - if havePartial { - // If we already found a partial then don't do anything else. - return - } - if ss.firstNeedsUpdate || ss.lastNeedsUpdate { - mb.recalculateForSubj(bytesToString(bsubj), ss) - } - if sseq <= ss.First { - update(ss) - } else if sseq <= ss.Last { - // We matched but its a partial. - havePartial = true - } - }) - - // If we did not encounter any partials we can return here. - if !havePartial { - return total, first, last - } - - // If we are here we need to scan the msgs. - // Clear what we had. - total, first, last = 0, 0, 0 - - // If we load the cache for a linear scan we want to expire that cache upon exit. - var shouldExpire bool - if mb.cacheNotLoaded() { - mb.loadMsgsWithLock() - shouldExpire = true - } - - var smv StoreMsg - for seq, lseq := sseq, atomic.LoadUint64(&mb.last.seq); seq <= lseq; seq++ { - sm, _ := mb.cacheLookup(seq, &smv) - if sm == nil { - continue - } - if isAll || isMatch(sm.subj) { - total++ - if first == 0 || seq < first { - first = seq - } - if seq > last { - last = seq - } - } - } - // If we loaded this block for this operation go ahead and expire it here. - if shouldExpire { - mb.tryForceExpireCacheLocked() - } - - return total, first, last -} - -// FilteredState will return the SimpleState associated with the filtered subject and a proposed starting sequence. -func (fs *fileStore) FilteredState(sseq uint64, subj string) SimpleState { - fs.mu.RLock() - defer fs.mu.RUnlock() - - lseq := fs.state.LastSeq - if sseq < fs.state.FirstSeq { - sseq = fs.state.FirstSeq - } - - // Returned state. - var ss SimpleState - - // If past the end no results. - if sseq > lseq { - // Make sure we track sequences - ss.First = fs.state.FirstSeq - ss.Last = fs.state.LastSeq - return ss - } - - // If we want all msgs that match we can shortcircuit. - // TODO(dlc) - This can be extended for all cases but would - // need to be careful on total msgs calculations etc. - if sseq == fs.state.FirstSeq { - fs.numFilteredPending(subj, &ss) - } else { - wc := subjectHasWildcard(subj) - // Tracking subject state. - // TODO(dlc) - Optimize for 2.10 with avl tree and no atomics per block. - for _, mb := range fs.blks { - // Skip blocks that are less than our starting sequence. - if sseq > atomic.LoadUint64(&mb.last.seq) { - continue - } - t, f, l := mb.filteredPending(subj, wc, sseq) - ss.Msgs += t - if ss.First == 0 || (f > 0 && f < ss.First) { - ss.First = f - } - if l > ss.Last { - ss.Last = l - } - } - } - - return ss -} - -// This is used to see if we can selectively jump start blocks based on filter subject and a starting block index. -// Will return -1 and ErrStoreEOF if no matches at all or no more from where we are. -func (fs *fileStore) checkSkipFirstBlock(filter string, wc bool, bi int) (int, error) { - // If we match everything, just move to next blk. - if filter == _EMPTY_ || filter == fwcs { - return bi + 1, nil - } - // Move through psim to gather start and stop bounds. - start, stop := uint32(math.MaxUint32), uint32(0) - if wc { - fs.psim.Match(stringToBytes(filter), func(_ []byte, psi *psi) { - if psi.fblk < start { - start = psi.fblk - } - if psi.lblk > stop { - stop = psi.lblk - } - }) - } else if psi, ok := fs.psim.Find(stringToBytes(filter)); ok { - start, stop = psi.fblk, psi.lblk - } - // Nothing was found. - if start == uint32(math.MaxUint32) { - return -1, ErrStoreEOF - } - // Can not be nil so ok to inline dereference. - mbi := fs.blks[bi].getIndex() - // All matching msgs are behind us. - // Less than AND equal is important because we were called because we missed searching bi. - if stop <= mbi { - return -1, ErrStoreEOF - } - // If start is > index return dereference of fs.blks index. - if start > mbi { - if mb := fs.bim[start]; mb != nil { - ni, _ := fs.selectMsgBlockWithIndex(atomic.LoadUint64(&mb.last.seq)) - return ni, nil - } - } - // Otherwise just bump to the next one. - return bi + 1, nil -} - -// Optimized way for getting all num pending matching a filter subject. -// Lock should be held. -func (fs *fileStore) numFilteredPending(filter string, ss *SimpleState) { - fs.numFilteredPendingWithLast(filter, true, ss) -} - -// Optimized way for getting all num pending matching a filter subject and first sequence only. -// Lock should be held. -func (fs *fileStore) numFilteredPendingNoLast(filter string, ss *SimpleState) { - fs.numFilteredPendingWithLast(filter, false, ss) -} - -// Optimized way for getting all num pending matching a filter subject. -// Optionally look up last sequence. Sometimes do not need last and this avoids cost. -// Read lock should be held. -func (fs *fileStore) numFilteredPendingWithLast(filter string, last bool, ss *SimpleState) { - isAll := filter == _EMPTY_ || filter == fwcs - - // If isAll we do not need to do anything special to calculate the first and last and total. - if isAll { - ss.First = fs.state.FirstSeq - ss.Last = fs.state.LastSeq - ss.Msgs = fs.state.Msgs - return - } - // Always reset. - ss.First, ss.Last, ss.Msgs = 0, 0, 0 - - // We do need to figure out the first and last sequences. - wc := subjectHasWildcard(filter) - start, stop := uint32(math.MaxUint32), uint32(0) - - if wc { - fs.psim.Match(stringToBytes(filter), func(_ []byte, psi *psi) { - ss.Msgs += psi.total - // Keep track of start and stop indexes for this subject. - if psi.fblk < start { - start = psi.fblk - } - if psi.lblk > stop { - stop = psi.lblk - } - }) - } else if psi, ok := fs.psim.Find(stringToBytes(filter)); ok { - ss.Msgs += psi.total - start, stop = psi.fblk, psi.lblk - } - - // Did not find anything. - if stop == 0 { - return - } - - // Do start - mb := fs.bim[start] - if mb != nil { - _, f, _ := mb.filteredPending(filter, wc, 0) - ss.First = f - } - - if ss.First == 0 { - // This is a miss. This can happen since psi.fblk is lazy. - // We will make sure to update fblk. - - // Hold this outside loop for psim fblk updates when done. - i := start + 1 - for ; i <= stop; i++ { - mb := fs.bim[i] - if mb == nil { - continue - } - if _, f, _ := mb.filteredPending(filter, wc, 0); f > 0 { - ss.First = f - break - } - } - // Update fblk since fblk was outdated. - // We only require read lock here as that is desirable, - // so we need to do this in a go routine to acquire write lock. - go func() { - fs.mu.Lock() - defer fs.mu.Unlock() - if !wc { - if info, ok := fs.psim.Find(stringToBytes(filter)); ok { - if i > info.fblk { - info.fblk = i - } - } - } else { - fs.psim.Match(stringToBytes(filter), func(subj []byte, psi *psi) { - if i > psi.fblk { - psi.fblk = i - } - }) - } - }() - } - // Now gather last sequence if asked to do so. - if last { - if mb = fs.bim[stop]; mb != nil { - _, _, l := mb.filteredPending(filter, wc, 0) - ss.Last = l - } - } -} - -// SubjectsState returns a map of SimpleState for all matching subjects. -func (fs *fileStore) SubjectsState(subject string) map[string]SimpleState { - fs.mu.RLock() - defer fs.mu.RUnlock() - - if fs.state.Msgs == 0 || fs.noTrackSubjects() { - return nil - } - - if subject == _EMPTY_ { - subject = fwcs - } - - start, stop := fs.blks[0], fs.lmb - // We can short circuit if not a wildcard using psim for start and stop. - if !subjectHasWildcard(subject) { - info, ok := fs.psim.Find(stringToBytes(subject)) - if !ok { - return nil - } - if f := fs.bim[info.fblk]; f != nil { - start = f - } - if l := fs.bim[info.lblk]; l != nil { - stop = l - } - } - - // Aggregate fss. - fss := make(map[string]SimpleState) - var startFound bool - - for _, mb := range fs.blks { - if !startFound { - if mb != start { - continue - } - startFound = true - } - - mb.mu.Lock() - var shouldExpire bool - if mb.fssNotLoaded() { - // Make sure we have fss loaded. - mb.loadMsgsWithLock() - shouldExpire = true - } - // Mark fss activity. - mb.lsts = time.Now().UnixNano() - mb.fss.Match(stringToBytes(subject), func(bsubj []byte, ss *SimpleState) { - subj := string(bsubj) - if ss.firstNeedsUpdate || ss.lastNeedsUpdate { - mb.recalculateForSubj(subj, ss) - } - oss := fss[subj] - if oss.First == 0 { // New - fss[subj] = *ss - } else { - // Merge here. - oss.Last, oss.Msgs = ss.Last, oss.Msgs+ss.Msgs - fss[subj] = oss - } - }) - if shouldExpire { - // Expire this cache before moving on. - mb.tryForceExpireCacheLocked() - } - mb.mu.Unlock() - - if mb == stop { - break - } - } - - return fss -} - -// MultiLastSeqs will return a sorted list of sequences that match all subjects presented in filters. -// We will not exceed the maxSeq, which if 0 becomes the store's last sequence. -func (fs *fileStore) MultiLastSeqs(filters []string, maxSeq uint64, maxAllowed int) ([]uint64, error) { - fs.mu.RLock() - defer fs.mu.RUnlock() - - if fs.state.Msgs == 0 || fs.noTrackSubjects() { - return nil, nil - } - - lastBlkIndex := len(fs.blks) - 1 - lastMB := fs.blks[lastBlkIndex] - - // Implied last sequence. - if maxSeq == 0 { - maxSeq = fs.state.LastSeq - } else { - // Udate last mb index if not last seq. - lastBlkIndex, lastMB = fs.selectMsgBlockWithIndex(maxSeq) - } - //Make sure non-nil - if lastMB == nil { - return nil, nil - } - - // Grab our last mb index (not same as blk index). - lastMB.mu.RLock() - lastMBIndex := lastMB.index - lastMB.mu.RUnlock() - - subs := make(map[string]*psi) - ltSeen := make(map[string]uint32) - for _, filter := range filters { - fs.psim.Match(stringToBytes(filter), func(subj []byte, psi *psi) { - s := string(subj) - subs[s] = psi - if psi.lblk < lastMBIndex { - ltSeen[s] = psi.lblk - } - }) - } - - // If all subjects have a lower last index, select the largest for our walk backwards. - if len(ltSeen) == len(subs) { - max := uint32(0) - for _, mbi := range ltSeen { - if mbi > max { - max = mbi - } - } - lastMB = fs.bim[max] - } - - // Collect all sequences needed. - seqs := make([]uint64, 0, len(subs)) - for i, lnf := lastBlkIndex, false; i >= 0; i-- { - if len(subs) == 0 { - break - } - mb := fs.blks[i] - if !lnf { - if mb != lastMB { - continue - } - lnf = true - } - // We can start properly looking here. - mb.mu.Lock() - mb.ensurePerSubjectInfoLoaded() - for subj, psi := range subs { - if ss, ok := mb.fss.Find(stringToBytes(subj)); ok && ss != nil { - if ss.Last <= maxSeq { - seqs = append(seqs, ss.Last) - delete(subs, subj) - } else { - // Need to search for it since last is > maxSeq. - if mb.cacheNotLoaded() { - mb.loadMsgsWithLock() - } - var smv StoreMsg - fseq := atomic.LoadUint64(&mb.first.seq) - for seq := maxSeq; seq >= fseq; seq-- { - sm, _ := mb.cacheLookup(seq, &smv) - if sm == nil || sm.subj != subj { - continue - } - seqs = append(seqs, sm.seq) - delete(subs, subj) - break - } - } - } else if mb.index <= psi.fblk { - // Track which subs are no longer applicable, meaning we will not find a valid msg at this point. - delete(subs, subj) - } - // TODO(dlc) we could track lblk like above in case some subs are very far apart. - // Not too bad if fss loaded since we will skip over quickly with it loaded, but might be worth it. - } - mb.mu.Unlock() - - // If maxAllowed was sepcified check that we will not exceed that. - if maxAllowed > 0 && len(seqs) > maxAllowed { - return nil, ErrTooManyResults - } - - } - if len(seqs) == 0 { - return nil, nil - } - slices.Sort(seqs) - return seqs, nil -} - -// NumPending will return the number of pending messages matching the filter subject starting at sequence. -// Optimized for stream num pending calculations for consumers. -func (fs *fileStore) NumPending(sseq uint64, filter string, lastPerSubject bool) (total, validThrough uint64) { - fs.mu.RLock() - defer fs.mu.RUnlock() - - // This can always be last for these purposes. - validThrough = fs.state.LastSeq - - if fs.state.Msgs == 0 || sseq > fs.state.LastSeq { - return 0, validThrough - } - - // If sseq is less then our first set to first. - if sseq < fs.state.FirstSeq { - sseq = fs.state.FirstSeq - } - // Track starting for both block for the sseq and staring block that matches any subject. - var seqStart int - // See if we need to figure out starting block per sseq. - if sseq > fs.state.FirstSeq { - // This should not, but can return -1, so make sure we check to avoid panic below. - if seqStart, _ = fs.selectMsgBlockWithIndex(sseq); seqStart < 0 { - seqStart = 0 - } - } - - isAll := filter == _EMPTY_ || filter == fwcs - if isAll && filter == _EMPTY_ { - filter = fwcs - } - wc := subjectHasWildcard(filter) - - // See if filter was provided but its the only subject. - if !isAll && !wc && fs.psim.Size() == 1 { - _, isAll = fs.psim.Find(stringToBytes(filter)) - } - // If we are isAll and have no deleted we can do a simpler calculation. - if !lastPerSubject && isAll && (fs.state.LastSeq-fs.state.FirstSeq+1) == fs.state.Msgs { - if sseq == 0 { - return fs.state.Msgs, validThrough - } - return fs.state.LastSeq - sseq + 1, validThrough - } - - _tsa, _fsa := [32]string{}, [32]string{} - tsa, fsa := _tsa[:0], _fsa[:0] - if wc { - fsa = tokenizeSubjectIntoSlice(fsa[:0], filter) - } - - isMatch := func(subj string) bool { - if isAll { - return true - } - if !wc { - return subj == filter - } - tsa = tokenizeSubjectIntoSlice(tsa[:0], subj) - return isSubsetMatchTokenized(tsa, fsa) - } - - // Handle last by subject a bit differently. - // We will scan PSIM since we accurately track the last block we have seen the subject in. This - // allows us to only need to load at most one block now. - // For the last block, we need to track the subjects that we know are in that block, and track seen - // while in the block itself, but complexity there worth it. - if lastPerSubject { - // If we want all and our start sequence is equal or less than first return number of subjects. - if isAll && sseq <= fs.state.FirstSeq { - return uint64(fs.psim.Size()), validThrough - } - // If we are here we need to scan. We are going to scan the PSIM looking for lblks that are >= seqStart. - // This will build up a list of all subjects from the selected block onward. - lbm := make(map[string]bool) - mb := fs.blks[seqStart] - bi := mb.index - - fs.psim.Match(stringToBytes(filter), func(subj []byte, psi *psi) { - // If the select blk start is greater than entry's last blk skip. - if bi > psi.lblk { - return - } - total++ - // We will track the subjects that are an exact match to the last block. - // This is needed for last block processing. - if psi.lblk == bi { - lbm[string(subj)] = true - } - }) - - // Now check if we need to inspect the seqStart block. - // Grab write lock in case we need to load in msgs. - mb.mu.Lock() - var updateLLTS bool - var shouldExpire bool - // We need to walk this block to correct accounting from above. - if sseq > mb.first.seq { - // Track the ones we add back in case more than one. - seen := make(map[string]bool) - // We need to discount the total by subjects seen before sseq, but also add them right back in if they are >= sseq for this blk. - // This only should be subjects we know have the last blk in this block. - if mb.cacheNotLoaded() { - mb.loadMsgsWithLock() - shouldExpire = true - } - var smv StoreMsg - for seq, lseq := atomic.LoadUint64(&mb.first.seq), atomic.LoadUint64(&mb.last.seq); seq <= lseq; seq++ { - if mb.dmap.Exists(seq) { - // Optimisation to avoid calling cacheLookup which hits time.Now(). - updateLLTS = true - continue - } - sm, _ := mb.cacheLookup(seq, &smv) - if sm == nil || sm.subj == _EMPTY_ || !lbm[sm.subj] { - continue - } - updateLLTS = false // cacheLookup already updated it. - if isMatch(sm.subj) { - // If less than sseq adjust off of total as long as this subject matched the last block. - if seq < sseq { - if !seen[sm.subj] { - total-- - seen[sm.subj] = true - } - } else if seen[sm.subj] { - // This is equal or more than sseq, so add back in. - total++ - // Make sure to not process anymore. - delete(seen, sm.subj) - } - } - } - } - // If we loaded the block try to force expire. - if shouldExpire { - mb.tryForceExpireCacheLocked() - } - if updateLLTS { - mb.llts = time.Now().UnixNano() - } - mb.mu.Unlock() - return total, validThrough - } - - // If we would need to scan more from the beginning, revert back to calculating directly here. - // TODO(dlc) - Redo properly with sublists etc for subject-based filtering. - if seqStart >= (len(fs.blks) / 2) { - for i := seqStart; i < len(fs.blks); i++ { - var shouldExpire bool - mb := fs.blks[i] - // Hold write lock in case we need to load cache. - mb.mu.Lock() - if isAll && sseq <= atomic.LoadUint64(&mb.first.seq) { - total += mb.msgs - mb.mu.Unlock() - continue - } - // If we are here we need to at least scan the subject fss. - // Make sure we have fss loaded. - if mb.fssNotLoaded() { - mb.loadMsgsWithLock() - shouldExpire = true - } - // Mark fss activity. - mb.lsts = time.Now().UnixNano() - - var t uint64 - var havePartial bool - mb.fss.Match(stringToBytes(filter), func(bsubj []byte, ss *SimpleState) { - if havePartial { - // If we already found a partial then don't do anything else. - return - } - subj := bytesToString(bsubj) - if ss.firstNeedsUpdate || ss.lastNeedsUpdate { - mb.recalculateForSubj(subj, ss) - } - if sseq <= ss.First { - t += ss.Msgs - } else if sseq <= ss.Last { - // We matched but its a partial. - havePartial = true - } - }) - - // See if we need to scan msgs here. - if havePartial { - // Make sure we have the cache loaded. - if mb.cacheNotLoaded() { - mb.loadMsgsWithLock() - shouldExpire = true - } - // Clear on partial. - t = 0 - start := sseq - if fseq := atomic.LoadUint64(&mb.first.seq); fseq > start { - start = fseq - } - var smv StoreMsg - for seq, lseq := start, atomic.LoadUint64(&mb.last.seq); seq <= lseq; seq++ { - if sm, _ := mb.cacheLookup(seq, &smv); sm != nil && isMatch(sm.subj) { - t++ - } - } - } - // If we loaded this block for this operation go ahead and expire it here. - if shouldExpire { - mb.tryForceExpireCacheLocked() - } - mb.mu.Unlock() - total += t - } - return total, validThrough - } - - // If we are here it's better to calculate totals from psim and adjust downward by scanning less blocks. - // TODO(dlc) - Eventually when sublist uses generics, make this sublist driven instead. - start := uint32(math.MaxUint32) - fs.psim.Match(stringToBytes(filter), func(_ []byte, psi *psi) { - total += psi.total - // Keep track of start index for this subject. - if psi.fblk < start { - start = psi.fblk - } - }) - // See if we were asked for all, if so we are done. - if sseq <= fs.state.FirstSeq { - return total, validThrough - } - - // If we are here we need to calculate partials for the first blocks. - firstSubjBlk := fs.bim[start] - var firstSubjBlkFound bool - // Adjust in case not found. - if firstSubjBlk == nil { - firstSubjBlkFound = true - } - - // Track how many we need to adjust against the total. - var adjust uint64 - for i := 0; i <= seqStart; i++ { - mb := fs.blks[i] - // We can skip blks if we know they are below the first one that has any subject matches. - if !firstSubjBlkFound { - if firstSubjBlkFound = (mb == firstSubjBlk); !firstSubjBlkFound { - continue - } - } - // We need to scan this block. - var shouldExpire bool - var updateLLTS bool - mb.mu.Lock() - // Check if we should include all of this block in adjusting. If so work with metadata. - if sseq > atomic.LoadUint64(&mb.last.seq) { - if isAll { - adjust += mb.msgs - } else { - // We need to adjust for all matches in this block. - // Make sure we have fss loaded. This loads whole block now. - if mb.fssNotLoaded() { - mb.loadMsgsWithLock() - shouldExpire = true - } - // Mark fss activity. - mb.lsts = time.Now().UnixNano() - - mb.fss.Match(stringToBytes(filter), func(bsubj []byte, ss *SimpleState) { - adjust += ss.Msgs - }) - } - } else { - // This is the last block. We need to scan per message here. - if mb.cacheNotLoaded() { - mb.loadMsgsWithLock() - shouldExpire = true - } - var last = atomic.LoadUint64(&mb.last.seq) - if sseq < last { - last = sseq - } - // We need to walk all messages in this block - var smv StoreMsg - for seq := atomic.LoadUint64(&mb.first.seq); seq < last; seq++ { - if mb.dmap.Exists(seq) { - // Optimisation to avoid calling cacheLookup which hits time.Now(). - updateLLTS = true - continue - } - sm, _ := mb.cacheLookup(seq, &smv) - if sm == nil || sm.subj == _EMPTY_ { - continue - } - updateLLTS = false // cacheLookup already updated it. - // Check if it matches our filter. - if sm.seq < sseq && isMatch(sm.subj) { - adjust++ - } - } - } - // If we loaded the block try to force expire. - if shouldExpire { - mb.tryForceExpireCacheLocked() - } - if updateLLTS { - mb.llts = time.Now().UnixNano() - } - mb.mu.Unlock() - } - // Make final adjustment. - total -= adjust - - return total, validThrough -} - -// NumPending will return the number of pending messages matching any subject in the sublist starting at sequence. -// Optimized for stream num pending calculations for consumers with lots of filtered subjects. -// Subjects should not overlap, this property is held when doing multi-filtered consumers. -func (fs *fileStore) NumPendingMulti(sseq uint64, sl *Sublist, lastPerSubject bool) (total, validThrough uint64) { - fs.mu.RLock() - defer fs.mu.RUnlock() - - // This can always be last for these purposes. - validThrough = fs.state.LastSeq - - if fs.state.Msgs == 0 || sseq > fs.state.LastSeq { - return 0, validThrough - } - - // If sseq is less then our first set to first. - if sseq < fs.state.FirstSeq { - sseq = fs.state.FirstSeq - } - // Track starting for both block for the sseq and staring block that matches any subject. - var seqStart int - // See if we need to figure out starting block per sseq. - if sseq > fs.state.FirstSeq { - // This should not, but can return -1, so make sure we check to avoid panic below. - if seqStart, _ = fs.selectMsgBlockWithIndex(sseq); seqStart < 0 { - seqStart = 0 - } - } - - isAll := sl == nil - - // See if filter was provided but its the only subject. - if !isAll && fs.psim.Size() == 1 { - fs.psim.IterFast(func(subject []byte, _ *psi) bool { - isAll = sl.HasInterest(bytesToString(subject)) - return true - }) - } - // If we are isAll and have no deleted we can do a simpler calculation. - if !lastPerSubject && isAll && (fs.state.LastSeq-fs.state.FirstSeq+1) == fs.state.Msgs { - if sseq == 0 { - return fs.state.Msgs, validThrough - } - return fs.state.LastSeq - sseq + 1, validThrough - } - // Setup the isMatch function. - isMatch := func(subj string) bool { - if isAll { - return true - } - return sl.HasInterest(subj) - } - - // Handle last by subject a bit differently. - // We will scan PSIM since we accurately track the last block we have seen the subject in. This - // allows us to only need to load at most one block now. - // For the last block, we need to track the subjects that we know are in that block, and track seen - // while in the block itself, but complexity there worth it. - if lastPerSubject { - // If we want all and our start sequence is equal or less than first return number of subjects. - if isAll && sseq <= fs.state.FirstSeq { - return uint64(fs.psim.Size()), validThrough - } - // If we are here we need to scan. We are going to scan the PSIM looking for lblks that are >= seqStart. - // This will build up a list of all subjects from the selected block onward. - lbm := make(map[string]bool) - mb := fs.blks[seqStart] - bi := mb.index - - subs := make([]*subscription, 0, sl.Count()) - sl.All(&subs) - for _, sub := range subs { - fs.psim.Match(sub.subject, func(subj []byte, psi *psi) { - // If the select blk start is greater than entry's last blk skip. - if bi > psi.lblk { - return - } - total++ - // We will track the subjects that are an exact match to the last block. - // This is needed for last block processing. - if psi.lblk == bi { - lbm[string(subj)] = true - } - }) - } - - // Now check if we need to inspect the seqStart block. - // Grab write lock in case we need to load in msgs. - mb.mu.Lock() - var shouldExpire bool - var updateLLTS bool - // We need to walk this block to correct accounting from above. - if sseq > mb.first.seq { - // Track the ones we add back in case more than one. - seen := make(map[string]bool) - // We need to discount the total by subjects seen before sseq, but also add them right back in if they are >= sseq for this blk. - // This only should be subjects we know have the last blk in this block. - if mb.cacheNotLoaded() { - mb.loadMsgsWithLock() - shouldExpire = true - } - var smv StoreMsg - for seq, lseq := atomic.LoadUint64(&mb.first.seq), atomic.LoadUint64(&mb.last.seq); seq <= lseq; seq++ { - if mb.dmap.Exists(seq) { - // Optimisation to avoid calling cacheLookup which hits time.Now(). - updateLLTS = true - continue - } - sm, _ := mb.cacheLookup(seq, &smv) - if sm == nil || sm.subj == _EMPTY_ || !lbm[sm.subj] { - continue - } - updateLLTS = false // cacheLookup already updated it. - if isMatch(sm.subj) { - // If less than sseq adjust off of total as long as this subject matched the last block. - if seq < sseq { - if !seen[sm.subj] { - total-- - seen[sm.subj] = true - } - } else if seen[sm.subj] { - // This is equal or more than sseq, so add back in. - total++ - // Make sure to not process anymore. - delete(seen, sm.subj) - } - } - } - } - // If we loaded the block try to force expire. - if shouldExpire { - mb.tryForceExpireCacheLocked() - } - if updateLLTS { - mb.llts = time.Now().UnixNano() - } - mb.mu.Unlock() - return total, validThrough - } - - // If we would need to scan more from the beginning, revert back to calculating directly here. - if seqStart >= (len(fs.blks) / 2) { - for i := seqStart; i < len(fs.blks); i++ { - var shouldExpire bool - mb := fs.blks[i] - // Hold write lock in case we need to load cache. - mb.mu.Lock() - if isAll && sseq <= atomic.LoadUint64(&mb.first.seq) { - total += mb.msgs - mb.mu.Unlock() - continue - } - // If we are here we need to at least scan the subject fss. - // Make sure we have fss loaded. - if mb.fssNotLoaded() { - mb.loadMsgsWithLock() - shouldExpire = true - } - // Mark fss activity. - mb.lsts = time.Now().UnixNano() - - var t uint64 - var havePartial bool - var updateLLTS bool - IntersectStree[SimpleState](mb.fss, sl, func(bsubj []byte, ss *SimpleState) { - subj := bytesToString(bsubj) - if havePartial { - // If we already found a partial then don't do anything else. - return - } - if ss.firstNeedsUpdate || ss.lastNeedsUpdate { - mb.recalculateForSubj(subj, ss) - } - if sseq <= ss.First { - t += ss.Msgs - } else if sseq <= ss.Last { - // We matched but its a partial. - havePartial = true - } - }) - - // See if we need to scan msgs here. - if havePartial { - // Make sure we have the cache loaded. - if mb.cacheNotLoaded() { - mb.loadMsgsWithLock() - shouldExpire = true - } - // Clear on partial. - t = 0 - start := sseq - if fseq := atomic.LoadUint64(&mb.first.seq); fseq > start { - start = fseq - } - var smv StoreMsg - for seq, lseq := start, atomic.LoadUint64(&mb.last.seq); seq <= lseq; seq++ { - if mb.dmap.Exists(seq) { - // Optimisation to avoid calling cacheLookup which hits time.Now(). - updateLLTS = true - continue - } - if sm, _ := mb.cacheLookup(seq, &smv); sm != nil && isMatch(sm.subj) { - t++ - updateLLTS = false // cacheLookup already updated it. - } - } - } - // If we loaded this block for this operation go ahead and expire it here. - if shouldExpire { - mb.tryForceExpireCacheLocked() - } - if updateLLTS { - mb.llts = time.Now().UnixNano() - } - mb.mu.Unlock() - total += t - } - return total, validThrough - } - - // If we are here it's better to calculate totals from psim and adjust downward by scanning less blocks. - start := uint32(math.MaxUint32) - subs := make([]*subscription, 0, sl.Count()) - sl.All(&subs) - for _, sub := range subs { - fs.psim.Match(sub.subject, func(_ []byte, psi *psi) { - total += psi.total - // Keep track of start index for this subject. - if psi.fblk < start { - start = psi.fblk - } - }) - } - // See if we were asked for all, if so we are done. - if sseq <= fs.state.FirstSeq { - return total, validThrough - } - - // If we are here we need to calculate partials for the first blocks. - firstSubjBlk := fs.bim[start] - var firstSubjBlkFound bool - // Adjust in case not found. - if firstSubjBlk == nil { - firstSubjBlkFound = true - } - - // Track how many we need to adjust against the total. - var adjust uint64 - for i := 0; i <= seqStart; i++ { - mb := fs.blks[i] - // We can skip blks if we know they are below the first one that has any subject matches. - if !firstSubjBlkFound { - if firstSubjBlkFound = (mb == firstSubjBlk); !firstSubjBlkFound { - continue - } - } - // We need to scan this block. - var shouldExpire bool - var updateLLTS bool - mb.mu.Lock() - // Check if we should include all of this block in adjusting. If so work with metadata. - if sseq > atomic.LoadUint64(&mb.last.seq) { - if isAll { - adjust += mb.msgs - } else { - // We need to adjust for all matches in this block. - // Make sure we have fss loaded. This loads whole block now. - if mb.fssNotLoaded() { - mb.loadMsgsWithLock() - shouldExpire = true - } - // Mark fss activity. - mb.lsts = time.Now().UnixNano() - IntersectStree(mb.fss, sl, func(bsubj []byte, ss *SimpleState) { - adjust += ss.Msgs - }) - } - } else { - // This is the last block. We need to scan per message here. - if mb.cacheNotLoaded() { - mb.loadMsgsWithLock() - shouldExpire = true - } - var last = atomic.LoadUint64(&mb.last.seq) - if sseq < last { - last = sseq - } - // We need to walk all messages in this block - var smv StoreMsg - for seq := atomic.LoadUint64(&mb.first.seq); seq < last; seq++ { - if mb.dmap.Exists(seq) { - // Optimisation to avoid calling cacheLookup which hits time.Now(). - updateLLTS = true - continue - } - sm, _ := mb.cacheLookup(seq, &smv) - if sm == nil || sm.subj == _EMPTY_ { - continue - } - updateLLTS = false // cacheLookup already updated it. - // Check if it matches our filter. - if sm.seq < sseq && isMatch(sm.subj) { - adjust++ - } - } - } - // If we loaded the block try to force expire. - if shouldExpire { - mb.tryForceExpireCacheLocked() - } - if updateLLTS { - mb.llts = time.Now().UnixNano() - } - mb.mu.Unlock() - } - // Make final adjustment. - total -= adjust - - return total, validThrough -} - -// SubjectsTotals return message totals per subject. -func (fs *fileStore) SubjectsTotals(filter string) map[string]uint64 { - fs.mu.RLock() - defer fs.mu.RUnlock() - - if fs.psim.Size() == 0 { - return nil - } - // Match all if no filter given. - if filter == _EMPTY_ { - filter = fwcs - } - fst := make(map[string]uint64) - fs.psim.Match(stringToBytes(filter), func(subj []byte, psi *psi) { - fst[string(subj)] = psi.total - }) - return fst -} - -// RegisterStorageUpdates registers a callback for updates to storage changes. -// It will present number of messages and bytes as a signed integer and an -// optional sequence number of the message if a single. -func (fs *fileStore) RegisterStorageUpdates(cb StorageUpdateHandler) { - fs.mu.Lock() - fs.scb = cb - bsz := fs.state.Bytes - fs.mu.Unlock() - if cb != nil && bsz > 0 { - cb(0, int64(bsz), 0, _EMPTY_) - } -} - -// RegisterSubjectDeleteMarkerUpdates registers a callback for updates to new tombstones. -func (fs *fileStore) RegisterSubjectDeleteMarkerUpdates(cb SubjectDeleteMarkerUpdateHandler) { - fs.mu.Lock() - fs.sdmcb = cb - fs.mu.Unlock() -} - -// Helper to get hash key for specific message block. -// Lock should be held -func (fs *fileStore) hashKeyForBlock(index uint32) []byte { - return []byte(fmt.Sprintf("%s-%d", fs.cfg.Name, index)) -} - -func (mb *msgBlock) setupWriteCache(buf []byte) { - // Make sure we have a cache setup. - if mb.cache != nil { - return - } - - // Setup simple cache. - mb.cache = &cache{buf: buf} - // Make sure we set the proper cache offset if we have existing data. - var fi os.FileInfo - if mb.mfd != nil { - fi, _ = mb.mfd.Stat() - } else if mb.mfn != _EMPTY_ { - fi, _ = os.Stat(mb.mfn) - } - if fi != nil { - mb.cache.off = int(fi.Size()) - } - mb.llts = time.Now().UnixNano() - mb.startCacheExpireTimer() -} - -// This rolls to a new append msg block. -// Lock should be held. -func (fs *fileStore) newMsgBlockForWrite() (*msgBlock, error) { - index := uint32(1) - var rbuf []byte - - if lmb := fs.lmb; lmb != nil { - index = lmb.index + 1 - // Determine if we can reclaim any resources here. - if fs.fip { - lmb.mu.Lock() - lmb.closeFDsLocked() - if lmb.cache != nil { - // Reset write timestamp and see if we can expire this cache. - rbuf = lmb.tryExpireWriteCache() - } - lmb.mu.Unlock() - } - } - - mb := fs.initMsgBlock(index) - // Lock should be held to quiet race detector. - mb.mu.Lock() - mb.setupWriteCache(rbuf) - mb.fss = stree.NewSubjectTree[SimpleState]() - - // Set cache time to creation time to start. - ts := time.Now().UnixNano() - mb.llts, mb.lwts = 0, ts - // Remember our last sequence number. - atomic.StoreUint64(&mb.first.seq, fs.state.LastSeq+1) - atomic.StoreUint64(&mb.last.seq, fs.state.LastSeq) - mb.mu.Unlock() - - // Now do local hash. - key := sha256.Sum256(fs.hashKeyForBlock(index)) - hh, err := highwayhash.New64(key[:]) - if err != nil { - return nil, fmt.Errorf("could not create hash: %v", err) - } - mb.hh = hh - - <-dios - mfd, err := os.OpenFile(mb.mfn, os.O_CREATE|os.O_RDWR, defaultFilePerms) - dios <- struct{}{} - - if err != nil { - if isPermissionError(err) { - return nil, err - } - mb.dirtyCloseWithRemove(true) - return nil, fmt.Errorf("Error creating msg block file: %v", err) - } - mb.mfd = mfd - - // Check if encryption is enabled. - if fs.prf != nil { - if err := fs.genEncryptionKeysForBlock(mb); err != nil { - return nil, err - } - } - - // If we know we will need this so go ahead and spin up. - if !fs.fip { - mb.spinUpFlushLoop() - } - - // Add to our list of blocks and mark as last. - fs.addMsgBlock(mb) - - return mb, nil -} - -// Generate the keys for this message block and write them out. -func (fs *fileStore) genEncryptionKeysForBlock(mb *msgBlock) error { - if mb == nil { - return nil - } - key, bek, seed, encrypted, err := fs.genEncryptionKeys(fmt.Sprintf("%s:%d", fs.cfg.Name, mb.index)) - if err != nil { - return err - } - mb.aek, mb.bek, mb.seed, mb.nonce = key, bek, seed, encrypted[:key.NonceSize()] - mdir := filepath.Join(fs.fcfg.StoreDir, msgDir) - keyFile := filepath.Join(mdir, fmt.Sprintf(keyScan, mb.index)) - if _, err := os.Stat(keyFile); err != nil && !os.IsNotExist(err) { - return err - } - err = fs.writeFileWithOptionalSync(keyFile, encrypted, defaultFilePerms) - if err != nil { - return err - } - mb.kfn = keyFile - return nil -} - -// Stores a raw message with expected sequence number and timestamp. -// Lock should be held. -func (fs *fileStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts, ttl int64) (err error) { - if fs.closed { - return ErrStoreClosed - } - - // Per subject max check needed. - mmp := uint64(fs.cfg.MaxMsgsPer) - var psmc uint64 - psmax := mmp > 0 && len(subj) > 0 - if psmax { - if info, ok := fs.psim.Find(stringToBytes(subj)); ok { - psmc = info.total - } - } - - var fseq uint64 - // Check if we are discarding new messages when we reach the limit. - if fs.cfg.Discard == DiscardNew { - var asl bool - if psmax && psmc >= mmp { - // If we are instructed to discard new per subject, this is an error. - if fs.cfg.DiscardNewPer { - return ErrMaxMsgsPerSubject - } - if fseq, err = fs.firstSeqForSubj(subj); err != nil { - return err - } - asl = true - } - // If we are discard new and limits policy and clustered, we do the enforcement - // above and should not disqualify the message here since it could cause replicas to drift. - if fs.cfg.Retention == LimitsPolicy || fs.cfg.Replicas == 1 { - if fs.cfg.MaxMsgs > 0 && fs.state.Msgs >= uint64(fs.cfg.MaxMsgs) && !asl { - return ErrMaxMsgs - } - if fs.cfg.MaxBytes > 0 && fs.state.Bytes+fileStoreMsgSize(subj, hdr, msg) >= uint64(fs.cfg.MaxBytes) { - if !asl || fs.sizeForSeq(fseq) <= int(fileStoreMsgSize(subj, hdr, msg)) { - return ErrMaxBytes - } - } - } - } - - // Check sequence. - if seq != fs.state.LastSeq+1 { - if seq > 0 { - return ErrSequenceMismatch - } - seq = fs.state.LastSeq + 1 - } - - // Write msg record. - // Add expiry bit to sequence if needed. This is so that if we need to - // rebuild, we know which messages to look at more quickly. - n, err := fs.writeMsgRecord(seq, ts, subj, hdr, msg) - if err != nil { - return err - } - - // Adjust top level tracking of per subject msg counts. - if len(subj) > 0 && fs.psim != nil { - index := fs.lmb.index - if info, ok := fs.psim.Find(stringToBytes(subj)); ok { - info.total++ - if index > info.lblk { - info.lblk = index - } - } else { - fs.psim.Insert(stringToBytes(subj), psi{total: 1, fblk: index, lblk: index}) - fs.tsl += len(subj) - } - } - - // Adjust first if needed. - now := time.Unix(0, ts).UTC() - if fs.state.Msgs == 0 { - fs.state.FirstSeq = seq - fs.state.FirstTime = now - } - - fs.state.Msgs++ - fs.state.Bytes += n - fs.state.LastSeq = seq - fs.state.LastTime = now - - // Enforce per message limits. - // We snapshotted psmc before our actual write, so >= comparison needed. - if psmax && psmc >= mmp { - // We may have done this above. - if fseq == 0 { - fseq, _ = fs.firstSeqForSubj(subj) - } - if ok, _ := fs.removeMsgViaLimits(fseq); ok { - // Make sure we are below the limit. - if psmc--; psmc >= mmp { - bsubj := stringToBytes(subj) - for info, ok := fs.psim.Find(bsubj); ok && info.total > mmp; info, ok = fs.psim.Find(bsubj) { - if seq, _ := fs.firstSeqForSubj(subj); seq > 0 { - if ok, _ := fs.removeMsgViaLimits(seq); !ok { - break - } - } else { - break - } - } - } - } else if mb := fs.selectMsgBlock(fseq); mb != nil { - // If we are here we could not remove fseq from above, so rebuild. - var ld *LostStreamData - if ld, _, _ = mb.rebuildState(); ld != nil { - fs.rebuildStateLocked(ld) - } - } - } - - // Limits checks and enforcement. - // If they do any deletions they will update the - // byte count on their own, so no need to compensate. - fs.enforceMsgLimit() - fs.enforceBytesLimit() - - // Per-message TTL. - if fs.ttls != nil && ttl > 0 { - expires := time.Duration(ts) + (time.Second * time.Duration(ttl)) - fs.ttls.Add(seq, int64(expires)) - fs.lmb.ttls++ - if seq > fs.ttlseq { - fs.ttlseq = seq - } - } - - // Check if we have and need the age expiration timer running. - switch { - case fs.ttls != nil && ttl > 0: - fs.resetAgeChk(0) - case fs.ageChk == nil && (fs.cfg.MaxAge > 0 || fs.ttls != nil): - fs.startAgeChk() - } - - return nil -} - -// StoreRawMsg stores a raw message with expected sequence number and timestamp. -func (fs *fileStore) StoreRawMsg(subj string, hdr, msg []byte, seq uint64, ts, ttl int64) error { - fs.mu.Lock() - err := fs.storeRawMsg(subj, hdr, msg, seq, ts, ttl) - cb := fs.scb - // Check if first message timestamp requires expiry - // sooner than initial replica expiry timer set to MaxAge when initializing. - if !fs.receivedAny && fs.cfg.MaxAge != 0 && ts > 0 { - fs.receivedAny = true - // don't block here by calling expireMsgs directly. - // Instead, set short timeout. - fs.resetAgeChk(int64(time.Millisecond * 50)) - } - fs.mu.Unlock() - - if err == nil && cb != nil { - cb(1, int64(fileStoreMsgSize(subj, hdr, msg)), seq, subj) - } - - return err -} - -// Store stores a message. We hold the main filestore lock for any write operation. -func (fs *fileStore) StoreMsg(subj string, hdr, msg []byte, ttl int64) (uint64, int64, error) { - fs.mu.Lock() - seq, ts := fs.state.LastSeq+1, time.Now().UnixNano() - err := fs.storeRawMsg(subj, hdr, msg, seq, ts, ttl) - cb := fs.scb - fs.mu.Unlock() - - if err != nil { - seq, ts = 0, 0 - } else if cb != nil { - cb(1, int64(fileStoreMsgSize(subj, hdr, msg)), seq, subj) - } - - return seq, ts, err -} - -// skipMsg will update this message block for a skipped message. -// If we do not have any messages, just update the metadata, otherwise -// we will place an empty record marking the sequence as used. The -// sequence will be marked erased. -// fs lock should be held. -func (mb *msgBlock) skipMsg(seq uint64, now time.Time) { - if mb == nil { - return - } - var needsRecord bool - - nowts := now.UnixNano() - - mb.mu.Lock() - // If we are empty can just do meta. - if mb.msgs == 0 { - atomic.StoreUint64(&mb.last.seq, seq) - mb.last.ts = nowts - atomic.StoreUint64(&mb.first.seq, seq+1) - mb.first.ts = nowts - needsRecord = mb == mb.fs.lmb - if needsRecord && mb.rbytes > 0 { - // We want to make sure since we have no messages - // that we write to the beginning since we only need last one. - mb.rbytes, mb.cache = 0, &cache{} - // If encrypted we need to reset counter since we just keep one. - if mb.bek != nil { - // Recreate to reset counter. - mb.bek, _ = genBlockEncryptionKey(mb.fs.fcfg.Cipher, mb.seed, mb.nonce) - } - } - } else { - needsRecord = true - mb.dmap.Insert(seq) - } - mb.mu.Unlock() - - if needsRecord { - mb.writeMsgRecord(emptyRecordLen, seq|ebit, _EMPTY_, nil, nil, nowts, true) - } else { - mb.kickFlusher() - } -} - -// SkipMsg will use the next sequence number but not store anything. -func (fs *fileStore) SkipMsg() uint64 { - fs.mu.Lock() - defer fs.mu.Unlock() - - // Grab our current last message block. - mb := fs.lmb - if mb == nil || mb.msgs > 0 && mb.blkSize()+emptyRecordLen > fs.fcfg.BlockSize { - if mb != nil && fs.fcfg.Compression != NoCompression { - // We've now reached the end of this message block, if we want - // to compress blocks then now's the time to do it. - go mb.recompressOnDiskIfNeeded() - } - var err error - if mb, err = fs.newMsgBlockForWrite(); err != nil { - return 0 - } - } - - // Grab time and last seq. - now, seq := time.Now().UTC(), fs.state.LastSeq+1 - - // Write skip msg. - mb.skipMsg(seq, now) - - // Update fs state. - fs.state.LastSeq, fs.state.LastTime = seq, now - if fs.state.Msgs == 0 { - fs.state.FirstSeq, fs.state.FirstTime = seq, now - } - if seq == fs.state.FirstSeq { - fs.state.FirstSeq, fs.state.FirstTime = seq+1, now - } - // Mark as dirty for stream state. - fs.dirty++ - - return seq -} - -// Skip multiple msgs. We will determine if we can fit into current lmb or we need to create a new block. -func (fs *fileStore) SkipMsgs(seq uint64, num uint64) error { - fs.mu.Lock() - defer fs.mu.Unlock() - - // Check sequence matches our last sequence. - if seq != fs.state.LastSeq+1 { - if seq > 0 { - return ErrSequenceMismatch - } - seq = fs.state.LastSeq + 1 - } - - // Limit number of dmap entries - const maxDeletes = 64 * 1024 - mb := fs.lmb - - numDeletes := int(num) - if mb != nil { - numDeletes += mb.dmap.Size() - } - if mb == nil || numDeletes > maxDeletes && mb.msgs > 0 || mb.msgs > 0 && mb.blkSize()+emptyRecordLen > fs.fcfg.BlockSize { - if mb != nil && fs.fcfg.Compression != NoCompression { - // We've now reached the end of this message block, if we want - // to compress blocks then now's the time to do it. - go mb.recompressOnDiskIfNeeded() - } - var err error - if mb, err = fs.newMsgBlockForWrite(); err != nil { - return err - } - } - - // Insert into dmap all entries and place last as marker. - now := time.Now().UTC() - nowts := now.UnixNano() - lseq := seq + num - 1 - - mb.mu.Lock() - // If we are empty update meta directly. - if mb.msgs == 0 { - atomic.StoreUint64(&mb.last.seq, lseq) - mb.last.ts = nowts - atomic.StoreUint64(&mb.first.seq, lseq+1) - mb.first.ts = nowts - } else { - for ; seq <= lseq; seq++ { - mb.dmap.Insert(seq) - } - } - mb.mu.Unlock() - - // Write out our placeholder. - mb.writeMsgRecord(emptyRecordLen, lseq|ebit, _EMPTY_, nil, nil, nowts, true) - - // Now update FS accounting. - // Update fs state. - fs.state.LastSeq, fs.state.LastTime = lseq, now - if fs.state.Msgs == 0 { - fs.state.FirstSeq, fs.state.FirstTime = lseq+1, now - } - - // Mark as dirty for stream state. - fs.dirty++ - - return nil -} - -// Lock should be held. -func (fs *fileStore) rebuildFirst() { - if len(fs.blks) == 0 { - return - } - fmb := fs.blks[0] - if fmb == nil { - return - } - - ld, _, _ := fmb.rebuildState() - fmb.mu.RLock() - isEmpty := fmb.msgs == 0 - fmb.mu.RUnlock() - if isEmpty { - fmb.mu.Lock() - fs.removeMsgBlock(fmb) - fmb.mu.Unlock() - } - fs.selectNextFirst() - fs.rebuildStateLocked(ld) -} - -// Optimized helper function to return first sequence. -// subj will always be publish subject here, meaning non-wildcard. -// We assume a fast check that this subj even exists already happened. -// Write lock should be held. -func (fs *fileStore) firstSeqForSubj(subj string) (uint64, error) { - if len(fs.blks) == 0 { - return 0, nil - } - - // See if we can optimize where we start. - start, stop := fs.blks[0].index, fs.lmb.index - if info, ok := fs.psim.Find(stringToBytes(subj)); ok { - start, stop = info.fblk, info.lblk - } - - for i := start; i <= stop; i++ { - mb := fs.bim[i] - if mb == nil { - continue - } - // If we need to load msgs here and we need to walk multiple blocks this - // could tie up the upper fs lock, so release while dealing with the block. - fs.mu.Unlock() - - mb.mu.Lock() - var shouldExpire bool - if mb.fssNotLoaded() { - // Make sure we have fss loaded. - if err := mb.loadMsgsWithLock(); err != nil { - mb.mu.Unlock() - // Re-acquire fs lock - fs.mu.Lock() - return 0, err - } - shouldExpire = true - } - // Mark fss activity. - mb.lsts = time.Now().UnixNano() - - bsubj := stringToBytes(subj) - if ss, ok := mb.fss.Find(bsubj); ok && ss != nil { - // Adjust first if it was not where we thought it should be. - if i != start { - if info, ok := fs.psim.Find(bsubj); ok { - info.fblk = i - } - } - if ss.firstNeedsUpdate || ss.lastNeedsUpdate { - mb.recalculateForSubj(subj, ss) - } - mb.mu.Unlock() - // Re-acquire fs lock - fs.mu.Lock() - return ss.First, nil - } - // If we did not find it and we loaded this msgBlock try to expire as long as not the last. - if shouldExpire { - // Expire this cache before moving on. - mb.tryForceExpireCacheLocked() - } - mb.mu.Unlock() - // Re-acquire fs lock - fs.mu.Lock() - } - return 0, nil -} - -// Will check the msg limit and drop firstSeq msg if needed. -// Lock should be held. -func (fs *fileStore) enforceMsgLimit() { - if fs.cfg.Discard != DiscardOld { - return - } - if fs.cfg.MaxMsgs <= 0 || fs.state.Msgs <= uint64(fs.cfg.MaxMsgs) { - return - } - for nmsgs := fs.state.Msgs; nmsgs > uint64(fs.cfg.MaxMsgs); nmsgs = fs.state.Msgs { - if removed, err := fs.deleteFirstMsg(); err != nil || !removed { - fs.rebuildFirst() - return - } - } -} - -// Will check the bytes limit and drop msgs if needed. -// Lock should be held. -func (fs *fileStore) enforceBytesLimit() { - if fs.cfg.Discard != DiscardOld { - return - } - if fs.cfg.MaxBytes <= 0 || fs.state.Bytes <= uint64(fs.cfg.MaxBytes) { - return - } - for bs := fs.state.Bytes; bs > uint64(fs.cfg.MaxBytes); bs = fs.state.Bytes { - if removed, err := fs.deleteFirstMsg(); err != nil || !removed { - fs.rebuildFirst() - return - } - } -} - -// Will make sure we have limits honored for max msgs per subject on recovery or config update. -// We will make sure to go through all msg blocks etc. but in practice this -// will most likely only be the last one, so can take a more conservative approach. -// Lock should be held. -func (fs *fileStore) enforceMsgPerSubjectLimit(fireCallback bool) { - start := time.Now() - defer func() { - if took := time.Since(start); took > time.Minute { - fs.warn("enforceMsgPerSubjectLimit took %v", took.Round(time.Millisecond)) - } - }() - - maxMsgsPer := uint64(fs.cfg.MaxMsgsPer) - - // We may want to suppress callbacks from remove during this process - // since these should have already been deleted and accounted for. - if !fireCallback { - cb := fs.scb - fs.scb = nil - defer func() { fs.scb = cb }() - } - - var numMsgs uint64 - - // collect all that are not correct. - needAttention := make(map[string]*psi) - fs.psim.IterFast(func(subj []byte, psi *psi) bool { - numMsgs += psi.total - if psi.total > maxMsgsPer { - needAttention[string(subj)] = psi - } - return true - }) - - // We had an issue with a use case where psim (and hence fss) were correct but idx was not and was not properly being caught. - // So do a quick sanity check here. If we detect a skew do a rebuild then re-check. - if numMsgs != fs.state.Msgs { - fs.warn("Detected skew in subject-based total (%d) vs raw total (%d), rebuilding", numMsgs, fs.state.Msgs) - // Clear any global subject state. - fs.psim, fs.tsl = fs.psim.Empty(), 0 - for _, mb := range fs.blks { - ld, _, err := mb.rebuildState() - if err != nil && ld != nil { - fs.addLostData(ld) - } - fs.populateGlobalPerSubjectInfo(mb) - } - // Rebuild fs state too. - fs.rebuildStateLocked(nil) - // Need to redo blocks that need attention. - needAttention = make(map[string]*psi) - fs.psim.IterFast(func(subj []byte, psi *psi) bool { - if psi.total > maxMsgsPer { - needAttention[string(subj)] = psi - } - return true - }) - } - - // Collect all the msgBlks we alter. - blks := make(map[*msgBlock]struct{}) - - // For re-use below. - var sm StoreMsg - - // Walk all subjects that need attention here. - for subj, info := range needAttention { - total, start, stop := info.total, info.fblk, info.lblk - - for i := start; i <= stop; i++ { - mb := fs.bim[i] - if mb == nil { - continue - } - // Grab the ss entry for this subject in case sparse. - mb.mu.Lock() - mb.ensurePerSubjectInfoLoaded() - ss, ok := mb.fss.Find(stringToBytes(subj)) - if ok && ss != nil && (ss.firstNeedsUpdate || ss.lastNeedsUpdate) { - mb.recalculateForSubj(subj, ss) - } - mb.mu.Unlock() - if ss == nil { - continue - } - for seq := ss.First; seq <= ss.Last && total > maxMsgsPer; { - m, _, err := mb.firstMatching(subj, false, seq, &sm) - if err == nil { - seq = m.seq + 1 - if removed, _ := fs.removeMsgViaLimits(m.seq); removed { - total-- - blks[mb] = struct{}{} - } - } else { - // On error just do single increment. - seq++ - } - } - } - } - - // Expire the cache if we can. - for mb := range blks { - mb.mu.Lock() - if mb.msgs > 0 { - mb.tryForceExpireCacheLocked() - } - mb.mu.Unlock() - } -} - -// Lock should be held. -func (fs *fileStore) deleteFirstMsg() (bool, error) { - return fs.removeMsgViaLimits(fs.state.FirstSeq) -} - -// If we remove via limits that can always be recovered on a restart we -// do not force the system to update the index file. -// Lock should be held. -func (fs *fileStore) removeMsgViaLimits(seq uint64) (bool, error) { - return fs.removeMsg(seq, false, true, false) -} - -// RemoveMsg will remove the message from this store. -// Will return the number of bytes removed. -func (fs *fileStore) RemoveMsg(seq uint64) (bool, error) { - return fs.removeMsg(seq, false, false, true) -} - -func (fs *fileStore) EraseMsg(seq uint64) (bool, error) { - return fs.removeMsg(seq, true, false, true) -} - -// Convenience function to remove per subject tracking at the filestore level. -// Lock should be held. Returns if we deleted the last message on the subject. -func (fs *fileStore) removePerSubject(subj string, marker bool) bool { - if len(subj) == 0 || fs.psim == nil { - return false - } - // We do not update sense of fblk here but will do so when we resolve during lookup. - bsubj := stringToBytes(subj) - if info, ok := fs.psim.Find(bsubj); ok { - info.total-- - if info.total == 1 { - info.fblk = info.lblk - } else if info.total == 0 { - if _, ok = fs.psim.Delete(bsubj); ok { - fs.tsl -= len(subj) - if marker { - fs.markers = append(fs.markers, subj) - } - return true - } - } - } - return false -} - -// Remove a message, optionally rewriting the mb file. -func (fs *fileStore) removeMsg(seq uint64, secure, viaLimits, needFSLock bool) (bool, error) { - if seq == 0 { - return false, ErrStoreMsgNotFound - } - fsLock := func() { - if needFSLock { - fs.mu.Lock() - } - } - fsUnlock := func() { - if needFSLock { - fs.mu.Unlock() - } - } - - fsLock() - - if fs.closed { - fsUnlock() - return false, ErrStoreClosed - } - if !viaLimits && fs.sips > 0 { - fsUnlock() - return false, ErrStoreSnapshotInProgress - } - // If in encrypted mode negate secure rewrite here. - if secure && fs.prf != nil { - secure = false - } - - mb := fs.selectMsgBlock(seq) - if mb == nil { - var err = ErrStoreEOF - if seq <= fs.state.LastSeq { - err = ErrStoreMsgNotFound - } - fsUnlock() - return false, err - } - - mb.mu.Lock() - - // See if we are closed or the sequence number is still relevant or if we know its deleted. - if mb.closed || seq < atomic.LoadUint64(&mb.first.seq) || mb.dmap.Exists(seq) { - mb.mu.Unlock() - fsUnlock() - return false, nil - } - - // We used to not have to load in the messages except with callbacks or the filtered subject state (which is now always on). - // Now just load regardless. - // TODO(dlc) - Figure out a way not to have to load it in, we need subject tracking outside main data block. - if mb.cacheNotLoaded() { - if err := mb.loadMsgsWithLock(); err != nil { - mb.mu.Unlock() - fsUnlock() - return false, err - } - } - - var smv StoreMsg - sm, err := mb.cacheLookup(seq, &smv) - if err != nil { - mb.mu.Unlock() - fsUnlock() - // Mimic err behavior from above check to dmap. No error returned if already removed. - if err == errDeletedMsg { - err = nil - } - return false, err - } - // Grab size - msz := fileStoreMsgSize(sm.subj, sm.hdr, sm.msg) - - // Set cache timestamp for last remove. - mb.lrts = time.Now().UnixNano() - - // Global stats - if fs.state.Msgs > 0 { - fs.state.Msgs-- - } - if msz < fs.state.Bytes { - fs.state.Bytes -= msz - } else { - fs.state.Bytes = 0 - } - - // Now local mb updates. - if mb.msgs > 0 { - mb.msgs-- - } - if msz < mb.bytes { - mb.bytes -= msz - } else { - mb.bytes = 0 - } - - // Allow us to check compaction again. - mb.noCompact = false - - // Mark as dirty for stream state. - fs.dirty++ - - // If we are tracking subjects here make sure we update that accounting. - mb.ensurePerSubjectInfoLoaded() - - // If we are tracking multiple subjects here make sure we update that accounting. - mb.removeSeqPerSubject(sm.subj, seq) - wasLast := fs.removePerSubject(sm.subj, false) - - if secure { - // Grab record info. - ri, rl, _, _ := mb.slotInfo(int(seq - mb.cache.fseq)) - if err := mb.eraseMsg(seq, int(ri), int(rl)); err != nil { - return false, err - } - } - - fifo := seq == atomic.LoadUint64(&mb.first.seq) - isLastBlock := mb == fs.lmb - isEmpty := mb.msgs == 0 - - if fifo { - mb.selectNextFirst() - if !isEmpty { - // Can update this one in place. - if seq == fs.state.FirstSeq { - fs.state.FirstSeq = atomic.LoadUint64(&mb.first.seq) // new one. - if mb.first.ts == 0 { - fs.state.FirstTime = time.Time{} - } else { - fs.state.FirstTime = time.Unix(0, mb.first.ts).UTC() - } - } - } - } else if !isEmpty { - // Out of order delete. - mb.dmap.Insert(seq) - // Make simple check here similar to Compact(). If we can save 50% and over a certain threshold do inline. - // All other more thorough cleanup will happen in syncBlocks logic. - // Note that we do not have to store empty records for the deleted, so don't use to calculate. - // TODO(dlc) - This should not be inline, should kick the sync routine. - if !isLastBlock && mb.shouldCompactInline() { - mb.compact() - } - } - - if secure { - if ld, _ := mb.flushPendingMsgsLocked(); ld != nil { - // We have the mb lock here, this needs the mb locks so do in its own go routine. - go fs.rebuildState(ld) - } - } - - // If empty remove this block and check if we need to update first sequence. - // We will write a tombstone at the end. - var firstSeqNeedsUpdate bool - if isEmpty { - // This writes tombstone iff mb == lmb, so no need to do below. - fs.removeMsgBlock(mb) - firstSeqNeedsUpdate = seq == fs.state.FirstSeq - } - mb.mu.Unlock() - - // If the deleted message was itself a delete marker then - // don't write out more of them or we'll churn endlessly. - var sdmcb func() - if wasLast && len(getHeader(JSMarkerReason, sm.hdr)) == 0 { // Not a marker. - if viaLimits { - sdmcb = fs.subjectDeleteMarkerIfNeeded(sm.subj, JSMarkerReasonMaxAge) - } - } - - // If we emptied the current message block and the seq was state.FirstSeq - // then we need to jump message blocks. We will also write the index so - // we don't lose track of the first sequence. - if firstSeqNeedsUpdate { - fs.selectNextFirst() - } - - // Check if we need to write a deleted record tombstone. - // This is for user initiated removes or to hold the first seq - // when the last block is empty. - - // If not via limits and not empty (empty writes tombstone above if last) write tombstone. - if !viaLimits && !isEmpty && sm != nil { - fs.writeTombstone(sm.seq, sm.ts) - } - - if cb := fs.scb; cb != nil || sdmcb != nil { - // If we have a callback registered we need to release lock regardless since cb might need it to lookup msg, etc. - fs.mu.Unlock() - // Storage updates. - if cb != nil { - var subj string - if sm != nil { - subj = sm.subj - } - delta := int64(msz) - cb(-1, -delta, seq, subj) - } - if sdmcb != nil { - sdmcb() - } - - if !needFSLock { - fs.mu.Lock() - } - } else if needFSLock { - // We acquired it so release it. - fs.mu.Unlock() - } - - return true, nil -} - -// Tests whether we should try to compact this block while inline removing msgs. -// We will want rbytes to be over the minimum and have a 2x potential savings. -// If we compacted before but rbytes didn't improve much, guard against constantly compacting. -// Lock should be held. -func (mb *msgBlock) shouldCompactInline() bool { - return mb.rbytes > compactMinimum && mb.bytes*2 < mb.rbytes && (mb.cbytes == 0 || mb.bytes*2 < mb.cbytes) -} - -// Tests whether we should try to compact this block while running periodic sync. -// We will want rbytes to be over the minimum and have a 2x potential savings. -// Ignores 2MB minimum. -// Lock should be held. -func (mb *msgBlock) shouldCompactSync() bool { - return mb.bytes*2 < mb.rbytes && !mb.noCompact -} - -// This will compact and rewrite this block. This version will not process any tombstone cleanup. -// Write lock needs to be held. -func (mb *msgBlock) compact() { - mb.compactWithFloor(0) -} - -// This will compact and rewrite this block. This should only be called when we know we want to rewrite this block. -// This should not be called on the lmb since we will prune tail deleted messages which could cause issues with -// writing new messages. We will silently bail on any issues with the underlying block and let someone else detect. -// if fseq > 0 we will attempt to cleanup stale tombstones. -// Write lock needs to be held. -func (mb *msgBlock) compactWithFloor(floor uint64) { - wasLoaded := mb.cacheAlreadyLoaded() - if !wasLoaded { - if err := mb.loadMsgsWithLock(); err != nil { - return - } - } - - buf := mb.cache.buf - nbuf := getMsgBlockBuf(len(buf)) - // Recycle our nbuf when we are done. - defer recycleMsgBlockBuf(nbuf) - - var le = binary.LittleEndian - var firstSet bool - - fseq := atomic.LoadUint64(&mb.first.seq) - isDeleted := func(seq uint64) bool { - return seq == 0 || seq&ebit != 0 || mb.dmap.Exists(seq) || seq < fseq - } - - for index, lbuf := uint32(0), uint32(len(buf)); index < lbuf; { - if index+msgHdrSize > lbuf { - return - } - hdr := buf[index : index+msgHdrSize] - rl, slen := le.Uint32(hdr[0:]), int(le.Uint16(hdr[20:])) - // Clear any headers bit that could be set. - rl &^= hbit - dlen := int(rl) - msgHdrSize - // Do some quick sanity checks here. - if dlen < 0 || slen > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh { - return - } - // Only need to process non-deleted messages. - seq := le.Uint64(hdr[4:]) - - if !isDeleted(seq) { - // Check for tombstones. - if seq&tbit != 0 { - seq = seq &^ tbit - // If this entry is for a lower seq than ours then keep around. - // We also check that it is greater than our floor. Floor is zero on normal - // calls to compact. - if seq < fseq && seq >= floor { - nbuf = append(nbuf, buf[index:index+rl]...) - } - } else { - // Normal message here. - nbuf = append(nbuf, buf[index:index+rl]...) - if !firstSet { - firstSet = true - atomic.StoreUint64(&mb.first.seq, seq) - } - } - } - // Advance to next record. - index += rl - } - - // Handle compression - if mb.cmp != NoCompression && len(nbuf) > 0 { - cbuf, err := mb.cmp.Compress(nbuf) - if err != nil { - return - } - meta := &CompressionInfo{ - Algorithm: mb.cmp, - OriginalSize: uint64(len(nbuf)), - } - nbuf = append(meta.MarshalMetadata(), cbuf...) - } - - // Check for encryption. - if mb.bek != nil && len(nbuf) > 0 { - // Recreate to reset counter. - rbek, err := genBlockEncryptionKey(mb.fs.fcfg.Cipher, mb.seed, mb.nonce) - if err != nil { - return - } - rbek.XORKeyStream(nbuf, nbuf) - } - - // Close FDs first. - mb.closeFDsLocked() - - // We will write to a new file and mv/rename it in case of failure. - mfn := filepath.Join(mb.fs.fcfg.StoreDir, msgDir, fmt.Sprintf(newScan, mb.index)) - <-dios - err := os.WriteFile(mfn, nbuf, defaultFilePerms) - dios <- struct{}{} - if err != nil { - os.Remove(mfn) - return - } - if err := os.Rename(mfn, mb.mfn); err != nil { - os.Remove(mfn) - return - } - - // Make sure to sync - mb.needSync = true - - // Capture the updated rbytes. - if rbytes := uint64(len(nbuf)); rbytes == mb.rbytes { - // No change, so set our noCompact bool here to avoid attempting to continually compress in syncBlocks. - mb.noCompact = true - } else { - mb.rbytes = rbytes - } - mb.cbytes = mb.bytes - - // Remove any seqs from the beginning of the blk. - for seq, nfseq := fseq, atomic.LoadUint64(&mb.first.seq); seq < nfseq; seq++ { - mb.dmap.Delete(seq) - } - // Make sure we clear the cache since no longer valid. - mb.clearCacheAndOffset() - // If we entered with the msgs loaded make sure to reload them. - if wasLoaded { - mb.loadMsgsWithLock() - } -} - -// Grab info from a slot. -// Lock should be held. -func (mb *msgBlock) slotInfo(slot int) (uint32, uint32, bool, error) { - if mb.cache == nil || slot >= len(mb.cache.idx) { - return 0, 0, false, errPartialCache - } - - bi := mb.cache.idx[slot] - ri, hashChecked := (bi &^ cbit), (bi&cbit) != 0 - - // If this is a deleted slot return here. - if bi == dbit { - return 0, 0, false, errDeletedMsg - } - - // Determine record length - var rl uint32 - if slot >= len(mb.cache.idx) { - rl = mb.cache.lrl - } else { - // Need to account for dbit markers in idx. - // So we will walk until we find valid idx slot to calculate rl. - for i := 1; slot+i < len(mb.cache.idx); i++ { - ni := mb.cache.idx[slot+i] &^ cbit - if ni == dbit { - continue - } - rl = ni - ri - break - } - // check if we had all trailing dbits. - // If so use len of cache buf minus ri. - if rl == 0 { - rl = uint32(len(mb.cache.buf)) - ri - } - } - if rl < msgHdrSize { - return 0, 0, false, errBadMsg - } - return uint32(ri), rl, hashChecked, nil -} - -func (fs *fileStore) isClosed() bool { - fs.mu.RLock() - closed := fs.closed - fs.mu.RUnlock() - return closed -} - -// Will spin up our flush loop. -func (mb *msgBlock) spinUpFlushLoop() { - mb.mu.Lock() - defer mb.mu.Unlock() - - // Are we already running or closed? - if mb.flusher || mb.closed { - return - } - mb.flusher = true - mb.fch = make(chan struct{}, 1) - mb.qch = make(chan struct{}) - fch, qch := mb.fch, mb.qch - - go mb.flushLoop(fch, qch) -} - -// Raw low level kicker for flush loops. -func kickFlusher(fch chan struct{}) { - if fch != nil { - select { - case fch <- struct{}{}: - default: - } - } -} - -// Kick flusher for this message block. -func (mb *msgBlock) kickFlusher() { - mb.mu.RLock() - defer mb.mu.RUnlock() - kickFlusher(mb.fch) -} - -func (mb *msgBlock) setInFlusher() { - mb.mu.Lock() - mb.flusher = true - mb.mu.Unlock() -} - -func (mb *msgBlock) clearInFlusher() { - mb.mu.Lock() - mb.flusher = false - mb.mu.Unlock() -} - -// flushLoop watches for messages, index info, or recently closed msg block updates. -func (mb *msgBlock) flushLoop(fch, qch chan struct{}) { - mb.setInFlusher() - defer mb.clearInFlusher() - - for { - select { - case <-fch: - // If we have pending messages process them first. - if waiting := mb.pendingWriteSize(); waiting != 0 { - ts := 1 * time.Millisecond - var waited time.Duration - - for waiting < coalesceMinimum { - time.Sleep(ts) - select { - case <-qch: - return - default: - } - newWaiting := mb.pendingWriteSize() - if waited = waited + ts; waited > maxFlushWait || newWaiting <= waiting { - break - } - waiting = newWaiting - ts *= 2 - } - mb.flushPendingMsgs() - // Check if we are no longer the last message block. If we are - // not we can close FDs and exit. - mb.fs.mu.RLock() - notLast := mb != mb.fs.lmb - mb.fs.mu.RUnlock() - if notLast { - if err := mb.closeFDs(); err == nil { - return - } - } - } - case <-qch: - return - } - } -} - -// Lock should be held. -func (mb *msgBlock) eraseMsg(seq uint64, ri, rl int) error { - var le = binary.LittleEndian - var hdr [msgHdrSize]byte - - le.PutUint32(hdr[0:], uint32(rl)) - le.PutUint64(hdr[4:], seq|ebit) - le.PutUint64(hdr[12:], 0) - le.PutUint16(hdr[20:], 0) - - // Randomize record - data := make([]byte, rl-emptyRecordLen) - if n, err := rand.Read(data); err != nil { - return err - } else if n != len(data) { - return fmt.Errorf("not enough overwrite bytes read (%d != %d)", n, len(data)) - } - - // Now write to underlying buffer. - var b bytes.Buffer - b.Write(hdr[:]) - b.Write(data) - - // Calculate hash. - mb.hh.Reset() - mb.hh.Write(hdr[4:20]) - mb.hh.Write(data) - checksum := mb.hh.Sum(nil) - // Write to msg record. - b.Write(checksum) - - // Update both cache and disk. - nbytes := b.Bytes() - - // Cache - if ri >= mb.cache.off { - li := ri - mb.cache.off - buf := mb.cache.buf[li : li+rl] - copy(buf, nbytes) - } - - // Disk - if mb.cache.off+mb.cache.wp > ri { - <-dios - mfd, err := os.OpenFile(mb.mfn, os.O_RDWR, defaultFilePerms) - dios <- struct{}{} - if err != nil { - return err - } - defer mfd.Close() - if _, err = mfd.WriteAt(nbytes, int64(ri)); err == nil { - mfd.Sync() - } - if err != nil { - return err - } - } - return nil -} - -// Truncate this message block to the storedMsg. -func (mb *msgBlock) truncate(sm *StoreMsg) (nmsgs, nbytes uint64, err error) { - mb.mu.Lock() - defer mb.mu.Unlock() - - // Make sure we are loaded to process messages etc. - if err := mb.loadMsgsWithLock(); err != nil { - return 0, 0, err - } - - // Calculate new eof using slot info from our new last sm. - ri, rl, _, err := mb.slotInfo(int(sm.seq - mb.cache.fseq)) - if err != nil { - return 0, 0, err - } - // Calculate new eof. - eof := int64(ri + rl) - - var purged, bytes uint64 - - checkDmap := mb.dmap.Size() > 0 - var smv StoreMsg - - for seq := atomic.LoadUint64(&mb.last.seq); seq > sm.seq; seq-- { - if checkDmap { - if mb.dmap.Exists(seq) { - // Delete and skip to next. - mb.dmap.Delete(seq) - checkDmap = !mb.dmap.IsEmpty() - continue - } - } - // We should have a valid msg to calculate removal stats. - if m, err := mb.cacheLookup(seq, &smv); err == nil { - if mb.msgs > 0 { - rl := fileStoreMsgSize(m.subj, m.hdr, m.msg) - mb.msgs-- - if rl > mb.bytes { - rl = mb.bytes - } - mb.bytes -= rl - mb.rbytes -= rl - // For return accounting. - purged++ - bytes += uint64(rl) - } - } - } - - // If the block is compressed then we have to load it into memory - // and decompress it, truncate it and then write it back out. - // Otherwise, truncate the file itself and close the descriptor. - if mb.cmp != NoCompression { - buf, err := mb.loadBlock(nil) - if err != nil { - return 0, 0, fmt.Errorf("failed to load block from disk: %w", err) - } - if mb.bek != nil && len(buf) > 0 { - bek, err := genBlockEncryptionKey(mb.fs.fcfg.Cipher, mb.seed, mb.nonce) - if err != nil { - return 0, 0, err - } - mb.bek = bek - mb.bek.XORKeyStream(buf, buf) - } - buf, err = mb.decompressIfNeeded(buf) - if err != nil { - return 0, 0, fmt.Errorf("failed to decompress block: %w", err) - } - buf = buf[:eof] - copy(mb.lchk[0:], buf[:len(buf)-checksumSize]) - buf, err = mb.cmp.Compress(buf) - if err != nil { - return 0, 0, fmt.Errorf("failed to recompress block: %w", err) - } - meta := &CompressionInfo{ - Algorithm: mb.cmp, - OriginalSize: uint64(eof), - } - buf = append(meta.MarshalMetadata(), buf...) - if mb.bek != nil && len(buf) > 0 { - bek, err := genBlockEncryptionKey(mb.fs.fcfg.Cipher, mb.seed, mb.nonce) - if err != nil { - return 0, 0, err - } - mb.bek = bek - mb.bek.XORKeyStream(buf, buf) - } - n, err := mb.writeAt(buf, 0) - if err != nil { - return 0, 0, fmt.Errorf("failed to rewrite compressed block: %w", err) - } - if n != len(buf) { - return 0, 0, fmt.Errorf("short write (%d != %d)", n, len(buf)) - } - mb.mfd.Truncate(int64(len(buf))) - mb.mfd.Sync() - } else if mb.mfd != nil { - mb.mfd.Truncate(eof) - mb.mfd.Sync() - // Update our checksum. - var lchk [8]byte - mb.mfd.ReadAt(lchk[:], eof-8) - copy(mb.lchk[0:], lchk[:]) - } else { - return 0, 0, fmt.Errorf("failed to truncate msg block %d, file not open", mb.index) - } - - // Update our last msg. - atomic.StoreUint64(&mb.last.seq, sm.seq) - mb.last.ts = sm.ts - - // Clear our cache. - mb.clearCacheAndOffset() - - // Redo per subject info for this block. - mb.resetPerSubjectInfo() - - // Load msgs again. - mb.loadMsgsWithLock() - - return purged, bytes, nil -} - -// Helper to determine if the mb is empty. -func (mb *msgBlock) isEmpty() bool { - return atomic.LoadUint64(&mb.first.seq) > atomic.LoadUint64(&mb.last.seq) -} - -// Lock should be held. -func (mb *msgBlock) selectNextFirst() { - var seq uint64 - fseq, lseq := atomic.LoadUint64(&mb.first.seq), atomic.LoadUint64(&mb.last.seq) - for seq = fseq + 1; seq <= lseq; seq++ { - if mb.dmap.Exists(seq) { - // We will move past this so we can delete the entry. - mb.dmap.Delete(seq) - } else { - break - } - } - // Set new first sequence. - atomic.StoreUint64(&mb.first.seq, seq) - - // Check if we are empty.. - if seq > lseq { - mb.first.ts = 0 - return - } - - // Need to get the timestamp. - // We will try the cache direct and fallback if needed. - var smv StoreMsg - sm, _ := mb.cacheLookup(seq, &smv) - if sm == nil { - // Slow path, need to unlock. - mb.mu.Unlock() - sm, _, _ = mb.fetchMsg(seq, &smv) - mb.mu.Lock() - } - if sm != nil { - mb.first.ts = sm.ts - } else { - mb.first.ts = 0 - } -} - -// Select the next FirstSeq -// Lock should be held. -func (fs *fileStore) selectNextFirst() { - if len(fs.blks) > 0 { - mb := fs.blks[0] - mb.mu.RLock() - fs.state.FirstSeq = atomic.LoadUint64(&mb.first.seq) - fs.state.FirstTime = time.Unix(0, mb.first.ts).UTC() - mb.mu.RUnlock() - } else { - // Could not find anything, so treat like purge - fs.state.FirstSeq = fs.state.LastSeq + 1 - fs.state.FirstTime = time.Time{} - } - // Mark first as moved. Plays into tombstone cleanup for syncBlocks. - fs.firstMoved = true -} - -// Lock should be held. -func (mb *msgBlock) resetCacheExpireTimer(td time.Duration) { - if td == 0 { - td = mb.cexp + 100*time.Millisecond - } - if mb.ctmr == nil { - mb.ctmr = time.AfterFunc(td, mb.expireCache) - } else { - mb.ctmr.Reset(td) - } -} - -// Lock should be held. -func (mb *msgBlock) startCacheExpireTimer() { - mb.resetCacheExpireTimer(0) -} - -// Used when we load in a message block. -// Lock should be held. -func (mb *msgBlock) clearCacheAndOffset() { - // Reset linear scan tracker. - mb.llseq = 0 - if mb.cache != nil { - mb.cache.off = 0 - mb.cache.wp = 0 - } - mb.clearCache() -} - -// Lock should be held. -func (mb *msgBlock) clearCache() { - if mb.ctmr != nil { - tsla := mb.sinceLastActivity() - if mb.fss == nil || tsla > mb.fexp { - // Force - mb.fss = nil - mb.ctmr.Stop() - mb.ctmr = nil - } else { - mb.resetCacheExpireTimer(mb.fexp - tsla) - } - } - - if mb.cache == nil { - return - } - - buf := mb.cache.buf - if mb.cache.off == 0 { - mb.cache = nil - } else { - // Clear msgs and index. - mb.cache.buf = nil - mb.cache.idx = nil - mb.cache.wp = 0 - } - recycleMsgBlockBuf(buf) -} - -// Called to possibly expire a message block cache. -func (mb *msgBlock) expireCache() { - mb.mu.Lock() - defer mb.mu.Unlock() - mb.expireCacheLocked() -} - -func (mb *msgBlock) tryForceExpireCache() { - mb.mu.Lock() - defer mb.mu.Unlock() - mb.tryForceExpireCacheLocked() -} - -// We will attempt to force expire this by temporarily clearing the last load time. -func (mb *msgBlock) tryForceExpireCacheLocked() { - llts := mb.llts - mb.llts = 0 - mb.expireCacheLocked() - mb.llts = llts -} - -// This is for expiration of the write cache, which will be partial with fip. -// So we want to bypass the Pools here. -// Lock should be held. -func (mb *msgBlock) tryExpireWriteCache() []byte { - if mb.cache == nil { - return nil - } - lwts, buf, llts, nra := mb.lwts, mb.cache.buf, mb.llts, mb.cache.nra - mb.lwts, mb.cache.nra = 0, true - mb.expireCacheLocked() - mb.lwts = lwts - if mb.cache != nil { - mb.cache.nra = nra - } - // We could check for a certain time since last load, but to be safe just reuse if no loads at all. - if llts == 0 && (mb.cache == nil || mb.cache.buf == nil) { - // Clear last write time since we now are about to move on to a new lmb. - mb.lwts = 0 - return buf[:0] - } - return nil -} - -// Lock should be held. -func (mb *msgBlock) expireCacheLocked() { - if mb.cache == nil && mb.fss == nil { - if mb.ctmr != nil { - mb.ctmr.Stop() - mb.ctmr = nil - } - return - } - - // Can't expire if we still have pending. - if mb.cache != nil && len(mb.cache.buf)-int(mb.cache.wp) > 0 { - mb.resetCacheExpireTimer(mb.cexp) - return - } - - // Grab timestamp to compare. - tns := time.Now().UnixNano() - - // For the core buffer of messages, we care about reads and writes, but not removes. - bufts := mb.llts - if mb.lwts > bufts { - bufts = mb.lwts - } - - // Check for activity on the cache that would prevent us from expiring. - if tns-bufts <= int64(mb.cexp) { - mb.resetCacheExpireTimer(mb.cexp - time.Duration(tns-bufts)) - return - } - - // If we are here we will at least expire the core msg buffer. - // We need to capture offset in case we do a write next before a full load. - if mb.cache != nil { - mb.cache.off += len(mb.cache.buf) - if !mb.cache.nra { - recycleMsgBlockBuf(mb.cache.buf) - } - mb.cache.buf = nil - mb.cache.wp = 0 - } - - // Check if we can clear out our idx unless under force expire. - // fss we keep longer and expire under sync timer checks. - mb.clearCache() -} - -func (fs *fileStore) startAgeChk() { - if fs.ageChk != nil { - return - } - if fs.cfg.MaxAge != 0 || fs.ttls != nil { - fs.ageChk = time.AfterFunc(fs.cfg.MaxAge, fs.expireMsgs) - } -} - -// Lock should be held. -func (fs *fileStore) resetAgeChk(delta int64) { - var next int64 = math.MaxInt64 - if fs.ttls != nil { - next = fs.ttls.GetNextExpiration(next) - } - - // If there's no MaxAge and there's nothing waiting to be expired then - // don't bother continuing. The next storeRawMsg() will wake us up if - // needs be. - if fs.cfg.MaxAge <= 0 && next == math.MaxInt64 { - clearTimer(&fs.ageChk) - return - } - - // Check to see if we should be firing sooner than MaxAge for an expiring TTL. - fireIn := fs.cfg.MaxAge - if next < math.MaxInt64 { - // Looks like there's a next expiration, use it either if there's no - // MaxAge set or if it looks to be sooner than MaxAge is. - if until := time.Until(time.Unix(0, next)); fireIn == 0 || until < fireIn { - fireIn = until - } - } - - // If not then look at the delta provided (usually gap to next age expiry). - if delta > 0 { - if fireIn == 0 || time.Duration(delta) < fireIn { - fireIn = time.Duration(delta) - } - } - - // Make sure we aren't firing too often either way, otherwise we can - // negatively impact stream ingest performance. - if fireIn < 250*time.Millisecond { - fireIn = 250 * time.Millisecond - } - - if fs.ageChk != nil { - fs.ageChk.Reset(fireIn) - } else { - fs.ageChk = time.AfterFunc(fireIn, fs.expireMsgs) - } -} - -// Lock should be held. -func (fs *fileStore) cancelAgeChk() { - if fs.ageChk != nil { - fs.ageChk.Stop() - fs.ageChk = nil - } -} - -// Lock must be held so that nothing else can interleave and write a -// new message on this subject before we get the chance to write the -// delete marker. If the delete marker is written successfully then -// this function returns a callback func to call scb and sdmcb after -// the lock has been released. -func (fs *fileStore) subjectDeleteMarkerIfNeeded(subj string, reason string) func() { - if fs.cfg.SubjectDeleteMarkerTTL <= 0 { - return nil - } - if _, ok := fs.psim.Find(stringToBytes(subj)); ok { - // There are still messages left with this subject, - // therefore it wasn't the last message deleted. - return nil - } - // Build the subject delete marker. If no TTL is specified then - // we'll default to 15 minutes — by that time every possible condition - // should have cleared (i.e. ordered consumer timeout, client timeouts, - // route/gateway interruptions, even device/client restarts etc). - ttl := int64(fs.cfg.SubjectDeleteMarkerTTL.Seconds()) - if ttl <= 0 { - return nil - } - var _hdr [128]byte - hdr := fmt.Appendf( - _hdr[:0], - "NATS/1.0\r\n%s: %s\r\n%s: %s\r\n%s: %d\r\n%s: %s\r\n\r\n\r\n", - JSMarkerReason, reason, - JSMessageTTL, time.Duration(ttl)*time.Second, - JSExpectedLastSubjSeq, 0, - JSExpectedLastSubjSeqSubj, subj, - ) - msg := &inMsg{ - subj: subj, - hdr: hdr, - } - sdmcb := fs.sdmcb - return func() { - if sdmcb != nil { - sdmcb(msg) - } - } -} - -// Filestore lock must be held but message block locks must not be. -// The caller should call the callback, if non-nil, after releasing -// the filestore lock. -func (fs *fileStore) subjectDeleteMarkersAfterOperation(reason string) func() { - if fs.cfg.SubjectDeleteMarkerTTL <= 0 || len(fs.markers) == 0 { - return nil - } - cbs := make([]func(), 0, len(fs.markers)) - for _, subject := range fs.markers { - if cb := fs.subjectDeleteMarkerIfNeeded(subject, reason); cb != nil { - cbs = append(cbs, cb) - } - } - fs.markers = nil - return func() { - for _, cb := range cbs { - cb() - } - } -} - -// Will expire msgs that are too old. -func (fs *fileStore) expireMsgs() { - // We need to delete one by one here and can not optimize for the time being. - // Reason is that we need more information to adjust ack pending in consumers. - var smv StoreMsg - var sm *StoreMsg - fs.mu.RLock() - maxAge := int64(fs.cfg.MaxAge) - minAge := time.Now().UnixNano() - maxAge - fs.mu.RUnlock() - - if maxAge > 0 { - var seq uint64 - for sm, seq, _ = fs.LoadNextMsg(fwcs, true, 0, &smv); sm != nil && sm.ts <= minAge; sm, seq, _ = fs.LoadNextMsg(fwcs, true, seq+1, &smv) { - if len(sm.hdr) > 0 { - if ttl, err := getMessageTTL(sm.hdr); err == nil && ttl < 0 { - // The message has a negative TTL, therefore it must "never expire". - minAge = time.Now().UnixNano() - maxAge - continue - } - } - // Remove the message and then, if LimitsTTL is enabled, try and work out - // if it was the last message of that particular subject that we just deleted. - fs.mu.Lock() - fs.removeMsgViaLimits(sm.seq) - fs.mu.Unlock() - // Recalculate in case we are expiring a bunch. - minAge = time.Now().UnixNano() - maxAge - } - } - - fs.mu.Lock() - defer fs.mu.Unlock() - - // TODO: Not great that we're holding the lock here, but the timed hash wheel isn't thread-safe. - nextTTL := int64(math.MaxInt64) - if fs.ttls != nil { - fs.ttls.ExpireTasks(func(seq uint64, ts int64) { - fs.removeMsg(seq, false, false, false) - }) - if maxAge > 0 { - // Only check if we're expiring something in the next MaxAge interval, saves us a bit - // of work if MaxAge will beat us to the next expiry anyway. - nextTTL = fs.ttls.GetNextExpiration(time.Now().Add(time.Duration(maxAge)).UnixNano()) - } else { - nextTTL = fs.ttls.GetNextExpiration(math.MaxInt64) - } - } - - // Only cancel if no message left, not on potential lookup error that would result in sm == nil. - if fs.state.Msgs == 0 && nextTTL == math.MaxInt64 { - fs.cancelAgeChk() - } else { - if sm == nil { - fs.resetAgeChk(0) - } else { - fs.resetAgeChk(sm.ts - minAge) - } - } -} - -// Lock should be held. -func (fs *fileStore) checkAndFlushAllBlocks() { - for _, mb := range fs.blks { - if mb.pendingWriteSize() > 0 { - // Since fs lock is held need to pull this apart in case we need to rebuild state. - mb.mu.Lock() - ld, _ := mb.flushPendingMsgsLocked() - mb.mu.Unlock() - if ld != nil { - fs.rebuildStateLocked(ld) - } - } - } -} - -// This will check all the checksums on messages and report back any sequence numbers with errors. -func (fs *fileStore) checkMsgs() *LostStreamData { - fs.mu.Lock() - defer fs.mu.Unlock() - - fs.checkAndFlushAllBlocks() - - // Clear any global subject state. - fs.psim, fs.tsl = fs.psim.Empty(), 0 - - for _, mb := range fs.blks { - // Make sure encryption loaded if needed for the block. - fs.loadEncryptionForMsgBlock(mb) - // FIXME(dlc) - check tombstones here too? - if ld, _, err := mb.rebuildState(); err != nil && ld != nil { - // Rebuild fs state too. - fs.rebuildStateLocked(ld) - } - fs.populateGlobalPerSubjectInfo(mb) - } - - return fs.ld -} - -// Lock should be held. -func (mb *msgBlock) enableForWriting(fip bool) error { - if mb == nil { - return errNoMsgBlk - } - if mb.mfd != nil { - return nil - } - <-dios - mfd, err := os.OpenFile(mb.mfn, os.O_CREATE|os.O_RDWR, defaultFilePerms) - dios <- struct{}{} - if err != nil { - return fmt.Errorf("error opening msg block file [%q]: %v", mb.mfn, err) - } - mb.mfd = mfd - - // Spin up our flusher loop if needed. - if !fip { - mb.spinUpFlushLoop() - } - - return nil -} - -// Helper function to place a delete tombstone. -func (mb *msgBlock) writeTombstone(seq uint64, ts int64) error { - return mb.writeMsgRecord(emptyRecordLen, seq|tbit, _EMPTY_, nil, nil, ts, true) -} - -// Will write the message record to the underlying message block. -// filestore lock will be held. -func (mb *msgBlock) writeMsgRecord(rl, seq uint64, subj string, mhdr, msg []byte, ts int64, flush bool) error { - mb.mu.Lock() - defer mb.mu.Unlock() - - // Enable for writing if our mfd is not open. - if mb.mfd == nil { - if err := mb.enableForWriting(flush); err != nil { - return err - } - } - - // Make sure we have a cache setup. - if mb.cache == nil { - mb.setupWriteCache(nil) - } - - // Check if we are tracking per subject for our simple state. - // Do this before changing the cache that would trigger a flush pending msgs call - // if we needed to regenerate the per subject info. - // Note that tombstones have no subject so will not trigger here. - if len(subj) > 0 && !mb.noTrack { - if err := mb.ensurePerSubjectInfoLoaded(); err != nil { - return err - } - // Mark fss activity. - mb.lsts = time.Now().UnixNano() - if ss, ok := mb.fss.Find(stringToBytes(subj)); ok && ss != nil { - ss.Msgs++ - ss.Last = seq - ss.lastNeedsUpdate = false - } else { - mb.fss.Insert(stringToBytes(subj), SimpleState{Msgs: 1, First: seq, Last: seq}) - } - } - - // Indexing - index := len(mb.cache.buf) + int(mb.cache.off) - - // Formats - // Format with no header - // total_len(4) sequence(8) timestamp(8) subj_len(2) subj msg hash(8) - // With headers, high bit on total length will be set. - // total_len(4) sequence(8) timestamp(8) subj_len(2) subj hdr_len(4) hdr msg hash(8) - - var le = binary.LittleEndian - - l := uint32(rl) - hasHeaders := len(mhdr) > 0 - if hasHeaders { - l |= hbit - } - - // Reserve space for the header on the underlying buffer. - mb.cache.buf = append(mb.cache.buf, make([]byte, msgHdrSize)...) - hdr := mb.cache.buf[len(mb.cache.buf)-msgHdrSize : len(mb.cache.buf)] - le.PutUint32(hdr[0:], l) - le.PutUint64(hdr[4:], seq) - le.PutUint64(hdr[12:], uint64(ts)) - le.PutUint16(hdr[20:], uint16(len(subj))) - - // Now write to underlying buffer. - mb.cache.buf = append(mb.cache.buf, subj...) - - if hasHeaders { - var hlen [4]byte - le.PutUint32(hlen[0:], uint32(len(mhdr))) - mb.cache.buf = append(mb.cache.buf, hlen[:]...) - mb.cache.buf = append(mb.cache.buf, mhdr...) - } - mb.cache.buf = append(mb.cache.buf, msg...) - - // Calculate hash. - mb.hh.Reset() - mb.hh.Write(hdr[4:20]) - mb.hh.Write(stringToBytes(subj)) - if hasHeaders { - mb.hh.Write(mhdr) - } - mb.hh.Write(msg) - checksum := mb.hh.Sum(mb.lchk[:0:highwayhash.Size64]) - copy(mb.lchk[0:], checksum) - - // Update write through cache. - // Write to msg record. - mb.cache.buf = append(mb.cache.buf, checksum...) - mb.cache.lrl = uint32(rl) - - // Set cache timestamp for last store. - mb.lwts = ts - - // Only update index and do accounting if not a delete tombstone. - if seq&tbit == 0 { - // Accounting, do this before stripping ebit, it is ebit aware. - mb.updateAccounting(seq, ts, rl) - // Strip ebit if set. - seq = seq &^ ebit - if mb.cache.fseq == 0 { - mb.cache.fseq = seq - } - // Write index - mb.cache.idx = append(mb.cache.idx, uint32(index)|cbit) - } else { - // Make sure to account for tombstones in rbytes. - mb.rbytes += rl - } - - fch, werr := mb.fch, mb.werr - - // If we should be flushing, or had a write error, do so here. - if flush || werr != nil { - ld, err := mb.flushPendingMsgsLocked() - if ld != nil && mb.fs != nil { - // We have the mb lock here, this needs the mb locks so do in its own go routine. - go mb.fs.rebuildState(ld) - } - if err != nil { - return err - } - } else { - // Kick the flusher here. - kickFlusher(fch) - } - - return nil -} - -// How many bytes pending to be written for this message block. -func (mb *msgBlock) pendingWriteSize() int { - if mb == nil { - return 0 - } - mb.mu.RLock() - defer mb.mu.RUnlock() - return mb.pendingWriteSizeLocked() -} - -// How many bytes pending to be written for this message block. -func (mb *msgBlock) pendingWriteSizeLocked() int { - if mb == nil { - return 0 - } - var pending int - if !mb.closed && mb.mfd != nil && mb.cache != nil { - pending = len(mb.cache.buf) - int(mb.cache.wp) - } - return pending -} - -// Try to close our FDs if we can. -func (mb *msgBlock) closeFDs() error { - mb.mu.Lock() - defer mb.mu.Unlock() - return mb.closeFDsLocked() -} - -func (mb *msgBlock) closeFDsLocked() error { - if buf, _ := mb.bytesPending(); len(buf) > 0 { - return errPendingData - } - mb.closeFDsLockedNoCheck() - return nil -} - -func (mb *msgBlock) closeFDsLockedNoCheck() { - if mb.mfd != nil { - mb.mfd.Close() - mb.mfd = nil - } -} - -// bytesPending returns the buffer to be used for writing to the underlying file. -// This marks we are in flush and will return nil if asked again until cleared. -// Lock should be held. -func (mb *msgBlock) bytesPending() ([]byte, error) { - if mb == nil || mb.mfd == nil { - return nil, errNoPending - } - if mb.cache == nil { - return nil, errNoCache - } - if len(mb.cache.buf) <= mb.cache.wp { - return nil, errNoPending - } - buf := mb.cache.buf[mb.cache.wp:] - if len(buf) == 0 { - return nil, errNoPending - } - return buf, nil -} - -// Returns the current blkSize including deleted msgs etc. -func (mb *msgBlock) blkSize() uint64 { - mb.mu.RLock() - nb := mb.rbytes - mb.mu.RUnlock() - return nb -} - -// Update accounting on a write msg. -// Lock should be held. -func (mb *msgBlock) updateAccounting(seq uint64, ts int64, rl uint64) { - isDeleted := seq&ebit != 0 - if isDeleted { - seq = seq &^ ebit - } - - fseq := atomic.LoadUint64(&mb.first.seq) - if (fseq == 0 || mb.first.ts == 0) && seq >= fseq { - atomic.StoreUint64(&mb.first.seq, seq) - mb.first.ts = ts - } - // Need atomics here for selectMsgBlock speed. - atomic.StoreUint64(&mb.last.seq, seq) - mb.last.ts = ts - mb.rbytes += rl - if !isDeleted { - mb.bytes += rl - mb.msgs++ - } -} - -// Lock should be held. -func (fs *fileStore) writeMsgRecord(seq uint64, ts int64, subj string, hdr, msg []byte) (uint64, error) { - var err error - - // Get size for this message. - rl := fileStoreMsgSize(subj, hdr, msg) - if rl&hbit != 0 { - return 0, ErrMsgTooLarge - } - // Grab our current last message block. - mb := fs.lmb - - // Mark as dirty for stream state. - fs.dirty++ - - if mb == nil || mb.msgs > 0 && mb.blkSize()+rl > fs.fcfg.BlockSize { - if mb != nil && fs.fcfg.Compression != NoCompression { - // We've now reached the end of this message block, if we want - // to compress blocks then now's the time to do it. - go mb.recompressOnDiskIfNeeded() - } - if mb, err = fs.newMsgBlockForWrite(); err != nil { - return 0, err - } - } - - // Ask msg block to store in write through cache. - err = mb.writeMsgRecord(rl, seq, subj, hdr, msg, ts, fs.fip) - - return rl, err -} - -// For writing tombstones to our lmb. This version will enforce maximum block sizes. -// Lock should be held. -func (fs *fileStore) writeTombstone(seq uint64, ts int64) error { - // Grab our current last message block. - lmb := fs.lmb - var err error - - if lmb == nil || lmb.blkSize()+emptyRecordLen > fs.fcfg.BlockSize { - if lmb != nil && fs.fcfg.Compression != NoCompression { - // We've now reached the end of this message block, if we want - // to compress blocks then now's the time to do it. - go lmb.recompressOnDiskIfNeeded() - } - if lmb, err = fs.newMsgBlockForWrite(); err != nil { - return err - } - } - return lmb.writeTombstone(seq, ts) -} - -func (mb *msgBlock) recompressOnDiskIfNeeded() error { - alg := mb.fs.fcfg.Compression - mb.mu.Lock() - defer mb.mu.Unlock() - - origFN := mb.mfn // The original message block on disk. - tmpFN := mb.mfn + compressTmpSuffix // The compressed block will be written here. - - // Open up the file block and read in the entire contents into memory. - // One of two things will happen: - // 1. The block will be compressed already and have a valid metadata - // header, in which case we do nothing. - // 2. The block will be uncompressed, in which case we will compress it - // and then write it back out to disk, re-encrypting if necessary. - <-dios - origBuf, err := os.ReadFile(origFN) - dios <- struct{}{} - - if err != nil { - return fmt.Errorf("failed to read original block from disk: %w", err) - } - - // If the block is encrypted then we will need to decrypt it before - // doing anything. We always encrypt after compressing because then the - // compression can be as efficient as possible on the raw data, whereas - // the encrypted ciphertext will not compress anywhere near as well. - // The block encryption also covers the optional compression metadata. - if mb.bek != nil && len(origBuf) > 0 { - bek, err := genBlockEncryptionKey(mb.fs.fcfg.Cipher, mb.seed, mb.nonce) - if err != nil { - return err - } - mb.bek = bek - mb.bek.XORKeyStream(origBuf, origBuf) - } - - meta := &CompressionInfo{} - if _, err := meta.UnmarshalMetadata(origBuf); err != nil { - // An error is only returned here if there's a problem with parsing - // the metadata. If the file has no metadata at all, no error is - // returned and the algorithm defaults to no compression. - return fmt.Errorf("failed to read existing metadata header: %w", err) - } - if meta.Algorithm == alg { - // The block is already compressed with the chosen algorithm so there - // is nothing else to do. This is not a common case, it is here only - // to ensure we don't do unnecessary work in case something asked us - // to recompress an already compressed block with the same algorithm. - return nil - } else if alg != NoCompression { - // The block is already compressed using some algorithm, so we need - // to decompress the block using the existing algorithm before we can - // recompress it with the new one. - if origBuf, err = meta.Algorithm.Decompress(origBuf); err != nil { - return fmt.Errorf("failed to decompress original block: %w", err) - } - } - - // Rather than modifying the existing block on disk (which is a dangerous - // operation if something goes wrong), create a new temporary file. We will - // write out the new block here and then swap the files around afterwards - // once everything else has succeeded correctly. - <-dios - tmpFD, err := os.OpenFile(tmpFN, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, defaultFilePerms) - dios <- struct{}{} - - if err != nil { - return fmt.Errorf("failed to create temporary file: %w", err) - } - - errorCleanup := func(err error) error { - tmpFD.Close() - os.Remove(tmpFN) - return err - } - - // The original buffer at this point is uncompressed, so we will now compress - // it if needed. Note that if the selected algorithm is NoCompression, the - // Compress function will just return the input buffer unmodified. - cmpBuf, err := alg.Compress(origBuf) - if err != nil { - return errorCleanup(fmt.Errorf("failed to compress block: %w", err)) - } - - // We only need to write out the metadata header if compression is enabled. - // If we're trying to uncompress the file on disk at this point, don't bother - // writing metadata. - if alg != NoCompression { - meta := &CompressionInfo{ - Algorithm: alg, - OriginalSize: uint64(len(origBuf)), - } - cmpBuf = append(meta.MarshalMetadata(), cmpBuf...) - } - - // Re-encrypt the block if necessary. - if mb.bek != nil && len(cmpBuf) > 0 { - bek, err := genBlockEncryptionKey(mb.fs.fcfg.Cipher, mb.seed, mb.nonce) - if err != nil { - return errorCleanup(err) - } - mb.bek = bek - mb.bek.XORKeyStream(cmpBuf, cmpBuf) - } - - // Write the new block data (which might be compressed or encrypted) to the - // temporary file. - if n, err := tmpFD.Write(cmpBuf); err != nil { - return errorCleanup(fmt.Errorf("failed to write to temporary file: %w", err)) - } else if n != len(cmpBuf) { - return errorCleanup(fmt.Errorf("short write to temporary file (%d != %d)", n, len(cmpBuf))) - } - if err := tmpFD.Sync(); err != nil { - return errorCleanup(fmt.Errorf("failed to sync temporary file: %w", err)) - } - if err := tmpFD.Close(); err != nil { - return errorCleanup(fmt.Errorf("failed to close temporary file: %w", err)) - } - - // Now replace the original file with the newly updated temp file. - if err := os.Rename(tmpFN, origFN); err != nil { - return fmt.Errorf("failed to move temporary file into place: %w", err) - } - - // Since the message block might be retained in memory, make sure the - // compression algorithm is up-to-date, since this will be needed when - // compacting or truncating. - mb.cmp = alg - - // Also update rbytes - mb.rbytes = uint64(len(cmpBuf)) - - return nil -} - -func (mb *msgBlock) decompressIfNeeded(buf []byte) ([]byte, error) { - var meta CompressionInfo - if n, err := meta.UnmarshalMetadata(buf); err != nil { - // There was a problem parsing the metadata header of the block. - // If there's no metadata header, an error isn't returned here, - // we will instead just use default values of no compression. - return nil, err - } else if n == 0 { - // There were no metadata bytes, so we assume the block is not - // compressed and return it as-is. - return buf, nil - } else { - // Metadata was present so it's quite likely the block contents - // are compressed. If by any chance the metadata claims that the - // block is uncompressed, then the input slice is just returned - // unmodified. - return meta.Algorithm.Decompress(buf[n:]) - } -} - -// Lock should be held. -func (mb *msgBlock) ensureRawBytesLoaded() error { - if mb.rbytes > 0 { - return nil - } - f, err := mb.openBlock() - if err != nil { - return err - } - defer f.Close() - if fi, err := f.Stat(); fi != nil && err == nil { - mb.rbytes = uint64(fi.Size()) - } else { - return err - } - return nil -} - -// Sync msg and index files as needed. This is called from a timer. -func (fs *fileStore) syncBlocks() { - fs.mu.Lock() - // If closed or a snapshot is in progress bail. - if fs.closed || fs.sips > 0 { - fs.mu.Unlock() - return - } - blks := append([]*msgBlock(nil), fs.blks...) - lmb, firstMoved, firstSeq := fs.lmb, fs.firstMoved, fs.state.FirstSeq - // Clear first moved. - fs.firstMoved = false - fs.mu.Unlock() - - var markDirty bool - for _, mb := range blks { - // Do actual sync. Hold lock for consistency. - mb.mu.Lock() - if mb.closed { - mb.mu.Unlock() - continue - } - // See if we can close FDs due to being idle. - if mb.mfd != nil && mb.sinceLastWriteActivity() > closeFDsIdle { - mb.dirtyCloseWithRemove(false) - } - - // If our first has moved and we are set to noCompact (which is from tombstones), - // clear so that we might cleanup tombstones. - if firstMoved && mb.noCompact { - mb.noCompact = false - } - // Check if we should compact here as well. - // Do not compact last mb. - var needsCompact bool - if mb != lmb && mb.ensureRawBytesLoaded() == nil && mb.shouldCompactSync() { - needsCompact = true - markDirty = true - } - - // Check if we need to sync. We will not hold lock during actual sync. - needSync := mb.needSync - if needSync { - // Flush anything that may be pending. - mb.flushPendingMsgsLocked() - } - mb.mu.Unlock() - - // Check if we should compact here. - // Need to hold fs lock in case we reference psim when loading in the mb and we may remove this block if truly empty. - if needsCompact { - fs.mu.RLock() - mb.mu.Lock() - mb.compactWithFloor(firstSeq) - // If this compact removed all raw bytes due to tombstone cleanup, schedule to remove. - shouldRemove := mb.rbytes == 0 - mb.mu.Unlock() - fs.mu.RUnlock() - - // Check if we should remove. This will not be common, so we will re-take fs write lock here vs changing - // it above which we would prefer to be a readlock such that other lookups can occur while compacting this block. - if shouldRemove { - fs.mu.Lock() - mb.mu.Lock() - fs.removeMsgBlock(mb) - mb.mu.Unlock() - fs.mu.Unlock() - needSync = false - } - } - - // Check if we need to sync this block. - if needSync { - mb.mu.Lock() - var fd *os.File - var didOpen bool - if mb.mfd != nil { - fd = mb.mfd - } else { - <-dios - fd, _ = os.OpenFile(mb.mfn, os.O_RDWR, defaultFilePerms) - dios <- struct{}{} - didOpen = true - } - // If we have an fd. - if fd != nil { - canClear := fd.Sync() == nil - // If we opened the file close the fd. - if didOpen { - fd.Close() - } - // Only clear sync flag on success. - if canClear { - mb.needSync = false - } - } - mb.mu.Unlock() - } - } - - fs.mu.Lock() - if fs.closed { - fs.mu.Unlock() - return - } - fs.setSyncTimer() - if markDirty { - fs.dirty++ - } - - // Sync state file if we are not running with sync always. - if !fs.fcfg.SyncAlways { - fn := filepath.Join(fs.fcfg.StoreDir, msgDir, streamStreamStateFile) - <-dios - fd, _ := os.OpenFile(fn, os.O_RDWR, defaultFilePerms) - dios <- struct{}{} - if fd != nil { - fd.Sync() - fd.Close() - } - } - fs.mu.Unlock() -} - -// Select the message block where this message should be found. -// Return nil if not in the set. -// Read lock should be held. -func (fs *fileStore) selectMsgBlock(seq uint64) *msgBlock { - _, mb := fs.selectMsgBlockWithIndex(seq) - return mb -} - -// Lock should be held. -func (fs *fileStore) selectMsgBlockWithIndex(seq uint64) (int, *msgBlock) { - // Check for out of range. - if seq < fs.state.FirstSeq || seq > fs.state.LastSeq || fs.state.Msgs == 0 { - return -1, nil - } - - const linearThresh = 32 - nb := len(fs.blks) - 1 - - if nb < linearThresh { - for i, mb := range fs.blks { - if seq <= atomic.LoadUint64(&mb.last.seq) { - return i, mb - } - } - return -1, nil - } - - // Do traditional binary search here since we know the blocks are sorted by sequence first and last. - for low, high, mid := 0, nb, nb/2; low <= high; mid = (low + high) / 2 { - mb := fs.blks[mid] - // Right now these atomic loads do not factor in, so fine to leave. Was considering - // uplifting these to fs scope to avoid atomic load but not needed. - first, last := atomic.LoadUint64(&mb.first.seq), atomic.LoadUint64(&mb.last.seq) - if seq > last { - low = mid + 1 - } else if seq < first { - // A message block's first sequence can change here meaning we could find a gap. - // We want to behave like above, which if inclusive (we check at start) should - // always return an index and a valid mb. - // If we have a gap then our seq would be > fs.blks[mid-1].last.seq - if mid == 0 || seq > atomic.LoadUint64(&fs.blks[mid-1].last.seq) { - return mid, mb - } - high = mid - 1 - } else { - return mid, mb - } - } - - return -1, nil -} - -// Select the message block where this message should be found. -// Return nil if not in the set. -func (fs *fileStore) selectMsgBlockForStart(minTime time.Time) *msgBlock { - fs.mu.RLock() - defer fs.mu.RUnlock() - - t := minTime.UnixNano() - for _, mb := range fs.blks { - mb.mu.RLock() - found := t <= mb.last.ts - mb.mu.RUnlock() - if found { - return mb - } - } - return nil -} - -// Index a raw msg buffer. -// Lock should be held. -func (mb *msgBlock) indexCacheBuf(buf []byte) error { - var le = binary.LittleEndian - - var fseq uint64 - var idx []uint32 - var index uint32 - - mbFirstSeq := atomic.LoadUint64(&mb.first.seq) - mbLastSeq := atomic.LoadUint64(&mb.last.seq) - - // Sanity check here since we calculate size to allocate based on this. - if mbFirstSeq > (mbLastSeq + 1) { // Purged state first == last + 1 - mb.fs.warn("indexCacheBuf corrupt state: mb.first %d mb.last %d", mbFirstSeq, mbLastSeq) - // This would cause idxSz to wrap. - return errCorruptState - } - - // Capture beginning size of dmap. - dms := uint64(mb.dmap.Size()) - idxSz := mbLastSeq - mbFirstSeq + 1 - - if mb.cache == nil { - // Approximation, may adjust below. - fseq = mbFirstSeq - idx = make([]uint32, 0, idxSz) - mb.cache = &cache{} - } else { - fseq = mb.cache.fseq - idx = mb.cache.idx - if len(idx) == 0 { - idx = make([]uint32, 0, idxSz) - } - index = uint32(len(mb.cache.buf)) - buf = append(mb.cache.buf, buf...) - } - - // Create FSS if we should track. - var popFss bool - if mb.fssNotLoaded() { - mb.fss = stree.NewSubjectTree[SimpleState]() - popFss = true - } - // Mark fss activity. - mb.lsts = time.Now().UnixNano() - mb.ttls = 0 - - lbuf := uint32(len(buf)) - var seq, ttls uint64 - var sm StoreMsg // Used for finding TTL headers - for index < lbuf { - if index+msgHdrSize > lbuf { - return errCorruptState - } - hdr := buf[index : index+msgHdrSize] - rl, slen := le.Uint32(hdr[0:]), int(le.Uint16(hdr[20:])) - seq = le.Uint64(hdr[4:]) - - // Clear any headers bit that could be set. - hasHeaders := rl&hbit != 0 - rl &^= hbit - dlen := int(rl) - msgHdrSize - - // Do some quick sanity checks here. - if dlen < 0 || slen > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh { - mb.fs.warn("indexCacheBuf corrupt record state: dlen %d slen %d index %d rl %d lbuf %d", dlen, slen, index, rl, lbuf) - // This means something is off. - // TODO(dlc) - Add into bad list? - return errCorruptState - } - - // Check for tombstones which we can skip in terms of indexing. - if seq&tbit != 0 { - index += rl - continue - } - - // Clear any erase bits. - erased := seq&ebit != 0 - seq = seq &^ ebit - - // We defer checksum checks to individual msg cache lookups to amortorize costs and - // not introduce latency for first message from a newly loaded block. - if seq >= mbFirstSeq { - // Track that we do not have holes. - if slot := int(seq - mbFirstSeq); slot != len(idx) { - // If we have a hole fill it. - for dseq := mbFirstSeq + uint64(len(idx)); dseq < seq; dseq++ { - idx = append(idx, dbit) - if dms == 0 { - mb.dmap.Insert(dseq) - } - } - } - // Add to our index. - idx = append(idx, index) - mb.cache.lrl = uint32(rl) - // Adjust if we guessed wrong. - if seq != 0 && seq < fseq { - fseq = seq - } - - // Make sure our dmap has this entry if it was erased. - if erased && dms == 0 { - mb.dmap.Insert(seq) - } - - // Handle FSS inline here. - if popFss && slen > 0 && !mb.noTrack && !erased && !mb.dmap.Exists(seq) { - bsubj := buf[index+msgHdrSize : index+msgHdrSize+uint32(slen)] - if ss, ok := mb.fss.Find(bsubj); ok && ss != nil { - ss.Msgs++ - ss.Last = seq - ss.lastNeedsUpdate = false - } else { - mb.fss.Insert(bsubj, SimpleState{ - Msgs: 1, - First: seq, - Last: seq, - }) - } - } - - // Count how many TTLs we think are in this message block. - // TODO(nat): Not terribly optimal... - if hasHeaders { - if fsm, err := mb.msgFromBuf(buf, &sm, nil); err == nil && fsm != nil { - if _, err = getMessageTTL(fsm.hdr); err == nil && len(fsm.hdr) > 0 { - ttls++ - } - } - } - } - index += rl - } - - // Track holes at the end of the block, these would be missed in the - // earlier loop if we've ran out of block file to look at, but should - // be easily noticed because the seq will be below the last seq from - // the index. - if seq > 0 && seq < mbLastSeq { - for dseq := seq; dseq < mbLastSeq; dseq++ { - idx = append(idx, dbit) - if dms == 0 { - mb.dmap.Insert(dseq) - } - } - } - - mb.cache.buf = buf - mb.cache.idx = idx - mb.cache.fseq = fseq - mb.cache.wp += int(lbuf) - mb.ttls = ttls - - return nil -} - -// flushPendingMsgs writes out any messages for this message block. -func (mb *msgBlock) flushPendingMsgs() error { - mb.mu.Lock() - fsLostData, err := mb.flushPendingMsgsLocked() - fs := mb.fs - mb.mu.Unlock() - - // Signals us that we need to rebuild filestore state. - if fsLostData != nil && fs != nil { - // Rebuild fs state too. - fs.rebuildState(fsLostData) - } - return err -} - -// Write function for actual data. -// mb.mfd should not be nil. -// Lock should held. -func (mb *msgBlock) writeAt(buf []byte, woff int64) (int, error) { - // Used to mock write failures. - if mb.mockWriteErr { - // Reset on trip. - mb.mockWriteErr = false - return 0, errors.New("mock write error") - } - <-dios - n, err := mb.mfd.WriteAt(buf, woff) - dios <- struct{}{} - return n, err -} - -// flushPendingMsgsLocked writes out any messages for this message block. -// Lock should be held. -func (mb *msgBlock) flushPendingMsgsLocked() (*LostStreamData, error) { - // Signals us that we need to rebuild filestore state. - var fsLostData *LostStreamData - - if mb.cache == nil || mb.mfd == nil { - return nil, nil - } - - buf, err := mb.bytesPending() - // If we got an error back return here. - if err != nil { - // No pending data to be written is not an error. - if err == errNoPending || err == errNoCache { - err = nil - } - return nil, err - } - - woff := int64(mb.cache.off + mb.cache.wp) - lob := len(buf) - - // TODO(dlc) - Normally we would not hold the lock across I/O so we can improve performance. - // We will hold to stabilize the code base, as we have had a few anomalies with partial cache errors - // under heavy load. - - // Check if we need to encrypt. - if mb.bek != nil && lob > 0 { - // Need to leave original alone. - var dst []byte - if lob <= defaultLargeBlockSize { - dst = getMsgBlockBuf(lob)[:lob] - } else { - dst = make([]byte, lob) - } - mb.bek.XORKeyStream(dst, buf) - buf = dst - } - - // Append new data to the message block file. - for lbb := lob; lbb > 0; lbb = len(buf) { - n, err := mb.writeAt(buf, woff) - if err != nil { - mb.dirtyCloseWithRemove(false) - ld, _, _ := mb.rebuildStateLocked() - mb.werr = err - return ld, err - } - // Update our write offset. - woff += int64(n) - // Partial write. - if n != lbb { - buf = buf[n:] - } else { - // Done. - break - } - } - - // Clear any error. - mb.werr = nil - - // Cache may be gone. - if mb.cache == nil || mb.mfd == nil { - return fsLostData, mb.werr - } - - // Check if we are in sync always mode. - if mb.syncAlways { - mb.mfd.Sync() - } else { - mb.needSync = true - } - - // Check for additional writes while we were writing to the disk. - moreBytes := len(mb.cache.buf) - mb.cache.wp - lob - - // Decide what we want to do with the buffer in hand. If we have load interest - // we will hold onto the whole thing, otherwise empty the buffer, possibly reusing it. - if ts := time.Now().UnixNano(); ts < mb.llts || (ts-mb.llts) <= int64(mb.cexp) { - mb.cache.wp += lob - } else { - if cap(mb.cache.buf) <= maxBufReuse { - buf = mb.cache.buf[:0] - } else { - recycleMsgBlockBuf(mb.cache.buf) - buf = nil - } - if moreBytes > 0 { - nbuf := mb.cache.buf[len(mb.cache.buf)-moreBytes:] - if moreBytes > (len(mb.cache.buf)/4*3) && cap(nbuf) <= maxBufReuse { - buf = nbuf - } else { - buf = append(buf, nbuf...) - } - } - // Update our cache offset. - mb.cache.off = int(woff) - // Reset write pointer. - mb.cache.wp = 0 - // Place buffer back in the cache structure. - mb.cache.buf = buf - // Mark fseq to 0 - mb.cache.fseq = 0 - } - - return fsLostData, mb.werr -} - -// Lock should be held. -func (mb *msgBlock) clearLoading() { - mb.loading = false -} - -// Will load msgs from disk. -func (mb *msgBlock) loadMsgs() error { - // We hold the lock here the whole time by design. - mb.mu.Lock() - defer mb.mu.Unlock() - return mb.loadMsgsWithLock() -} - -// Lock should be held. -func (mb *msgBlock) cacheAlreadyLoaded() bool { - if mb.cache == nil || mb.cache.off != 0 || mb.cache.fseq == 0 || len(mb.cache.buf) == 0 { - return false - } - numEntries := mb.msgs + uint64(mb.dmap.Size()) + (atomic.LoadUint64(&mb.first.seq) - mb.cache.fseq) - return numEntries == uint64(len(mb.cache.idx)) -} - -// Lock should be held. -func (mb *msgBlock) cacheNotLoaded() bool { - return !mb.cacheAlreadyLoaded() -} - -// Report if our fss is not loaded. -// Lock should be held. -func (mb *msgBlock) fssNotLoaded() bool { - return mb.fss == nil && !mb.noTrack -} - -// Wrap openBlock for the gated semaphore processing. -// Lock should be held -func (mb *msgBlock) openBlock() (*os.File, error) { - // Gate with concurrent IO semaphore. - <-dios - f, err := os.Open(mb.mfn) - dios <- struct{}{} - return f, err -} - -// Used to load in the block contents. -// Lock should be held and all conditionals satisfied prior. -func (mb *msgBlock) loadBlock(buf []byte) ([]byte, error) { - var f *os.File - // Re-use if we have mfd open. - if mb.mfd != nil { - f = mb.mfd - if n, err := f.Seek(0, 0); n != 0 || err != nil { - f = nil - mb.closeFDsLockedNoCheck() - } - } - if f == nil { - var err error - f, err = mb.openBlock() - if err != nil { - if os.IsNotExist(err) { - err = errNoBlkData - } - return nil, err - } - defer f.Close() - } - - var sz int - if info, err := f.Stat(); err == nil { - sz64 := info.Size() - if int64(int(sz64)) == sz64 { - sz = int(sz64) - } else { - return nil, errMsgBlkTooBig - } - } - - if buf == nil { - buf = getMsgBlockBuf(sz) - if sz > cap(buf) { - // We know we will make a new one so just recycle for now. - recycleMsgBlockBuf(buf) - buf = nil - } - } - - if sz > cap(buf) { - buf = make([]byte, sz) - } else { - buf = buf[:sz] - } - - <-dios - n, err := io.ReadFull(f, buf) - dios <- struct{}{} - // On success capture raw bytes size. - if err == nil { - mb.rbytes = uint64(n) - } - return buf[:n], err -} - -// Lock should be held. -func (mb *msgBlock) loadMsgsWithLock() error { - // Check for encryption, we do not load keys on startup anymore so might need to load them here. - if mb.fs != nil && mb.fs.prf != nil && (mb.aek == nil || mb.bek == nil) { - if err := mb.fs.loadEncryptionForMsgBlock(mb); err != nil { - return err - } - } - - // Check to see if we are loading already. - if mb.loading { - return nil - } - - // Set loading status. - mb.loading = true - defer mb.clearLoading() - - var nchecks int - -checkCache: - nchecks++ - if nchecks > 8 { - return errCorruptState - } - - // Check to see if we have a full cache. - if mb.cacheAlreadyLoaded() { - return nil - } - - mb.llts = time.Now().UnixNano() - - // FIXME(dlc) - We could be smarter here. - if buf, _ := mb.bytesPending(); len(buf) > 0 { - ld, err := mb.flushPendingMsgsLocked() - if ld != nil && mb.fs != nil { - // We do not know if fs is locked or not at this point. - // This should be an exceptional condition so do so in Go routine. - go mb.fs.rebuildState(ld) - } - if err != nil { - return err - } - goto checkCache - } - - // Load in the whole block. - // We want to hold the mb lock here to avoid any changes to state. - buf, err := mb.loadBlock(nil) - if err != nil { - mb.fs.warn("loadBlock error: %v", err) - if err == errNoBlkData { - if ld, _, err := mb.rebuildStateLocked(); err != nil && ld != nil { - // Rebuild fs state too. - go mb.fs.rebuildState(ld) - } - } - return err - } - - // Reset the cache since we just read everything in. - // Make sure this is cleared in case we had a partial when we started. - mb.clearCacheAndOffset() - - // Check if we need to decrypt. - if mb.bek != nil && len(buf) > 0 { - bek, err := genBlockEncryptionKey(mb.fs.fcfg.Cipher, mb.seed, mb.nonce) - if err != nil { - return err - } - mb.bek = bek - mb.bek.XORKeyStream(buf, buf) - } - - // Check for compression. - if buf, err = mb.decompressIfNeeded(buf); err != nil { - return err - } - - if err := mb.indexCacheBuf(buf); err != nil { - if err == errCorruptState { - var ld *LostStreamData - if ld, _, err = mb.rebuildStateLocked(); ld != nil { - // We do not know if fs is locked or not at this point. - // This should be an exceptional condition so do so in Go routine. - go mb.fs.rebuildState(ld) - } - } - if err != nil { - return err - } - goto checkCache - } - - if len(buf) > 0 { - mb.cloads++ - mb.startCacheExpireTimer() - } - - return nil -} - -// Fetch a message from this block, possibly reading in and caching the messages. -// We assume the block was selected and is correct, so we do not do range checks. -func (mb *msgBlock) fetchMsg(seq uint64, sm *StoreMsg) (*StoreMsg, bool, error) { - mb.mu.Lock() - defer mb.mu.Unlock() - - fseq, lseq := atomic.LoadUint64(&mb.first.seq), atomic.LoadUint64(&mb.last.seq) - if seq < fseq || seq > lseq { - return nil, false, ErrStoreMsgNotFound - } - - // See if we can short circuit if we already know msg deleted. - if mb.dmap.Exists(seq) { - // Update for scanning like cacheLookup would have. - llseq := mb.llseq - if mb.llseq == 0 || seq < mb.llseq || seq == mb.llseq+1 || seq == mb.llseq-1 { - mb.llseq = seq - } - expireOk := (seq == lseq && llseq == seq-1) || (seq == fseq && llseq == seq+1) - return nil, expireOk, errDeletedMsg - } - - if mb.cacheNotLoaded() { - if err := mb.loadMsgsWithLock(); err != nil { - return nil, false, err - } - } - llseq := mb.llseq - - fsm, err := mb.cacheLookup(seq, sm) - if err != nil { - return nil, false, err - } - expireOk := (seq == lseq && llseq == seq-1) || (seq == fseq && llseq == seq+1) - return fsm, expireOk, err -} - -var ( - errNoCache = errors.New("no message cache") - errBadMsg = errors.New("malformed or corrupt message") - errDeletedMsg = errors.New("deleted message") - errPartialCache = errors.New("partial cache") - errNoPending = errors.New("message block does not have pending data") - errNotReadable = errors.New("storage directory not readable") - errCorruptState = errors.New("corrupt state file") - errPriorState = errors.New("prior state file") - errPendingData = errors.New("pending data still present") - errNoEncryption = errors.New("encryption not enabled") - errBadKeySize = errors.New("encryption bad key size") - errNoMsgBlk = errors.New("no message block") - errMsgBlkTooBig = errors.New("message block size exceeded int capacity") - errUnknownCipher = errors.New("unknown cipher") - errNoMainKey = errors.New("encrypted store encountered with no main key") - errNoBlkData = errors.New("message block data missing") - errStateTooBig = errors.New("store state too big for optional write") -) - -const ( - // "Checksum bit" is used in "mb.cache.idx" for marking messages that have had their checksums checked. - cbit = 1 << 31 - // "Delete bit" is used in "mb.cache.idx" to mark an index as deleted and non-existent. - dbit = 1 << 30 - // "Header bit" is used in "rl" to signal a message record with headers. - hbit = 1 << 31 - // "Erase bit" is used in "seq" for marking erased messages sequences. - ebit = 1 << 63 - // "Tombstone bit" is used in "seq" for marking tombstone sequences. - tbit = 1 << 62 -) - -// Will do a lookup from cache. -// Lock should be held. -func (mb *msgBlock) cacheLookup(seq uint64, sm *StoreMsg) (*StoreMsg, error) { - if seq < atomic.LoadUint64(&mb.first.seq) || seq > atomic.LoadUint64(&mb.last.seq) { - return nil, ErrStoreMsgNotFound - } - - // The llseq signals us when we can expire a cache at the end of a linear scan. - // We want to only update when we know the last reads (multiple consumers) are sequential. - // We want to account for forwards and backwards linear scans. - if mb.llseq == 0 || seq < mb.llseq || seq == mb.llseq+1 || seq == mb.llseq-1 { - mb.llseq = seq - } - - // If we have a delete map check it. - if mb.dmap.Exists(seq) { - mb.llts = time.Now().UnixNano() - return nil, errDeletedMsg - } - - // Detect no cache loaded. - if mb.cache == nil || mb.cache.fseq == 0 || len(mb.cache.idx) == 0 || len(mb.cache.buf) == 0 { - var reason string - if mb.cache == nil { - reason = "no cache" - } else if mb.cache.fseq == 0 { - reason = "fseq is 0" - } else if len(mb.cache.idx) == 0 { - reason = "no idx present" - } else { - reason = "cache buf empty" - } - mb.fs.warn("Cache lookup detected no cache: %s", reason) - return nil, errNoCache - } - // Check partial cache status. - if seq < mb.cache.fseq { - mb.fs.warn("Cache lookup detected partial cache: seq %d vs cache fseq %d", seq, mb.cache.fseq) - return nil, errPartialCache - } - - bi, _, hashChecked, err := mb.slotInfo(int(seq - mb.cache.fseq)) - if err != nil { - return nil, err - } - - // Update cache activity. - mb.llts = time.Now().UnixNano() - - li := int(bi) - mb.cache.off - if li >= len(mb.cache.buf) { - return nil, errPartialCache - } - buf := mb.cache.buf[li:] - - // We use the high bit to denote we have already checked the checksum. - var hh hash.Hash64 - if !hashChecked { - hh = mb.hh // This will force the hash check in msgFromBuf. - } - - // Parse from the raw buffer. - fsm, err := mb.msgFromBuf(buf, sm, hh) - if err != nil || fsm == nil { - return nil, err - } - - // Deleted messages that are decoded return a 0 for sequence. - if fsm.seq == 0 { - return nil, errDeletedMsg - } - - if seq != fsm.seq { - recycleMsgBlockBuf(mb.cache.buf) - mb.cache.buf = nil - return nil, fmt.Errorf("sequence numbers for cache load did not match, %d vs %d", seq, fsm.seq) - } - - // Clear the check bit here after we know all is good. - if !hashChecked { - mb.cache.idx[seq-mb.cache.fseq] = (bi | cbit) - } - - return fsm, nil -} - -// Used when we are checking if discarding a message due to max msgs per subject will give us -// enough room for a max bytes condition. -// Lock should be already held. -func (fs *fileStore) sizeForSeq(seq uint64) int { - if seq == 0 { - return 0 - } - var smv StoreMsg - if mb := fs.selectMsgBlock(seq); mb != nil { - if sm, _, _ := mb.fetchMsg(seq, &smv); sm != nil { - return int(fileStoreMsgSize(sm.subj, sm.hdr, sm.msg)) - } - } - return 0 -} - -// Will return message for the given sequence number. -func (fs *fileStore) msgForSeq(seq uint64, sm *StoreMsg) (*StoreMsg, error) { - // TODO(dlc) - Since Store, Remove, Skip all hold the write lock on fs this will - // be stalled. Need another lock if want to happen in parallel. - fs.mu.RLock() - if fs.closed { - fs.mu.RUnlock() - return nil, ErrStoreClosed - } - // Indicates we want first msg. - if seq == 0 { - seq = fs.state.FirstSeq - } - // Make sure to snapshot here. - mb, lseq := fs.selectMsgBlock(seq), fs.state.LastSeq - fs.mu.RUnlock() - - if mb == nil { - var err = ErrStoreEOF - if seq <= lseq { - err = ErrStoreMsgNotFound - } - return nil, err - } - - fsm, expireOk, err := mb.fetchMsg(seq, sm) - if err != nil { - return nil, err - } - - // We detected a linear scan and access to the last message. - // If we are not the last message block we can try to expire the cache. - if expireOk { - mb.tryForceExpireCache() - } - - return fsm, nil -} - -// Internal function to return msg parts from a raw buffer. -// Lock should be held. -func (mb *msgBlock) msgFromBuf(buf []byte, sm *StoreMsg, hh hash.Hash64) (*StoreMsg, error) { - if len(buf) < emptyRecordLen { - return nil, errBadMsg - } - var le = binary.LittleEndian - - hdr := buf[:msgHdrSize] - rl := le.Uint32(hdr[0:]) - hasHeaders := rl&hbit != 0 - rl &^= hbit // clear header bit - dlen := int(rl) - msgHdrSize - slen := int(le.Uint16(hdr[20:])) - // Simple sanity check. - if dlen < 0 || slen > (dlen-recordHashSize) || dlen > int(rl) || int(rl) > len(buf) || rl > rlBadThresh { - return nil, errBadMsg - } - data := buf[msgHdrSize : msgHdrSize+dlen] - // Do checksum tests here if requested. - if hh != nil { - hh.Reset() - hh.Write(hdr[4:20]) - hh.Write(data[:slen]) - if hasHeaders { - hh.Write(data[slen+4 : dlen-recordHashSize]) - } else { - hh.Write(data[slen : dlen-recordHashSize]) - } - if !bytes.Equal(hh.Sum(nil), data[len(data)-8:]) { - return nil, errBadMsg - } - } - seq := le.Uint64(hdr[4:]) - if seq&ebit != 0 { - seq = 0 - } - ts := int64(le.Uint64(hdr[12:])) - - // Create a StoreMsg if needed. - if sm == nil { - sm = new(StoreMsg) - } else { - sm.clear() - } - // To recycle the large blocks we can never pass back a reference, so need to copy for the upper - // layers and for us to be safe to expire, and recycle, the large msgBlocks. - end := dlen - 8 - - if hasHeaders { - hl := le.Uint32(data[slen:]) - bi := slen + 4 - li := bi + int(hl) - sm.buf = append(sm.buf, data[bi:end]...) - li, end = li-bi, end-bi - sm.hdr = sm.buf[0:li:li] - sm.msg = sm.buf[li:end] - } else { - sm.buf = append(sm.buf, data[slen:end]...) - sm.msg = sm.buf[0 : end-slen] - } - sm.seq, sm.ts = seq, ts - if slen > 0 { - // Make a copy since sm.subj lifetime may last longer. - sm.subj = string(data[:slen]) - } - - return sm, nil -} - -// LoadMsg will lookup the message by sequence number and return it if found. -func (fs *fileStore) LoadMsg(seq uint64, sm *StoreMsg) (*StoreMsg, error) { - return fs.msgForSeq(seq, sm) -} - -// loadLast will load the last message for a subject. Subject should be non empty and not ">". -func (fs *fileStore) loadLast(subj string, sm *StoreMsg) (lsm *StoreMsg, err error) { - fs.mu.RLock() - defer fs.mu.RUnlock() - - if fs.closed || fs.lmb == nil { - return nil, ErrStoreClosed - } - - if len(fs.blks) == 0 { - return nil, ErrStoreMsgNotFound - } - - wc := subjectHasWildcard(subj) - var start, stop uint32 - - // If literal subject check for presence. - if wc { - start = fs.lmb.index - fs.psim.Match(stringToBytes(subj), func(_ []byte, psi *psi) { - // Keep track of start and stop indexes for this subject. - if psi.fblk < start { - start = psi.fblk - } - if psi.lblk > stop { - stop = psi.lblk - } - }) - // None matched. - if stop == 0 { - return nil, ErrStoreMsgNotFound - } - // These need to be swapped. - start, stop = stop, start - } else if info, ok := fs.psim.Find(stringToBytes(subj)); ok { - start, stop = info.lblk, info.fblk - } else { - return nil, ErrStoreMsgNotFound - } - - // Walk blocks backwards. - for i := start; i >= stop; i-- { - mb := fs.bim[i] - if mb == nil { - continue - } - mb.mu.Lock() - if err := mb.ensurePerSubjectInfoLoaded(); err != nil { - mb.mu.Unlock() - return nil, err - } - // Mark fss activity. - mb.lsts = time.Now().UnixNano() - - var l uint64 - // Optimize if subject is not a wildcard. - if !wc { - if ss, ok := mb.fss.Find(stringToBytes(subj)); ok && ss != nil { - l = ss.Last - } - } - if l == 0 { - _, _, l = mb.filteredPendingLocked(subj, wc, atomic.LoadUint64(&mb.first.seq)) - } - if l > 0 { - if mb.cacheNotLoaded() { - if err := mb.loadMsgsWithLock(); err != nil { - mb.mu.Unlock() - return nil, err - } - } - lsm, err = mb.cacheLookup(l, sm) - } - mb.mu.Unlock() - if l > 0 { - break - } - } - return lsm, err -} - -// LoadLastMsg will return the last message we have that matches a given subject. -// The subject can be a wildcard. -func (fs *fileStore) LoadLastMsg(subject string, smv *StoreMsg) (sm *StoreMsg, err error) { - if subject == _EMPTY_ || subject == fwcs { - sm, err = fs.msgForSeq(fs.lastSeq(), smv) - } else { - sm, err = fs.loadLast(subject, smv) - } - if sm == nil || (err != nil && err != ErrStoreClosed) { - err = ErrStoreMsgNotFound - } - return sm, err -} - -// LoadNextMsgMulti will find the next message matching any entry in the sublist. -func (fs *fileStore) LoadNextMsgMulti(sl *Sublist, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error) { - if sl == nil { - return fs.LoadNextMsg(_EMPTY_, false, start, smp) - } - fs.mu.RLock() - defer fs.mu.RUnlock() - - if fs.closed { - return nil, 0, ErrStoreClosed - } - if fs.state.Msgs == 0 || start > fs.state.LastSeq { - return nil, fs.state.LastSeq, ErrStoreEOF - } - if start < fs.state.FirstSeq { - start = fs.state.FirstSeq - } - - if bi, _ := fs.selectMsgBlockWithIndex(start); bi >= 0 { - for i := bi; i < len(fs.blks); i++ { - mb := fs.blks[i] - if sm, expireOk, err := mb.firstMatchingMulti(sl, start, smp); err == nil { - if expireOk { - mb.tryForceExpireCache() - } - return sm, sm.seq, nil - } else if err != ErrStoreMsgNotFound { - return nil, 0, err - } else if expireOk { - mb.tryForceExpireCache() - } - } - } - - return nil, fs.state.LastSeq, ErrStoreEOF - -} - -func (fs *fileStore) LoadNextMsg(filter string, wc bool, start uint64, sm *StoreMsg) (*StoreMsg, uint64, error) { - fs.mu.RLock() - defer fs.mu.RUnlock() - - if fs.closed { - return nil, 0, ErrStoreClosed - } - if fs.state.Msgs == 0 || start > fs.state.LastSeq { - return nil, fs.state.LastSeq, ErrStoreEOF - } - if start < fs.state.FirstSeq { - start = fs.state.FirstSeq - } - - // If start is less than or equal to beginning of our stream, meaning our first call, - // let's check the psim to see if we can skip ahead. - if start <= fs.state.FirstSeq { - var ss SimpleState - fs.numFilteredPendingNoLast(filter, &ss) - // Nothing available. - if ss.Msgs == 0 { - return nil, fs.state.LastSeq, ErrStoreEOF - } - // We can skip ahead. - if ss.First > start { - start = ss.First - } - } - - if bi, _ := fs.selectMsgBlockWithIndex(start); bi >= 0 { - for i := bi; i < len(fs.blks); i++ { - mb := fs.blks[i] - if sm, expireOk, err := mb.firstMatching(filter, wc, start, sm); err == nil { - if expireOk { - mb.tryForceExpireCache() - } - return sm, sm.seq, nil - } else if err != ErrStoreMsgNotFound { - return nil, 0, err - } else { - // Nothing found in this block. We missed, if first block (bi) check psim. - // Similar to above if start <= first seq. - // TODO(dlc) - For v2 track these by filter subject since they will represent filtered consumers. - // We should not do this at all if we are already on the last block. - // Also if we are a wildcard do not check if large subject space. - const wcMaxSizeToCheck = 64 * 1024 - if i == bi && i < len(fs.blks)-1 && (!wc || fs.psim.Size() < wcMaxSizeToCheck) { - nbi, err := fs.checkSkipFirstBlock(filter, wc, bi) - // Nothing available. - if err == ErrStoreEOF { - return nil, fs.state.LastSeq, ErrStoreEOF - } - // See if we can jump ahead here. - // Right now we can only spin on first, so if we have interior sparseness need to favor checking per block fss if loaded. - // For v2 will track all blocks that have matches for psim. - if nbi > i { - i = nbi - 1 // For the iterator condition i++ - } - } - // Check is we can expire. - if expireOk { - mb.tryForceExpireCache() - } - } - } - } - - return nil, fs.state.LastSeq, ErrStoreEOF -} - -// Will load the next non-deleted msg starting at the start sequence and walking backwards. -func (fs *fileStore) LoadPrevMsg(start uint64, smp *StoreMsg) (sm *StoreMsg, err error) { - fs.mu.RLock() - defer fs.mu.RUnlock() - - if fs.closed { - return nil, ErrStoreClosed - } - if fs.state.Msgs == 0 || start < fs.state.FirstSeq { - return nil, ErrStoreEOF - } - - if start > fs.state.LastSeq { - start = fs.state.LastSeq - } - if smp == nil { - smp = new(StoreMsg) - } - - if bi, _ := fs.selectMsgBlockWithIndex(start); bi >= 0 { - for i := bi; i >= 0; i-- { - mb := fs.blks[i] - mb.mu.Lock() - // Need messages loaded from here on out. - if mb.cacheNotLoaded() { - if err := mb.loadMsgsWithLock(); err != nil { - mb.mu.Unlock() - return nil, err - } - } - - lseq, fseq := atomic.LoadUint64(&mb.last.seq), atomic.LoadUint64(&mb.first.seq) - if start > lseq { - start = lseq - } - for seq := start; seq >= fseq; seq-- { - if mb.dmap.Exists(seq) { - continue - } - if sm, err := mb.cacheLookup(seq, smp); err == nil { - mb.mu.Unlock() - return sm, nil - } - } - mb.mu.Unlock() - } - } - - return nil, ErrStoreEOF -} - -// Type returns the type of the underlying store. -func (fs *fileStore) Type() StorageType { - return FileStorage -} - -// Returns number of subjects in this store. -// Lock should be held. -func (fs *fileStore) numSubjects() int { - return fs.psim.Size() -} - -// numConsumers uses new lock. -func (fs *fileStore) numConsumers() int { - fs.cmu.RLock() - defer fs.cmu.RUnlock() - return len(fs.cfs) -} - -// FastState will fill in state with only the following. -// Msgs, Bytes, First and Last Sequence and Time and NumDeleted. -func (fs *fileStore) FastState(state *StreamState) { - fs.mu.RLock() - state.Msgs = fs.state.Msgs - state.Bytes = fs.state.Bytes - state.FirstSeq = fs.state.FirstSeq - state.FirstTime = fs.state.FirstTime - state.LastSeq = fs.state.LastSeq - state.LastTime = fs.state.LastTime - // Make sure to reset if being re-used. - state.Deleted, state.NumDeleted = nil, 0 - if state.LastSeq > state.FirstSeq { - state.NumDeleted = int((state.LastSeq - state.FirstSeq + 1) - state.Msgs) - if state.NumDeleted < 0 { - state.NumDeleted = 0 - } - } - state.Consumers = fs.numConsumers() - state.NumSubjects = fs.numSubjects() - fs.mu.RUnlock() -} - -// State returns the current state of the stream. -func (fs *fileStore) State() StreamState { - fs.mu.RLock() - state := fs.state - state.Consumers = fs.numConsumers() - state.NumSubjects = fs.numSubjects() - state.Deleted = nil // make sure. - - if numDeleted := int((state.LastSeq - state.FirstSeq + 1) - state.Msgs); numDeleted > 0 { - state.Deleted = make([]uint64, 0, numDeleted) - cur := fs.state.FirstSeq - - for _, mb := range fs.blks { - mb.mu.Lock() - fseq := atomic.LoadUint64(&mb.first.seq) - // Account for messages missing from the head. - if fseq > cur { - for seq := cur; seq < fseq; seq++ { - state.Deleted = append(state.Deleted, seq) - } - } - // Only advance cur if we are increasing. We could have marker blocks with just tombstones. - if last := atomic.LoadUint64(&mb.last.seq); last >= cur { - cur = last + 1 // Expected next first. - } - // Add in deleted. - mb.dmap.Range(func(seq uint64) bool { - state.Deleted = append(state.Deleted, seq) - return true - }) - mb.mu.Unlock() - } - } - fs.mu.RUnlock() - - state.Lost = fs.lostData() - - // Can not be guaranteed to be sorted. - if len(state.Deleted) > 0 { - slices.Sort(state.Deleted) - state.NumDeleted = len(state.Deleted) - } - return state -} - -func (fs *fileStore) Utilization() (total, reported uint64, err error) { - fs.mu.RLock() - defer fs.mu.RUnlock() - for _, mb := range fs.blks { - mb.mu.RLock() - reported += mb.bytes - total += mb.rbytes - mb.mu.RUnlock() - } - return total, reported, nil -} - -func fileStoreMsgSize(subj string, hdr, msg []byte) uint64 { - if len(hdr) == 0 { - // length of the message record (4bytes) + seq(8) + ts(8) + subj_len(2) + subj + msg + hash(8) - return uint64(22 + len(subj) + len(msg) + 8) - } - // length of the message record (4bytes) + seq(8) + ts(8) + subj_len(2) + subj + hdr_len(4) + hdr + msg + hash(8) - return uint64(22 + len(subj) + 4 + len(hdr) + len(msg) + 8) -} - -func fileStoreMsgSizeEstimate(slen, maxPayload int) uint64 { - return uint64(emptyRecordLen + slen + 4 + maxPayload) -} - -// Determine time since any last activity, read/load, write or remove. -func (mb *msgBlock) sinceLastActivity() time.Duration { - if mb.closed { - return 0 - } - last := mb.lwts - if mb.lrts > last { - last = mb.lrts - } - if mb.llts > last { - last = mb.llts - } - if mb.lsts > last { - last = mb.lsts - } - return time.Since(time.Unix(0, last).UTC()) -} - -// Determine time since last write or remove of a message. -// Read lock should be held. -func (mb *msgBlock) sinceLastWriteActivity() time.Duration { - if mb.closed { - return 0 - } - last := mb.lwts - if mb.lrts > last { - last = mb.lrts - } - return time.Since(time.Unix(0, last).UTC()) -} - -func checkNewHeader(hdr []byte) error { - if len(hdr) < 2 || hdr[0] != magic || - (hdr[1] != version && hdr[1] != newVersion) { - return errCorruptState - } - return nil -} - -// readIndexInfo will read in the index information for the message block. -func (mb *msgBlock) readIndexInfo() error { - ifn := filepath.Join(mb.fs.fcfg.StoreDir, msgDir, fmt.Sprintf(indexScan, mb.index)) - buf, err := os.ReadFile(ifn) - if err != nil { - return err - } - - // Set if first time. - if mb.liwsz == 0 { - mb.liwsz = int64(len(buf)) - } - - // Decrypt if needed. - if mb.aek != nil { - buf, err = mb.aek.Open(buf[:0], mb.nonce, buf, nil) - if err != nil { - return err - } - } - - if err := checkNewHeader(buf); err != nil { - defer os.Remove(ifn) - return fmt.Errorf("bad index file") - } - - bi := hdrLen - - // Helpers, will set i to -1 on error. - readSeq := func() uint64 { - if bi < 0 { - return 0 - } - seq, n := binary.Uvarint(buf[bi:]) - if n <= 0 { - bi = -1 - return 0 - } - bi += n - return seq &^ ebit - } - readCount := readSeq - readTimeStamp := func() int64 { - if bi < 0 { - return 0 - } - ts, n := binary.Varint(buf[bi:]) - if n <= 0 { - bi = -1 - return -1 - } - bi += n - return ts - } - mb.msgs = readCount() - mb.bytes = readCount() - atomic.StoreUint64(&mb.first.seq, readSeq()) - mb.first.ts = readTimeStamp() - atomic.StoreUint64(&mb.last.seq, readSeq()) - mb.last.ts = readTimeStamp() - dmapLen := readCount() - - // Check if this is a short write index file. - if bi < 0 || bi+checksumSize > len(buf) { - os.Remove(ifn) - return fmt.Errorf("short index file") - } - - // Check for consistency if accounting. If something is off bail and we will rebuild. - if mb.msgs != (atomic.LoadUint64(&mb.last.seq)-atomic.LoadUint64(&mb.first.seq)+1)-dmapLen { - os.Remove(ifn) - return fmt.Errorf("accounting inconsistent") - } - - // Checksum - copy(mb.lchk[0:], buf[bi:bi+checksumSize]) - bi += checksumSize - - // Now check for presence of a delete map - if dmapLen > 0 { - // New version is encoded avl seqset. - if buf[1] == newVersion { - dmap, _, err := avl.Decode(buf[bi:]) - if err != nil { - return fmt.Errorf("could not decode avl dmap: %v", err) - } - mb.dmap = *dmap - } else { - // This is the old version. - for i, fseq := 0, atomic.LoadUint64(&mb.first.seq); i < int(dmapLen); i++ { - seq := readSeq() - if seq == 0 { - break - } - mb.dmap.Insert(seq + fseq) - } - } - } - - return nil -} - -// Will return total number of cache loads. -func (fs *fileStore) cacheLoads() uint64 { - var tl uint64 - fs.mu.RLock() - for _, mb := range fs.blks { - tl += mb.cloads - } - fs.mu.RUnlock() - return tl -} - -// Will return total number of cached bytes. -func (fs *fileStore) cacheSize() uint64 { - var sz uint64 - fs.mu.RLock() - for _, mb := range fs.blks { - mb.mu.RLock() - if mb.cache != nil { - sz += uint64(len(mb.cache.buf)) - } - mb.mu.RUnlock() - } - fs.mu.RUnlock() - return sz -} - -// Will return total number of dmapEntries for all msg blocks. -func (fs *fileStore) dmapEntries() int { - var total int - fs.mu.RLock() - for _, mb := range fs.blks { - total += mb.dmap.Size() - } - fs.mu.RUnlock() - return total -} - -// Fixed helper for iterating. -func subjectsEqual(a, b string) bool { - return a == b -} - -func subjectsAll(a, b string) bool { - return true -} - -func compareFn(subject string) func(string, string) bool { - if subject == _EMPTY_ || subject == fwcs { - return subjectsAll - } - if subjectHasWildcard(subject) { - return subjectIsSubsetMatch - } - return subjectsEqual -} - -// PurgeEx will remove messages based on subject filters, sequence and number of messages to keep. -// Will return the number of purged messages. -func (fs *fileStore) PurgeEx(subject string, sequence, keep uint64, _ /* noMarkers */ bool) (purged uint64, err error) { - // TODO: Don't write markers on purge until we have solved performance - // issues with them. - noMarkers := true - - if subject == _EMPTY_ || subject == fwcs { - if keep == 0 && sequence == 0 { - return fs.purge(0, noMarkers) - } - if sequence > 1 { - return fs.compact(sequence, noMarkers) - } - } - - eq, wc := compareFn(subject), subjectHasWildcard(subject) - var firstSeqNeedsUpdate bool - var bytes uint64 - - // If we have a "keep" designation need to get full filtered state so we know how many to purge. - var maxp uint64 - if keep > 0 { - ss := fs.FilteredState(1, subject) - if keep >= ss.Msgs { - return 0, nil - } - maxp = ss.Msgs - keep - } - - var smv StoreMsg - var tombs []msgId - - fs.mu.Lock() - // We may remove blocks as we purge, so don't range directly on fs.blks - // otherwise we may jump over some (see https://github.com/nats-io/nats-server/issues/3528) - for i := 0; i < len(fs.blks); i++ { - mb := fs.blks[i] - mb.mu.Lock() - - // If we do not have our fss, try to expire the cache if we have no items in this block. - shouldExpire := mb.fssNotLoaded() - - t, f, l := mb.filteredPendingLocked(subject, wc, atomic.LoadUint64(&mb.first.seq)) - if t == 0 { - // Expire if we were responsible for loading. - if shouldExpire { - // Expire this cache before moving on. - mb.tryForceExpireCacheLocked() - } - mb.mu.Unlock() - continue - } - - if sequence > 1 && sequence <= l { - l = sequence - 1 - } - - if mb.cacheNotLoaded() { - mb.loadMsgsWithLock() - shouldExpire = true - } - - for seq := f; seq <= l; seq++ { - if sm, _ := mb.cacheLookup(seq, &smv); sm != nil && eq(sm.subj, subject) { - rl := fileStoreMsgSize(sm.subj, sm.hdr, sm.msg) - // Do fast in place remove. - // Stats - if mb.msgs > 0 { - // Msgs - fs.state.Msgs-- - mb.msgs-- - // Bytes, make sure to not go negative. - if rl > fs.state.Bytes { - rl = fs.state.Bytes - } - if rl > mb.bytes { - rl = mb.bytes - } - fs.state.Bytes -= rl - mb.bytes -= rl - // Totals - purged++ - bytes += rl - } - // PSIM and FSS updates. - mb.removeSeqPerSubject(sm.subj, seq) - fs.removePerSubject(sm.subj, !noMarkers && fs.cfg.SubjectDeleteMarkerTTL > 0) - // Track tombstones we need to write. - tombs = append(tombs, msgId{sm.seq, sm.ts}) - - // Check for first message. - if seq == atomic.LoadUint64(&mb.first.seq) { - mb.selectNextFirst() - if mb.isEmpty() { - fs.removeMsgBlock(mb) - i-- - // keep flag set, if set previously - firstSeqNeedsUpdate = firstSeqNeedsUpdate || seq == fs.state.FirstSeq - } else if seq == fs.state.FirstSeq { - fs.state.FirstSeq = atomic.LoadUint64(&mb.first.seq) // new one. - fs.state.FirstTime = time.Unix(0, mb.first.ts).UTC() - } - } else { - // Out of order delete. - mb.dmap.Insert(seq) - } - - if maxp > 0 && purged >= maxp { - break - } - } - } - // Expire if we were responsible for loading. - if shouldExpire { - // Expire this cache before moving on. - mb.tryForceExpireCacheLocked() - } - mb.mu.Unlock() - - // Check if we should break out of top level too. - if maxp > 0 && purged >= maxp { - break - } - } - if firstSeqNeedsUpdate { - fs.selectNextFirst() - } - fseq := fs.state.FirstSeq - - // Write any tombstones as needed. - for _, tomb := range tombs { - if tomb.seq > fseq { - fs.writeTombstone(tomb.seq, tomb.ts) - } - } - - os.Remove(filepath.Join(fs.fcfg.StoreDir, msgDir, streamStreamStateFile)) - fs.dirty++ - cb := fs.scb - sdmcb := fs.subjectDeleteMarkersAfterOperation(JSMarkerReasonPurge) - fs.mu.Unlock() - - if cb != nil { - cb(-int64(purged), -int64(bytes), 0, _EMPTY_) - } - if sdmcb != nil { - sdmcb() - } - - return purged, nil -} - -// Purge will remove all messages from this store. -// Will return the number of purged messages. -func (fs *fileStore) Purge() (uint64, error) { - return fs.purge(0, false) -} - -func (fs *fileStore) purge(fseq uint64, _ /* noMarkers */ bool) (uint64, error) { - // TODO: Don't write markers on purge until we have solved performance - // issues with them. - noMarkers := true - - fs.mu.Lock() - if fs.closed { - fs.mu.Unlock() - return 0, ErrStoreClosed - } - - purged := fs.state.Msgs - rbytes := int64(fs.state.Bytes) - - fs.state.FirstSeq = fs.state.LastSeq + 1 - fs.state.FirstTime = time.Time{} - - fs.state.Bytes = 0 - fs.state.Msgs = 0 - - for _, mb := range fs.blks { - mb.dirtyClose() - } - - fs.blks = nil - fs.lmb = nil - fs.bim = make(map[uint32]*msgBlock) - // Subject delete markers if needed. - if !noMarkers && fs.cfg.SubjectDeleteMarkerTTL > 0 { - fs.psim.IterOrdered(func(subject []byte, _ *psi) bool { - fs.markers = append(fs.markers, string(subject)) - return true - }) - } - // Clear any per subject tracking. - fs.psim, fs.tsl = fs.psim.Empty(), 0 - // Mark dirty. - fs.dirty++ - - // Move the msgs directory out of the way, will delete out of band. - // FIXME(dlc) - These can error and we need to change api above to propagate? - mdir := filepath.Join(fs.fcfg.StoreDir, msgDir) - pdir := filepath.Join(fs.fcfg.StoreDir, purgeDir) - // If purge directory still exists then we need to wait - // in place and remove since rename would fail. - if _, err := os.Stat(pdir); err == nil { - <-dios - os.RemoveAll(pdir) - dios <- struct{}{} - } - - <-dios - os.Rename(mdir, pdir) - dios <- struct{}{} - - go func() { - <-dios - os.RemoveAll(pdir) - dios <- struct{}{} - }() - - // Create new one. - <-dios - os.MkdirAll(mdir, defaultDirPerms) - dios <- struct{}{} - - // Make sure we have a lmb to write to. - if _, err := fs.newMsgBlockForWrite(); err != nil { - fs.mu.Unlock() - return purged, err - } - - // Check if we need to set the first seq to a new number. - if fseq > fs.state.FirstSeq { - fs.state.FirstSeq = fseq - fs.state.LastSeq = fseq - 1 - } - - lmb := fs.lmb - atomic.StoreUint64(&lmb.first.seq, fs.state.FirstSeq) - atomic.StoreUint64(&lmb.last.seq, fs.state.LastSeq) - lmb.last.ts = fs.state.LastTime.UnixNano() - - if lseq := atomic.LoadUint64(&lmb.last.seq); lseq > 1 { - // Leave a tombstone so we can remember our starting sequence in case - // full state becomes corrupted. - fs.writeTombstone(lseq, lmb.last.ts) - } - - cb := fs.scb - sdmcb := fs.subjectDeleteMarkersAfterOperation(JSMarkerReasonPurge) - fs.mu.Unlock() - - // Force a new index.db to be written. - if purged > 0 { - fs.forceWriteFullState() - } - - if cb != nil { - cb(-int64(purged), -rbytes, 0, _EMPTY_) - } - if sdmcb != nil { - sdmcb() - } - - return purged, nil -} - -// Compact will remove all messages from this store up to -// but not including the seq parameter. -// No subject delete markers will be left if they are enabled. If they are disabled, -// then this is functionally equivalent to a normal Compact() call. -// Will return the number of purged messages. -func (fs *fileStore) Compact(seq uint64) (uint64, error) { - return fs.compact(seq, false) -} - -func (fs *fileStore) compact(seq uint64, _ /* noMarkers */ bool) (uint64, error) { - // TODO: Don't write markers on compact until we have solved performance - // issues with them. - noMarkers := true - - if seq == 0 { - return fs.purge(seq, noMarkers) - } - - var purged, bytes uint64 - - fs.mu.Lock() - // Same as purge all. - if lseq := fs.state.LastSeq; seq > lseq { - fs.mu.Unlock() - return fs.purge(seq, noMarkers) - } - // We have to delete interior messages. - smb := fs.selectMsgBlock(seq) - if smb == nil { - fs.mu.Unlock() - return 0, nil - } - - // All msgblocks up to this one can be thrown away. - var deleted int - for _, mb := range fs.blks { - if mb == smb { - break - } - mb.mu.Lock() - purged += mb.msgs - bytes += mb.bytes - // Make sure we do subject cleanup as well. - mb.ensurePerSubjectInfoLoaded() - mb.fss.IterOrdered(func(bsubj []byte, ss *SimpleState) bool { - subj := bytesToString(bsubj) - for i := uint64(0); i < ss.Msgs; i++ { - fs.removePerSubject(subj, !noMarkers && fs.cfg.SubjectDeleteMarkerTTL > 0) - } - return true - }) - // Now close. - mb.dirtyCloseWithRemove(true) - mb.mu.Unlock() - deleted++ - } - - var smv StoreMsg - var err error - - smb.mu.Lock() - if atomic.LoadUint64(&smb.first.seq) == seq { - fs.state.FirstSeq = atomic.LoadUint64(&smb.first.seq) - fs.state.FirstTime = time.Unix(0, smb.first.ts).UTC() - goto SKIP - } - - // Make sure we have the messages loaded. - if smb.cacheNotLoaded() { - if err = smb.loadMsgsWithLock(); err != nil { - goto SKIP - } - } - for mseq := atomic.LoadUint64(&smb.first.seq); mseq < seq; mseq++ { - sm, err := smb.cacheLookup(mseq, &smv) - if err == errDeletedMsg { - // Update dmap. - if !smb.dmap.IsEmpty() { - smb.dmap.Delete(mseq) - } - } else if sm != nil { - sz := fileStoreMsgSize(sm.subj, sm.hdr, sm.msg) - if smb.msgs > 0 { - smb.msgs-- - if sz > smb.bytes { - sz = smb.bytes - } - smb.bytes -= sz - bytes += sz - purged++ - } - // Update fss - smb.removeSeqPerSubject(sm.subj, mseq) - fs.removePerSubject(sm.subj, !noMarkers && fs.cfg.SubjectDeleteMarkerTTL > 0) - } - } - - // Check if empty after processing, could happen if tail of messages are all deleted. - if isEmpty := smb.msgs == 0; isEmpty { - // Only remove if not the last block. - if smb != fs.lmb { - smb.dirtyCloseWithRemove(true) - deleted++ - } else { - // Make sure to sync changes. - smb.needSync = true - } - // Update fs first here as well. - fs.state.FirstSeq = atomic.LoadUint64(&smb.last.seq) + 1 - fs.state.FirstTime = time.Time{} - - } else { - // Make sure to sync changes. - smb.needSync = true - // Update fs first seq and time. - atomic.StoreUint64(&smb.first.seq, seq-1) // Just for start condition for selectNextFirst. - smb.selectNextFirst() - - fs.state.FirstSeq = atomic.LoadUint64(&smb.first.seq) - fs.state.FirstTime = time.Unix(0, smb.first.ts).UTC() - - // Check if we should reclaim the head space from this block. - // This will be optimistic only, so don't continue if we encounter any errors here. - if smb.rbytes > compactMinimum && smb.bytes*2 < smb.rbytes { - var moff uint32 - moff, _, _, err = smb.slotInfo(int(atomic.LoadUint64(&smb.first.seq) - smb.cache.fseq)) - if err != nil || moff >= uint32(len(smb.cache.buf)) { - goto SKIP - } - buf := smb.cache.buf[moff:] - // Don't reuse, copy to new recycled buf. - nbuf := getMsgBlockBuf(len(buf)) - nbuf = append(nbuf, buf...) - smb.closeFDsLockedNoCheck() - // Check for encryption. - if smb.bek != nil && len(nbuf) > 0 { - // Recreate to reset counter. - bek, err := genBlockEncryptionKey(smb.fs.fcfg.Cipher, smb.seed, smb.nonce) - if err != nil { - goto SKIP - } - // For future writes make sure to set smb.bek to keep counter correct. - smb.bek = bek - smb.bek.XORKeyStream(nbuf, nbuf) - } - // Recompress if necessary (smb.cmp contains the algorithm used when - // the block was loaded from disk, or defaults to NoCompression if not) - if nbuf, err = smb.cmp.Compress(nbuf); err != nil { - goto SKIP - } - <-dios - err = os.WriteFile(smb.mfn, nbuf, defaultFilePerms) - dios <- struct{}{} - if err != nil { - goto SKIP - } - // Make sure to remove fss state. - smb.fss = nil - smb.clearCacheAndOffset() - smb.rbytes = uint64(len(nbuf)) - } - } - -SKIP: - smb.mu.Unlock() - - if deleted > 0 { - // Update block map. - if fs.bim != nil { - for _, mb := range fs.blks[:deleted] { - delete(fs.bim, mb.index) - } - } - // Update blks slice. - fs.blks = copyMsgBlocks(fs.blks[deleted:]) - if lb := len(fs.blks); lb == 0 { - fs.lmb = nil - } else { - fs.lmb = fs.blks[lb-1] - } - } - - // Update top level accounting. - if purged > fs.state.Msgs { - purged = fs.state.Msgs - } - fs.state.Msgs -= purged - if fs.state.Msgs == 0 { - fs.state.FirstSeq = fs.state.LastSeq + 1 - fs.state.FirstTime = time.Time{} - } - - if bytes > fs.state.Bytes { - bytes = fs.state.Bytes - } - fs.state.Bytes -= bytes - - // Any existing state file no longer applicable. We will force write a new one - // after we release the lock. - os.Remove(filepath.Join(fs.fcfg.StoreDir, msgDir, streamStreamStateFile)) - fs.dirty++ - // Subject delete markers if needed. - sdmcb := fs.subjectDeleteMarkersAfterOperation(JSMarkerReasonPurge) - cb := fs.scb - fs.mu.Unlock() - - // Force a new index.db to be written. - if purged > 0 { - fs.forceWriteFullState() - } - - if cb != nil && purged > 0 { - cb(-int64(purged), -int64(bytes), 0, _EMPTY_) - } - if sdmcb != nil { - sdmcb() - } - - return purged, err -} - -// Will completely reset our store. -func (fs *fileStore) reset() error { - fs.mu.Lock() - if fs.closed { - fs.mu.Unlock() - return ErrStoreClosed - } - if fs.sips > 0 { - fs.mu.Unlock() - return ErrStoreSnapshotInProgress - } - - var purged, bytes uint64 - cb := fs.scb - - for _, mb := range fs.blks { - mb.mu.Lock() - purged += mb.msgs - bytes += mb.bytes - mb.dirtyCloseWithRemove(true) - mb.mu.Unlock() - } - - // Reset - fs.state.FirstSeq = 0 - fs.state.FirstTime = time.Time{} - fs.state.LastSeq = 0 - fs.state.LastTime = time.Now().UTC() - // Update msgs and bytes. - fs.state.Msgs = 0 - fs.state.Bytes = 0 - - // Reset blocks. - fs.blks, fs.lmb = nil, nil - - // Reset subject mappings. - fs.psim, fs.tsl = fs.psim.Empty(), 0 - fs.bim = make(map[uint32]*msgBlock) - - // If we purged anything, make sure we kick flush state loop. - if purged > 0 { - fs.dirty++ - } - - fs.mu.Unlock() - - if cb != nil { - cb(-int64(purged), -int64(bytes), 0, _EMPTY_) - } - - return nil -} - -// Return all active tombstones in this msgBlock. -func (mb *msgBlock) tombs() []msgId { - mb.mu.Lock() - defer mb.mu.Unlock() - return mb.tombsLocked() -} - -// Return all active tombstones in this msgBlock. -// Write lock should be held. -func (mb *msgBlock) tombsLocked() []msgId { - if mb.cacheNotLoaded() { - if err := mb.loadMsgsWithLock(); err != nil { - return nil - } - } - - var tombs []msgId - var le = binary.LittleEndian - buf := mb.cache.buf - - for index, lbuf := uint32(0), uint32(len(buf)); index < lbuf; { - if index+msgHdrSize > lbuf { - return tombs - } - hdr := buf[index : index+msgHdrSize] - rl, seq := le.Uint32(hdr[0:]), le.Uint64(hdr[4:]) - // Clear any headers bit that could be set. - rl &^= hbit - // Check for tombstones. - if seq&tbit != 0 { - ts := int64(le.Uint64(hdr[12:])) - tombs = append(tombs, msgId{seq &^ tbit, ts}) - } - // Advance to next record. - index += rl - } - - return tombs -} - -// Truncate will truncate a stream store up to seq. Sequence needs to be valid. -func (fs *fileStore) Truncate(seq uint64) error { - // Check for request to reset. - if seq == 0 { - return fs.reset() - } - - fs.mu.Lock() - - if fs.closed { - fs.mu.Unlock() - return ErrStoreClosed - } - if fs.sips > 0 { - fs.mu.Unlock() - return ErrStoreSnapshotInProgress - } - - nlmb := fs.selectMsgBlock(seq) - if nlmb == nil { - fs.mu.Unlock() - return ErrInvalidSequence - } - lsm, _, _ := nlmb.fetchMsg(seq, nil) - if lsm == nil { - fs.mu.Unlock() - return ErrInvalidSequence - } - - // Set lmb to nlmb and make sure writeable. - fs.lmb = nlmb - if err := nlmb.enableForWriting(fs.fip); err != nil { - fs.mu.Unlock() - return err - } - // Collect all tombstones, we want to put these back so we can survive - // a restore without index.db properly. - var tombs []msgId - tombs = append(tombs, nlmb.tombs()...) - - var purged, bytes uint64 - - // Truncate our new last message block. - nmsgs, nbytes, err := nlmb.truncate(lsm) - if err != nil { - fs.mu.Unlock() - return fmt.Errorf("nlmb.truncate: %w", err) - } - // Account for the truncated msgs and bytes. - purged += nmsgs - bytes += nbytes - - // Remove any left over msg blocks. - getLastMsgBlock := func() *msgBlock { return fs.blks[len(fs.blks)-1] } - for mb := getLastMsgBlock(); mb != nlmb; mb = getLastMsgBlock() { - mb.mu.Lock() - // We do this to load tombs. - tombs = append(tombs, mb.tombsLocked()...) - purged += mb.msgs - bytes += mb.bytes - fs.removeMsgBlock(mb) - mb.mu.Unlock() - } - - // Reset last. - fs.state.LastSeq = lsm.seq - fs.state.LastTime = time.Unix(0, lsm.ts).UTC() - // Update msgs and bytes. - if purged > fs.state.Msgs { - purged = fs.state.Msgs - } - fs.state.Msgs -= purged - if bytes > fs.state.Bytes { - bytes = fs.state.Bytes - } - fs.state.Bytes -= bytes - - // Reset our subject lookup info. - fs.resetGlobalPerSubjectInfo() - - // Always create new write block. - fs.newMsgBlockForWrite() - - // Write any tombstones as needed. - for _, tomb := range tombs { - if tomb.seq <= lsm.seq { - fs.writeTombstone(tomb.seq, tomb.ts) - } - } - - // Any existing state file no longer applicable. We will force write a new one - // after we release the lock. - os.Remove(filepath.Join(fs.fcfg.StoreDir, msgDir, streamStreamStateFile)) - fs.dirty++ - - cb := fs.scb - fs.mu.Unlock() - - // Force a new index.db to be written. - if purged > 0 { - fs.forceWriteFullState() - } - - if cb != nil { - cb(-int64(purged), -int64(bytes), 0, _EMPTY_) - } - - return nil -} - -func (fs *fileStore) lastSeq() uint64 { - fs.mu.RLock() - seq := fs.state.LastSeq - fs.mu.RUnlock() - return seq -} - -// Returns number of msg blks. -func (fs *fileStore) numMsgBlocks() int { - fs.mu.RLock() - defer fs.mu.RUnlock() - return len(fs.blks) -} - -// Will add a new msgBlock. -// Lock should be held. -func (fs *fileStore) addMsgBlock(mb *msgBlock) { - fs.blks = append(fs.blks, mb) - fs.lmb = mb - fs.bim[mb.index] = mb -} - -// Remove from our list of blks. -// Both locks should be held. -func (fs *fileStore) removeMsgBlockFromList(mb *msgBlock) { - // Remove from list. - for i, omb := range fs.blks { - if mb == omb { - fs.dirty++ - blks := append(fs.blks[:i], fs.blks[i+1:]...) - fs.blks = copyMsgBlocks(blks) - if fs.bim != nil { - delete(fs.bim, mb.index) - } - break - } - } -} - -// Removes the msgBlock -// Both locks should be held. -func (fs *fileStore) removeMsgBlock(mb *msgBlock) { - mb.dirtyCloseWithRemove(true) - fs.removeMsgBlockFromList(mb) - // Check for us being last message block - if mb == fs.lmb { - lseq, lts := atomic.LoadUint64(&mb.last.seq), mb.last.ts - // Creating a new message write block requires that the lmb lock is not held. - mb.mu.Unlock() - // Write the tombstone to remember since this was last block. - if lmb, _ := fs.newMsgBlockForWrite(); lmb != nil { - fs.writeTombstone(lseq, lts) - } - mb.mu.Lock() - } -} - -// Called by purge to simply get rid of the cache and close our fds. -// Lock should not be held. -func (mb *msgBlock) dirtyClose() { - mb.mu.Lock() - defer mb.mu.Unlock() - mb.dirtyCloseWithRemove(false) -} - -// Should be called with lock held. -func (mb *msgBlock) dirtyCloseWithRemove(remove bool) error { - if mb == nil { - return nil - } - // Stop cache expiration timer. - if mb.ctmr != nil { - mb.ctmr.Stop() - mb.ctmr = nil - } - // Close cache - mb.clearCacheAndOffset() - // Quit our loops. - if mb.qch != nil { - close(mb.qch) - mb.qch = nil - } - if mb.mfd != nil { - mb.mfd.Close() - mb.mfd = nil - } - if remove { - // Clear any tracking by subject if we are removing. - mb.fss = nil - if mb.mfn != _EMPTY_ { - err := os.Remove(mb.mfn) - if isPermissionError(err) { - return err - } - mb.mfn = _EMPTY_ - } - if mb.kfn != _EMPTY_ { - err := os.Remove(mb.kfn) - if isPermissionError(err) { - return err - } - } - } - return nil -} - -// Remove a seq from the fss and select new first. -// Lock should be held. -func (mb *msgBlock) removeSeqPerSubject(subj string, seq uint64) { - mb.ensurePerSubjectInfoLoaded() - if mb.fss == nil { - return - } - bsubj := stringToBytes(subj) - ss, ok := mb.fss.Find(bsubj) - if !ok || ss == nil { - return - } - - if ss.Msgs == 1 { - mb.fss.Delete(bsubj) - return - } - - ss.Msgs-- - - // Only one left. - if ss.Msgs == 1 { - if !ss.lastNeedsUpdate && seq != ss.Last { - ss.First = ss.Last - ss.firstNeedsUpdate = false - return - } - if !ss.firstNeedsUpdate && seq != ss.First { - ss.Last = ss.First - ss.lastNeedsUpdate = false - return - } - } - - // We can lazily calculate the first/last sequence when needed. - ss.firstNeedsUpdate = seq == ss.First || ss.firstNeedsUpdate - ss.lastNeedsUpdate = seq == ss.Last || ss.lastNeedsUpdate -} - -// Will recalculate the first and/or last sequence for this subject in this block. -// Will avoid slower path message lookups and scan the cache directly instead. -func (mb *msgBlock) recalculateForSubj(subj string, ss *SimpleState) { - // Need to make sure messages are loaded. - if mb.cacheNotLoaded() { - if err := mb.loadMsgsWithLock(); err != nil { - return - } - } - - startSlot := int(ss.First - mb.cache.fseq) - if startSlot < 0 { - startSlot = 0 - } - if startSlot >= len(mb.cache.idx) { - ss.First = ss.Last - ss.firstNeedsUpdate = false - ss.lastNeedsUpdate = false - return - } - - endSlot := int(ss.Last - mb.cache.fseq) - if endSlot < 0 { - endSlot = 0 - } - if endSlot >= len(mb.cache.idx) || startSlot > endSlot { - return - } - - var le = binary.LittleEndian - if ss.firstNeedsUpdate { - // Mark first as updated. - ss.firstNeedsUpdate = false - - fseq := ss.First + 1 - if mbFseq := atomic.LoadUint64(&mb.first.seq); fseq < mbFseq { - fseq = mbFseq - } - for slot := startSlot; slot < len(mb.cache.idx); slot++ { - bi := mb.cache.idx[slot] &^ cbit - if bi == dbit { - // delete marker so skip. - continue - } - li := int(bi) - mb.cache.off - if li >= len(mb.cache.buf) { - ss.First = ss.Last - // Only need to reset ss.lastNeedsUpdate, ss.firstNeedsUpdate is already reset above. - ss.lastNeedsUpdate = false - return - } - buf := mb.cache.buf[li:] - hdr := buf[:msgHdrSize] - slen := int(le.Uint16(hdr[20:])) - if subj == bytesToString(buf[msgHdrSize:msgHdrSize+slen]) { - seq := le.Uint64(hdr[4:]) - if seq < fseq || seq&ebit != 0 || mb.dmap.Exists(seq) { - continue - } - ss.First = seq - if ss.Msgs == 1 { - ss.Last = seq - ss.lastNeedsUpdate = false - return - } - // Skip the start slot ahead, if we need to recalculate last we can stop early. - startSlot = slot - break - } - } - } - if ss.lastNeedsUpdate { - // Mark last as updated. - ss.lastNeedsUpdate = false - - lseq := ss.Last - 1 - if mbLseq := atomic.LoadUint64(&mb.last.seq); lseq > mbLseq { - lseq = mbLseq - } - for slot := endSlot; slot >= startSlot; slot-- { - bi := mb.cache.idx[slot] &^ cbit - if bi == dbit { - // delete marker so skip. - continue - } - li := int(bi) - mb.cache.off - if li >= len(mb.cache.buf) { - // Can't overwrite ss.Last, just skip. - return - } - buf := mb.cache.buf[li:] - hdr := buf[:msgHdrSize] - slen := int(le.Uint16(hdr[20:])) - if subj == bytesToString(buf[msgHdrSize:msgHdrSize+slen]) { - seq := le.Uint64(hdr[4:]) - if seq > lseq || seq&ebit != 0 || mb.dmap.Exists(seq) { - continue - } - // Sequence should never be lower, but guard against it nonetheless. - if seq < ss.First { - seq = ss.First - } - ss.Last = seq - if ss.Msgs == 1 { - ss.First = seq - ss.firstNeedsUpdate = false - } - return - } - } - } -} - -// Lock should be held. -func (fs *fileStore) resetGlobalPerSubjectInfo() { - // Clear any global subject state. - fs.psim, fs.tsl = fs.psim.Empty(), 0 - for _, mb := range fs.blks { - fs.populateGlobalPerSubjectInfo(mb) - } -} - -// Lock should be held. -func (mb *msgBlock) resetPerSubjectInfo() error { - mb.fss = nil - return mb.generatePerSubjectInfo() -} - -// generatePerSubjectInfo will generate the per subject info via the raw msg block. -// Lock should be held. -func (mb *msgBlock) generatePerSubjectInfo() error { - // Check if this mb is empty. This can happen when its the last one and we are holding onto it for seq and timestamp info. - if mb.msgs == 0 { - return nil - } - - if mb.cacheNotLoaded() { - if err := mb.loadMsgsWithLock(); err != nil { - return err - } - // indexCacheBuf can produce fss now, so if non-nil we are good. - if mb.fss != nil { - return nil - } - } - - // Create new one regardless. - mb.fss = stree.NewSubjectTree[SimpleState]() - - var smv StoreMsg - fseq, lseq := atomic.LoadUint64(&mb.first.seq), atomic.LoadUint64(&mb.last.seq) - for seq := fseq; seq <= lseq; seq++ { - if mb.dmap.Exists(seq) { - // Optimisation to avoid calling cacheLookup which hits time.Now(). - // It gets set later on if the fss is non-empty anyway. - continue - } - sm, err := mb.cacheLookup(seq, &smv) - if err != nil { - // Since we are walking by sequence we can ignore some errors that are benign to rebuilding our state. - if err == ErrStoreMsgNotFound || err == errDeletedMsg { - continue - } - if err == errNoCache { - return nil - } - return err - } - if sm != nil && len(sm.subj) > 0 { - if ss, ok := mb.fss.Find(stringToBytes(sm.subj)); ok && ss != nil { - ss.Msgs++ - ss.Last = seq - ss.lastNeedsUpdate = false - } else { - mb.fss.Insert(stringToBytes(sm.subj), SimpleState{Msgs: 1, First: seq, Last: seq}) - } - } - } - - if mb.fss.Size() > 0 { - // Make sure we run the cache expire timer. - mb.llts = time.Now().UnixNano() - // Mark fss activity. - mb.lsts = time.Now().UnixNano() - mb.startCacheExpireTimer() - } - return nil -} - -// Helper to make sure fss loaded if we are tracking. -// Lock should be held -func (mb *msgBlock) ensurePerSubjectInfoLoaded() error { - if mb.fss != nil || mb.noTrack { - if mb.fss != nil { - // Mark fss activity. - mb.lsts = time.Now().UnixNano() - } - return nil - } - if mb.msgs == 0 { - mb.fss = stree.NewSubjectTree[SimpleState]() - return nil - } - return mb.generatePerSubjectInfo() -} - -// Called on recovery to populate the global psim state. -// Lock should be held. -func (fs *fileStore) populateGlobalPerSubjectInfo(mb *msgBlock) { - mb.mu.Lock() - defer mb.mu.Unlock() - - if err := mb.ensurePerSubjectInfoLoaded(); err != nil { - return - } - - // Now populate psim. - mb.fss.IterFast(func(bsubj []byte, ss *SimpleState) bool { - if len(bsubj) > 0 { - if info, ok := fs.psim.Find(bsubj); ok { - info.total += ss.Msgs - if mb.index > info.lblk { - info.lblk = mb.index - } - } else { - fs.psim.Insert(bsubj, psi{total: ss.Msgs, fblk: mb.index, lblk: mb.index}) - fs.tsl += len(bsubj) - } - } - return true - }) -} - -// Close the message block. -func (mb *msgBlock) close(sync bool) { - if mb == nil { - return - } - mb.mu.Lock() - defer mb.mu.Unlock() - - if mb.closed { - return - } - - // Stop cache expiration timer. - if mb.ctmr != nil { - mb.ctmr.Stop() - mb.ctmr = nil - } - - // Clear fss. - mb.fss = nil - - // Close cache - mb.clearCacheAndOffset() - // Quit our loops. - if mb.qch != nil { - close(mb.qch) - mb.qch = nil - } - if mb.mfd != nil { - if sync { - mb.mfd.Sync() - } - mb.mfd.Close() - } - mb.mfd = nil - // Mark as closed. - mb.closed = true -} - -func (fs *fileStore) closeAllMsgBlocks(sync bool) { - for _, mb := range fs.blks { - mb.close(sync) - } -} - -func (fs *fileStore) Delete() error { - if fs.isClosed() { - // Always attempt to remove since we could have been closed beforehand. - os.RemoveAll(fs.fcfg.StoreDir) - // Since we did remove, if we did have anything remaining make sure to - // call into any storage updates that had been registered. - fs.mu.Lock() - cb, msgs, bytes := fs.scb, int64(fs.state.Msgs), int64(fs.state.Bytes) - // Guard against double accounting if called twice. - fs.state.Msgs, fs.state.Bytes = 0, 0 - fs.mu.Unlock() - if msgs > 0 && cb != nil { - cb(-msgs, -bytes, 0, _EMPTY_) - } - return ErrStoreClosed - } - - pdir := filepath.Join(fs.fcfg.StoreDir, purgeDir) - // If purge directory still exists then we need to wait - // in place and remove since rename would fail. - if _, err := os.Stat(pdir); err == nil { - os.RemoveAll(pdir) - } - - // Quickly close all blocks and simulate a purge w/o overhead an new write block. - fs.mu.Lock() - for _, mb := range fs.blks { - mb.dirtyClose() - } - dmsgs := fs.state.Msgs - dbytes := int64(fs.state.Bytes) - fs.state.Msgs, fs.state.Bytes = 0, 0 - fs.blks = nil - cb := fs.scb - fs.mu.Unlock() - - if cb != nil { - cb(-int64(dmsgs), -dbytes, 0, _EMPTY_) - } - - if err := fs.stop(true, false); err != nil { - return err - } - - // Make sure we will not try to recover if killed before removal below completes. - if err := os.Remove(filepath.Join(fs.fcfg.StoreDir, JetStreamMetaFile)); err != nil { - return err - } - // Now move into different directory with "." prefix. - ndir := filepath.Join(filepath.Dir(fs.fcfg.StoreDir), tsep+filepath.Base(fs.fcfg.StoreDir)) - if err := os.Rename(fs.fcfg.StoreDir, ndir); err != nil { - return err - } - // Do this in separate Go routine in case lots of blocks. - // Purge above protects us as does the removal of meta artifacts above. - go func() { - <-dios - err := os.RemoveAll(ndir) - dios <- struct{}{} - if err == nil { - return - } - ttl := time.Now().Add(time.Second) - for time.Now().Before(ttl) { - time.Sleep(10 * time.Millisecond) - <-dios - err = os.RemoveAll(ndir) - dios <- struct{}{} - if err == nil { - return - } - } - }() - - return nil -} - -// Lock should be held. -func (fs *fileStore) setSyncTimer() { - if fs.syncTmr != nil { - fs.syncTmr.Reset(fs.fcfg.SyncInterval) - } else { - // First time this fires will be between SyncInterval/2 and SyncInterval, - // so that different stores are spread out, rather than having many of - // them trying to all sync at once, causing blips and contending dios. - start := (fs.fcfg.SyncInterval / 2) + (time.Duration(mrand.Int63n(int64(fs.fcfg.SyncInterval / 2)))) - fs.syncTmr = time.AfterFunc(start, fs.syncBlocks) - } -} - -// Lock should be held. -func (fs *fileStore) cancelSyncTimer() { - if fs.syncTmr != nil { - fs.syncTmr.Stop() - fs.syncTmr = nil - } -} - -// The full state file is versioned. -// - 0x1: original binary index.db format -// - 0x2: adds support for TTL count field after num deleted -const ( - fullStateMagic = uint8(11) - fullStateMinVersion = uint8(1) // What is the minimum version we know how to parse? - fullStateVersion = uint8(2) // What is the current version written out to index.db? -) - -// This go routine periodically writes out our full stream state index. -func (fs *fileStore) flushStreamStateLoop(qch, done chan struct{}) { - // Signal we are done on exit. - defer close(done) - - // Make sure we do not try to write these out too fast. - // Spread these out for large numbers on a server restart. - const writeThreshold = 2 * time.Minute - writeJitter := time.Duration(mrand.Int63n(int64(30 * time.Second))) - t := time.NewTicker(writeThreshold + writeJitter) - defer t.Stop() - - for { - select { - case <-t.C: - err := fs.writeFullState() - if isPermissionError(err) && fs.srv != nil { - fs.warn("File system permission denied when flushing stream state, disabling JetStream: %v", err) - // messages in block cache could be lost in the worst case. - // In the clustered mode it is very highly unlikely as a result of replication. - fs.srv.DisableJetStream() - return - } - - case <-qch: - return - } - } -} - -// Helper since unixnano of zero time undefined. -func timestampNormalized(t time.Time) int64 { - if t.IsZero() { - return 0 - } - return t.UnixNano() -} - -// writeFullState will proceed to write the full meta state iff not complex and time consuming. -// Since this is for quick recovery it is optional and should not block/stall normal operations. -func (fs *fileStore) writeFullState() error { - return fs._writeFullState(false) -} - -// forceWriteFullState will proceed to write the full meta state. This should only be called by stop() -func (fs *fileStore) forceWriteFullState() error { - return fs._writeFullState(true) -} - -// This will write the full binary state for the stream. -// This plus everything new since last hash will be the total recovered state. -// This state dump will have the following. -// 1. Stream summary - Msgs, Bytes, First and Last (Sequence and Timestamp) -// 2. PSIM - Per Subject Index Map - Tracks first and last blocks with subjects present. -// 3. MBs - Index, Bytes, First and Last Sequence and Timestamps, and the deleted map (avl.seqset). -// 4. Last block index and hash of record inclusive to this stream state. -func (fs *fileStore) _writeFullState(force bool) error { - start := time.Now() - fs.mu.Lock() - if fs.closed || fs.dirty == 0 { - fs.mu.Unlock() - return nil - } - - // For calculating size and checking time costs for non forced calls. - numSubjects := fs.numSubjects() - - // If we are not being forced to write out our state, check the complexity for time costs as to not - // block or stall normal operations. - // We will base off of number of subjects and interior deletes. A very large number of msg blocks could also - // be used, but for next server version will redo all meta handling to be disk based. So this is temporary. - if !force { - const numThreshold = 1_000_000 - // Calculate interior deletes. - var numDeleted int - if fs.state.LastSeq > fs.state.FirstSeq { - numDeleted = int((fs.state.LastSeq - fs.state.FirstSeq + 1) - fs.state.Msgs) - } - if numSubjects > numThreshold || numDeleted > numThreshold { - fs.mu.Unlock() - return errStateTooBig - } - } - - // We track this through subsequent runs to get an avg per blk used for subsequent runs. - avgDmapLen := fs.adml - // If first time through could be 0 - if avgDmapLen == 0 && ((fs.state.LastSeq-fs.state.FirstSeq+1)-fs.state.Msgs) > 0 { - avgDmapLen = 1024 - } - - // Calculate and estimate of the uper bound on the size to avoid multiple allocations. - sz := hdrLen + // Magic and Version - (binary.MaxVarintLen64 * 6) + // FS data - binary.MaxVarintLen64 + fs.tsl + // NumSubjects + total subject length - numSubjects*(binary.MaxVarintLen64*4) + // psi record - binary.MaxVarintLen64 + // Num blocks. - len(fs.blks)*((binary.MaxVarintLen64*8)+avgDmapLen) + // msg blocks, avgDmapLen is est for dmaps - binary.MaxVarintLen64 + 8 + 8 // last index + record checksum + full state checksum - - // Do 4k on stack if possible. - const ssz = 4 * 1024 - var buf []byte - - if sz <= ssz { - var _buf [ssz]byte - buf, sz = _buf[0:hdrLen:ssz], ssz - } else { - buf = make([]byte, hdrLen, sz) - } - - buf[0], buf[1] = fullStateMagic, fullStateVersion - buf = binary.AppendUvarint(buf, fs.state.Msgs) - buf = binary.AppendUvarint(buf, fs.state.Bytes) - buf = binary.AppendUvarint(buf, fs.state.FirstSeq) - buf = binary.AppendVarint(buf, timestampNormalized(fs.state.FirstTime)) - buf = binary.AppendUvarint(buf, fs.state.LastSeq) - buf = binary.AppendVarint(buf, timestampNormalized(fs.state.LastTime)) - - // Do per subject information map if applicable. - buf = binary.AppendUvarint(buf, uint64(numSubjects)) - if numSubjects > 0 { - fs.psim.Match([]byte(fwcs), func(subj []byte, psi *psi) { - buf = binary.AppendUvarint(buf, uint64(len(subj))) - buf = append(buf, subj...) - buf = binary.AppendUvarint(buf, psi.total) - buf = binary.AppendUvarint(buf, uint64(psi.fblk)) - if psi.total > 1 { - buf = binary.AppendUvarint(buf, uint64(psi.lblk)) - } - }) - } - - // Now walk all blocks and write out first and last and optional dmap encoding. - var lbi uint32 - var lchk [8]byte - - nb := len(fs.blks) - buf = binary.AppendUvarint(buf, uint64(nb)) - - // Use basetime to save some space. - baseTime := timestampNormalized(fs.state.FirstTime) - var scratch [8 * 1024]byte - - // Track the state as represented by the mbs. - var mstate StreamState - - var dmapTotalLen int - for _, mb := range fs.blks { - mb.mu.RLock() - buf = binary.AppendUvarint(buf, uint64(mb.index)) - buf = binary.AppendUvarint(buf, mb.bytes) - buf = binary.AppendUvarint(buf, atomic.LoadUint64(&mb.first.seq)) - buf = binary.AppendVarint(buf, mb.first.ts-baseTime) - buf = binary.AppendUvarint(buf, atomic.LoadUint64(&mb.last.seq)) - buf = binary.AppendVarint(buf, mb.last.ts-baseTime) - - numDeleted := mb.dmap.Size() - buf = binary.AppendUvarint(buf, uint64(numDeleted)) - buf = binary.AppendUvarint(buf, mb.ttls) // Field is new in version 2 - if numDeleted > 0 { - dmap, _ := mb.dmap.Encode(scratch[:0]) - dmapTotalLen += len(dmap) - buf = append(buf, dmap...) - } - // If this is the last one grab the last checksum and the block index, e.g. 22.blk, 22 is the block index. - // We use this to quickly open this file on recovery. - if mb == fs.lmb { - lbi = mb.index - mb.ensureLastChecksumLoaded() - copy(lchk[0:], mb.lchk[:]) - } - updateTrackingState(&mstate, mb) - mb.mu.RUnlock() - } - if dmapTotalLen > 0 { - fs.adml = dmapTotalLen / len(fs.blks) - } - - // Place block index and hash onto the end. - buf = binary.AppendUvarint(buf, uint64(lbi)) - buf = append(buf, lchk[:]...) - - // Encrypt if needed. - if fs.prf != nil { - if err := fs.setupAEK(); err != nil { - fs.mu.Unlock() - return err - } - nonce := make([]byte, fs.aek.NonceSize(), fs.aek.NonceSize()+len(buf)+fs.aek.Overhead()) - if n, err := rand.Read(nonce); err != nil { - return err - } else if n != len(nonce) { - return fmt.Errorf("not enough nonce bytes read (%d != %d)", n, len(nonce)) - } - buf = fs.aek.Seal(nonce, nonce, buf, nil) - } - - fn := filepath.Join(fs.fcfg.StoreDir, msgDir, streamStreamStateFile) - - fs.hh.Reset() - fs.hh.Write(buf) - buf = fs.hh.Sum(buf) - - // Snapshot prior dirty count. - priorDirty := fs.dirty - - statesEqual := trackingStatesEqual(&fs.state, &mstate) - // Release lock. - fs.mu.Unlock() - - // Check consistency here. - if !statesEqual { - fs.warn("Stream state encountered internal inconsistency on write") - // Rebuild our fs state from the mb state. - fs.rebuildState(nil) - return errCorruptState - } - - if cap(buf) > sz { - fs.debug("WriteFullState reallocated from %d to %d", sz, cap(buf)) - } - - // Only warn about construction time since file write not holding any locks. - if took := time.Since(start); took > time.Minute { - fs.warn("WriteFullState took %v (%d bytes)", took.Round(time.Millisecond), len(buf)) - } - - // Write our update index.db - // Protect with dios. - <-dios - err := os.WriteFile(fn, buf, defaultFilePerms) - // if file system is not writable isPermissionError is set to true - dios <- struct{}{} - if isPermissionError(err) { - return err - } - - // Update dirty if successful. - if err == nil { - fs.mu.Lock() - fs.dirty -= priorDirty - fs.mu.Unlock() - } - - return fs.writeTTLState() -} - -func (fs *fileStore) writeTTLState() error { - if fs.ttls == nil { - return nil - } - - fs.mu.RLock() - fn := filepath.Join(fs.fcfg.StoreDir, msgDir, ttlStreamStateFile) - buf := fs.ttls.Encode(fs.state.LastSeq) - fs.mu.RUnlock() - - <-dios - err := os.WriteFile(fn, buf, defaultFilePerms) - dios <- struct{}{} - - return err -} - -// Stop the current filestore. -func (fs *fileStore) Stop() error { - return fs.stop(false, true) -} - -// Stop the current filestore. -func (fs *fileStore) stop(delete, writeState bool) error { - fs.mu.Lock() - if fs.closed || fs.closing { - fs.mu.Unlock() - return ErrStoreClosed - } - - // Mark as closing. Do before releasing the lock to writeFullState - // so we don't end up with this function running more than once. - fs.closing = true - - if writeState { - fs.checkAndFlushAllBlocks() - } - fs.closeAllMsgBlocks(false) - - fs.cancelSyncTimer() - fs.cancelAgeChk() - - // Release the state flusher loop. - if fs.qch != nil { - close(fs.qch) - fs.qch = nil - } - - if writeState { - // Wait for the state flush loop to exit. - fsld := fs.fsld - fs.mu.Unlock() - <-fsld - // Write full state if needed. If not dirty this is a no-op. - fs.forceWriteFullState() - fs.mu.Lock() - } - - // Mark as closed. Last message block needs to be cleared after - // writeFullState has completed. - fs.closed = true - fs.lmb = nil - - // We should update the upper usage layer on a stop. - cb, bytes := fs.scb, int64(fs.state.Bytes) - fs.mu.Unlock() - - fs.cmu.Lock() - var _cfs [256]ConsumerStore - cfs := append(_cfs[:0], fs.cfs...) - fs.cfs = nil - fs.cmu.Unlock() - - for _, o := range cfs { - if delete { - o.StreamDelete() - } else { - o.Stop() - } - } - - if bytes > 0 && cb != nil { - cb(0, -bytes, 0, _EMPTY_) - } - - return nil -} - -const errFile = "errors.txt" - -// Stream our snapshot through S2 compression and tar. -func (fs *fileStore) streamSnapshot(w io.WriteCloser, includeConsumers bool, errCh chan string) { - defer close(errCh) - defer w.Close() - - enc := s2.NewWriter(w) - defer enc.Close() - - tw := tar.NewWriter(enc) - defer tw.Close() - - defer func() { - fs.mu.Lock() - fs.sips-- - fs.mu.Unlock() - }() - - modTime := time.Now().UTC() - - writeFile := func(name string, buf []byte) error { - hdr := &tar.Header{ - Name: name, - Mode: 0600, - ModTime: modTime, - Uname: "nats", - Gname: "nats", - Size: int64(len(buf)), - Format: tar.FormatPAX, - } - if err := tw.WriteHeader(hdr); err != nil { - return err - } - if _, err := tw.Write(buf); err != nil { - return err - } - return nil - } - - writeErr := func(err string) { - writeFile(errFile, []byte(err)) - errCh <- err - } - - fs.mu.Lock() - blks := fs.blks - // Grab our general meta data. - // We do this now instead of pulling from files since they could be encrypted. - meta, err := json.Marshal(fs.cfg) - if err != nil { - fs.mu.Unlock() - writeErr(fmt.Sprintf("Could not gather stream meta file: %v", err)) - return - } - hh := fs.hh - hh.Reset() - hh.Write(meta) - sum := []byte(hex.EncodeToString(fs.hh.Sum(nil))) - fs.mu.Unlock() - - // Meta first. - if writeFile(JetStreamMetaFile, meta) != nil { - return - } - if writeFile(JetStreamMetaFileSum, sum) != nil { - return - } - - // Can't use join path here, tar only recognizes relative paths with forward slashes. - msgPre := msgDir + "/" - var bbuf []byte - - // Now do messages themselves. - for _, mb := range blks { - if mb.pendingWriteSize() > 0 { - mb.flushPendingMsgs() - } - mb.mu.Lock() - // We could stream but don't want to hold the lock and prevent changes, so just read in and - // release the lock for now. - bbuf, err = mb.loadBlock(bbuf) - if err != nil { - mb.mu.Unlock() - writeErr(fmt.Sprintf("Could not read message block [%d]: %v", mb.index, err)) - return - } - // Check for encryption. - if mb.bek != nil && len(bbuf) > 0 { - rbek, err := genBlockEncryptionKey(fs.fcfg.Cipher, mb.seed, mb.nonce) - if err != nil { - mb.mu.Unlock() - writeErr(fmt.Sprintf("Could not create encryption key for message block [%d]: %v", mb.index, err)) - return - } - rbek.XORKeyStream(bbuf, bbuf) - } - // Check for compression. - if bbuf, err = mb.decompressIfNeeded(bbuf); err != nil { - mb.mu.Unlock() - writeErr(fmt.Sprintf("Could not decompress message block [%d]: %v", mb.index, err)) - return - } - mb.mu.Unlock() - - // Do this one unlocked. - if writeFile(msgPre+fmt.Sprintf(blkScan, mb.index), bbuf) != nil { - return - } - } - - // Do index.db last. We will force a write as well. - // Write out full state as well before proceeding. - if err := fs.forceWriteFullState(); err == nil { - const minLen = 32 - sfn := filepath.Join(fs.fcfg.StoreDir, msgDir, streamStreamStateFile) - if buf, err := os.ReadFile(sfn); err == nil && len(buf) >= minLen { - if fs.aek != nil { - ns := fs.aek.NonceSize() - buf, err = fs.aek.Open(nil, buf[:ns], buf[ns:len(buf)-highwayhash.Size64], nil) - if err == nil { - // Redo hash checksum at end on plaintext. - fs.mu.Lock() - hh.Reset() - hh.Write(buf) - buf = fs.hh.Sum(buf) - fs.mu.Unlock() - } - } - if err == nil && writeFile(msgPre+streamStreamStateFile, buf) != nil { - return - } - } - } - - // Bail if no consumers requested. - if !includeConsumers { - return - } - - // Do consumers' state last. - fs.cmu.RLock() - cfs := fs.cfs - fs.cmu.RUnlock() - - for _, cs := range cfs { - o, ok := cs.(*consumerFileStore) - if !ok { - continue - } - o.mu.Lock() - // Grab our general meta data. - // We do this now instead of pulling from files since they could be encrypted. - meta, err := json.Marshal(o.cfg) - if err != nil { - o.mu.Unlock() - writeErr(fmt.Sprintf("Could not gather consumer meta file for %q: %v", o.name, err)) - return - } - o.hh.Reset() - o.hh.Write(meta) - sum := []byte(hex.EncodeToString(o.hh.Sum(nil))) - - // We can have the running state directly encoded now. - state, err := o.encodeState() - if err != nil { - o.mu.Unlock() - writeErr(fmt.Sprintf("Could not encode consumer state for %q: %v", o.name, err)) - return - } - odirPre := filepath.Join(consumerDir, o.name) - o.mu.Unlock() - - // Write all the consumer files. - if writeFile(filepath.Join(odirPre, JetStreamMetaFile), meta) != nil { - return - } - if writeFile(filepath.Join(odirPre, JetStreamMetaFileSum), sum) != nil { - return - } - writeFile(filepath.Join(odirPre, consumerState), state) - } -} - -// Create a snapshot of this stream and its consumer's state along with messages. -func (fs *fileStore) Snapshot(deadline time.Duration, checkMsgs, includeConsumers bool) (*SnapshotResult, error) { - fs.mu.Lock() - if fs.closed { - fs.mu.Unlock() - return nil, ErrStoreClosed - } - // Only allow one at a time. - if fs.sips > 0 { - fs.mu.Unlock() - return nil, ErrStoreSnapshotInProgress - } - // Mark us as snapshotting - fs.sips += 1 - fs.mu.Unlock() - - if checkMsgs { - ld := fs.checkMsgs() - if ld != nil && len(ld.Msgs) > 0 { - return nil, fmt.Errorf("snapshot check detected %d bad messages", len(ld.Msgs)) - } - } - - pr, pw := net.Pipe() - - // Set a write deadline here to protect ourselves. - if deadline > 0 { - pw.SetWriteDeadline(time.Now().Add(deadline)) - } - - // We can add to our stream while snapshotting but not "user" delete anything. - var state StreamState - fs.FastState(&state) - - // Stream in separate Go routine. - errCh := make(chan string, 1) - go fs.streamSnapshot(pw, includeConsumers, errCh) - - return &SnapshotResult{pr, state, errCh}, nil -} - -// Helper to return the config. -func (fs *fileStore) fileStoreConfig() FileStoreConfig { - fs.mu.RLock() - defer fs.mu.RUnlock() - return fs.fcfg -} - -// Read lock all existing message blocks. -// Lock held on entry. -func (fs *fileStore) readLockAllMsgBlocks() { - for _, mb := range fs.blks { - mb.mu.RLock() - } -} - -// Read unlock all existing message blocks. -// Lock held on entry. -func (fs *fileStore) readUnlockAllMsgBlocks() { - for _, mb := range fs.blks { - mb.mu.RUnlock() - } -} - -// Binary encoded state snapshot, >= v2.10 server. -func (fs *fileStore) EncodedStreamState(failed uint64) ([]byte, error) { - fs.mu.RLock() - defer fs.mu.RUnlock() - - // Calculate deleted. - var numDeleted int64 - if fs.state.LastSeq > fs.state.FirstSeq { - numDeleted = int64(fs.state.LastSeq-fs.state.FirstSeq+1) - int64(fs.state.Msgs) - if numDeleted < 0 { - numDeleted = 0 - } - } - - // Encoded is Msgs, Bytes, FirstSeq, LastSeq, Failed, NumDeleted and optional DeletedBlocks - var buf [1024]byte - buf[0], buf[1] = streamStateMagic, streamStateVersion - n := hdrLen - n += binary.PutUvarint(buf[n:], fs.state.Msgs) - n += binary.PutUvarint(buf[n:], fs.state.Bytes) - n += binary.PutUvarint(buf[n:], fs.state.FirstSeq) - n += binary.PutUvarint(buf[n:], fs.state.LastSeq) - n += binary.PutUvarint(buf[n:], failed) - n += binary.PutUvarint(buf[n:], uint64(numDeleted)) - - b := buf[0:n] - - if numDeleted > 0 { - var scratch [4 * 1024]byte - - fs.readLockAllMsgBlocks() - defer fs.readUnlockAllMsgBlocks() - - for _, db := range fs.deleteBlocks() { - switch db := db.(type) { - case *DeleteRange: - first, _, num := db.State() - scratch[0] = runLengthMagic - i := 1 - i += binary.PutUvarint(scratch[i:], first) - i += binary.PutUvarint(scratch[i:], num) - b = append(b, scratch[0:i]...) - case *avl.SequenceSet: - buf, err := db.Encode(scratch[:0]) - if err != nil { - return nil, err - } - b = append(b, buf...) - default: - return nil, errors.New("no impl") - } - } - } - - return b, nil -} - -// We used to be more sophisticated to save memory, but speed is more important. -// All blocks should be at least read locked. -func (fs *fileStore) deleteBlocks() DeleteBlocks { - var dbs DeleteBlocks - var prevLast uint64 - - for _, mb := range fs.blks { - // Detect if we have a gap between these blocks. - fseq := atomic.LoadUint64(&mb.first.seq) - if prevLast > 0 && prevLast+1 != fseq { - dbs = append(dbs, &DeleteRange{First: prevLast + 1, Num: fseq - prevLast - 1}) - } - if mb.dmap.Size() > 0 { - dbs = append(dbs, &mb.dmap) - } - prevLast = atomic.LoadUint64(&mb.last.seq) - } - return dbs -} - -// SyncDeleted will make sure this stream has same deleted state as dbs. -// This will only process deleted state within our current state. -func (fs *fileStore) SyncDeleted(dbs DeleteBlocks) { - if len(dbs) == 0 { - return - } - - fs.mu.Lock() - defer fs.mu.Unlock() - - lseq := fs.state.LastSeq - var needsCheck DeleteBlocks - - fs.readLockAllMsgBlocks() - mdbs := fs.deleteBlocks() - for i, db := range dbs { - first, last, num := db.State() - // If the block is same as what we have we can skip. - if i < len(mdbs) { - eFirst, eLast, eNum := mdbs[i].State() - if first == eFirst && last == eLast && num == eNum { - continue - } - } else if first > lseq { - // Skip blocks not applicable to our current state. - continue - } - // Need to insert these. - needsCheck = append(needsCheck, db) - } - fs.readUnlockAllMsgBlocks() - - for _, db := range needsCheck { - db.Range(func(dseq uint64) bool { - fs.removeMsg(dseq, false, true, false) - return true - }) - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Consumers -//////////////////////////////////////////////////////////////////////////////// - -type consumerFileStore struct { - mu sync.Mutex - fs *fileStore - cfg *FileConsumerInfo - prf keyGen - aek cipher.AEAD - name string - odir string - ifn string - hh hash.Hash64 - state ConsumerState - fch chan struct{} - qch chan struct{} - flusher bool - writing bool - dirty bool - closed bool -} - -func (fs *fileStore) ConsumerStore(name string, cfg *ConsumerConfig) (ConsumerStore, error) { - if fs == nil { - return nil, fmt.Errorf("filestore is nil") - } - if fs.isClosed() { - return nil, ErrStoreClosed - } - if cfg == nil || name == _EMPTY_ { - return nil, fmt.Errorf("bad consumer config") - } - - // We now allow overrides from a stream being a filestore type and forcing a consumer to be memory store. - if cfg.MemoryStorage { - // Create directly here. - o := &consumerMemStore{ms: fs, cfg: *cfg} - fs.AddConsumer(o) - return o, nil - } - - odir := filepath.Join(fs.fcfg.StoreDir, consumerDir, name) - if err := os.MkdirAll(odir, defaultDirPerms); err != nil { - return nil, fmt.Errorf("could not create consumer directory - %v", err) - } - csi := &FileConsumerInfo{Name: name, Created: time.Now().UTC(), ConsumerConfig: *cfg} - o := &consumerFileStore{ - fs: fs, - cfg: csi, - prf: fs.prf, - name: name, - odir: odir, - ifn: filepath.Join(odir, consumerState), - } - key := sha256.Sum256([]byte(fs.cfg.Name + "/" + name)) - hh, err := highwayhash.New64(key[:]) - if err != nil { - return nil, fmt.Errorf("could not create hash: %v", err) - } - o.hh = hh - - // Check for encryption. - if o.prf != nil { - if ekey, err := os.ReadFile(filepath.Join(odir, JetStreamMetaFileKey)); err == nil { - if len(ekey) < minBlkKeySize { - return nil, errBadKeySize - } - // Recover key encryption key. - rb, err := fs.prf([]byte(fs.cfg.Name + tsep + o.name)) - if err != nil { - return nil, err - } - - sc := fs.fcfg.Cipher - kek, err := genEncryptionKey(sc, rb) - if err != nil { - return nil, err - } - ns := kek.NonceSize() - nonce := ekey[:ns] - seed, err := kek.Open(nil, nonce, ekey[ns:], nil) - if err != nil { - // We may be here on a cipher conversion, so attempt to convert. - if err = o.convertCipher(); err != nil { - return nil, err - } - } else { - o.aek, err = genEncryptionKey(sc, seed) - } - if err != nil { - return nil, err - } - } - } - - // Track if we are creating the directory so that we can clean up if we encounter an error. - var didCreate bool - - // Write our meta data iff does not exist. - meta := filepath.Join(odir, JetStreamMetaFile) - if _, err := os.Stat(meta); err != nil && os.IsNotExist(err) { - didCreate = true - csi.Created = time.Now().UTC() - if err := o.writeConsumerMeta(); err != nil { - os.RemoveAll(odir) - return nil, err - } - } - - // If we expect to be encrypted check that what we are restoring is not plaintext. - // This can happen on snapshot restores or conversions. - if o.prf != nil { - keyFile := filepath.Join(odir, JetStreamMetaFileKey) - if _, err := os.Stat(keyFile); err != nil && os.IsNotExist(err) { - if err := o.writeConsumerMeta(); err != nil { - if didCreate { - os.RemoveAll(odir) - } - return nil, err - } - // Redo the state file as well here if we have one and we can tell it was plaintext. - if buf, err := os.ReadFile(o.ifn); err == nil { - if _, err := decodeConsumerState(buf); err == nil { - state, err := o.encryptState(buf) - if err != nil { - return nil, err - } - err = fs.writeFileWithOptionalSync(o.ifn, state, defaultFilePerms) - if err != nil { - if didCreate { - os.RemoveAll(odir) - } - return nil, err - } - } - } - } - } - - // Create channels to control our flush go routine. - o.fch = make(chan struct{}, 1) - o.qch = make(chan struct{}) - go o.flushLoop(o.fch, o.qch) - - // Make sure to load in our state from disk if needed. - o.loadState() - - // Assign to filestore. - fs.AddConsumer(o) - - return o, nil -} - -func (o *consumerFileStore) convertCipher() error { - fs := o.fs - odir := filepath.Join(fs.fcfg.StoreDir, consumerDir, o.name) - - ekey, err := os.ReadFile(filepath.Join(odir, JetStreamMetaFileKey)) - if err != nil { - return err - } - if len(ekey) < minBlkKeySize { - return errBadKeySize - } - // Recover key encryption key. - rb, err := fs.prf([]byte(fs.cfg.Name + tsep + o.name)) - if err != nil { - return err - } - - // Do these in reverse since converting. - sc := fs.fcfg.Cipher - osc := AES - if sc == AES { - osc = ChaCha - } - kek, err := genEncryptionKey(osc, rb) - if err != nil { - return err - } - ns := kek.NonceSize() - nonce := ekey[:ns] - seed, err := kek.Open(nil, nonce, ekey[ns:], nil) - if err != nil { - return err - } - aek, err := genEncryptionKey(osc, seed) - if err != nil { - return err - } - // Now read in and decode our state using the old cipher. - buf, err := os.ReadFile(o.ifn) - if err != nil { - return err - } - buf, err = aek.Open(nil, buf[:ns], buf[ns:], nil) - if err != nil { - return err - } - - // Since we are here we recovered our old state. - // Now write our meta, which will generate the new keys with the new cipher. - if err := o.writeConsumerMeta(); err != nil { - return err - } - - // Now write out or state with the new cipher. - return o.writeState(buf) -} - -// Kick flusher for this consumer. -// Lock should be held. -func (o *consumerFileStore) kickFlusher() { - if o.fch != nil { - select { - case o.fch <- struct{}{}: - default: - } - } - o.dirty = true -} - -// Set in flusher status -func (o *consumerFileStore) setInFlusher() { - o.mu.Lock() - o.flusher = true - o.mu.Unlock() -} - -// Clear in flusher status -func (o *consumerFileStore) clearInFlusher() { - o.mu.Lock() - o.flusher = false - o.mu.Unlock() -} - -// Report in flusher status -func (o *consumerFileStore) inFlusher() bool { - o.mu.Lock() - defer o.mu.Unlock() - return o.flusher -} - -// flushLoop watches for consumer updates and the quit channel. -func (o *consumerFileStore) flushLoop(fch, qch chan struct{}) { - - o.setInFlusher() - defer o.clearInFlusher() - - // Maintain approximately 10 updates per second per consumer under load. - const minTime = 100 * time.Millisecond - var lastWrite time.Time - var dt *time.Timer - - setDelayTimer := func(addWait time.Duration) { - if dt == nil { - dt = time.NewTimer(addWait) - return - } - if !dt.Stop() { - select { - case <-dt.C: - default: - } - } - dt.Reset(addWait) - } - - for { - select { - case <-fch: - if ts := time.Since(lastWrite); ts < minTime { - setDelayTimer(minTime - ts) - select { - case <-dt.C: - case <-qch: - return - } - } - o.mu.Lock() - if o.closed { - o.mu.Unlock() - return - } - buf, err := o.encodeState() - o.mu.Unlock() - if err != nil { - return - } - // TODO(dlc) - if we error should start failing upwards. - if err := o.writeState(buf); err == nil { - lastWrite = time.Now() - } - case <-qch: - return - } - } -} - -// SetStarting sets our starting stream sequence. -func (o *consumerFileStore) SetStarting(sseq uint64) error { - o.mu.Lock() - o.state.Delivered.Stream = sseq - buf, err := o.encodeState() - o.mu.Unlock() - if err != nil { - return err - } - return o.writeState(buf) -} - -// UpdateStarting updates our starting stream sequence. -func (o *consumerFileStore) UpdateStarting(sseq uint64) { - o.mu.Lock() - defer o.mu.Unlock() - - if sseq > o.state.Delivered.Stream { - o.state.Delivered.Stream = sseq - // For AckNone just update delivered and ackfloor at the same time. - if o.cfg.AckPolicy == AckNone { - o.state.AckFloor.Stream = sseq - } - } - // Make sure we flush to disk. - o.kickFlusher() -} - -// HasState returns if this store has a recorded state. -func (o *consumerFileStore) HasState() bool { - o.mu.Lock() - // We have a running state, or stored on disk but not yet initialized. - if o.state.Delivered.Consumer != 0 || o.state.Delivered.Stream != 0 { - o.mu.Unlock() - return true - } - _, err := os.Stat(o.ifn) - o.mu.Unlock() - return err == nil -} - -// UpdateDelivered is called whenever a new message has been delivered. -func (o *consumerFileStore) UpdateDelivered(dseq, sseq, dc uint64, ts int64) error { - o.mu.Lock() - defer o.mu.Unlock() - - if dc != 1 && o.cfg.AckPolicy == AckNone { - return ErrNoAckPolicy - } - - // On restarts the old leader may get a replay from the raft logs that are old. - if dseq <= o.state.AckFloor.Consumer { - return nil - } - - // See if we expect an ack for this. - if o.cfg.AckPolicy != AckNone { - // Need to create pending records here. - if o.state.Pending == nil { - o.state.Pending = make(map[uint64]*Pending) - } - var p *Pending - // Check for an update to a message already delivered. - if sseq <= o.state.Delivered.Stream { - if p = o.state.Pending[sseq]; p != nil { - // Do not update p.Sequence, that should be the original delivery sequence. - p.Timestamp = ts - } - } else { - // Add to pending. - o.state.Pending[sseq] = &Pending{dseq, ts} - } - // Update delivered as needed. - if dseq > o.state.Delivered.Consumer { - o.state.Delivered.Consumer = dseq - } - if sseq > o.state.Delivered.Stream { - o.state.Delivered.Stream = sseq - } - - if dc > 1 { - if maxdc := uint64(o.cfg.MaxDeliver); maxdc > 0 && dc > maxdc { - // Make sure to remove from pending. - delete(o.state.Pending, sseq) - } - if o.state.Redelivered == nil { - o.state.Redelivered = make(map[uint64]uint64) - } - // Only update if greater than what we already have. - if o.state.Redelivered[sseq] < dc-1 { - o.state.Redelivered[sseq] = dc - 1 - } - } - } else { - // For AckNone just update delivered and ackfloor at the same time. - if dseq > o.state.Delivered.Consumer { - o.state.Delivered.Consumer = dseq - o.state.AckFloor.Consumer = dseq - } - if sseq > o.state.Delivered.Stream { - o.state.Delivered.Stream = sseq - o.state.AckFloor.Stream = sseq - } - } - // Make sure we flush to disk. - o.kickFlusher() - - return nil -} - -// UpdateAcks is called whenever a consumer with explicit ack or ack all acks a message. -func (o *consumerFileStore) UpdateAcks(dseq, sseq uint64) error { - o.mu.Lock() - defer o.mu.Unlock() - - if o.cfg.AckPolicy == AckNone { - return ErrNoAckPolicy - } - - // On restarts the old leader may get a replay from the raft logs that are old. - if dseq <= o.state.AckFloor.Consumer { - return nil - } - - if len(o.state.Pending) == 0 || o.state.Pending[sseq] == nil { - delete(o.state.Redelivered, sseq) - return ErrStoreMsgNotFound - } - - // Check for AckAll here. - if o.cfg.AckPolicy == AckAll { - sgap := sseq - o.state.AckFloor.Stream - o.state.AckFloor.Consumer = dseq - o.state.AckFloor.Stream = sseq - if sgap > uint64(len(o.state.Pending)) { - for seq := range o.state.Pending { - if seq <= sseq { - delete(o.state.Pending, seq) - delete(o.state.Redelivered, seq) - } - } - } else { - for seq := sseq; seq > sseq-sgap && len(o.state.Pending) > 0; seq-- { - delete(o.state.Pending, seq) - delete(o.state.Redelivered, seq) - } - } - o.kickFlusher() - return nil - } - - // AckExplicit - - // First delete from our pending state. - if p, ok := o.state.Pending[sseq]; ok { - delete(o.state.Pending, sseq) - if dseq > p.Sequence && p.Sequence > 0 { - dseq = p.Sequence // Use the original. - } - } - if len(o.state.Pending) == 0 { - o.state.AckFloor.Consumer = o.state.Delivered.Consumer - o.state.AckFloor.Stream = o.state.Delivered.Stream - } else if dseq == o.state.AckFloor.Consumer+1 { - o.state.AckFloor.Consumer = dseq - o.state.AckFloor.Stream = sseq - - if o.state.Delivered.Consumer > dseq { - for ss := sseq + 1; ss <= o.state.Delivered.Stream; ss++ { - if p, ok := o.state.Pending[ss]; ok { - if p.Sequence > 0 { - o.state.AckFloor.Consumer = p.Sequence - 1 - o.state.AckFloor.Stream = ss - 1 - } - break - } - } - } - } - // We do these regardless. - delete(o.state.Redelivered, sseq) - - o.kickFlusher() - return nil -} - -const seqsHdrSize = 6*binary.MaxVarintLen64 + hdrLen - -// Encode our consumer state, version 2. -// Lock should be held. - -func (o *consumerFileStore) EncodedState() ([]byte, error) { - o.mu.Lock() - defer o.mu.Unlock() - return o.encodeState() -} - -func (o *consumerFileStore) encodeState() ([]byte, error) { - // Grab reference to state, but make sure we load in if needed, so do not reference o.state directly. - state, err := o.stateWithCopyLocked(false) - if err != nil { - return nil, err - } - return encodeConsumerState(state), nil -} - -func (o *consumerFileStore) UpdateConfig(cfg *ConsumerConfig) error { - o.mu.Lock() - defer o.mu.Unlock() - - // This is mostly unchecked here. We are assuming the upper layers have done sanity checking. - csi := o.cfg - csi.ConsumerConfig = *cfg - - return o.writeConsumerMeta() -} - -func (o *consumerFileStore) Update(state *ConsumerState) error { - // Sanity checks. - if state.AckFloor.Consumer > state.Delivered.Consumer { - return fmt.Errorf("bad ack floor for consumer") - } - if state.AckFloor.Stream > state.Delivered.Stream { - return fmt.Errorf("bad ack floor for stream") - } - - // Copy to our state. - var pending map[uint64]*Pending - var redelivered map[uint64]uint64 - if len(state.Pending) > 0 { - pending = make(map[uint64]*Pending, len(state.Pending)) - for seq, p := range state.Pending { - pending[seq] = &Pending{p.Sequence, p.Timestamp} - if seq <= state.AckFloor.Stream || seq > state.Delivered.Stream { - return fmt.Errorf("bad pending entry, sequence [%d] out of range", seq) - } - } - } - if len(state.Redelivered) > 0 { - redelivered = make(map[uint64]uint64, len(state.Redelivered)) - for seq, dc := range state.Redelivered { - redelivered[seq] = dc - } - } - - // Replace our state. - o.mu.Lock() - defer o.mu.Unlock() - - // Check to see if this is an outdated update. - if state.Delivered.Consumer < o.state.Delivered.Consumer || state.AckFloor.Stream < o.state.AckFloor.Stream { - return fmt.Errorf("old update ignored") - } - - o.state.Delivered = state.Delivered - o.state.AckFloor = state.AckFloor - o.state.Pending = pending - o.state.Redelivered = redelivered - - o.kickFlusher() - - return nil -} - -// Will encrypt the state with our asset key. Will be a no-op if encryption not enabled. -// Lock should be held. -func (o *consumerFileStore) encryptState(buf []byte) ([]byte, error) { - if o.aek == nil { - return buf, nil - } - // TODO(dlc) - Optimize on space usage a bit? - nonce := make([]byte, o.aek.NonceSize(), o.aek.NonceSize()+len(buf)+o.aek.Overhead()) - if n, err := rand.Read(nonce); err != nil { - return nil, err - } else if n != len(nonce) { - return nil, fmt.Errorf("not enough nonce bytes read (%d != %d)", n, len(nonce)) - } - return o.aek.Seal(nonce, nonce, buf, nil), nil -} - -// Used to limit number of disk IO calls in flight since they could all be blocking an OS thread. -// https://github.com/nats-io/nats-server/issues/2742 -var dios chan struct{} - -// Used to setup our simplistic counting semaphore using buffered channels. -// golang.org's semaphore seemed a bit heavy. -func init() { - // Limit ourselves to a sensible number of blocking I/O calls. Range between - // 4-16 concurrent disk I/Os based on CPU cores, or 50% of cores if greater - // than 32 cores. - mp := runtime.GOMAXPROCS(-1) - nIO := min(16, max(4, mp)) - if mp > 32 { - // If the system has more than 32 cores then limit dios to 50% of cores. - nIO = max(16, min(mp, mp/2)) - } - dios = make(chan struct{}, nIO) - // Fill it up to start. - for i := 0; i < nIO; i++ { - dios <- struct{}{} - } -} - -func (o *consumerFileStore) writeState(buf []byte) error { - // Check if we have the index file open. - o.mu.Lock() - if o.writing || len(buf) == 0 { - o.mu.Unlock() - return nil - } - - // Check on encryption. - if o.aek != nil { - var err error - if buf, err = o.encryptState(buf); err != nil { - return err - } - } - - o.writing = true - o.dirty = false - ifn := o.ifn - o.mu.Unlock() - - // Lock not held here but we do limit number of outstanding calls that could block OS threads. - err := o.fs.writeFileWithOptionalSync(ifn, buf, defaultFilePerms) - - o.mu.Lock() - if err != nil { - o.dirty = true - } - o.writing = false - o.mu.Unlock() - - return err -} - -// Will upodate the config. Only used when recovering ephemerals. -func (o *consumerFileStore) updateConfig(cfg ConsumerConfig) error { - o.mu.Lock() - defer o.mu.Unlock() - o.cfg = &FileConsumerInfo{ConsumerConfig: cfg} - return o.writeConsumerMeta() -} - -// Write out the consumer meta data, i.e. state. -// Lock should be held. -func (cfs *consumerFileStore) writeConsumerMeta() error { - meta := filepath.Join(cfs.odir, JetStreamMetaFile) - if _, err := os.Stat(meta); err != nil && !os.IsNotExist(err) { - return err - } - - if cfs.prf != nil && cfs.aek == nil { - fs := cfs.fs - key, _, _, encrypted, err := fs.genEncryptionKeys(fs.cfg.Name + tsep + cfs.name) - if err != nil { - return err - } - cfs.aek = key - keyFile := filepath.Join(cfs.odir, JetStreamMetaFileKey) - if _, err := os.Stat(keyFile); err != nil && !os.IsNotExist(err) { - return err - } - err = cfs.fs.writeFileWithOptionalSync(keyFile, encrypted, defaultFilePerms) - if err != nil { - return err - } - } - - b, err := json.Marshal(cfs.cfg) - if err != nil { - return err - } - // Encrypt if needed. - if cfs.aek != nil { - nonce := make([]byte, cfs.aek.NonceSize(), cfs.aek.NonceSize()+len(b)+cfs.aek.Overhead()) - if n, err := rand.Read(nonce); err != nil { - return err - } else if n != len(nonce) { - return fmt.Errorf("not enough nonce bytes read (%d != %d)", n, len(nonce)) - } - b = cfs.aek.Seal(nonce, nonce, b, nil) - } - - err = cfs.fs.writeFileWithOptionalSync(meta, b, defaultFilePerms) - if err != nil { - return err - } - cfs.hh.Reset() - cfs.hh.Write(b) - checksum := hex.EncodeToString(cfs.hh.Sum(nil)) - sum := filepath.Join(cfs.odir, JetStreamMetaFileSum) - - err = cfs.fs.writeFileWithOptionalSync(sum, []byte(checksum), defaultFilePerms) - if err != nil { - return err - } - return nil -} - -// Consumer version. -func checkConsumerHeader(hdr []byte) (uint8, error) { - if len(hdr) < 2 || hdr[0] != magic { - return 0, errCorruptState - } - version := hdr[1] - switch version { - case 1, 2: - return version, nil - } - return 0, fmt.Errorf("unsupported version: %d", version) -} - -func (o *consumerFileStore) copyPending() map[uint64]*Pending { - pending := make(map[uint64]*Pending, len(o.state.Pending)) - for seq, p := range o.state.Pending { - pending[seq] = &Pending{p.Sequence, p.Timestamp} - } - return pending -} - -func (o *consumerFileStore) copyRedelivered() map[uint64]uint64 { - redelivered := make(map[uint64]uint64, len(o.state.Redelivered)) - for seq, dc := range o.state.Redelivered { - redelivered[seq] = dc - } - return redelivered -} - -// Type returns the type of the underlying store. -func (o *consumerFileStore) Type() StorageType { return FileStorage } - -// State retrieves the state from the state file. -// This is not expected to be called in high performance code, only on startup. -func (o *consumerFileStore) State() (*ConsumerState, error) { - return o.stateWithCopy(true) -} - -// This will not copy pending or redelivered, so should only be done under the -// consumer owner's lock. -func (o *consumerFileStore) BorrowState() (*ConsumerState, error) { - return o.stateWithCopy(false) -} - -func (o *consumerFileStore) stateWithCopy(doCopy bool) (*ConsumerState, error) { - o.mu.Lock() - defer o.mu.Unlock() - return o.stateWithCopyLocked(doCopy) -} - -// Lock should be held. -func (o *consumerFileStore) stateWithCopyLocked(doCopy bool) (*ConsumerState, error) { - if o.closed { - return nil, ErrStoreClosed - } - - state := &ConsumerState{} - - // See if we have a running state or if we need to read in from disk. - if o.state.Delivered.Consumer != 0 || o.state.Delivered.Stream != 0 { - state.Delivered = o.state.Delivered - state.AckFloor = o.state.AckFloor - if len(o.state.Pending) > 0 { - if doCopy { - state.Pending = o.copyPending() - } else { - state.Pending = o.state.Pending - } - } - if len(o.state.Redelivered) > 0 { - if doCopy { - state.Redelivered = o.copyRedelivered() - } else { - state.Redelivered = o.state.Redelivered - } - } - return state, nil - } - - // Read the state in here from disk.. - <-dios - buf, err := os.ReadFile(o.ifn) - dios <- struct{}{} - - if err != nil && !os.IsNotExist(err) { - return nil, err - } - - if len(buf) == 0 { - return state, nil - } - - // Check on encryption. - if o.aek != nil { - ns := o.aek.NonceSize() - buf, err = o.aek.Open(nil, buf[:ns], buf[ns:], nil) - if err != nil { - return nil, err - } - } - - state, err = decodeConsumerState(buf) - if err != nil { - return nil, err - } - - // Copy this state into our own. - o.state.Delivered = state.Delivered - o.state.AckFloor = state.AckFloor - if len(state.Pending) > 0 { - if doCopy { - o.state.Pending = make(map[uint64]*Pending, len(state.Pending)) - for seq, p := range state.Pending { - o.state.Pending[seq] = &Pending{p.Sequence, p.Timestamp} - } - } else { - o.state.Pending = state.Pending - } - } - if len(state.Redelivered) > 0 { - if doCopy { - o.state.Redelivered = make(map[uint64]uint64, len(state.Redelivered)) - for seq, dc := range state.Redelivered { - o.state.Redelivered[seq] = dc - } - } else { - o.state.Redelivered = state.Redelivered - } - } - - return state, nil -} - -// Lock should be held. Called at startup. -func (o *consumerFileStore) loadState() { - if _, err := os.Stat(o.ifn); err == nil { - // This will load our state in from disk. - o.stateWithCopyLocked(false) - } -} - -// Decode consumer state. -func decodeConsumerState(buf []byte) (*ConsumerState, error) { - version, err := checkConsumerHeader(buf) - if err != nil { - return nil, err - } - - bi := hdrLen - // Helpers, will set i to -1 on error. - readSeq := func() uint64 { - if bi < 0 { - return 0 - } - seq, n := binary.Uvarint(buf[bi:]) - if n <= 0 { - bi = -1 - return 0 - } - bi += n - return seq - } - readTimeStamp := func() int64 { - if bi < 0 { - return 0 - } - ts, n := binary.Varint(buf[bi:]) - if n <= 0 { - bi = -1 - return -1 - } - bi += n - return ts - } - // Just for clarity below. - readLen := readSeq - readCount := readSeq - - state := &ConsumerState{} - state.AckFloor.Consumer = readSeq() - state.AckFloor.Stream = readSeq() - state.Delivered.Consumer = readSeq() - state.Delivered.Stream = readSeq() - - if bi == -1 { - return nil, errCorruptState - } - if version == 1 { - // Adjust back. Version 1 also stored delivered as next to be delivered, - // so adjust that back down here. - if state.AckFloor.Consumer > 1 { - state.Delivered.Consumer += state.AckFloor.Consumer - 1 - } - if state.AckFloor.Stream > 1 { - state.Delivered.Stream += state.AckFloor.Stream - 1 - } - } - - // Protect ourselves against rolling backwards. - const hbit = 1 << 63 - if state.AckFloor.Stream&hbit != 0 || state.Delivered.Stream&hbit != 0 { - return nil, errCorruptState - } - - // We have additional stuff. - if numPending := readLen(); numPending > 0 { - mints := readTimeStamp() - state.Pending = make(map[uint64]*Pending, numPending) - for i := 0; i < int(numPending); i++ { - sseq := readSeq() - var dseq uint64 - if version == 2 { - dseq = readSeq() - } - ts := readTimeStamp() - // Check the state machine for corruption, not the value which could be -1. - if bi == -1 { - return nil, errCorruptState - } - // Adjust seq back. - sseq += state.AckFloor.Stream - if sseq == 0 { - return nil, errCorruptState - } - if version == 2 { - dseq += state.AckFloor.Consumer - } - // Adjust the timestamp back. - if version == 1 { - ts = (ts + mints) * int64(time.Second) - } else { - ts = (mints - ts) * int64(time.Second) - } - // Store in pending. - state.Pending[sseq] = &Pending{dseq, ts} - } - } - - // We have redelivered entries here. - if numRedelivered := readLen(); numRedelivered > 0 { - state.Redelivered = make(map[uint64]uint64, numRedelivered) - for i := 0; i < int(numRedelivered); i++ { - if seq, n := readSeq(), readCount(); seq > 0 && n > 0 { - // Adjust seq back. - seq += state.AckFloor.Stream - state.Redelivered[seq] = n - } - } - } - - return state, nil -} - -// Stop the processing of the consumers's state. -func (o *consumerFileStore) Stop() error { - o.mu.Lock() - if o.closed { - o.mu.Unlock() - return nil - } - if o.qch != nil { - close(o.qch) - o.qch = nil - } - - var err error - var buf []byte - - if o.dirty { - // Make sure to write this out.. - if buf, err = o.encodeState(); err == nil && len(buf) > 0 { - if o.aek != nil { - if buf, err = o.encryptState(buf); err != nil { - return err - } - } - } - } - - o.odir = _EMPTY_ - o.closed = true - ifn, fs := o.ifn, o.fs - o.mu.Unlock() - - fs.RemoveConsumer(o) - - if len(buf) > 0 { - o.waitOnFlusher() - err = o.fs.writeFileWithOptionalSync(ifn, buf, defaultFilePerms) - } - return err -} - -func (o *consumerFileStore) waitOnFlusher() { - if !o.inFlusher() { - return - } - - timeout := time.Now().Add(100 * time.Millisecond) - for time.Now().Before(timeout) { - if !o.inFlusher() { - return - } - time.Sleep(10 * time.Millisecond) - } -} - -// Delete the consumer. -func (o *consumerFileStore) Delete() error { - return o.delete(false) -} - -func (o *consumerFileStore) StreamDelete() error { - return o.delete(true) -} - -func (o *consumerFileStore) delete(streamDeleted bool) error { - o.mu.Lock() - if o.closed { - o.mu.Unlock() - return nil - } - if o.qch != nil { - close(o.qch) - o.qch = nil - } - - var err error - odir := o.odir - o.odir = _EMPTY_ - o.closed = true - fs := o.fs - o.mu.Unlock() - - // If our stream was not deleted this will remove the directories. - if odir != _EMPTY_ && !streamDeleted { - <-dios - err = os.RemoveAll(odir) - dios <- struct{}{} - } - - if !streamDeleted { - fs.RemoveConsumer(o) - } - - return err -} - -func (fs *fileStore) AddConsumer(o ConsumerStore) error { - fs.cmu.Lock() - defer fs.cmu.Unlock() - fs.cfs = append(fs.cfs, o) - return nil -} - -func (fs *fileStore) RemoveConsumer(o ConsumerStore) error { - fs.cmu.Lock() - defer fs.cmu.Unlock() - for i, cfs := range fs.cfs { - if o == cfs { - fs.cfs = append(fs.cfs[:i], fs.cfs[i+1:]...) - break - } - } - return nil -} - -//////////////////////////////////////////////////////////////////////////////// -// Templates -//////////////////////////////////////////////////////////////////////////////// - -type templateFileStore struct { - dir string - hh hash.Hash64 -} - -func newTemplateFileStore(storeDir string) *templateFileStore { - tdir := filepath.Join(storeDir, tmplsDir) - key := sha256.Sum256([]byte("templates")) - hh, err := highwayhash.New64(key[:]) - if err != nil { - return nil - } - return &templateFileStore{dir: tdir, hh: hh} -} - -func (ts *templateFileStore) Store(t *streamTemplate) error { - dir := filepath.Join(ts.dir, t.Name) - if err := os.MkdirAll(dir, defaultDirPerms); err != nil { - return fmt.Errorf("could not create templates storage directory for %q- %v", t.Name, err) - } - meta := filepath.Join(dir, JetStreamMetaFile) - if _, err := os.Stat(meta); (err != nil && !os.IsNotExist(err)) || err == nil { - return err - } - t.mu.Lock() - b, err := json.Marshal(t) - t.mu.Unlock() - if err != nil { - return err - } - if err := os.WriteFile(meta, b, defaultFilePerms); err != nil { - return err - } - // FIXME(dlc) - Do checksum - ts.hh.Reset() - ts.hh.Write(b) - checksum := hex.EncodeToString(ts.hh.Sum(nil)) - sum := filepath.Join(dir, JetStreamMetaFileSum) - if err := os.WriteFile(sum, []byte(checksum), defaultFilePerms); err != nil { - return err - } - return nil -} - -func (ts *templateFileStore) Delete(t *streamTemplate) error { - return os.RemoveAll(filepath.Join(ts.dir, t.Name)) -} - -//////////////////////////////////////////////////////////////////////////////// -// Compression -//////////////////////////////////////////////////////////////////////////////// - -type CompressionInfo struct { - Algorithm StoreCompression - OriginalSize uint64 -} - -func (c *CompressionInfo) MarshalMetadata() []byte { - b := make([]byte, 14) // 4 + potentially up to 10 for uint64 - b[0], b[1], b[2] = 'c', 'm', 'p' - b[3] = byte(c.Algorithm) - n := binary.PutUvarint(b[4:], c.OriginalSize) - return b[:4+n] -} - -func (c *CompressionInfo) UnmarshalMetadata(b []byte) (int, error) { - c.Algorithm = NoCompression - c.OriginalSize = 0 - if len(b) < 5 { // 4 + min 1 for uvarint uint64 - return 0, nil - } - if b[0] != 'c' || b[1] != 'm' || b[2] != 'p' { - return 0, nil - } - var n int - c.Algorithm = StoreCompression(b[3]) - c.OriginalSize, n = binary.Uvarint(b[4:]) - if n <= 0 { - return 0, fmt.Errorf("metadata incomplete") - } - return 4 + n, nil -} - -func (alg StoreCompression) Compress(buf []byte) ([]byte, error) { - if len(buf) < checksumSize { - return nil, fmt.Errorf("uncompressed buffer is too short") - } - bodyLen := int64(len(buf) - checksumSize) - var output bytes.Buffer - var writer io.WriteCloser - switch alg { - case NoCompression: - return buf, nil - case S2Compression: - writer = s2.NewWriter(&output) - default: - return nil, fmt.Errorf("compression algorithm not known") - } - - input := bytes.NewReader(buf[:bodyLen]) - checksum := buf[bodyLen:] - - // Compress the block content, but don't compress the checksum. - // We will preserve it at the end of the block as-is. - if n, err := io.CopyN(writer, input, bodyLen); err != nil { - return nil, fmt.Errorf("error writing to compression writer: %w", err) - } else if n != bodyLen { - return nil, fmt.Errorf("short write on body (%d != %d)", n, bodyLen) - } - if err := writer.Close(); err != nil { - return nil, fmt.Errorf("error closing compression writer: %w", err) - } - - // Now add the checksum back onto the end of the block. - if n, err := output.Write(checksum); err != nil { - return nil, fmt.Errorf("error writing checksum: %w", err) - } else if n != checksumSize { - return nil, fmt.Errorf("short write on checksum (%d != %d)", n, checksumSize) - } - - return output.Bytes(), nil -} - -func (alg StoreCompression) Decompress(buf []byte) ([]byte, error) { - if len(buf) < checksumSize { - return nil, fmt.Errorf("compressed buffer is too short") - } - bodyLen := int64(len(buf) - checksumSize) - input := bytes.NewReader(buf[:bodyLen]) - - var reader io.ReadCloser - switch alg { - case NoCompression: - return buf, nil - case S2Compression: - reader = io.NopCloser(s2.NewReader(input)) - default: - return nil, fmt.Errorf("compression algorithm not known") - } - - // Decompress the block content. The checksum isn't compressed so - // we can preserve it from the end of the block as-is. - checksum := buf[bodyLen:] - output, err := io.ReadAll(reader) - if err != nil { - return nil, fmt.Errorf("error reading compression reader: %w", err) - } - output = append(output, checksum...) - - return output, reader.Close() -} - -// writeFileWithOptionalSync is equivalent to os.WriteFile() but optionally -// sets O_SYNC on the open file if SyncAlways is set. The dios semaphore is -// handled automatically by this function, so don't wrap calls to it in dios. -func (fs *fileStore) writeFileWithOptionalSync(name string, data []byte, perm fs.FileMode) error { - if fs.fcfg.SyncAlways { - return writeFileWithSync(name, data, perm) - } - <-dios - defer func() { - dios <- struct{}{} - }() - return os.WriteFile(name, data, perm) -} - -func writeFileWithSync(name string, data []byte, perm fs.FileMode) error { - <-dios - defer func() { - dios <- struct{}{} - }() - flags := os.O_WRONLY | os.O_CREATE | os.O_TRUNC | os.O_SYNC - f, err := os.OpenFile(name, flags, perm) - if err != nil { - return err - } - if _, err = f.Write(data); err != nil { - _ = f.Close() - return err - } - return f.Close() -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/fuzz.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/fuzz.go deleted file mode 100644 index 361ab7c5..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/fuzz.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2020-2022 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build gofuzz - -package server - -var defaultFuzzServerOptions = Options{ - Host: "127.0.0.1", - Trace: true, - Debug: true, - DisableShortFirstPing: true, - NoLog: true, - NoSigs: true, -} - -func dummyFuzzClient() *client { - return &client{srv: New(&defaultFuzzServerOptions), msubs: -1, mpay: MAX_PAYLOAD_SIZE, mcl: MAX_CONTROL_LINE_SIZE} -} - -func FuzzClient(data []byte) int { - if len(data) < 100 { - return -1 - } - c := dummyFuzzClient() - - err := c.parse(data[:50]) - if err != nil { - return 0 - } - - err = c.parse(data[50:]) - if err != nil { - return 0 - } - return 1 -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/gateway.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/gateway.go deleted file mode 100644 index 6257e3f6..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/gateway.go +++ /dev/null @@ -1,3377 +0,0 @@ -// Copyright 2018-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "cmp" - "crypto/sha256" - "crypto/tls" - "encoding/json" - "errors" - "fmt" - "math/rand" - "net" - "net/url" - "slices" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" -) - -const ( - defaultSolicitGatewaysDelay = time.Second - defaultGatewayConnectDelay = time.Second - defaultGatewayReconnectDelay = time.Second - defaultGatewayRecentSubExpiration = 2 * time.Second - defaultGatewayMaxRUnsubBeforeSwitch = 1000 - - oldGWReplyPrefix = "$GR." - oldGWReplyPrefixLen = len(oldGWReplyPrefix) - oldGWReplyStart = oldGWReplyPrefixLen + 5 // len of prefix above + len of hash (4) + "." - - // The new prefix is "_GR_..." where is 6 characters - // hash of origin cluster name and is 6 characters hash of origin server pub key. - gwReplyPrefix = "_GR_." - gwReplyPrefixLen = len(gwReplyPrefix) - gwHashLen = 6 - gwClusterOffset = gwReplyPrefixLen - gwServerOffset = gwClusterOffset + gwHashLen + 1 - gwSubjectOffset = gwServerOffset + gwHashLen + 1 - - // Gateway connections send PINGs regardless of traffic. The interval is - // either Options.PingInterval or this value, whichever is the smallest. - gwMaxPingInterval = 15 * time.Second -) - -var ( - gatewayConnectDelay = defaultGatewayConnectDelay - gatewayReconnectDelay = defaultGatewayReconnectDelay - gatewayMaxRUnsubBeforeSwitch = defaultGatewayMaxRUnsubBeforeSwitch - gatewaySolicitDelay = int64(defaultSolicitGatewaysDelay) - gatewayMaxPingInterval = gwMaxPingInterval -) - -// Warning when user configures gateway TLS insecure -const gatewayTLSInsecureWarning = "TLS certificate chain and hostname of solicited gateways will not be verified. DO NOT USE IN PRODUCTION!" - -// SetGatewaysSolicitDelay sets the initial delay before gateways -// connections are initiated. -// Used by tests. -func SetGatewaysSolicitDelay(delay time.Duration) { - atomic.StoreInt64(&gatewaySolicitDelay, int64(delay)) -} - -// ResetGatewaysSolicitDelay resets the initial delay before gateways -// connections are initiated to its default values. -// Used by tests. -func ResetGatewaysSolicitDelay() { - atomic.StoreInt64(&gatewaySolicitDelay, int64(defaultSolicitGatewaysDelay)) -} - -const ( - gatewayCmdGossip byte = 1 - gatewayCmdAllSubsStart byte = 2 - gatewayCmdAllSubsComplete byte = 3 -) - -// GatewayInterestMode represents an account interest mode for a gateway connection -type GatewayInterestMode byte - -// GatewayInterestMode values -const ( - // optimistic is the default mode where a cluster will send - // to a gateway unless it is been told that there is no interest - // (this is for plain subscribers only). - Optimistic GatewayInterestMode = iota - // transitioning is when a gateway has to send too many - // no interest on subjects to the remote and decides that it is - // now time to move to modeInterestOnly (this is on a per account - // basis). - Transitioning - // interestOnly means that a cluster sends all it subscriptions - // interest to the gateway, which in return does not send a message - // unless it knows that there is explicit interest. - InterestOnly -) - -func (im GatewayInterestMode) String() string { - switch im { - case Optimistic: - return "Optimistic" - case InterestOnly: - return "Interest-Only" - case Transitioning: - return "Transitioning" - default: - return "Unknown" - } -} - -var gwDoNotForceInterestOnlyMode bool - -// GatewayDoNotForceInterestOnlyMode is used ONLY in tests. -// DO NOT USE in normal code or if you embed the NATS Server. -func GatewayDoNotForceInterestOnlyMode(doNotForce bool) { - gwDoNotForceInterestOnlyMode = doNotForce -} - -type srvGateway struct { - totalQSubs int64 //total number of queue subs in all remote gateways (used with atomic operations) - sync.RWMutex - enabled bool // Immutable, true if both a name and port are configured - name string // Name of the Gateway on this server - out map[string]*client // outbound gateways - outo []*client // outbound gateways maintained in an order suitable for sending msgs (currently based on RTT) - in map[uint64]*client // inbound gateways - remotes map[string]*gatewayCfg // Config of remote gateways - URLs refCountedUrlSet // Set of all Gateway URLs in the cluster - URL string // This server gateway URL (after possible random port is resolved) - info *Info // Gateway Info protocol - infoJSON []byte // Marshal'ed Info protocol - runknown bool // Rejects unknown (not configured) gateway connections - replyPfx []byte // Will be "$GNR.<1:reserved>.<8:cluster hash>.<8:server hash>." - - // For backward compatibility - oldReplyPfx []byte - oldHash []byte - - // We maintain the interest of subjects and queues per account. - // For a given account, entries in the map could be something like this: - // foo.bar {n: 3} // 3 subs on foo.bar - // foo.> {n: 6} // 6 subs on foo.> - // foo bar {n: 1, q: true} // 1 qsub on foo, queue bar - // foo baz {n: 3, q: true} // 3 qsubs on foo, queue baz - pasi struct { - // Protect map since accessed from different go-routine and avoid - // possible race resulting in RS+ being sent before RS- resulting - // in incorrect interest suppression. - // Will use while sending QSubs (on GW connection accept) and when - // switching to the send-all-subs mode. - sync.Mutex - m map[string]map[string]*sitally - } - - // This is to track recent subscriptions for a given account - rsubs sync.Map - - resolver netResolver // Used to resolve host name before calling net.Dial() - sqbsz int // Max buffer size to send queue subs protocol. Used for testing. - recSubExp time.Duration // For how long do we check if there is a subscription match for a message with reply - - // These are used for routing of mapped replies. - sIDHash []byte // Server ID hash (6 bytes) - routesIDByHash sync.Map // Route's server ID is hashed (6 bytes) and stored in this map. - - // If a server has its own configuration in the "Gateways" remotes configuration - // we will keep track of the URLs that are defined in the config so they can - // be reported in monitoring. - ownCfgURLs []string -} - -// Subject interest tally. Also indicates if the key in the map is a -// queue or not. -type sitally struct { - n int32 // number of subscriptions directly matching - q bool // indicate that this is a queue -} - -type gatewayCfg struct { - sync.RWMutex - *RemoteGatewayOpts - hash []byte - oldHash []byte - urls map[string]*url.URL - connAttempts int - tlsName string - implicit bool - varzUpdateURLs bool // Tells monitoring code to update URLs when varz is inspected. -} - -// Struct for client's gateway related fields -type gateway struct { - name string - cfg *gatewayCfg - connectURL *url.URL // Needed when sending CONNECT after receiving INFO from remote - outsim *sync.Map // Per-account subject interest (or no-interest) (outbound conn) - insim map[string]*insie // Per-account subject no-interest sent or modeInterestOnly mode (inbound conn) - - // This is an outbound GW connection - outbound bool - // Set/check in readLoop without lock. This is to know that an inbound has sent the CONNECT protocol first - connected bool - // Set to true if outbound is to a server that only knows about $GR, not $GNR - useOldPrefix bool - // If true, it indicates that the inbound side will switch any account to - // interest-only mode "immediately", so the outbound should disregard - // the optimistic mode when checking for interest. - interestOnlyMode bool - // Name of the remote server - remoteName string -} - -// Outbound subject interest entry. -type outsie struct { - sync.RWMutex - // Indicate that all subs should be stored. This is - // set to true when receiving the command from the - // remote that we are about to receive all its subs. - mode GatewayInterestMode - // If not nil, used for no-interest for plain subs. - // If a subject is present in this map, it means that - // the remote is not interested in that subject. - // When we have received the command that says that - // the remote has sent all its subs, this is set to nil. - ni map[string]struct{} - // Contains queue subscriptions when in optimistic mode, - // and all subs when pk is > 0. - sl *Sublist - // Number of queue subs - qsubs int -} - -// Inbound subject interest entry. -// If `ni` is not nil, it stores the subjects for which an -// RS- was sent to the remote gateway. When a subscription -// is created, this is used to know if we need to send -// an RS+ to clear the no-interest in the remote. -// When an account is switched to modeInterestOnly (we send -// all subs of an account to the remote), then `ni` is nil and -// when all subs have been sent, mode is set to modeInterestOnly -type insie struct { - ni map[string]struct{} // Record if RS- was sent for given subject - mode GatewayInterestMode -} - -type gwReplyMap struct { - ms string - exp int64 -} - -type gwReplyMapping struct { - // Indicate if we should check the map or not. Since checking the map is done - // when processing inbound messages and requires the lock we want to - // check only when needed. This is set/get using atomic, so needs to - // be memory aligned. - check int32 - // To keep track of gateway replies mapping - mapping map[string]*gwReplyMap -} - -// Returns the corresponding gw routed subject, and `true` to indicate that a -// mapping was found. If no entry is found, the passed subject is returned -// as-is and `false` is returned to indicate that no mapping was found. -// Caller is responsible to ensure the locking. -func (g *gwReplyMapping) get(subject []byte) ([]byte, bool) { - rm, ok := g.mapping[string(subject)] - if !ok { - return subject, false - } - subj := []byte(rm.ms) - return subj, true -} - -// clone returns a deep copy of the RemoteGatewayOpts object -func (r *RemoteGatewayOpts) clone() *RemoteGatewayOpts { - if r == nil { - return nil - } - clone := &RemoteGatewayOpts{ - Name: r.Name, - URLs: deepCopyURLs(r.URLs), - } - if r.TLSConfig != nil { - clone.TLSConfig = r.TLSConfig.Clone() - clone.TLSTimeout = r.TLSTimeout - } - return clone -} - -// Ensure that gateway is properly configured. -func validateGatewayOptions(o *Options) error { - if o.Gateway.Name == _EMPTY_ && o.Gateway.Port == 0 { - return nil - } - if o.Gateway.Name == _EMPTY_ { - return errors.New("gateway has no name") - } - if strings.Contains(o.Gateway.Name, " ") { - return ErrGatewayNameHasSpaces - } - if o.Gateway.Port == 0 { - return fmt.Errorf("gateway %q has no port specified (select -1 for random port)", o.Gateway.Name) - } - for i, g := range o.Gateway.Gateways { - if g.Name == _EMPTY_ { - return fmt.Errorf("gateway in the list %d has no name", i) - } - if len(g.URLs) == 0 { - return fmt.Errorf("gateway %q has no URL", g.Name) - } - } - if err := validatePinnedCerts(o.Gateway.TLSPinnedCerts); err != nil { - return fmt.Errorf("gateway %q: %v", o.Gateway.Name, err) - } - return nil -} - -// Computes a hash of 6 characters for the name. -// This will be used for routing of replies. -func getGWHash(name string) []byte { - return []byte(getHashSize(name, gwHashLen)) -} - -func getOldHash(name string) []byte { - sha := sha256.New() - sha.Write([]byte(name)) - fullHash := []byte(fmt.Sprintf("%x", sha.Sum(nil))) - return fullHash[:4] -} - -// Initialize the s.gateway structure. We do this even if the server -// does not have a gateway configured. In some part of the code, the -// server will check the number of outbound gateways, etc.. and so -// we don't have to check if s.gateway is nil or not. -func (s *Server) newGateway(opts *Options) error { - gateway := &srvGateway{ - name: opts.Gateway.Name, - out: make(map[string]*client), - outo: make([]*client, 0, 4), - in: make(map[uint64]*client), - remotes: make(map[string]*gatewayCfg), - URLs: make(refCountedUrlSet), - resolver: opts.Gateway.resolver, - runknown: opts.Gateway.RejectUnknown, - oldHash: getOldHash(opts.Gateway.Name), - } - gateway.Lock() - defer gateway.Unlock() - - gateway.sIDHash = getGWHash(s.info.ID) - clusterHash := getGWHash(opts.Gateway.Name) - prefix := make([]byte, 0, gwSubjectOffset) - prefix = append(prefix, gwReplyPrefix...) - prefix = append(prefix, clusterHash...) - prefix = append(prefix, '.') - prefix = append(prefix, gateway.sIDHash...) - prefix = append(prefix, '.') - gateway.replyPfx = prefix - - prefix = make([]byte, 0, oldGWReplyStart) - prefix = append(prefix, oldGWReplyPrefix...) - prefix = append(prefix, gateway.oldHash...) - prefix = append(prefix, '.') - gateway.oldReplyPfx = prefix - - gateway.pasi.m = make(map[string]map[string]*sitally) - - if gateway.resolver == nil { - gateway.resolver = netResolver(net.DefaultResolver) - } - - // Create remote gateways - for _, rgo := range opts.Gateway.Gateways { - // Ignore if there is a remote gateway with our name. - if rgo.Name == gateway.name { - gateway.ownCfgURLs = getURLsAsString(rgo.URLs) - continue - } - cfg := &gatewayCfg{ - RemoteGatewayOpts: rgo.clone(), - hash: getGWHash(rgo.Name), - oldHash: getOldHash(rgo.Name), - urls: make(map[string]*url.URL, len(rgo.URLs)), - } - if opts.Gateway.TLSConfig != nil && cfg.TLSConfig == nil { - cfg.TLSConfig = opts.Gateway.TLSConfig.Clone() - } - if cfg.TLSTimeout == 0 { - cfg.TLSTimeout = opts.Gateway.TLSTimeout - } - for _, u := range rgo.URLs { - // For TLS, look for a hostname that we can use for TLSConfig.ServerName - cfg.saveTLSHostname(u) - cfg.urls[u.Host] = u - } - gateway.remotes[cfg.Name] = cfg - } - - gateway.sqbsz = opts.Gateway.sendQSubsBufSize - if gateway.sqbsz == 0 { - gateway.sqbsz = maxBufSize - } - gateway.recSubExp = defaultGatewayRecentSubExpiration - - gateway.enabled = opts.Gateway.Name != "" && opts.Gateway.Port != 0 - s.gateway = gateway - return nil -} - -// Update remote gateways TLS configurations after a config reload. -func (g *srvGateway) updateRemotesTLSConfig(opts *Options) { - g.Lock() - defer g.Unlock() - - for _, ro := range opts.Gateway.Gateways { - if ro.Name == g.name { - continue - } - if cfg, ok := g.remotes[ro.Name]; ok { - cfg.Lock() - // If TLS config is in remote, use that one, otherwise, - // use the TLS config from the main block. - if ro.TLSConfig != nil { - cfg.TLSConfig = ro.TLSConfig.Clone() - } else if opts.Gateway.TLSConfig != nil { - cfg.TLSConfig = opts.Gateway.TLSConfig.Clone() - } - - // Ensure that OCSP callbacks are always setup after a reload if needed. - mustStaple := opts.OCSPConfig != nil && opts.OCSPConfig.Mode == OCSPModeAlways - if mustStaple && opts.Gateway.TLSConfig != nil { - clientCB := opts.Gateway.TLSConfig.GetClientCertificate - verifyCB := opts.Gateway.TLSConfig.VerifyConnection - if mustStaple && cfg.TLSConfig != nil { - if clientCB != nil && cfg.TLSConfig.GetClientCertificate == nil { - cfg.TLSConfig.GetClientCertificate = clientCB - } - if verifyCB != nil && cfg.TLSConfig.VerifyConnection == nil { - cfg.TLSConfig.VerifyConnection = verifyCB - } - } - } - - cfg.Unlock() - } - } -} - -// Returns if this server rejects connections from gateways that are not -// explicitly configured. -func (g *srvGateway) rejectUnknown() bool { - g.RLock() - reject := g.runknown - g.RUnlock() - return reject -} - -// Starts the gateways accept loop and solicit explicit gateways -// after an initial delay. This delay is meant to give a chance to -// the cluster to form and this server gathers gateway URLs for this -// cluster in order to send that as part of the connect/info process. -func (s *Server) startGateways() { - s.startGatewayAcceptLoop() - - // Delay start of creation of gateways to give a chance - // to the local cluster to form. - s.startGoRoutine(func() { - defer s.grWG.Done() - - dur := s.getOpts().gatewaysSolicitDelay - if dur == 0 { - dur = time.Duration(atomic.LoadInt64(&gatewaySolicitDelay)) - } - - select { - case <-time.After(dur): - s.solicitGateways() - case <-s.quitCh: - return - } - }) -} - -// This starts the gateway accept loop in a go routine, unless it -// is detected that the server has already been shutdown. -func (s *Server) startGatewayAcceptLoop() { - if s.isShuttingDown() { - return - } - - // Snapshot server options. - opts := s.getOpts() - - port := opts.Gateway.Port - if port == -1 { - port = 0 - } - - s.mu.Lock() - hp := net.JoinHostPort(opts.Gateway.Host, strconv.Itoa(port)) - l, e := natsListen("tcp", hp) - s.gatewayListenerErr = e - if e != nil { - s.mu.Unlock() - s.Fatalf("Error listening on gateway port: %d - %v", opts.Gateway.Port, e) - return - } - s.Noticef("Gateway name is %s", s.getGatewayName()) - s.Noticef("Listening for gateways connections on %s", - net.JoinHostPort(opts.Gateway.Host, strconv.Itoa(l.Addr().(*net.TCPAddr).Port))) - - tlsReq := opts.Gateway.TLSConfig != nil - authRequired := opts.Gateway.Username != "" - info := &Info{ - ID: s.info.ID, - Name: opts.ServerName, - Version: s.info.Version, - AuthRequired: authRequired, - TLSRequired: tlsReq, - TLSVerify: tlsReq, - MaxPayload: s.info.MaxPayload, - Gateway: opts.Gateway.Name, - GatewayNRP: true, - Headers: s.supportsHeaders(), - Proto: s.getServerProto(), - } - // Unless in some tests we want to keep the old behavior, we are now - // (since v2.9.0) indicate that this server will switch all accounts - // to InterestOnly mode when accepting an inbound or when a new - // account is fetched. - if !gwDoNotForceInterestOnlyMode { - info.GatewayIOM = true - } - - // If we have selected a random port... - if port == 0 { - // Write resolved port back to options. - opts.Gateway.Port = l.Addr().(*net.TCPAddr).Port - } - // Possibly override Host/Port based on Gateway.Advertise - if err := s.setGatewayInfoHostPort(info, opts); err != nil { - s.Fatalf("Error setting gateway INFO with Gateway.Advertise value of %s, err=%v", opts.Gateway.Advertise, err) - l.Close() - s.mu.Unlock() - return - } - // Setup state that can enable shutdown - s.gatewayListener = l - - // Warn if insecure is configured in the main Gateway configuration - // or any of the RemoteGateway's. This means that we need to check - // remotes even if TLS would not be configured for the accept. - warn := tlsReq && opts.Gateway.TLSConfig.InsecureSkipVerify - if !warn { - for _, g := range opts.Gateway.Gateways { - if g.TLSConfig != nil && g.TLSConfig.InsecureSkipVerify { - warn = true - break - } - } - } - if warn { - s.Warnf(gatewayTLSInsecureWarning) - } - go s.acceptConnections(l, "Gateway", func(conn net.Conn) { s.createGateway(nil, nil, conn) }, nil) - s.mu.Unlock() -} - -// Similar to setInfoHostPortAndGenerateJSON, but for gatewayInfo. -func (s *Server) setGatewayInfoHostPort(info *Info, o *Options) error { - gw := s.gateway - gw.Lock() - defer gw.Unlock() - gw.URLs.removeUrl(gw.URL) - if o.Gateway.Advertise != "" { - advHost, advPort, err := parseHostPort(o.Gateway.Advertise, o.Gateway.Port) - if err != nil { - return err - } - info.Host = advHost - info.Port = advPort - } else { - info.Host = o.Gateway.Host - info.Port = o.Gateway.Port - // If the host is "0.0.0.0" or "::" we need to resolve to a public IP. - // This will return at most 1 IP. - hostIsIPAny, ips, err := s.getNonLocalIPsIfHostIsIPAny(info.Host, false) - if err != nil { - return err - } - if hostIsIPAny { - if len(ips) == 0 { - // TODO(ik): Should we fail here (prevent starting)? If not, we - // are going to "advertise" the 0.0.0.0: url, which means - // that remote are going to try to connect to 0.0.0.0:, - // which means a connect to loopback address, which is going - // to fail with either TLS error, conn refused if the remote - // is using different gateway port than this one, or error - // saying that it tried to connect to itself. - s.Errorf("Could not find any non-local IP for gateway %q with listen specification %q", - gw.name, info.Host) - } else { - // Take the first from the list... - info.Host = ips[0] - } - } - } - gw.URL = net.JoinHostPort(info.Host, strconv.Itoa(info.Port)) - if o.Gateway.Advertise != "" { - s.Noticef("Advertise address for gateway %q is set to %s", gw.name, gw.URL) - } else { - s.Noticef("Address for gateway %q is %s", gw.name, gw.URL) - } - gw.URLs[gw.URL]++ - gw.info = info - info.GatewayURL = gw.URL - // (re)generate the gatewayInfoJSON byte array - gw.generateInfoJSON() - return nil -} - -// Generates the Gateway INFO protocol. -// The gateway lock is held on entry -func (g *srvGateway) generateInfoJSON() { - // We could be here when processing a route INFO that has a gateway URL, - // but this server is not configured for gateways, so simply ignore here. - // The configuration mismatch is reported somewhere else. - if !g.enabled || g.info == nil { - return - } - g.info.GatewayURLs = g.URLs.getAsStringSlice() - b, err := json.Marshal(g.info) - if err != nil { - panic(err) - } - g.infoJSON = []byte(fmt.Sprintf(InfoProto, b)) -} - -// Goes through the list of registered gateways and try to connect to those. -// The list (remotes) is initially containing the explicit remote gateways, -// but the list is augmented with any implicit (discovered) gateway. Therefore, -// this function only solicit explicit ones. -func (s *Server) solicitGateways() { - gw := s.gateway - gw.RLock() - defer gw.RUnlock() - for _, cfg := range gw.remotes { - // Since we delay the creation of gateways, it is - // possible that server starts to receive inbound from - // other clusters and in turn create outbounds. So here - // we create only the ones that are configured. - if !cfg.isImplicit() { - cfg := cfg // Create new instance for the goroutine. - s.startGoRoutine(func() { - s.solicitGateway(cfg, true) - s.grWG.Done() - }) - } - } -} - -// Reconnect to the gateway after a little wait period. For explicit -// gateways, we also wait for the default reconnect time. -func (s *Server) reconnectGateway(cfg *gatewayCfg) { - defer s.grWG.Done() - - delay := time.Duration(rand.Intn(100)) * time.Millisecond - if !cfg.isImplicit() { - delay += gatewayReconnectDelay - } - select { - case <-time.After(delay): - case <-s.quitCh: - return - } - s.solicitGateway(cfg, false) -} - -// This function will loop trying to connect to any URL attached -// to the given Gateway. It will return once a connection has been created. -func (s *Server) solicitGateway(cfg *gatewayCfg, firstConnect bool) { - var ( - opts = s.getOpts() - isImplicit = cfg.isImplicit() - attempts int - typeStr string - ) - if isImplicit { - typeStr = "implicit" - } else { - typeStr = "explicit" - } - - const connFmt = "Connecting to %s gateway %q (%s) at %s (attempt %v)" - const connErrFmt = "Error connecting to %s gateway %q (%s) at %s (attempt %v): %v" - - for s.isRunning() { - urls := cfg.getURLs() - if len(urls) == 0 { - break - } - attempts++ - report := s.shouldReportConnectErr(firstConnect, attempts) - // Iteration is random - for _, u := range urls { - address, err := s.getRandomIP(s.gateway.resolver, u.Host, nil) - if err != nil { - s.Errorf("Error getting IP for %s gateway %q (%s): %v", typeStr, cfg.Name, u.Host, err) - continue - } - if report { - s.Noticef(connFmt, typeStr, cfg.Name, u.Host, address, attempts) - } else { - s.Debugf(connFmt, typeStr, cfg.Name, u.Host, address, attempts) - } - conn, err := natsDialTimeout("tcp", address, DEFAULT_ROUTE_DIAL) - if err == nil { - // We could connect, create the gateway connection and return. - s.createGateway(cfg, u, conn) - return - } - if report { - s.Errorf(connErrFmt, typeStr, cfg.Name, u.Host, address, attempts, err) - } else { - s.Debugf(connErrFmt, typeStr, cfg.Name, u.Host, address, attempts, err) - } - // Break this loop if server is being shutdown... - if !s.isRunning() { - break - } - } - if isImplicit { - if opts.Gateway.ConnectRetries == 0 || attempts > opts.Gateway.ConnectRetries { - s.gateway.Lock() - // We could have just accepted an inbound for this remote gateway. - // So if there is an inbound, let's try again to connect. - if s.gateway.hasInbound(cfg.Name) { - s.gateway.Unlock() - continue - } - delete(s.gateway.remotes, cfg.Name) - s.gateway.Unlock() - return - } - } - select { - case <-s.quitCh: - return - case <-time.After(gatewayConnectDelay): - continue - } - } -} - -// Returns true if there is an inbound for the given `name`. -// Lock held on entry. -func (g *srvGateway) hasInbound(name string) bool { - for _, ig := range g.in { - ig.mu.Lock() - igname := ig.gw.name - ig.mu.Unlock() - if igname == name { - return true - } - } - return false -} - -// Called when a gateway connection is either accepted or solicited. -// If accepted, the gateway is marked as inbound. -// If solicited, the gateway is marked as outbound. -func (s *Server) createGateway(cfg *gatewayCfg, url *url.URL, conn net.Conn) { - // Snapshot server options. - opts := s.getOpts() - - now := time.Now() - c := &client{srv: s, nc: conn, start: now, last: now, kind: GATEWAY} - - // Are we creating the gateway based on the configuration - solicit := cfg != nil - var tlsRequired bool - - s.gateway.RLock() - infoJSON := s.gateway.infoJSON - s.gateway.RUnlock() - - // Perform some initialization under the client lock - c.mu.Lock() - c.initClient() - c.gw = &gateway{} - if solicit { - // This is an outbound gateway connection - cfg.RLock() - tlsRequired = cfg.TLSConfig != nil - cfgName := cfg.Name - cfg.RUnlock() - c.gw.outbound = true - c.gw.name = cfgName - c.gw.cfg = cfg - cfg.bumpConnAttempts() - // Since we are delaying the connect until after receiving - // the remote's INFO protocol, save the URL we need to connect to. - c.gw.connectURL = url - - c.Noticef("Creating outbound gateway connection to %q", cfgName) - } else { - c.flags.set(expectConnect) - // Inbound gateway connection - c.Noticef("Processing inbound gateway connection") - // Check if TLS is required for inbound GW connections. - tlsRequired = opts.Gateway.TLSConfig != nil - // We expect a CONNECT from the accepted connection. - c.setAuthTimer(secondsToDuration(opts.Gateway.AuthTimeout)) - } - - // Check for TLS - if tlsRequired { - var tlsConfig *tls.Config - var tlsName string - var timeout float64 - - if solicit { - var ( - mustStaple = opts.OCSPConfig != nil && opts.OCSPConfig.Mode == OCSPModeAlways - clientCB func(*tls.CertificateRequestInfo) (*tls.Certificate, error) - verifyCB func(tls.ConnectionState) error - ) - // Snapshot callbacks for OCSP outside an ongoing reload which might be happening. - if mustStaple { - s.reloadMu.RLock() - s.optsMu.RLock() - clientCB = s.opts.Gateway.TLSConfig.GetClientCertificate - verifyCB = s.opts.Gateway.TLSConfig.VerifyConnection - s.optsMu.RUnlock() - s.reloadMu.RUnlock() - } - - cfg.RLock() - tlsName = cfg.tlsName - tlsConfig = cfg.TLSConfig.Clone() - timeout = cfg.TLSTimeout - - // Ensure that OCSP callbacks are always setup on gateway reconnect when OCSP policy is set to always. - if mustStaple { - if clientCB != nil && tlsConfig.GetClientCertificate == nil { - tlsConfig.GetClientCertificate = clientCB - } - if verifyCB != nil && tlsConfig.VerifyConnection == nil { - tlsConfig.VerifyConnection = verifyCB - } - } - cfg.RUnlock() - } else { - tlsConfig = opts.Gateway.TLSConfig - timeout = opts.Gateway.TLSTimeout - } - - // Perform (either server or client side) TLS handshake. - if resetTLSName, err := c.doTLSHandshake("gateway", solicit, url, tlsConfig, tlsName, timeout, opts.Gateway.TLSPinnedCerts); err != nil { - if resetTLSName { - cfg.Lock() - cfg.tlsName = _EMPTY_ - cfg.Unlock() - } - c.mu.Unlock() - return - } - } - - // Do final client initialization - c.in.pacache = make(map[string]*perAccountCache) - if solicit { - // This is an outbound gateway connection - c.gw.outsim = &sync.Map{} - } else { - // Inbound gateway connection - c.gw.insim = make(map[string]*insie) - } - - // Register in temp map for now until gateway properly registered - // in out or in gateways. - if !s.addToTempClients(c.cid, c) { - c.mu.Unlock() - c.closeConnection(ServerShutdown) - return - } - - // Only send if we accept a connection. Will send CONNECT+INFO as an - // outbound only after processing peer's INFO protocol. - if !solicit { - c.enqueueProto(infoJSON) - } - - // Spin up the read loop. - s.startGoRoutine(func() { c.readLoop(nil) }) - - // Spin up the write loop. - s.startGoRoutine(func() { c.writeLoop() }) - - if tlsRequired { - c.Debugf("TLS handshake complete") - cs := c.nc.(*tls.Conn).ConnectionState() - c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tlsCipher(cs.CipherSuite)) - } - - // For outbound, we can't set the normal ping timer yet since the other - // side would fail with a parse error should it receive anything but the - // CONNECT protocol as the first protocol. We still want to make sure - // that the connection is not stale until the first INFO from the remote - // is received. - if solicit { - c.watchForStaleConnection(adjustPingInterval(GATEWAY, opts.PingInterval), opts.MaxPingsOut) - } - - c.mu.Unlock() - - // Announce ourselves again to new connections. - if solicit && s.EventsEnabled() { - s.sendStatszUpdate() - } -} - -// Builds and sends the CONNECT protocol for a gateway. -// Client lock held on entry. -func (c *client) sendGatewayConnect(opts *Options) { - // FIXME: This can race with updateRemotesTLSConfig - tlsRequired := c.gw.cfg.TLSConfig != nil - url := c.gw.connectURL - c.gw.connectURL = nil - var user, pass string - if userInfo := url.User; userInfo != nil { - user = userInfo.Username() - pass, _ = userInfo.Password() - } else if opts != nil { - user = opts.Gateway.Username - pass = opts.Gateway.Password - } - cinfo := connectInfo{ - Verbose: false, - Pedantic: false, - User: user, - Pass: pass, - TLS: tlsRequired, - Name: c.srv.info.ID, - Gateway: c.srv.gateway.name, - } - b, err := json.Marshal(cinfo) - if err != nil { - panic(err) - } - c.enqueueProto([]byte(fmt.Sprintf(ConProto, b))) -} - -// Process the CONNECT protocol from a gateway connection. -// Returns an error to the connection if the CONNECT is not from a gateway -// (for instance a client or route connecting to the gateway port), or -// if the destination does not match the gateway name of this server. -// -// -func (c *client) processGatewayConnect(arg []byte) error { - connect := &connectInfo{} - if err := json.Unmarshal(arg, connect); err != nil { - return err - } - - // Coming from a client or a route, reject - if connect.Gateway == "" { - c.sendErrAndErr(ErrClientOrRouteConnectedToGatewayPort.Error()) - c.closeConnection(WrongPort) - return ErrClientOrRouteConnectedToGatewayPort - } - - c.mu.Lock() - s := c.srv - c.mu.Unlock() - - // If we reject unknown gateways, make sure we have it configured, - // otherwise return an error. - if s.gateway.rejectUnknown() && s.getRemoteGateway(connect.Gateway) == nil { - c.Errorf("Rejecting connection from gateway %q", connect.Gateway) - c.sendErr(fmt.Sprintf("Connection to gateway %q rejected", s.getGatewayName())) - c.closeConnection(WrongGateway) - return ErrWrongGateway - } - - c.mu.Lock() - c.gw.connected = true - // Set the Ping timer after sending connect and info. - c.setFirstPingTimer() - c.mu.Unlock() - - return nil -} - -// Process the INFO protocol from a gateway connection. -// -// If the gateway connection is an outbound (this server initiated the connection), -// this function checks that the incoming INFO contains the Gateway field. If empty, -// it means that this is a response from an older server or that this server connected -// to the wrong port. -// The outbound gateway may also receive a gossip INFO protocol from the remote gateway, -// indicating other gateways that the remote knows about. This server will try to connect -// to those gateways (if not explicitly configured or already implicitly connected). -// In both cases (explicit or implicit), the local cluster is notified about the existence -// of this new gateway. This allows servers in the cluster to ensure that they have an -// outbound connection to this gateway. -// -// For an inbound gateway, the gateway is simply registered and the info protocol -// is saved to be used after processing the CONNECT. -// -// -func (c *client) processGatewayInfo(info *Info) { - var ( - gwName string - cfg *gatewayCfg - ) - c.mu.Lock() - s := c.srv - cid := c.cid - - // Check if this is the first INFO. (this call sets the flag if not already set). - isFirstINFO := c.flags.setIfNotSet(infoReceived) - - isOutbound := c.gw.outbound - if isOutbound { - gwName = c.gw.name - cfg = c.gw.cfg - } else if isFirstINFO { - c.gw.name = info.Gateway - } - if isFirstINFO { - c.opts.Name = info.ID - // Get the protocol version from the INFO protocol. This will be checked - // to see if this connection supports message tracing for instance. - c.opts.Protocol = info.Proto - c.gw.remoteName = info.Name - } - c.mu.Unlock() - - // For an outbound connection... - if isOutbound { - // Check content of INFO for fields indicating that it comes from a gateway. - // If we incorrectly connect to the wrong port (client or route), we won't - // have the Gateway field set. - if info.Gateway == "" { - c.sendErrAndErr(fmt.Sprintf("Attempt to connect to gateway %q using wrong port", gwName)) - c.closeConnection(WrongPort) - return - } - // Check that the gateway name we got is what we expect - if info.Gateway != gwName { - // Unless this is the very first INFO, it may be ok if this is - // a gossip request to connect to other gateways. - if !isFirstINFO && info.GatewayCmd == gatewayCmdGossip { - // If we are configured to reject unknown, do not attempt to - // connect to one that we don't have configured. - if s.gateway.rejectUnknown() && s.getRemoteGateway(info.Gateway) == nil { - return - } - s.processImplicitGateway(info) - return - } - // Otherwise, this is a failure... - // We are reporting this error in the log... - c.Errorf("Failing connection to gateway %q, remote gateway name is %q", - gwName, info.Gateway) - // ...and sending this back to the remote so that the error - // makes more sense in the remote server's log. - c.sendErr(fmt.Sprintf("Connection from %q rejected, wanted to connect to %q, this is %q", - s.getGatewayName(), gwName, info.Gateway)) - c.closeConnection(WrongGateway) - return - } - - // Check for duplicate server name with servers in our cluster - if s.isDuplicateServerName(info.Name) { - c.Errorf("Remote server has a duplicate name: %q", info.Name) - c.closeConnection(DuplicateServerName) - return - } - - // Possibly add URLs that we get from the INFO protocol. - if len(info.GatewayURLs) > 0 { - cfg.updateURLs(info.GatewayURLs) - } - - // If this is the first INFO, send our connect - if isFirstINFO { - s.gateway.RLock() - infoJSON := s.gateway.infoJSON - s.gateway.RUnlock() - - supportsHeaders := s.supportsHeaders() - opts := s.getOpts() - - // Note, if we want to support NKeys, then we would get the nonce - // from this INFO protocol and can sign it in the CONNECT we are - // going to send now. - c.mu.Lock() - c.gw.interestOnlyMode = info.GatewayIOM - c.sendGatewayConnect(opts) - c.Debugf("Gateway connect protocol sent to %q", gwName) - // Send INFO too - c.enqueueProto(infoJSON) - c.gw.useOldPrefix = !info.GatewayNRP - c.headers = supportsHeaders && info.Headers - c.mu.Unlock() - - // Register as an outbound gateway.. if we had a protocol to ack our connect, - // then we should do that when process that ack. - if s.registerOutboundGatewayConnection(gwName, c) { - c.Noticef("Outbound gateway connection to %q (%s) registered", gwName, info.ID) - // Now that the outbound gateway is registered, we can remove from temp map. - s.removeFromTempClients(cid) - // Set the Ping timer after sending connect and info. - c.mu.Lock() - c.setFirstPingTimer() - c.mu.Unlock() - } else { - // There was a bug that would cause a connection to possibly - // be called twice resulting in reconnection of twice the - // same outbound connection. The issue is fixed, but adding - // defensive code above that if we did not register this connection - // because we already have an outbound for this name, then - // close this connection (and make sure it does not try to reconnect) - c.mu.Lock() - c.flags.set(noReconnect) - c.mu.Unlock() - c.closeConnection(WrongGateway) - return - } - } else if info.GatewayCmd > 0 { - switch info.GatewayCmd { - case gatewayCmdAllSubsStart: - c.gatewayAllSubsReceiveStart(info) - return - case gatewayCmdAllSubsComplete: - c.gatewayAllSubsReceiveComplete(info) - return - default: - s.Warnf("Received unknown command %v from gateway %q", info.GatewayCmd, gwName) - return - } - } - - // Flood local cluster with information about this gateway. - // Servers in this cluster will ensure that they have (or otherwise create) - // an outbound connection to this gateway. - s.forwardNewGatewayToLocalCluster(info) - - } else if isFirstINFO { - // This is the first INFO of an inbound connection... - - // Check for duplicate server name with servers in our cluster - if s.isDuplicateServerName(info.Name) { - c.Errorf("Remote server has a duplicate name: %q", info.Name) - c.closeConnection(DuplicateServerName) - return - } - - s.registerInboundGatewayConnection(cid, c) - c.Noticef("Inbound gateway connection from %q (%s) registered", info.Gateway, info.ID) - - // Now that it is registered, we can remove from temp map. - s.removeFromTempClients(cid) - - // Send our QSubs. - s.sendQueueSubsToGateway(c) - - // Initiate outbound connection. This function will behave correctly if - // we have already one. - s.processImplicitGateway(info) - - // Send back to the server that initiated this gateway connection the - // list of all remote gateways known on this server. - s.gossipGatewaysToInboundGateway(info.Gateway, c) - - // Now make sure if we have any knowledge of connected leafnodes that we resend the - // connect events to switch those accounts into interest only mode. - s.mu.Lock() - s.ensureGWsInterestOnlyForLeafNodes() - s.mu.Unlock() - js := s.js.Load() - - // If running in some tests, maintain the original behavior. - if gwDoNotForceInterestOnlyMode && js != nil { - // Switch JetStream accounts to interest-only mode. - var accounts []string - js.mu.Lock() - if len(js.accounts) > 0 { - accounts = make([]string, 0, len(js.accounts)) - for accName := range js.accounts { - accounts = append(accounts, accName) - } - } - js.mu.Unlock() - for _, accName := range accounts { - if acc, err := s.LookupAccount(accName); err == nil && acc != nil { - if acc.JetStreamEnabled() { - s.switchAccountToInterestMode(acc.GetName()) - } - } - } - } else if !gwDoNotForceInterestOnlyMode { - // Starting 2.9.0, we are phasing out the optimistic mode, so change - // all accounts to interest-only mode, unless instructed not to do so - // in some tests. - s.accounts.Range(func(_, v any) bool { - acc := v.(*Account) - s.switchAccountToInterestMode(acc.GetName()) - return true - }) - } - } -} - -// Sends to the given inbound gateway connection a gossip INFO protocol -// for each gateway known by this server. This allows for a "full mesh" -// of gateways. -func (s *Server) gossipGatewaysToInboundGateway(gwName string, c *client) { - gw := s.gateway - gw.RLock() - defer gw.RUnlock() - for gwCfgName, cfg := range gw.remotes { - // Skip the gateway that we just created - if gwCfgName == gwName { - continue - } - info := Info{ - ID: s.info.ID, - GatewayCmd: gatewayCmdGossip, - } - urls := cfg.getURLsAsStrings() - if len(urls) > 0 { - info.Gateway = gwCfgName - info.GatewayURLs = urls - b, _ := json.Marshal(&info) - c.mu.Lock() - c.enqueueProto([]byte(fmt.Sprintf(InfoProto, b))) - c.mu.Unlock() - } - } -} - -// Sends the INFO protocol of a gateway to all routes known by this server. -func (s *Server) forwardNewGatewayToLocalCluster(oinfo *Info) { - // Need to protect s.routes here, so use server's lock - s.mu.Lock() - defer s.mu.Unlock() - - // We don't really need the ID to be set, but, we need to make sure - // that it is not set to the server ID so that if we were to connect - // to an older server that does not expect a "gateway" INFO, it - // would think that it needs to create an implicit route (since info.ID - // would not match the route's remoteID), but will fail to do so because - // the sent protocol will not have host/port defined. - info := &Info{ - ID: "GW" + s.info.ID, - Name: s.getOpts().ServerName, - Gateway: oinfo.Gateway, - GatewayURLs: oinfo.GatewayURLs, - GatewayCmd: gatewayCmdGossip, - } - b, _ := json.Marshal(info) - infoJSON := []byte(fmt.Sprintf(InfoProto, b)) - - s.forEachRemote(func(r *client) { - r.mu.Lock() - r.enqueueProto(infoJSON) - r.mu.Unlock() - }) -} - -// Sends queue subscriptions interest to remote gateway. -// This is sent from the inbound side, that is, the side that receives -// messages from the remote's outbound connection. This side is -// the one sending the subscription interest. -func (s *Server) sendQueueSubsToGateway(c *client) { - s.sendSubsToGateway(c, _EMPTY_) -} - -// Sends all subscriptions for the given account to the remove gateway -// This is sent from the inbound side, that is, the side that receives -// messages from the remote's outbound connection. This side is -// the one sending the subscription interest. -func (s *Server) sendAccountSubsToGateway(c *client, accName string) { - s.sendSubsToGateway(c, accName) -} - -func gwBuildSubProto(buf *bytes.Buffer, accName string, acc map[string]*sitally, doQueues bool) { - for saq, si := range acc { - if doQueues && si.q || !doQueues && !si.q { - buf.Write(rSubBytes) - buf.WriteString(accName) - buf.WriteByte(' ') - // For queue subs (si.q is true), saq will be - // subject + ' ' + queue, for plain subs, this is - // just the subject. - buf.WriteString(saq) - if doQueues { - buf.WriteString(" 1") - } - buf.WriteString(CR_LF) - } - } -} - -// Sends subscriptions to remote gateway. -func (s *Server) sendSubsToGateway(c *client, accountName string) { - var ( - bufa = [32 * 1024]byte{} - bbuf = bytes.NewBuffer(bufa[:0]) - ) - - gw := s.gateway - - // This needs to run under this lock for the whole duration - gw.pasi.Lock() - defer gw.pasi.Unlock() - - // If account is specified... - if accountName != _EMPTY_ { - // Simply send all plain subs (no queues) for this specific account - gwBuildSubProto(bbuf, accountName, gw.pasi.m[accountName], false) - // Instruct to send all subs (RS+/-) for this account from now on. - c.mu.Lock() - e := c.gw.insim[accountName] - if e == nil { - e = &insie{} - c.gw.insim[accountName] = e - } - e.mode = InterestOnly - c.mu.Unlock() - } else { - // Send queues for all accounts - for accName, acc := range gw.pasi.m { - gwBuildSubProto(bbuf, accName, acc, true) - } - } - - buf := bbuf.Bytes() - - // Nothing to send. - if len(buf) == 0 { - return - } - if len(buf) > cap(bufa) { - s.Debugf("Sending subscriptions to %q, buffer size: %v", c.gw.name, len(buf)) - } - // Send - c.mu.Lock() - c.enqueueProto(buf) - c.Debugf("Sent queue subscriptions to gateway") - c.mu.Unlock() -} - -// This is invoked when getting an INFO protocol for gateway on the ROUTER port. -// This function will then execute appropriate function based on the command -// contained in the protocol. -// -func (s *Server) processGatewayInfoFromRoute(info *Info, routeSrvID string) { - switch info.GatewayCmd { - case gatewayCmdGossip: - s.processImplicitGateway(info) - default: - s.Errorf("Unknown command %d from server %v", info.GatewayCmd, routeSrvID) - } -} - -// Sends INFO protocols to the given route connection for each known Gateway. -// These will be processed by the route and delegated to the gateway code to -// invoke processImplicitGateway. -func (s *Server) sendGatewayConfigsToRoute(route *client) { - gw := s.gateway - gw.RLock() - // Send only to gateways for which we have actual outbound connection to. - if len(gw.out) == 0 { - gw.RUnlock() - return - } - // Collect gateway configs for which we have an outbound connection. - gwCfgsa := [16]*gatewayCfg{} - gwCfgs := gwCfgsa[:0] - for _, c := range gw.out { - c.mu.Lock() - if c.gw.cfg != nil { - gwCfgs = append(gwCfgs, c.gw.cfg) - } - c.mu.Unlock() - } - gw.RUnlock() - if len(gwCfgs) == 0 { - return - } - - // Check forwardNewGatewayToLocalCluster() as to why we set ID this way. - info := Info{ - ID: "GW" + s.info.ID, - GatewayCmd: gatewayCmdGossip, - } - for _, cfg := range gwCfgs { - urls := cfg.getURLsAsStrings() - if len(urls) > 0 { - info.Gateway = cfg.Name - info.GatewayURLs = urls - b, _ := json.Marshal(&info) - route.mu.Lock() - route.enqueueProto([]byte(fmt.Sprintf(InfoProto, b))) - route.mu.Unlock() - } - } -} - -// Initiates a gateway connection using the info contained in the INFO protocol. -// If a gateway with the same name is already registered (either because explicitly -// configured, or already implicitly connected), this function will augmment the -// remote URLs with URLs present in the info protocol and return. -// Otherwise, this function will register this remote (to prevent multiple connections -// to the same remote) and call solicitGateway (which will run in a different go-routine). -func (s *Server) processImplicitGateway(info *Info) { - s.gateway.Lock() - defer s.gateway.Unlock() - // Name of the gateway to connect to is the Info.Gateway field. - gwName := info.Gateway - // If this is our name, bail. - if gwName == s.gateway.name { - return - } - // Check if we already have this config, and if so, we are done - cfg := s.gateway.remotes[gwName] - if cfg != nil { - // However, possibly augment the list of URLs with the given - // info.GatewayURLs content. - cfg.Lock() - cfg.addURLs(info.GatewayURLs) - cfg.Unlock() - return - } - opts := s.getOpts() - cfg = &gatewayCfg{ - RemoteGatewayOpts: &RemoteGatewayOpts{Name: gwName}, - hash: getGWHash(gwName), - oldHash: getOldHash(gwName), - urls: make(map[string]*url.URL, len(info.GatewayURLs)), - implicit: true, - } - if opts.Gateway.TLSConfig != nil { - cfg.TLSConfig = opts.Gateway.TLSConfig.Clone() - cfg.TLSTimeout = opts.Gateway.TLSTimeout - } - - // Since we know we don't have URLs (no config, so just based on what we - // get from INFO), directly call addURLs(). We don't need locking since - // we just created that structure and no one else has access to it yet. - cfg.addURLs(info.GatewayURLs) - // If there is no URL, we can't proceed. - if len(cfg.urls) == 0 { - return - } - s.gateway.remotes[gwName] = cfg - s.startGoRoutine(func() { - s.solicitGateway(cfg, true) - s.grWG.Done() - }) -} - -// NumOutboundGateways is public here mostly for testing. -func (s *Server) NumOutboundGateways() int { - return s.numOutboundGateways() -} - -// Returns the number of outbound gateway connections -func (s *Server) numOutboundGateways() int { - s.gateway.RLock() - n := len(s.gateway.out) - s.gateway.RUnlock() - return n -} - -// Returns the number of inbound gateway connections -func (s *Server) numInboundGateways() int { - s.gateway.RLock() - n := len(s.gateway.in) - s.gateway.RUnlock() - return n -} - -// Returns the remoteGateway (if any) that has the given `name` -func (s *Server) getRemoteGateway(name string) *gatewayCfg { - s.gateway.RLock() - cfg := s.gateway.remotes[name] - s.gateway.RUnlock() - return cfg -} - -// Used in tests -func (g *gatewayCfg) bumpConnAttempts() { - g.Lock() - g.connAttempts++ - g.Unlock() -} - -// Used in tests -func (g *gatewayCfg) getConnAttempts() int { - g.Lock() - ca := g.connAttempts - g.Unlock() - return ca -} - -// Used in tests -func (g *gatewayCfg) resetConnAttempts() { - g.Lock() - g.connAttempts = 0 - g.Unlock() -} - -// Returns if this remote gateway is implicit or not. -func (g *gatewayCfg) isImplicit() bool { - g.RLock() - ii := g.implicit - g.RUnlock() - return ii -} - -// getURLs returns an array of URLs in random order suitable for -// an iteration to try to connect. -func (g *gatewayCfg) getURLs() []*url.URL { - g.RLock() - a := make([]*url.URL, 0, len(g.urls)) - for _, u := range g.urls { - a = append(a, u) - } - g.RUnlock() - // Map iteration is random, but not that good with small maps. - rand.Shuffle(len(a), func(i, j int) { - a[i], a[j] = a[j], a[i] - }) - return a -} - -// Similar to getURLs but returns the urls as an array of strings. -func (g *gatewayCfg) getURLsAsStrings() []string { - g.RLock() - a := make([]string, 0, len(g.urls)) - for _, u := range g.urls { - a = append(a, u.Host) - } - g.RUnlock() - return a -} - -// updateURLs creates the urls map with the content of the config's URLs array -// and the given array that we get from the INFO protocol. -func (g *gatewayCfg) updateURLs(infoURLs []string) { - g.Lock() - // Clear the map... - g.urls = make(map[string]*url.URL, len(g.URLs)+len(infoURLs)) - // Add the urls from the config URLs array. - for _, u := range g.URLs { - g.urls[u.Host] = u - } - // Then add the ones from the infoURLs array we got. - g.addURLs(infoURLs) - // The call above will set varzUpdateURLs only when finding ULRs in infoURLs - // that are not present in the config. That does not cover the case where - // previously "discovered" URLs are now gone. We could check "before" size - // of g.urls and if bigger than current size, set the boolean to true. - // Not worth it... simply set this to true to allow a refresh of gateway - // URLs in varz. - g.varzUpdateURLs = true - g.Unlock() -} - -// Saves the hostname of the given URL (if not already done). -// This may be used as the ServerName of the TLSConfig when initiating a -// TLS connection. -// Write lock held on entry. -func (g *gatewayCfg) saveTLSHostname(u *url.URL) { - if g.TLSConfig != nil && g.tlsName == "" && net.ParseIP(u.Hostname()) == nil { - g.tlsName = u.Hostname() - } -} - -// add URLs from the given array to the urls map only if not already present. -// remoteGateway write lock is assumed to be held on entry. -// Write lock is held on entry. -func (g *gatewayCfg) addURLs(infoURLs []string) { - var scheme string - if g.TLSConfig != nil { - scheme = "tls" - } else { - scheme = "nats" - } - for _, iu := range infoURLs { - if _, present := g.urls[iu]; !present { - // Urls in Info.GatewayURLs come without scheme. Add it to parse - // the url (otherwise it fails). - if u, err := url.Parse(fmt.Sprintf("%s://%s", scheme, iu)); err == nil { - // Also, if a tlsName has not been set yet and we are dealing - // with a hostname and not a bare IP, save the hostname. - g.saveTLSHostname(u) - // Use u.Host for the key. - g.urls[u.Host] = u - // Signal that we have updated the list. Used by monitoring code. - g.varzUpdateURLs = true - } - } - } -} - -// Adds this URL to the set of Gateway URLs. -// Returns true if the URL has been added, false otherwise. -// Server lock held on entry -func (s *Server) addGatewayURL(urlStr string) bool { - s.gateway.Lock() - added := s.gateway.URLs.addUrl(urlStr) - if added { - s.gateway.generateInfoJSON() - } - s.gateway.Unlock() - return added -} - -// Removes this URL from the set of gateway URLs. -// Returns true if the URL has been removed, false otherwise. -// Server lock held on entry -func (s *Server) removeGatewayURL(urlStr string) bool { - if s.isShuttingDown() { - return false - } - s.gateway.Lock() - removed := s.gateway.URLs.removeUrl(urlStr) - if removed { - s.gateway.generateInfoJSON() - } - s.gateway.Unlock() - return removed -} - -// Sends a Gateway's INFO to all inbound GW connections. -// Server lock is held on entry -func (s *Server) sendAsyncGatewayInfo() { - s.gateway.RLock() - for _, ig := range s.gateway.in { - ig.mu.Lock() - ig.enqueueProto(s.gateway.infoJSON) - ig.mu.Unlock() - } - s.gateway.RUnlock() -} - -// This returns the URL of the Gateway listen spec, or empty string -// if the server has no gateway configured. -func (s *Server) getGatewayURL() string { - s.gateway.RLock() - url := s.gateway.URL - s.gateway.RUnlock() - return url -} - -// Returns this server gateway name. -// Same than calling s.gateway.getName() -func (s *Server) getGatewayName() string { - // This is immutable - return s.gateway.name -} - -// All gateway connections (outbound and inbound) are put in the given map. -func (s *Server) getAllGatewayConnections(conns map[uint64]*client) { - gw := s.gateway - gw.RLock() - for _, c := range gw.out { - c.mu.Lock() - cid := c.cid - c.mu.Unlock() - conns[cid] = c - } - for cid, c := range gw.in { - conns[cid] = c - } - gw.RUnlock() -} - -// Register the given gateway connection (*client) in the inbound gateways -// map. The key is the connection ID (like for clients and routes). -func (s *Server) registerInboundGatewayConnection(cid uint64, gwc *client) { - s.gateway.Lock() - s.gateway.in[cid] = gwc - s.gateway.Unlock() -} - -// Register the given gateway connection (*client) in the outbound gateways -// map with the given name as the key. -func (s *Server) registerOutboundGatewayConnection(name string, gwc *client) bool { - s.gateway.Lock() - if _, exist := s.gateway.out[name]; exist { - s.gateway.Unlock() - return false - } - s.gateway.out[name] = gwc - s.gateway.outo = append(s.gateway.outo, gwc) - s.gateway.orderOutboundConnectionsLocked() - s.gateway.Unlock() - return true -} - -// Returns the outbound gateway connection (*client) with the given name, -// or nil if not found -func (s *Server) getOutboundGatewayConnection(name string) *client { - s.gateway.RLock() - gwc := s.gateway.out[name] - s.gateway.RUnlock() - return gwc -} - -// Returns all outbound gateway connections in the provided array. -// The order of the gateways is suited for the sending of a message. -// Current ordering is based on individual gateway's RTT value. -func (s *Server) getOutboundGatewayConnections(a *[]*client) { - s.gateway.RLock() - for i := 0; i < len(s.gateway.outo); i++ { - *a = append(*a, s.gateway.outo[i]) - } - s.gateway.RUnlock() -} - -// Orders the array of outbound connections. -// Current ordering is by lowest RTT. -// Gateway write lock is held on entry -func (g *srvGateway) orderOutboundConnectionsLocked() { - // Order the gateways by lowest RTT - slices.SortFunc(g.outo, func(i, j *client) int { return cmp.Compare(i.getRTTValue(), j.getRTTValue()) }) -} - -// Orders the array of outbound connections. -// Current ordering is by lowest RTT. -func (g *srvGateway) orderOutboundConnections() { - g.Lock() - g.orderOutboundConnectionsLocked() - g.Unlock() -} - -// Returns all inbound gateway connections in the provided array -func (s *Server) getInboundGatewayConnections(a *[]*client) { - s.gateway.RLock() - for _, gwc := range s.gateway.in { - *a = append(*a, gwc) - } - s.gateway.RUnlock() -} - -// This is invoked when a gateway connection is closed and the server -// is removing this connection from its state. -func (s *Server) removeRemoteGatewayConnection(c *client) { - c.mu.Lock() - cid := c.cid - isOutbound := c.gw.outbound - gwName := c.gw.name - if isOutbound && c.gw.outsim != nil { - // We do this to allow the GC to release this connection. - // Since the map is used by the rest of the code without client lock, - // we can't simply set it to nil, instead, just make sure we empty it. - c.gw.outsim.Range(func(k, _ any) bool { - c.gw.outsim.Delete(k) - return true - }) - } - c.mu.Unlock() - - gw := s.gateway - gw.Lock() - if isOutbound { - delete(gw.out, gwName) - louto := len(gw.outo) - reorder := false - for i := 0; i < len(gw.outo); i++ { - if gw.outo[i] == c { - // If last, simply remove and no need to reorder - if i != louto-1 { - gw.outo[i] = gw.outo[louto-1] - reorder = true - } - gw.outo = gw.outo[:louto-1] - } - } - if reorder { - gw.orderOutboundConnectionsLocked() - } - } else { - delete(gw.in, cid) - } - gw.Unlock() - s.removeFromTempClients(cid) - - if isOutbound { - // Update number of totalQSubs for this gateway - qSubsRemoved := int64(0) - c.mu.Lock() - for _, sub := range c.subs { - if sub.queue != nil { - qSubsRemoved++ - } - } - c.subs = nil - c.mu.Unlock() - // Update total count of qsubs in remote gateways. - atomic.AddInt64(&c.srv.gateway.totalQSubs, -qSubsRemoved) - - } else { - var subsa [1024]*subscription - var subs = subsa[:0] - - // For inbound GW connection, if we have subs, those are - // local subs on "_R_." subjects. - c.mu.Lock() - for _, sub := range c.subs { - subs = append(subs, sub) - } - c.subs = nil - c.mu.Unlock() - for _, sub := range subs { - c.removeReplySub(sub) - } - } -} - -// GatewayAddr returns the net.Addr object for the gateway listener. -func (s *Server) GatewayAddr() *net.TCPAddr { - s.mu.Lock() - defer s.mu.Unlock() - if s.gatewayListener == nil { - return nil - } - return s.gatewayListener.Addr().(*net.TCPAddr) -} - -// A- protocol received from the remote after sending messages -// on an account that it has no interest in. Mark this account -// with a "no interest" marker to prevent further messages send. -// -func (c *client) processGatewayAccountUnsub(accName string) { - // Just to indicate activity around "subscriptions" events. - c.in.subs++ - // This account may have an entry because of queue subs. - // If that's the case, we can reset the no-interest map, - // but not set the entry to nil. - setToNil := true - if ei, ok := c.gw.outsim.Load(accName); ei != nil { - e := ei.(*outsie) - e.Lock() - // Reset the no-interest map if we have queue subs - // and don't set the entry to nil. - if e.qsubs > 0 { - e.ni = make(map[string]struct{}) - setToNil = false - } - e.Unlock() - } else if ok { - // Already set to nil, so skip - setToNil = false - } - if setToNil { - c.gw.outsim.Store(accName, nil) - } -} - -// A+ protocol received from remote gateway if it had previously -// sent an A-. Clear the "no interest" marker for this account. -// -func (c *client) processGatewayAccountSub(accName string) error { - // Just to indicate activity around "subscriptions" events. - c.in.subs++ - // If this account has an entry because of queue subs, we - // can't delete the entry. - remove := true - if ei, ok := c.gw.outsim.Load(accName); ei != nil { - e := ei.(*outsie) - e.Lock() - if e.qsubs > 0 { - remove = false - } - e.Unlock() - } else if !ok { - // There is no entry, so skip - remove = false - } - if remove { - c.gw.outsim.Delete(accName) - } - return nil -} - -// RS- protocol received from the remote after sending messages -// on a subject that it has no interest in (but knows about the -// account). Mark this subject with a "no interest" marker to -// prevent further messages being sent. -// If in modeInterestOnly or for a queue sub, remove from -// the sublist if present. -// -func (c *client) processGatewayRUnsub(arg []byte) error { - _, accName, subject, queue, err := c.parseUnsubProto(arg, true, false) - if err != nil { - return fmt.Errorf("processGatewaySubjectUnsub %s", err.Error()) - } - - var ( - e *outsie - useSl bool - newe bool - callUpdate bool - srv *Server - sub *subscription - ) - - // Possibly execute this on exit after all locks have been released. - // If callUpdate is true, srv and sub will be not nil. - defer func() { - if callUpdate { - srv.updateInterestForAccountOnGateway(accName, sub, -1) - } - }() - - c.mu.Lock() - if c.gw.outsim == nil { - c.Errorf("Received RS- from gateway on inbound connection") - c.mu.Unlock() - c.closeConnection(ProtocolViolation) - return nil - } - defer c.mu.Unlock() - // If closed, c.subs map will be nil, so bail out. - if c.isClosed() { - return nil - } - - ei, _ := c.gw.outsim.Load(accName) - if ei != nil { - e = ei.(*outsie) - e.Lock() - defer e.Unlock() - // If there is an entry, for plain sub we need - // to know if we should store the sub - useSl = queue != nil || e.mode != Optimistic - } else if queue != nil { - // should not even happen... - c.Debugf("Received RS- without prior RS+ for subject %q, queue %q", subject, queue) - return nil - } else { - // Plain sub, assume optimistic sends, create entry. - e = &outsie{ni: make(map[string]struct{}), sl: NewSublistWithCache()} - newe = true - } - // This is when a sub or queue sub is supposed to be in - // the sublist. Look for it and remove. - if useSl { - var ok bool - key := arg - // m[string()] does not cause mem allocation - sub, ok = c.subs[string(key)] - // if RS- for a sub that we don't have, just ignore. - if !ok { - return nil - } - if e.sl.Remove(sub) == nil { - delete(c.subs, bytesToString(key)) - if queue != nil { - e.qsubs-- - atomic.AddInt64(&c.srv.gateway.totalQSubs, -1) - } - // If last, we can remove the whole entry only - // when in optimistic mode and there is no element - // in the `ni` map. - if e.sl.Count() == 0 && e.mode == Optimistic && len(e.ni) == 0 { - c.gw.outsim.Delete(accName) - } - } - // We are going to call updateInterestForAccountOnGateway on exit. - srv = c.srv - callUpdate = true - } else { - e.ni[string(subject)] = struct{}{} - if newe { - c.gw.outsim.Store(accName, e) - } - } - return nil -} - -// For plain subs, RS+ protocol received from remote gateway if it -// had previously sent a RS-. Clear the "no interest" marker for -// this subject (under this account). -// For queue subs, or if in modeInterestOnly, register interest -// from remote gateway. -// -func (c *client) processGatewayRSub(arg []byte) error { - // Indicate activity. - c.in.subs++ - - var ( - queue []byte - qw int32 - ) - - args := splitArg(arg) - switch len(args) { - case 2: - case 4: - queue = args[2] - qw = int32(parseSize(args[3])) - default: - return fmt.Errorf("processGatewaySubjectSub Parse Error: '%s'", arg) - } - accName := args[0] - subject := args[1] - - var ( - e *outsie - useSl bool - newe bool - callUpdate bool - srv *Server - sub *subscription - ) - - // Possibly execute this on exit after all locks have been released. - // If callUpdate is true, srv and sub will be not nil. - defer func() { - if callUpdate { - srv.updateInterestForAccountOnGateway(string(accName), sub, 1) - } - }() - - c.mu.Lock() - if c.gw.outsim == nil { - c.Errorf("Received RS+ from gateway on inbound connection") - c.mu.Unlock() - c.closeConnection(ProtocolViolation) - return nil - } - defer c.mu.Unlock() - // If closed, c.subs map will be nil, so bail out. - if c.isClosed() { - return nil - } - - ei, _ := c.gw.outsim.Load(bytesToString(accName)) - // We should always have an existing entry for plain subs because - // in optimistic mode we would have received RS- first, and - // in full knowledge, we are receiving RS+ for an account after - // getting many RS- from the remote.. - if ei != nil { - e = ei.(*outsie) - e.Lock() - defer e.Unlock() - useSl = queue != nil || e.mode != Optimistic - } else if queue == nil { - return nil - } else { - e = &outsie{ni: make(map[string]struct{}), sl: NewSublistWithCache()} - newe = true - useSl = true - } - if useSl { - var key []byte - // We store remote subs by account/subject[/queue]. - // For queue, remove the trailing weight - if queue != nil { - key = arg[:len(arg)-len(args[3])-1] - } else { - key = arg - } - // If RS+ for a sub that we already have, ignore. - // (m[string()] does not allocate memory) - if _, ok := c.subs[string(key)]; ok { - return nil - } - // new subscription. copy subject (and queue) to - // not reference the underlying possibly big buffer. - var csubject []byte - var cqueue []byte - if queue != nil { - // make single allocation and use different slices - // to point to subject and queue name. - cbuf := make([]byte, len(subject)+1+len(queue)) - copy(cbuf, key[len(accName)+1:]) - csubject = cbuf[:len(subject)] - cqueue = cbuf[len(subject)+1:] - } else { - csubject = make([]byte, len(subject)) - copy(csubject, subject) - } - sub = &subscription{client: c, subject: csubject, queue: cqueue, qw: qw} - // If no error inserting in sublist... - if e.sl.Insert(sub) == nil { - c.subs[string(key)] = sub - if queue != nil { - e.qsubs++ - atomic.AddInt64(&c.srv.gateway.totalQSubs, 1) - } - if newe { - c.gw.outsim.Store(string(accName), e) - } - } - // We are going to call updateInterestForAccountOnGateway on exit. - srv = c.srv - callUpdate = true - } else { - subj := bytesToString(subject) - // If this is an RS+ for a wc subject, then - // remove from the no interest map all subjects - // that are a subset of this wc subject. - if subjectHasWildcard(subj) { - for k := range e.ni { - if subjectIsSubsetMatch(k, subj) { - delete(e.ni, k) - } - } - } else { - delete(e.ni, subj) - } - } - return nil -} - -// Returns true if this gateway has possible interest in the -// given account/subject (which means, it does not have a registered -// no-interest on the account and/or subject) and the sublist result -// for queue subscriptions. -// -func (c *client) gatewayInterest(acc string, subj []byte) (bool, *SublistResult) { - ei, accountInMap := c.gw.outsim.Load(acc) - // If there is an entry for this account and ei is nil, - // it means that the remote is not interested at all in - // this account and we could not possibly have queue subs. - if accountInMap && ei == nil { - return false, nil - } - // Assume interest if account not in map, unless we support - // only interest-only mode. - psi := !accountInMap && !c.gw.interestOnlyMode - var r *SublistResult - if accountInMap { - // If in map, check for subs interest with sublist. - e := ei.(*outsie) - e.RLock() - // Unless each side has agreed on interest-only mode, - // we may be in transition to modeInterestOnly - // but until e.ni is nil, use it to know if we - // should suppress interest or not. - if !c.gw.interestOnlyMode && e.ni != nil { - if _, inMap := e.ni[string(subj)]; !inMap { - psi = true - } - } - // If we are in modeInterestOnly (e.ni will be nil) - // or if we have queue subs, we also need to check sl.Match. - if e.ni == nil || e.qsubs > 0 { - r = e.sl.MatchBytes(subj) - if len(r.psubs) > 0 { - psi = true - } - } - e.RUnlock() - // Since callers may just check if the sublist result is nil or not, - // make sure that if what is returned by sl.Match() is the emptyResult, then - // we return nil to the caller. - if r == emptyResult { - r = nil - } - } - return psi, r -} - -// switchAccountToInterestMode will switch an account over to interestMode. -// Lock should NOT be held. -func (s *Server) switchAccountToInterestMode(accName string) { - gwsa := [16]*client{} - gws := gwsa[:0] - s.getInboundGatewayConnections(&gws) - - for _, gin := range gws { - var e *insie - var ok bool - - gin.mu.Lock() - if e, ok = gin.gw.insim[accName]; !ok || e == nil { - e = &insie{} - gin.gw.insim[accName] = e - } - // Do it only if we are in Optimistic mode - if e.mode == Optimistic { - gin.gatewaySwitchAccountToSendAllSubs(e, accName) - } - gin.mu.Unlock() - } -} - -// This is invoked when registering (or unregistering) the first -// (or last) subscription on a given account/subject. For each -// GWs inbound connections, we will check if we need to send an RS+ or A+ -// protocol. -func (s *Server) maybeSendSubOrUnsubToGateways(accName string, sub *subscription, added bool) { - if sub.queue != nil { - return - } - gwsa := [16]*client{} - gws := gwsa[:0] - s.getInboundGatewayConnections(&gws) - if len(gws) == 0 { - return - } - var ( - rsProtoa [512]byte - rsProto []byte - accProtoa [256]byte - accProto []byte - proto []byte - subject = bytesToString(sub.subject) - hasWC = subjectHasWildcard(subject) - ) - for _, c := range gws { - proto = nil - c.mu.Lock() - e, inMap := c.gw.insim[accName] - // If there is a inbound subject interest entry... - if e != nil { - sendProto := false - // In optimistic mode, we care only about possibly sending RS+ (or A+) - // so if e.ni is not nil we do things only when adding a new subscription. - if e.ni != nil && added { - // For wildcard subjects, we will remove from our no-interest - // map, all subjects that are a subset of this wc subject, but we - // still send the wc subject and let the remote do its own cleanup. - if hasWC { - for enis := range e.ni { - if subjectIsSubsetMatch(enis, subject) { - delete(e.ni, enis) - sendProto = true - } - } - } else if _, noInterest := e.ni[subject]; noInterest { - delete(e.ni, subject) - sendProto = true - } - } else if e.mode == InterestOnly { - // We are in the mode where we always send RS+/- protocols. - sendProto = true - } - if sendProto { - if rsProto == nil { - // Construct the RS+/- only once - proto = rsProtoa[:0] - if added { - proto = append(proto, rSubBytes...) - } else { - proto = append(proto, rUnsubBytes...) - } - proto = append(proto, accName...) - proto = append(proto, ' ') - proto = append(proto, sub.subject...) - proto = append(proto, CR_LF...) - rsProto = proto - } else { - // Point to the already constructed RS+/- - proto = rsProto - } - } - } else if added && inMap { - // Here, we have a `nil` entry for this account in - // the map, which means that we have previously sent - // an A-. We have a new subscription, so we need to - // send an A+ and delete the entry from the map so - // that we do this only once. - delete(c.gw.insim, accName) - if accProto == nil { - // Construct the A+ only once - proto = accProtoa[:0] - proto = append(proto, aSubBytes...) - proto = append(proto, accName...) - proto = append(proto, CR_LF...) - accProto = proto - } else { - // Point to the already constructed A+ - proto = accProto - } - } - if proto != nil { - c.enqueueProto(proto) - if c.trace { - c.traceOutOp("", proto[:len(proto)-LEN_CR_LF]) - } - } - c.mu.Unlock() - } -} - -// This is invoked when the first (or last) queue subscription on a -// given subject/group is registered (or unregistered). Sent to all -// inbound gateways. -func (s *Server) sendQueueSubOrUnsubToGateways(accName string, qsub *subscription, added bool) { - if qsub.queue == nil { - return - } - - gwsa := [16]*client{} - gws := gwsa[:0] - s.getInboundGatewayConnections(&gws) - if len(gws) == 0 { - return - } - - var protoa [512]byte - var proto []byte - for _, c := range gws { - if proto == nil { - proto = protoa[:0] - if added { - proto = append(proto, rSubBytes...) - } else { - proto = append(proto, rUnsubBytes...) - } - proto = append(proto, accName...) - proto = append(proto, ' ') - proto = append(proto, qsub.subject...) - proto = append(proto, ' ') - proto = append(proto, qsub.queue...) - if added { - // For now, just use 1 for the weight - proto = append(proto, ' ', '1') - } - proto = append(proto, CR_LF...) - } - c.mu.Lock() - // If we add a queue sub, and we had previously sent an A-, - // we don't need to send an A+ here, but we need to clear - // the fact that we did sent the A- so that we don't send - // an A+ when we will get the first non-queue sub registered. - if added { - if ei, ok := c.gw.insim[accName]; ok && ei == nil { - delete(c.gw.insim, accName) - } - } - c.enqueueProto(proto) - if c.trace { - c.traceOutOp("", proto[:len(proto)-LEN_CR_LF]) - } - c.mu.Unlock() - } -} - -// This is invoked when a subscription (plain or queue) is -// added/removed locally or in our cluster. We use ref counting -// to know when to update the inbound gateways. -// -func (s *Server) gatewayUpdateSubInterest(accName string, sub *subscription, change int32) { - if sub.si { - return - } - - var ( - keya [1024]byte - key = keya[:0] - entry *sitally - isNew bool - ) - - s.gateway.pasi.Lock() - defer s.gateway.pasi.Unlock() - - accMap := s.gateway.pasi.m - - // First see if we have the account - st := accMap[accName] - if st == nil { - // Ignore remove of something we don't have - if change < 0 { - return - } - st = make(map[string]*sitally) - accMap[accName] = st - isNew = true - } - // Lookup: build the key as subject[+' '+queue] - key = append(key, sub.subject...) - if sub.queue != nil { - key = append(key, ' ') - key = append(key, sub.queue...) - } - if !isNew { - entry = st[string(key)] - } - first := false - last := false - if entry == nil { - // Ignore remove of something we don't have - if change < 0 { - return - } - entry = &sitally{n: change, q: sub.queue != nil} - st[string(key)] = entry - first = true - } else { - entry.n += change - if entry.n <= 0 { - delete(st, bytesToString(key)) - last = true - if len(st) == 0 { - delete(accMap, accName) - } - } - } - if sub.client != nil { - rsubs := &s.gateway.rsubs - acc := sub.client.acc - sli, _ := rsubs.Load(acc) - if change > 0 { - var sl *Sublist - if sli == nil { - sl = NewSublistNoCache() - rsubs.Store(acc, sl) - } else { - sl = sli.(*Sublist) - } - sl.Insert(sub) - time.AfterFunc(s.gateway.recSubExp, func() { - sl.Remove(sub) - }) - } else if sli != nil { - sl := sli.(*Sublist) - sl.Remove(sub) - if sl.Count() == 0 { - rsubs.Delete(acc) - } - } - } - if first || last { - if entry.q { - s.sendQueueSubOrUnsubToGateways(accName, sub, first) - } else { - s.maybeSendSubOrUnsubToGateways(accName, sub, first) - } - } -} - -// Returns true if the given subject is a GW routed reply subject, -// that is, starts with $GNR and is long enough to contain cluster/server hash -// and subject. -func isGWRoutedReply(subj []byte) bool { - return len(subj) > gwSubjectOffset && bytesToString(subj[:gwReplyPrefixLen]) == gwReplyPrefix -} - -// Same than isGWRoutedReply but accepts the old prefix $GR and returns -// a boolean indicating if this is the old prefix -func isGWRoutedSubjectAndIsOldPrefix(subj []byte) (bool, bool) { - if isGWRoutedReply(subj) { - return true, false - } - if len(subj) > oldGWReplyStart && bytesToString(subj[:oldGWReplyPrefixLen]) == oldGWReplyPrefix { - return true, true - } - return false, false -} - -// Returns true if subject starts with "$GNR.". This is to check that -// clients can't publish on this subject. -func hasGWRoutedReplyPrefix(subj []byte) bool { - return len(subj) > gwReplyPrefixLen && bytesToString(subj[:gwReplyPrefixLen]) == gwReplyPrefix -} - -// Evaluates if the given reply should be mapped or not. -func (g *srvGateway) shouldMapReplyForGatewaySend(acc *Account, reply []byte) bool { - // If for this account there is a recent matching subscription interest - // then we will map. - sli, _ := g.rsubs.Load(acc) - if sli == nil { - return false - } - sl := sli.(*Sublist) - if sl.Count() > 0 { - if sl.HasInterest(string(reply)) { - return true - } - } - - return false -} - -var subPool = &sync.Pool{ - New: func() any { - return &subscription{} - }, -} - -// May send a message to all outbound gateways. It is possible -// that the message is not sent to a given gateway if for instance -// it is known that this gateway has no interest in the account or -// subject, etc.. -// When invoked from a LEAF connection, `checkLeafQF` should be passed as `true` -// so that we skip any queue subscription interest that is not part of the -// `c.pa.queues` filter (similar to what we do in `processMsgResults`). However, -// when processing service imports, then this boolean should be passes as `false`, -// regardless if it is a LEAF connection or not. -// -func (c *client) sendMsgToGateways(acc *Account, msg, subject, reply []byte, qgroups [][]byte, checkLeafQF bool) bool { - // We had some times when we were sending across a GW with no subject, and the other side would break - // due to parser error. These need to be fixed upstream but also double check here. - if len(subject) == 0 { - return false - } - gwsa := [16]*client{} - gws := gwsa[:0] - // This is in fast path, so avoid calling functions when possible. - // Get the outbound connections in place instead of calling - // getOutboundGatewayConnections(). - srv := c.srv - gw := srv.gateway - gw.RLock() - for i := 0; i < len(gw.outo); i++ { - gws = append(gws, gw.outo[i]) - } - thisClusterReplyPrefix := gw.replyPfx - thisClusterOldReplyPrefix := gw.oldReplyPfx - gw.RUnlock() - if len(gws) == 0 { - return false - } - - mt, _ := c.isMsgTraceEnabled() - if mt != nil { - pa := c.pa - msg = mt.setOriginAccountHeaderIfNeeded(c, acc, msg) - defer func() { c.pa = pa }() - } - - var ( - queuesa = [512]byte{} - queues = queuesa[:0] - accName = acc.Name - mreplya [256]byte - mreply []byte - dstHash []byte - checkReply = len(reply) > 0 - didDeliver bool - prodIsMQTT = c.isMqtt() - dlvMsgs int64 - ) - - // Get a subscription from the pool - sub := subPool.Get().(*subscription) - - // Check if the subject is on the reply prefix, if so, we - // need to send that message directly to the origin cluster. - directSend, old := isGWRoutedSubjectAndIsOldPrefix(subject) - if directSend { - if old { - dstHash = subject[oldGWReplyPrefixLen : oldGWReplyStart-1] - } else { - dstHash = subject[gwClusterOffset : gwClusterOffset+gwHashLen] - } - } - for i := 0; i < len(gws); i++ { - gwc := gws[i] - if directSend { - gwc.mu.Lock() - var ok bool - if gwc.gw.cfg != nil { - if old { - ok = bytes.Equal(dstHash, gwc.gw.cfg.oldHash) - } else { - ok = bytes.Equal(dstHash, gwc.gw.cfg.hash) - } - } - gwc.mu.Unlock() - if !ok { - continue - } - } else { - // Plain sub interest and queue sub results for this account/subject - psi, qr := gwc.gatewayInterest(accName, subject) - if !psi && qr == nil { - continue - } - queues = queuesa[:0] - if qr != nil { - for i := 0; i < len(qr.qsubs); i++ { - qsubs := qr.qsubs[i] - if len(qsubs) > 0 { - queue := qsubs[0].queue - if checkLeafQF { - // Skip any queue that is not in the leaf's queue filter. - skip := true - for _, qn := range c.pa.queues { - if bytes.Equal(queue, qn) { - skip = false - break - } - } - if skip { - continue - } - // Now we still need to check that it was not delivered - // locally by checking the given `qgroups`. - } - add := true - for _, qn := range qgroups { - if bytes.Equal(queue, qn) { - add = false - break - } - } - if add { - qgroups = append(qgroups, queue) - queues = append(queues, queue...) - queues = append(queues, ' ') - } - } - } - } - if !psi && len(queues) == 0 { - continue - } - } - if checkReply { - // Check/map only once - checkReply = false - // Assume we will use original - mreply = reply - // Decide if we should map. - if gw.shouldMapReplyForGatewaySend(acc, reply) { - mreply = mreplya[:0] - gwc.mu.Lock() - useOldPrefix := gwc.gw.useOldPrefix - gwc.mu.Unlock() - if useOldPrefix { - mreply = append(mreply, thisClusterOldReplyPrefix...) - } else { - mreply = append(mreply, thisClusterReplyPrefix...) - } - mreply = append(mreply, reply...) - } - } - - if mt != nil { - msg = mt.setHopHeader(c, msg) - } - - // Setup the message header. - // Make sure we are an 'R' proto by default - c.msgb[0] = 'R' - mh := c.msgb[:msgHeadProtoLen] - mh = append(mh, accName...) - mh = append(mh, ' ') - mh = append(mh, subject...) - mh = append(mh, ' ') - if len(queues) > 0 { - if len(reply) > 0 { - mh = append(mh, "+ "...) // Signal that there is a reply. - mh = append(mh, mreply...) - mh = append(mh, ' ') - } else { - mh = append(mh, "| "...) // Only queues - } - mh = append(mh, queues...) - } else if len(reply) > 0 { - mh = append(mh, mreply...) - mh = append(mh, ' ') - } - // Headers - hasHeader := c.pa.hdr > 0 - canReceiveHeader := gwc.headers - - if hasHeader { - if canReceiveHeader { - mh[0] = 'H' - mh = append(mh, c.pa.hdb...) - mh = append(mh, ' ') - mh = append(mh, c.pa.szb...) - } else { - // If we are here we need to truncate the payload size - nsz := strconv.Itoa(c.pa.size - c.pa.hdr) - mh = append(mh, nsz...) - } - } else { - mh = append(mh, c.pa.szb...) - } - - mh = append(mh, CR_LF...) - - // We reuse the subscription object that we pass to deliverMsg. - // So set/reset important fields. - sub.nm, sub.max = 0, 0 - sub.client = gwc - sub.subject = subject - if c.deliverMsg(prodIsMQTT, sub, acc, subject, mreply, mh, msg, false) { - // We don't count internal deliveries so count only if sub.icb is nil - if sub.icb == nil { - dlvMsgs++ - } - didDeliver = true - } - } - if dlvMsgs > 0 { - totalBytes := dlvMsgs * int64(len(msg)) - // For non MQTT producers, remove the CR_LF * number of messages - if !prodIsMQTT { - totalBytes -= dlvMsgs * int64(LEN_CR_LF) - } - if acc != nil { - atomic.AddInt64(&acc.outMsgs, dlvMsgs) - atomic.AddInt64(&acc.outBytes, totalBytes) - } - atomic.AddInt64(&srv.outMsgs, dlvMsgs) - atomic.AddInt64(&srv.outBytes, totalBytes) - } - // Done with subscription, put back to pool. We don't need - // to reset content since we explicitly set when using it. - // However, make sure to not hold a reference to a connection. - sub.client = nil - subPool.Put(sub) - return didDeliver -} - -// Possibly sends an A- to the remote gateway `c`. -// Invoked when processing an inbound message and the account is not found. -// A check under a lock that protects processing of SUBs and UNSUBs is -// done to make sure that we don't send the A- if a subscription has just -// been created at the same time, which would otherwise results in the -// remote never sending messages on this account until a new subscription -// is created. -func (s *Server) gatewayHandleAccountNoInterest(c *client, accName []byte) { - // Check and possibly send the A- under this lock. - s.gateway.pasi.Lock() - defer s.gateway.pasi.Unlock() - - si, inMap := s.gateway.pasi.m[string(accName)] - if inMap && si != nil && len(si) > 0 { - return - } - c.sendAccountUnsubToGateway(accName) -} - -// Helper that sends an A- to this remote gateway if not already done. -// This function should not be invoked directly but instead be invoked -// by functions holding the gateway.pasi's Lock. -func (c *client) sendAccountUnsubToGateway(accName []byte) { - // Check if we have sent the A- or not. - c.mu.Lock() - e, sent := c.gw.insim[string(accName)] - if e != nil || !sent { - // Add a nil value to indicate that we have sent an A- - // so that we know to send A+ when needed. - c.gw.insim[string(accName)] = nil - var protoa [256]byte - proto := protoa[:0] - proto = append(proto, aUnsubBytes...) - proto = append(proto, accName...) - proto = append(proto, CR_LF...) - c.enqueueProto(proto) - if c.trace { - c.traceOutOp("", proto[:len(proto)-LEN_CR_LF]) - } - } - c.mu.Unlock() -} - -// Possibly sends an A- for this account or RS- for this subject. -// Invoked when processing an inbound message and the account is found -// but there is no interest on this subject. -// A test is done under a lock that protects processing of SUBs and UNSUBs -// and if there is no subscription at this time, we send an A-. If there -// is at least a subscription, but no interest on this subject, we send -// an RS- for this subject (if not already done). -func (s *Server) gatewayHandleSubjectNoInterest(c *client, acc *Account, accName, subject []byte) { - s.gateway.pasi.Lock() - defer s.gateway.pasi.Unlock() - - // If there is no subscription for this account, we would normally - // send an A-, however, if this account has the internal subscription - // for service reply, send a specific RS- for the subject instead. - // Need to grab the lock here since sublist can change during reload. - acc.mu.RLock() - hasSubs := acc.sl.Count() > 0 || acc.siReply != nil - acc.mu.RUnlock() - - // If there is at least a subscription, possibly send RS- - if hasSubs { - sendProto := false - c.mu.Lock() - // Send an RS- protocol if not already done and only if - // not in the modeInterestOnly. - e := c.gw.insim[string(accName)] - if e == nil { - e = &insie{ni: make(map[string]struct{})} - e.ni[string(subject)] = struct{}{} - c.gw.insim[string(accName)] = e - sendProto = true - } else if e.ni != nil { - // If we are not in modeInterestOnly, check if we - // have already sent an RS- - if _, alreadySent := e.ni[string(subject)]; !alreadySent { - // TODO(ik): pick some threshold as to when - // we need to switch mode - if len(e.ni) >= gatewayMaxRUnsubBeforeSwitch { - // If too many RS-, switch to all-subs-mode. - c.gatewaySwitchAccountToSendAllSubs(e, string(accName)) - } else { - e.ni[string(subject)] = struct{}{} - sendProto = true - } - } - } - if sendProto { - var ( - protoa = [512]byte{} - proto = protoa[:0] - ) - proto = append(proto, rUnsubBytes...) - proto = append(proto, accName...) - proto = append(proto, ' ') - proto = append(proto, subject...) - proto = append(proto, CR_LF...) - c.enqueueProto(proto) - if c.trace { - c.traceOutOp("", proto[:len(proto)-LEN_CR_LF]) - } - } - c.mu.Unlock() - } else { - // There is not a single subscription, send an A- (if not already done). - c.sendAccountUnsubToGateway([]byte(acc.Name)) - } -} - -// Returns the cluster hash from the gateway reply prefix -func (g *srvGateway) getClusterHash() []byte { - g.RLock() - clusterHash := g.replyPfx[gwClusterOffset : gwClusterOffset+gwHashLen] - g.RUnlock() - return clusterHash -} - -// Store this route in map with the key being the remote server's name hash -// and the remote server's ID hash used by gateway replies mapping routing. -func (s *Server) storeRouteByHash(srvIDHash string, c *client) { - if !s.gateway.enabled { - return - } - s.gateway.routesIDByHash.Store(srvIDHash, c) -} - -// Remove the route with the given keys from the map. -func (s *Server) removeRouteByHash(srvIDHash string) { - if !s.gateway.enabled { - return - } - s.gateway.routesIDByHash.Delete(srvIDHash) -} - -// Returns the route with given hash or nil if not found. -// This is for gateways only. -func (s *Server) getRouteByHash(hash, accName []byte) (*client, bool) { - id := bytesToString(hash) - var perAccount bool - if v, ok := s.accRouteByHash.Load(bytesToString(accName)); ok { - if v == nil { - id += bytesToString(accName) - perAccount = true - } else { - id += strconv.Itoa(v.(int)) - } - } - if v, ok := s.gateway.routesIDByHash.Load(id); ok { - return v.(*client), perAccount - } else if !perAccount { - // Check if we have a "no pool" connection at index 0. - if v, ok := s.gateway.routesIDByHash.Load(bytesToString(hash) + "0"); ok { - if r := v.(*client); r != nil { - r.mu.Lock() - noPool := r.route.noPool - r.mu.Unlock() - if noPool { - return r, false - } - } - } - } - return nil, perAccount -} - -// Returns the subject from the routed reply -func getSubjectFromGWRoutedReply(reply []byte, isOldPrefix bool) []byte { - if isOldPrefix { - return reply[oldGWReplyStart:] - } - return reply[gwSubjectOffset:] -} - -// This should be invoked only from processInboundGatewayMsg() or -// processInboundRoutedMsg() and is checking if the subject -// (c.pa.subject) has the _GR_ prefix. If so, this is processed -// as a GW reply and `true` is returned to indicate to the caller -// that it should stop processing. -// If gateway is not enabled on this server or if the subject -// does not start with _GR_, `false` is returned and caller should -// process message as usual. -func (c *client) handleGatewayReply(msg []byte) (processed bool) { - // Do not handle GW prefixed messages if this server does not have - // gateway enabled or if the subject does not start with the previx. - if !c.srv.gateway.enabled { - return false - } - isGWPrefix, oldPrefix := isGWRoutedSubjectAndIsOldPrefix(c.pa.subject) - if !isGWPrefix { - return false - } - // Save original subject (in case we have to forward) - orgSubject := c.pa.subject - - var clusterHash []byte - var srvHash []byte - var subject []byte - - if oldPrefix { - clusterHash = c.pa.subject[oldGWReplyPrefixLen : oldGWReplyStart-1] - // Check if this reply is intended for our cluster. - if !bytes.Equal(clusterHash, c.srv.gateway.oldHash) { - // We could report, for now, just drop. - return true - } - subject = c.pa.subject[oldGWReplyStart:] - } else { - clusterHash = c.pa.subject[gwClusterOffset : gwClusterOffset+gwHashLen] - // Check if this reply is intended for our cluster. - if !bytes.Equal(clusterHash, c.srv.gateway.getClusterHash()) { - // We could report, for now, just drop. - return true - } - srvHash = c.pa.subject[gwServerOffset : gwServerOffset+gwHashLen] - subject = c.pa.subject[gwSubjectOffset:] - } - - var route *client - var perAccount bool - - // If the origin is not this server, get the route this should be sent to. - if c.kind == GATEWAY && srvHash != nil && !bytes.Equal(srvHash, c.srv.gateway.sIDHash) { - route, perAccount = c.srv.getRouteByHash(srvHash, c.pa.account) - // This will be possibly nil, and in this case we will try to process - // the interest from this server. - } - - // Adjust the subject - c.pa.subject = subject - - // Use a stack buffer to rewrite c.pa.cache since we only need it for - // getAccAndResultFromCache() - var _pacache [256]byte - pacache := _pacache[:0] - // For routes that are dedicated to an account, do not put the account - // name in the pacache. - if c.kind == GATEWAY || (c.kind == ROUTER && c.route != nil && len(c.route.accName) == 0) { - pacache = append(pacache, c.pa.account...) - pacache = append(pacache, ' ') - } - pacache = append(pacache, c.pa.subject...) - c.pa.pacache = pacache - - acc, r := c.getAccAndResultFromCache() - if acc == nil { - typeConn := "routed" - if c.kind == GATEWAY { - typeConn = "gateway" - } - c.Debugf("Unknown account %q for %s message on subject: %q", c.pa.account, typeConn, c.pa.subject) - if c.kind == GATEWAY { - c.srv.gatewayHandleAccountNoInterest(c, c.pa.account) - } - return true - } - - // If route is nil, we will process the incoming message locally. - if route == nil { - // Check if this is a service reply subject (_R_) - isServiceReply := isServiceReply(c.pa.subject) - - var queues [][]byte - if len(r.psubs)+len(r.qsubs) > 0 { - flags := pmrCollectQueueNames | pmrIgnoreEmptyQueueFilter - // If this message came from a ROUTE, allow to pick queue subs - // only if the message was directly sent by the "gateway" server - // in our cluster that received it. - if c.kind == ROUTER { - flags |= pmrAllowSendFromRouteToRoute - } - _, queues = c.processMsgResults(acc, r, msg, nil, c.pa.subject, c.pa.reply, flags) - } - // Since this was a reply that made it to the origin cluster, - // we now need to send the message with the real subject to - // gateways in case they have interest on that reply subject. - if !isServiceReply { - c.sendMsgToGateways(acc, msg, c.pa.subject, c.pa.reply, queues, false) - } - } else if c.kind == GATEWAY { - // Only if we are a gateway connection should we try to route - // to the server where the request originated. - var bufa [256]byte - var buf = bufa[:0] - buf = append(buf, msgHeadProto...) - if !perAccount { - buf = append(buf, acc.Name...) - buf = append(buf, ' ') - } - buf = append(buf, orgSubject...) - buf = append(buf, ' ') - if len(c.pa.reply) > 0 { - buf = append(buf, c.pa.reply...) - buf = append(buf, ' ') - } - szb := c.pa.szb - if c.pa.hdr >= 0 { - if route.headers { - buf[0] = 'H' - buf = append(buf, c.pa.hdb...) - buf = append(buf, ' ') - } else { - szb = []byte(strconv.Itoa(c.pa.size - c.pa.hdr)) - msg = msg[c.pa.hdr:] - } - } - buf = append(buf, szb...) - mhEnd := len(buf) - buf = append(buf, _CRLF_...) - buf = append(buf, msg...) - - route.mu.Lock() - route.enqueueProto(buf) - if route.trace { - route.traceOutOp("", buf[:mhEnd]) - } - route.mu.Unlock() - } - return true -} - -// Process a message coming from a remote gateway. Send to any sub/qsub -// in our cluster that is matching. When receiving a message for an -// account or subject for which there is no interest in this cluster -// an A-/RS- protocol may be send back. -// -func (c *client) processInboundGatewayMsg(msg []byte) { - // Update statistics - c.in.msgs++ - // The msg includes the CR_LF, so pull back out for accounting. - c.in.bytes += int32(len(msg) - LEN_CR_LF) - - if c.opts.Verbose { - c.sendOK() - } - - // Mostly under testing scenarios. - if c.srv == nil { - return - } - - // If the subject (c.pa.subject) has the gateway prefix, this function will - // handle it. - if c.handleGatewayReply(msg) { - // We are done here. - return - } - - acc, r := c.getAccAndResultFromCache() - if acc == nil { - c.Debugf("Unknown account %q for gateway message on subject: %q", c.pa.account, c.pa.subject) - c.srv.gatewayHandleAccountNoInterest(c, c.pa.account) - return - } - - // Check if this is a service reply subject (_R_) - noInterest := len(r.psubs) == 0 - checkNoInterest := true - if acc.NumServiceImports() > 0 { - if isServiceReply(c.pa.subject) { - checkNoInterest = false - } else { - // We need to eliminate the subject interest from the service imports here to - // make sure we send the proper no interest if the service import is the only interest. - noInterest = true - for _, sub := range r.psubs { - // sub.si indicates that this is a subscription for service import, and is immutable. - // So sub.si is false, then this is a subscription for something else, so there is - // actually proper interest. - if !sub.si { - noInterest = false - break - } - } - } - } - if checkNoInterest && noInterest { - // If there is no interest on plain subs, possibly send an RS-, - // even if there is qsubs interest. - c.srv.gatewayHandleSubjectNoInterest(c, acc, c.pa.account, c.pa.subject) - - // If there is also no queue filter, then no point in continuing - // (even if r.qsubs i > 0). - if len(c.pa.queues) == 0 { - return - } - } - c.processMsgResults(acc, r, msg, nil, c.pa.subject, c.pa.reply, pmrNoFlag) -} - -// Indicates that the remote which we are sending messages to -// has decided to send us all its subs interest so that we -// stop doing optimistic sends. -// -func (c *client) gatewayAllSubsReceiveStart(info *Info) { - account := getAccountFromGatewayCommand(c, info, "start") - if account == "" { - return - } - - c.Debugf("Gateway %q: switching account %q to %s mode", - info.Gateway, account, InterestOnly) - - // Since the remote would send us this start command - // only after sending us too many RS- for this account, - // we should always have an entry here. - // TODO(ik): Should we close connection with protocol violation - // error if that happens? - ei, _ := c.gw.outsim.Load(account) - if ei != nil { - e := ei.(*outsie) - e.Lock() - e.mode = Transitioning - e.Unlock() - } else { - e := &outsie{sl: NewSublistWithCache()} - e.mode = Transitioning - c.mu.Lock() - c.gw.outsim.Store(account, e) - c.mu.Unlock() - } -} - -// Indicates that the remote has finished sending all its -// subscriptions and we should now not send unless we know -// there is explicit interest. -// -func (c *client) gatewayAllSubsReceiveComplete(info *Info) { - account := getAccountFromGatewayCommand(c, info, "complete") - if account == _EMPTY_ { - return - } - // Done receiving all subs from remote. Set the `ni` - // map to nil so that gatewayInterest() no longer - // uses it. - ei, _ := c.gw.outsim.Load(account) - if ei != nil { - e := ei.(*outsie) - // Needs locking here since `ni` is checked by - // many go-routines calling gatewayInterest() - e.Lock() - e.ni = nil - e.mode = InterestOnly - e.Unlock() - - c.Debugf("Gateway %q: switching account %q to %s mode complete", - info.Gateway, account, InterestOnly) - } -} - -// small helper to get the account name from the INFO command. -func getAccountFromGatewayCommand(c *client, info *Info, cmd string) string { - if info.GatewayCmdPayload == nil { - c.sendErrAndErr(fmt.Sprintf("Account absent from receive-all-subscriptions-%s command", cmd)) - c.closeConnection(ProtocolViolation) - return _EMPTY_ - } - return string(info.GatewayCmdPayload) -} - -// Switch to send-all-subs mode for the given gateway and account. -// This is invoked when processing an inbound message and we -// reach a point where we had to send a lot of RS- for this -// account. We will send an INFO protocol to indicate that we -// start sending all our subs (for this account), followed by -// all subs (RS+) and finally an INFO to indicate the end of it. -// The remote will then send messages only if it finds explicit -// interest in the sublist created based on all RS+ that we just -// sent. -// The client's lock is held on entry. -// -func (c *client) gatewaySwitchAccountToSendAllSubs(e *insie, accName string) { - // Set this map to nil so that the no-interest is no longer checked. - e.ni = nil - // Switch mode to transitioning to prevent switchAccountToInterestMode - // to possibly call this function multiple times. - e.mode = Transitioning - s := c.srv - - remoteGWName := c.gw.name - c.Debugf("Gateway %q: switching account %q to %s mode", - remoteGWName, accName, InterestOnly) - - // Function that will create an INFO protocol - // and set proper command. - sendCmd := func(cmd byte, useLock bool) { - // Use bare server info and simply set the - // gateway name and command - info := Info{ - Gateway: s.gateway.name, - GatewayCmd: cmd, - GatewayCmdPayload: stringToBytes(accName), - } - - b, _ := json.Marshal(&info) - infoJSON := []byte(fmt.Sprintf(InfoProto, b)) - if useLock { - c.mu.Lock() - } - c.enqueueProto(infoJSON) - if useLock { - c.mu.Unlock() - } - } - // Send the start command. When remote receives this, - // it may continue to send optimistic messages, but - // it will start to register RS+/RS- in sublist instead - // of noInterest map. - sendCmd(gatewayCmdAllSubsStart, false) - - // Execute this in separate go-routine as to not block - // the readLoop (which may cause the otherside to close - // the connection due to slow consumer) - s.startGoRoutine(func() { - defer s.grWG.Done() - - s.sendAccountSubsToGateway(c, accName) - // Send the complete command. When the remote receives - // this, it will not send a message unless it has a - // matching sub from us. - sendCmd(gatewayCmdAllSubsComplete, true) - - c.Debugf("Gateway %q: switching account %q to %s mode complete", - remoteGWName, accName, InterestOnly) - }) -} - -// Keeps track of the routed reply to be used when/if application sends back a -// message on the reply without the prefix. -// If `client` is not nil, it will be stored in the client gwReplyMapping structure, -// and client lock is held on entry. -// If `client` is nil, the mapping is stored in the client's account's gwReplyMapping -// structure. Account lock will be explicitly acquired. -// This is a server receiver because we use a timer interval that is avail in -// Server.gateway object. -func (s *Server) trackGWReply(c *client, acc *Account, reply, routedReply []byte) { - var l sync.Locker - var g *gwReplyMapping - if acc != nil { - acc.mu.Lock() - defer acc.mu.Unlock() - g = &acc.gwReplyMapping - l = &acc.mu - } else { - g = &c.gwReplyMapping - l = &c.mu - } - ttl := s.gateway.recSubExp - wasEmpty := len(g.mapping) == 0 - if g.mapping == nil { - g.mapping = make(map[string]*gwReplyMap) - } - // The reason we pass both `reply` and `routedReply`, is that in some cases, - // `routedReply` may have a deliver subject appended, something look like: - // "_GR_.xxx.yyy.$JS.ACK.$MQTT_msgs.someid.1.1.1.1620086713306484000.0@$MQTT.msgs.foo" - // but `reply` has already been cleaned up (delivery subject removed from tail): - // "$JS.ACK.$MQTT_msgs.someid.1.1.1.1620086713306484000.0" - // So we will use that knowledge so we don't have to make any cleaning here. - routedReply = routedReply[:gwSubjectOffset+len(reply)] - // We need to make a copy so that we don't reference the underlying - // read buffer. - ms := string(routedReply) - grm := &gwReplyMap{ms: ms, exp: time.Now().Add(ttl).UnixNano()} - // If we are here with the same key but different mapped replies - // (say $GNR._.A.srv1.bar and then $GNR._.B.srv2.bar), we need to - // store it otherwise we would take the risk of the reply not - // making it back. - g.mapping[ms[gwSubjectOffset:]] = grm - if wasEmpty { - atomic.StoreInt32(&g.check, 1) - s.gwrm.m.Store(g, l) - if atomic.CompareAndSwapInt32(&s.gwrm.w, 0, 1) { - select { - case s.gwrm.ch <- ttl: - default: - } - } - } -} - -// Starts a long lived go routine that is responsible to -// remove GW reply mapping that have expired. -func (s *Server) startGWReplyMapExpiration() { - s.mu.Lock() - s.gwrm.ch = make(chan time.Duration, 1) - s.mu.Unlock() - s.startGoRoutine(func() { - defer s.grWG.Done() - - t := time.NewTimer(time.Hour) - var ttl time.Duration - for { - select { - case <-t.C: - if ttl == 0 { - t.Reset(time.Hour) - continue - } - now := time.Now().UnixNano() - mapEmpty := true - s.gwrm.m.Range(func(k, v any) bool { - g := k.(*gwReplyMapping) - l := v.(sync.Locker) - l.Lock() - for k, grm := range g.mapping { - if grm.exp <= now { - delete(g.mapping, k) - if len(g.mapping) == 0 { - atomic.StoreInt32(&g.check, 0) - s.gwrm.m.Delete(g) - } - } - } - l.Unlock() - mapEmpty = false - return true - }) - if mapEmpty && atomic.CompareAndSwapInt32(&s.gwrm.w, 1, 0) { - ttl = 0 - t.Reset(time.Hour) - } else { - t.Reset(ttl) - } - case cttl := <-s.gwrm.ch: - ttl = cttl - if !t.Stop() { - select { - case <-t.C: - default: - } - } - t.Reset(ttl) - case <-s.quitCh: - return - } - } - }) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/gsl/gsl.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/gsl/gsl.go deleted file mode 100644 index 377fabe2..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/gsl/gsl.go +++ /dev/null @@ -1,532 +0,0 @@ -// Copyright 2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package gsl - -import ( - "errors" - "sync" - - "github.com/nats-io/nats-server/v2/server/stree" -) - -// Sublist is a routing mechanism to handle subject distribution and -// provides a facility to match subjects from published messages to -// interested subscribers. Subscribers can have wildcard subjects to -// match multiple published subjects. - -// Common byte variables for wildcards and token separator. -const ( - pwc = '*' - pwcs = "*" - fwc = '>' - fwcs = ">" - tsep = "." - btsep = '.' - _EMPTY_ = "" -) - -// Sublist related errors -var ( - ErrInvalidSubject = errors.New("gsl: invalid subject") - ErrNotFound = errors.New("gsl: no matches found") - ErrNilChan = errors.New("gsl: nil channel") - ErrAlreadyRegistered = errors.New("gsl: notification already registered") -) - -// A GenericSublist stores and efficiently retrieves subscriptions. -type GenericSublist[T comparable] struct { - sync.RWMutex - root *level[T] - count uint32 -} - -// A node contains subscriptions and a pointer to the next level. -type node[T comparable] struct { - next *level[T] - subs map[T]string // value -> subject -} - -// A level represents a group of nodes and special pointers to -// wildcard nodes. -type level[T comparable] struct { - nodes map[string]*node[T] - pwc, fwc *node[T] -} - -// Create a new default node. -func newNode[T comparable]() *node[T] { - return &node[T]{subs: make(map[T]string)} -} - -// Create a new default level. -func newLevel[T comparable]() *level[T] { - return &level[T]{nodes: make(map[string]*node[T])} -} - -// NewSublist will create a default sublist with caching enabled per the flag. -func NewSublist[T comparable]() *GenericSublist[T] { - return &GenericSublist[T]{root: newLevel[T]()} -} - -// Insert adds a subscription into the sublist -func (s *GenericSublist[T]) Insert(subject string, value T) error { - tsa := [32]string{} - tokens := tsa[:0] - start := 0 - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - tokens = append(tokens, subject[start:i]) - start = i + 1 - } - } - tokens = append(tokens, subject[start:]) - - s.Lock() - - var sfwc bool - var n *node[T] - l := s.root - - for _, t := range tokens { - lt := len(t) - if lt == 0 || sfwc { - s.Unlock() - return ErrInvalidSubject - } - - if lt > 1 { - n = l.nodes[t] - } else { - switch t[0] { - case pwc: - n = l.pwc - case fwc: - n = l.fwc - sfwc = true - default: - n = l.nodes[t] - } - } - if n == nil { - n = newNode[T]() - if lt > 1 { - l.nodes[t] = n - } else { - switch t[0] { - case pwc: - l.pwc = n - case fwc: - l.fwc = n - default: - l.nodes[t] = n - } - } - } - if n.next == nil { - n.next = newLevel[T]() - } - l = n.next - } - - n.subs[value] = subject - - s.count++ - s.Unlock() - - return nil -} - -// Match will match all entries to the literal subject. -// It will return a set of results for both normal and queue subscribers. -func (s *GenericSublist[T]) Match(subject string, cb func(T)) { - s.match(subject, cb, true) -} - -// MatchBytes will match all entries to the literal subject. -// It will return a set of results for both normal and queue subscribers. -func (s *GenericSublist[T]) MatchBytes(subject []byte, cb func(T)) { - s.match(string(subject), cb, true) -} - -// HasInterest will return whether or not there is any interest in the subject. -// In cases where more detail is not required, this may be faster than Match. -func (s *GenericSublist[T]) HasInterest(subject string) bool { - return s.hasInterest(subject, true, nil) -} - -// NumInterest will return the number of subs interested in the subject. -// In cases where more detail is not required, this may be faster than Match. -func (s *GenericSublist[T]) NumInterest(subject string) (np int) { - s.hasInterest(subject, true, &np) - return -} - -func (s *GenericSublist[T]) match(subject string, cb func(T), doLock bool) { - tsa := [32]string{} - tokens := tsa[:0] - start := 0 - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - if i-start == 0 { - return - } - tokens = append(tokens, subject[start:i]) - start = i + 1 - } - } - if start >= len(subject) { - return - } - tokens = append(tokens, subject[start:]) - - if doLock { - s.RLock() - defer s.RUnlock() - } - matchLevel(s.root, tokens, cb) -} - -func (s *GenericSublist[T]) hasInterest(subject string, doLock bool, np *int) bool { - tsa := [32]string{} - tokens := tsa[:0] - start := 0 - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - if i-start == 0 { - return false - } - tokens = append(tokens, subject[start:i]) - start = i + 1 - } - } - if start >= len(subject) { - return false - } - tokens = append(tokens, subject[start:]) - - if doLock { - s.RLock() - defer s.RUnlock() - } - return matchLevelForAny(s.root, tokens, np) -} - -func matchLevelForAny[T comparable](l *level[T], toks []string, np *int) bool { - var pwc, n *node[T] - for i, t := range toks { - if l == nil { - return false - } - if l.fwc != nil { - if np != nil { - *np += len(l.fwc.subs) - } - return true - } - if pwc = l.pwc; pwc != nil { - if match := matchLevelForAny(pwc.next, toks[i+1:], np); match { - return true - } - } - n = l.nodes[t] - if n != nil { - l = n.next - } else { - l = nil - } - } - if n != nil { - if np != nil { - *np += len(n.subs) - } - return len(n.subs) > 0 - } - if pwc != nil { - if np != nil { - *np += len(pwc.subs) - } - return len(pwc.subs) > 0 - } - return false -} - -// callbacksForResults will make the necessary callbacks for each -// result in this node. -func callbacksForResults[T comparable](n *node[T], cb func(T)) { - for sub := range n.subs { - cb(sub) - } -} - -// matchLevel is used to recursively descend into the trie. -func matchLevel[T comparable](l *level[T], toks []string, cb func(T)) { - var pwc, n *node[T] - for i, t := range toks { - if l == nil { - return - } - if l.fwc != nil { - callbacksForResults(l.fwc, cb) - } - if pwc = l.pwc; pwc != nil { - matchLevel(pwc.next, toks[i+1:], cb) - } - n = l.nodes[t] - if n != nil { - l = n.next - } else { - l = nil - } - } - if n != nil { - callbacksForResults(n, cb) - } - if pwc != nil { - callbacksForResults(pwc, cb) - } -} - -// lnt is used to track descent into levels for a removal for pruning. -type lnt[T comparable] struct { - l *level[T] - n *node[T] - t string -} - -// Raw low level remove, can do batches with lock held outside. -func (s *GenericSublist[T]) remove(subject string, value T, shouldLock bool) error { - tsa := [32]string{} - tokens := tsa[:0] - start := 0 - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - tokens = append(tokens, subject[start:i]) - start = i + 1 - } - } - tokens = append(tokens, subject[start:]) - - if shouldLock { - s.Lock() - defer s.Unlock() - } - - var sfwc bool - var n *node[T] - l := s.root - - // Track levels for pruning - var lnts [32]lnt[T] - levels := lnts[:0] - - for _, t := range tokens { - lt := len(t) - if lt == 0 || sfwc { - return ErrInvalidSubject - } - if l == nil { - return ErrNotFound - } - if lt > 1 { - n = l.nodes[t] - } else { - switch t[0] { - case pwc: - n = l.pwc - case fwc: - n = l.fwc - sfwc = true - default: - n = l.nodes[t] - } - } - if n != nil { - levels = append(levels, lnt[T]{l, n, t}) - l = n.next - } else { - l = nil - } - } - - if !s.removeFromNode(n, value) { - return ErrNotFound - } - - s.count-- - - for i := len(levels) - 1; i >= 0; i-- { - l, n, t := levels[i].l, levels[i].n, levels[i].t - if n.isEmpty() { - l.pruneNode(n, t) - } - } - - return nil -} - -// Remove will remove a subscription. -func (s *GenericSublist[T]) Remove(subject string, value T) error { - return s.remove(subject, value, true) -} - -// pruneNode is used to prune an empty node from the tree. -func (l *level[T]) pruneNode(n *node[T], t string) { - if n == nil { - return - } - if n == l.fwc { - l.fwc = nil - } else if n == l.pwc { - l.pwc = nil - } else { - delete(l.nodes, t) - } -} - -// isEmpty will test if the node has any entries. Used -// in pruning. -func (n *node[T]) isEmpty() bool { - return len(n.subs) == 0 && (n.next == nil || n.next.numNodes() == 0) -} - -// Return the number of nodes for the given level. -func (l *level[T]) numNodes() int { - num := len(l.nodes) - if l.pwc != nil { - num++ - } - if l.fwc != nil { - num++ - } - return num -} - -// Remove the sub for the given node. -func (s *GenericSublist[T]) removeFromNode(n *node[T], value T) (found bool) { - if n == nil { - return false - } - if _, found = n.subs[value]; found { - delete(n.subs, value) - } - return found -} - -// Count returns the number of subscriptions. -func (s *GenericSublist[T]) Count() uint32 { - s.RLock() - defer s.RUnlock() - return s.count -} - -// numLevels will return the maximum number of levels -// contained in the Sublist tree. -func (s *GenericSublist[T]) numLevels() int { - return visitLevel(s.root, 0) -} - -// visitLevel is used to descend the Sublist tree structure -// recursively. -func visitLevel[T comparable](l *level[T], depth int) int { - if l == nil || l.numNodes() == 0 { - return depth - } - - depth++ - maxDepth := depth - - for _, n := range l.nodes { - if n == nil { - continue - } - newDepth := visitLevel(n.next, depth) - if newDepth > maxDepth { - maxDepth = newDepth - } - } - if l.pwc != nil { - pwcDepth := visitLevel(l.pwc.next, depth) - if pwcDepth > maxDepth { - maxDepth = pwcDepth - } - } - if l.fwc != nil { - fwcDepth := visitLevel(l.fwc.next, depth) - if fwcDepth > maxDepth { - maxDepth = fwcDepth - } - } - return maxDepth -} - -// IntersectStree will match all items in the given subject tree that -// have interest expressed in the given sublist. The callback will only be called -// once for each subject, regardless of overlapping subscriptions in the sublist. -func IntersectStree[T1 any, T2 comparable](st *stree.SubjectTree[T1], sl *GenericSublist[T2], cb func(subj []byte, entry *T1)) { - var _subj [255]byte - intersectStree(st, sl.root, _subj[:0], cb) -} - -func intersectStree[T1 any, T2 comparable](st *stree.SubjectTree[T1], r *level[T2], subj []byte, cb func(subj []byte, entry *T1)) { - if r.numNodes() == 0 { - // For wildcards we can't avoid Match, but if it's a literal subject at - // this point, using Find is considerably cheaper. - if subjectHasWildcard(string(subj)) { - st.Match(subj, cb) - } else if e, ok := st.Find(subj); ok { - cb(subj, e) - } - return - } - nsubj := subj - if len(nsubj) > 0 { - nsubj = append(subj, '.') - } - switch { - case r.fwc != nil: - // We've reached a full wildcard, do a FWC match on the stree at this point - // and don't keep iterating downward. - nsubj := append(nsubj, '>') - st.Match(nsubj, cb) - case r.pwc != nil: - // We've found a partial wildcard. We'll keep iterating downwards, but first - // check whether there's interest at this level (without triggering dupes) and - // match if so. - nsubj := append(nsubj, '*') - if len(r.pwc.subs) > 0 && r.pwc.next != nil && r.pwc.next.numNodes() > 0 { - st.Match(nsubj, cb) - } - intersectStree(st, r.pwc.next, nsubj, cb) - case r.numNodes() > 0: - // Normal node with subject literals, keep iterating. - for t, n := range r.nodes { - nsubj := append(nsubj, t...) - intersectStree(st, n.next, nsubj, cb) - } - } -} - -// Determine if a subject has any wildcard tokens. -func subjectHasWildcard(subject string) bool { - // This one exits earlier then !subjectIsLiteral(subject) - for i, c := range subject { - if c == pwc || c == fwc { - if (i == 0 || subject[i-1] == btsep) && - (i+1 == len(subject) || subject[i+1] == btsep) { - return true - } - } - } - return false -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ipqueue.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ipqueue.go deleted file mode 100644 index 094c522e..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ipqueue.go +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2021-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "errors" - "sync" - "sync/atomic" -) - -const ipQueueDefaultMaxRecycleSize = 4 * 1024 - -// This is a generic intra-process queue. -type ipQueue[T any] struct { - inprogress int64 - sync.Mutex - ch chan struct{} - elts []T - pos int - pool *sync.Pool - sz uint64 // Calculated size (only if calc != nil) - name string - m *sync.Map - ipQueueOpts[T] -} - -type ipQueueOpts[T any] struct { - mrs int // Max recycle size - calc func(e T) uint64 // Calc function for tracking size - msz uint64 // Limit by total calculated size - mlen int // Limit by number of entries -} - -type ipQueueOpt[T any] func(*ipQueueOpts[T]) - -// This option allows to set the maximum recycle size when attempting -// to put back a slice to the pool. -func ipqMaxRecycleSize[T any](max int) ipQueueOpt[T] { - return func(o *ipQueueOpts[T]) { - o.mrs = max - } -} - -// This option enables total queue size counting by passing in a function -// that evaluates the size of each entry as it is pushed/popped. This option -// enables the size() function. -func ipqSizeCalculation[T any](calc func(e T) uint64) ipQueueOpt[T] { - return func(o *ipQueueOpts[T]) { - o.calc = calc - } -} - -// This option allows setting the maximum queue size. Once the limit is -// reached, then push() will stop returning true and no more entries will -// be stored until some more are popped. The ipQueue_SizeCalculation must -// be provided for this to work. -func ipqLimitBySize[T any](max uint64) ipQueueOpt[T] { - return func(o *ipQueueOpts[T]) { - o.msz = max - } -} - -// This option allows setting the maximum queue length. Once the limit is -// reached, then push() will stop returning true and no more entries will -// be stored until some more are popped. -func ipqLimitByLen[T any](max int) ipQueueOpt[T] { - return func(o *ipQueueOpts[T]) { - o.mlen = max - } -} - -var errIPQLenLimitReached = errors.New("IPQ len limit reached") -var errIPQSizeLimitReached = errors.New("IPQ size limit reached") - -func newIPQueue[T any](s *Server, name string, opts ...ipQueueOpt[T]) *ipQueue[T] { - q := &ipQueue[T]{ - ch: make(chan struct{}, 1), - pool: &sync.Pool{ - New: func() any { - // Reason we use pointer to slice instead of slice is explained - // here: https://staticcheck.io/docs/checks#SA6002 - res := make([]T, 0, 32) - return &res - }, - }, - name: name, - m: &s.ipQueues, - ipQueueOpts: ipQueueOpts[T]{ - mrs: ipQueueDefaultMaxRecycleSize, - }, - } - for _, o := range opts { - o(&q.ipQueueOpts) - } - s.ipQueues.Store(name, q) - return q -} - -// Add the element `e` to the queue, notifying the queue channel's `ch` if the -// entry is the first to be added, and returns the length of the queue after -// this element is added. -func (q *ipQueue[T]) push(e T) (int, error) { - q.Lock() - l := len(q.elts) - q.pos - if q.mlen > 0 && l == q.mlen { - q.Unlock() - return l, errIPQLenLimitReached - } - if q.calc != nil { - sz := q.calc(e) - if q.msz > 0 && q.sz+sz > q.msz { - q.Unlock() - return l, errIPQSizeLimitReached - } - q.sz += sz - } - if q.elts == nil { - // What comes out of the pool is already of size 0, so no need for [:0]. - q.elts = *(q.pool.Get().(*[]T)) - } - q.elts = append(q.elts, e) - q.Unlock() - if l == 0 { - select { - case q.ch <- struct{}{}: - default: - } - } - return l + 1, nil -} - -// Returns the whole list of elements currently present in the queue, -// emptying the queue. This should be called after receiving a notification -// from the queue's `ch` notification channel that indicates that there -// is something in the queue. -// However, in cases where `drain()` may be called from another go -// routine, it is possible that a routine is notified that there is -// something, but by the time it calls `pop()`, the drain() would have -// emptied the queue. So the caller should never assume that pop() will -// return a slice of 1 or more, it could return `nil`. -func (q *ipQueue[T]) pop() []T { - if q == nil { - return nil - } - q.Lock() - if len(q.elts)-q.pos == 0 { - q.Unlock() - return nil - } - var elts []T - if q.pos == 0 { - elts = q.elts - } else { - elts = q.elts[q.pos:] - } - q.elts, q.pos, q.sz = nil, 0, 0 - atomic.AddInt64(&q.inprogress, int64(len(elts))) - q.Unlock() - return elts -} - -// Returns the first element from the queue, if any. See comment above -// regarding calling after being notified that there is something and -// the use of drain(). In short, the caller should always check the -// boolean return value to ensure that the value is genuine and not a -// default empty value. -func (q *ipQueue[T]) popOne() (T, bool) { - q.Lock() - l := len(q.elts) - q.pos - if l == 0 { - q.Unlock() - var empty T - return empty, false - } - e := q.elts[q.pos] - if l--; l > 0 { - q.pos++ - if q.calc != nil { - q.sz -= q.calc(e) - } - // We need to re-signal - select { - case q.ch <- struct{}{}: - default: - } - } else { - // We have just emptied the queue, so we can reuse unless it is too big. - if cap(q.elts) <= q.mrs { - q.elts = q.elts[:0] - } else { - q.elts = nil - } - q.pos, q.sz = 0, 0 - } - q.Unlock() - return e, true -} - -// After a pop(), the slice can be recycled for the next push() when -// a first element is added to the queue. -// This will also decrement the "in progress" count with the length -// of the slice. -// WARNING: The caller MUST never reuse `elts`. -func (q *ipQueue[T]) recycle(elts *[]T) { - // If invoked with a nil list, nothing to do. - if elts == nil || *elts == nil { - return - } - // Update the in progress count. - if len(*elts) > 0 { - atomic.AddInt64(&q.inprogress, int64(-(len(*elts)))) - } - // We also don't want to recycle huge slices, so check against the max. - // q.mrs is normally immutable but can be changed, in a safe way, in some tests. - if cap(*elts) > q.mrs { - return - } - (*elts) = (*elts)[:0] - q.pool.Put(elts) -} - -// Returns the current length of the queue. -func (q *ipQueue[T]) len() int { - q.Lock() - defer q.Unlock() - return len(q.elts) - q.pos -} - -// Returns the calculated size of the queue (if ipQueue_SizeCalculation has been -// passed in), otherwise returns zero. -func (q *ipQueue[T]) size() uint64 { - q.Lock() - defer q.Unlock() - return q.sz -} - -// Empty the queue and consumes the notification signal if present. -// Returns the number of items that were drained from the queue. -// Note that this could cause a reader go routine that has been -// notified that there is something in the queue (reading from queue's `ch`) -// may then get nothing if `drain()` is invoked before the `pop()` or `popOne()`. -func (q *ipQueue[T]) drain() int { - if q == nil { - return 0 - } - q.Lock() - olen := len(q.elts) - q.pos - q.elts, q.pos, q.sz = nil, 0, 0 - // Consume the signal if it was present to reduce the chance of a reader - // routine to be think that there is something in the queue... - select { - case <-q.ch: - default: - } - q.Unlock() - return olen -} - -// Since the length of the queue goes to 0 after a pop(), it is good to -// have an insight on how many elements are yet to be processed after a pop(). -// For that reason, the queue maintains a count of elements returned through -// the pop() API. When the caller will call q.recycle(), this count will -// be reduced by the size of the slice returned by pop(). -func (q *ipQueue[T]) inProgress() int64 { - return atomic.LoadInt64(&q.inprogress) -} - -// Remove this queue from the server's map of ipQueues. -// All ipQueue operations (such as push/pop/etc..) are still possible. -func (q *ipQueue[T]) unregister() { - if q == nil { - return - } - q.m.Delete(q.name) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go deleted file mode 100644 index 1dff7f2b..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream.go +++ /dev/null @@ -1,3046 +0,0 @@ -// Copyright 2019-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "crypto/hmac" - "crypto/sha256" - "encoding/binary" - "encoding/hex" - "encoding/json" - "fmt" - "math" - "os" - "path/filepath" - "runtime/debug" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/minio/highwayhash" - "github.com/nats-io/nats-server/v2/server/sysmem" - "github.com/nats-io/nats-server/v2/server/tpm" - "github.com/nats-io/nkeys" - "github.com/nats-io/nuid" -) - -// JetStreamConfig determines this server's configuration. -// MaxMemory and MaxStore are in bytes. -type JetStreamConfig struct { - MaxMemory int64 `json:"max_memory"` - MaxStore int64 `json:"max_storage"` - StoreDir string `json:"store_dir,omitempty"` - SyncInterval time.Duration `json:"sync_interval,omitempty"` - SyncAlways bool `json:"sync_always,omitempty"` - Domain string `json:"domain,omitempty"` - CompressOK bool `json:"compress_ok,omitempty"` - UniqueTag string `json:"unique_tag,omitempty"` - Strict bool `json:"strict,omitempty"` -} - -// Statistics about JetStream for this server. -type JetStreamStats struct { - Memory uint64 `json:"memory"` - Store uint64 `json:"storage"` - ReservedMemory uint64 `json:"reserved_memory"` - ReservedStore uint64 `json:"reserved_storage"` - Accounts int `json:"accounts"` - HAAssets int `json:"ha_assets"` - API JetStreamAPIStats `json:"api"` -} - -type JetStreamAccountLimits struct { - MaxMemory int64 `json:"max_memory"` - MaxStore int64 `json:"max_storage"` - MaxStreams int `json:"max_streams"` - MaxConsumers int `json:"max_consumers"` - MaxAckPending int `json:"max_ack_pending"` - MemoryMaxStreamBytes int64 `json:"memory_max_stream_bytes"` - StoreMaxStreamBytes int64 `json:"storage_max_stream_bytes"` - MaxBytesRequired bool `json:"max_bytes_required"` -} - -type JetStreamTier struct { - Memory uint64 `json:"memory"` - Store uint64 `json:"storage"` - ReservedMemory uint64 `json:"reserved_memory"` - ReservedStore uint64 `json:"reserved_storage"` - Streams int `json:"streams"` - Consumers int `json:"consumers"` - Limits JetStreamAccountLimits `json:"limits"` -} - -// JetStreamAccountStats returns current statistics about the account's JetStream usage. -type JetStreamAccountStats struct { - JetStreamTier // in case tiers are used, reflects totals with limits not set - Domain string `json:"domain,omitempty"` - API JetStreamAPIStats `json:"api"` - Tiers map[string]JetStreamTier `json:"tiers,omitempty"` // indexed by tier name -} - -type JetStreamAPIStats struct { - Level int `json:"level"` - Total uint64 `json:"total"` - Errors uint64 `json:"errors"` - Inflight uint64 `json:"inflight,omitempty"` -} - -// This is for internal accounting for JetStream for this server. -type jetStream struct { - // These are here first because of atomics on 32bit systems. - apiInflight int64 - apiTotal int64 - apiErrors int64 - memReserved int64 - storeReserved int64 - memUsed int64 - storeUsed int64 - queueLimit int64 - clustered int32 - mu sync.RWMutex - srv *Server - config JetStreamConfig - cluster *jetStreamCluster - accounts map[string]*jsAccount - apiSubs *Sublist - started time.Time - - // System level request to purge a stream move - accountPurge *subscription - - // Some bools regarding general state. - metaRecovering bool - standAlone bool - oos bool - shuttingDown bool - - // Atomic versions - disabled atomic.Bool -} - -type remoteUsage struct { - tiers map[string]*jsaUsage // indexed by tier name - api uint64 - err uint64 -} - -type jsaStorage struct { - total jsaUsage - local jsaUsage -} - -// This represents a jetstream enabled account. -// Worth noting that we include the jetstream pointer, this is because -// in general we want to be very efficient when receiving messages on -// an internal sub for a stream, so we will direct link to the stream -// and walk backwards as needed vs multiple hash lookups and locks, etc. -type jsAccount struct { - mu sync.RWMutex - js *jetStream - account *Account - storeDir string - inflight sync.Map - streams map[string]*stream - templates map[string]*streamTemplate - store TemplateStore - - // From server - sendq *ipQueue[*pubMsg] - - // For limiting only running one checkAndSync at a time. - sync atomic.Bool - - // Usage/limits related fields that will be protected by usageMu - usageMu sync.RWMutex - limits map[string]JetStreamAccountLimits // indexed by tierName - usage map[string]*jsaStorage // indexed by tierName - rusage map[string]*remoteUsage // indexed by node id - apiTotal uint64 - apiErrors uint64 - usageApi uint64 - usageErr uint64 - updatesPub string - updatesSub *subscription - lupdate time.Time - utimer *time.Timer - - // Which account to send NRG traffic into. Empty string is system account. - nrgAccount string -} - -// Track general usage for this account. -type jsaUsage struct { - mem int64 - store int64 -} - -// EnableJetStream will enable JetStream support on this server with the given configuration. -// A nil configuration will dynamically choose the limits and temporary file storage directory. -func (s *Server) EnableJetStream(config *JetStreamConfig) error { - if s.JetStreamEnabled() { - return fmt.Errorf("jetstream already enabled") - } - - s.Noticef("Starting JetStream") - if config == nil || config.MaxMemory <= 0 || config.MaxStore <= 0 { - var storeDir, domain, uniqueTag string - var maxStore, maxMem int64 - if config != nil { - storeDir, domain, uniqueTag = config.StoreDir, config.Domain, config.UniqueTag - maxStore, maxMem = config.MaxStore, config.MaxMemory - } - config = s.dynJetStreamConfig(storeDir, maxStore, maxMem) - if maxMem > 0 { - config.MaxMemory = maxMem - } - if domain != _EMPTY_ { - config.Domain = domain - } - if uniqueTag != _EMPTY_ { - config.UniqueTag = uniqueTag - } - s.Debugf("JetStream creating dynamic configuration - %s memory, %s disk", friendlyBytes(config.MaxMemory), friendlyBytes(config.MaxStore)) - } else if config.StoreDir != _EMPTY_ { - config.StoreDir = filepath.Join(config.StoreDir, JetStreamStoreDir) - } - - cfg := *config - if cfg.StoreDir == _EMPTY_ { - cfg.StoreDir = filepath.Join(os.TempDir(), JetStreamStoreDir) - s.Warnf("Temporary storage directory used, data could be lost on system reboot") - } - - // We will consistently place the 'jetstream' directory under the storedir that was handed to us. Prior to 2.2.3 though - // we could have a directory on disk without the 'jetstream' directory. This will check and fix if needed. - if err := s.checkStoreDir(&cfg); err != nil { - return err - } - - return s.enableJetStream(cfg) -} - -// Function signature to generate a key encryption key. -type keyGen func(context []byte) ([]byte, error) - -// Return a key generation function or nil if encryption not enabled. -// keyGen defined in filestore.go - keyGen func(iv, context []byte) []byte -func (s *Server) jsKeyGen(jsKey, info string) keyGen { - if ek := jsKey; ek != _EMPTY_ { - return func(context []byte) ([]byte, error) { - h := hmac.New(sha256.New, []byte(ek)) - if _, err := h.Write([]byte(info)); err != nil { - return nil, err - } - if _, err := h.Write(context); err != nil { - return nil, err - } - return h.Sum(nil), nil - } - } - return nil -} - -// Decode the encrypted metafile. -func (s *Server) decryptMeta(sc StoreCipher, ekey, buf []byte, acc, context string) ([]byte, bool, error) { - if len(ekey) < minMetaKeySize { - return nil, false, errBadKeySize - } - var osc StoreCipher - switch sc { - case AES: - osc = ChaCha - case ChaCha: - osc = AES - } - type prfWithCipher struct { - keyGen - StoreCipher - } - var prfs []prfWithCipher - if prf := s.jsKeyGen(s.getOpts().JetStreamKey, acc); prf == nil { - return nil, false, errNoEncryption - } else { - // First of all, try our current encryption keys with both - // store cipher algorithms. - prfs = append(prfs, prfWithCipher{prf, sc}) - prfs = append(prfs, prfWithCipher{prf, osc}) - } - if prf := s.jsKeyGen(s.getOpts().JetStreamOldKey, acc); prf != nil { - // Then, if we have an old encryption key, try with also with - // both store cipher algorithms. - prfs = append(prfs, prfWithCipher{prf, sc}) - prfs = append(prfs, prfWithCipher{prf, osc}) - } - - for i, prf := range prfs { - rb, err := prf.keyGen([]byte(context)) - if err != nil { - continue - } - kek, err := genEncryptionKey(prf.StoreCipher, rb) - if err != nil { - continue - } - ns := kek.NonceSize() - seed, err := kek.Open(nil, ekey[:ns], ekey[ns:], nil) - if err != nil { - continue - } - aek, err := genEncryptionKey(prf.StoreCipher, seed) - if err != nil { - continue - } - if aek.NonceSize() != kek.NonceSize() { - continue - } - plain, err := aek.Open(nil, buf[:ns], buf[ns:], nil) - if err != nil { - continue - } - return plain, i > 0, nil - } - return nil, false, fmt.Errorf("unable to recover keys") -} - -// Check to make sure directory has the jetstream directory. -// We will have it properly configured here now regardless, so need to look inside. -func (s *Server) checkStoreDir(cfg *JetStreamConfig) error { - fis, _ := os.ReadDir(cfg.StoreDir) - // If we have nothing underneath us, could be just starting new, but if we see this we can check. - if len(fis) != 0 { - return nil - } - // Let's check the directory above. If it has us 'jetstream' but also other stuff that we can - // identify as accounts then we can fix. - fis, _ = os.ReadDir(filepath.Dir(cfg.StoreDir)) - // If just one that is us 'jetstream' and all is ok. - if len(fis) == 1 { - return nil - } - - haveJetstreamDir := false - for _, fi := range fis { - if fi.Name() == JetStreamStoreDir { - haveJetstreamDir = true - break - } - } - - for _, fi := range fis { - // Skip the 'jetstream' directory. - if fi.Name() == JetStreamStoreDir { - continue - } - // Let's see if this is an account. - if accName := fi.Name(); accName != _EMPTY_ { - _, ok := s.accounts.Load(accName) - if !ok && s.AccountResolver() != nil && nkeys.IsValidPublicAccountKey(accName) { - // Account is not local but matches the NKEY account public key, - // this is enough indication to move this directory, no need to - // fetch the account. - ok = true - } - // If this seems to be an account go ahead and move the directory. This will include all assets - // like streams and consumers. - if ok { - if !haveJetstreamDir { - err := os.Mkdir(filepath.Join(filepath.Dir(cfg.StoreDir), JetStreamStoreDir), defaultDirPerms) - if err != nil { - return err - } - haveJetstreamDir = true - } - old := filepath.Join(filepath.Dir(cfg.StoreDir), fi.Name()) - new := filepath.Join(cfg.StoreDir, fi.Name()) - s.Noticef("JetStream relocated account %q to %q", old, new) - if err := os.Rename(old, new); err != nil { - return err - } - } - } - } - - return nil -} - -// This function sets/updates the jetstream encryption key and cipher based -// on options. If the TPM options have been specified, a key is generated -// and sealed by the TPM. -func (s *Server) initJetStreamEncryption() (err error) { - opts := s.getOpts() - - // The TPM settings and other encryption settings are mutually exclusive. - if opts.JetStreamKey != _EMPTY_ && opts.JetStreamTpm.KeysFile != _EMPTY_ { - return fmt.Errorf("JetStream encryption key may not be used with TPM options") - } - // if we are using the standard method to set the encryption key just return and carry on. - if opts.JetStreamKey != _EMPTY_ { - return nil - } - // if the tpm options are not used then no encryption has been configured and return. - if opts.JetStreamTpm.KeysFile == _EMPTY_ { - return nil - } - - if opts.JetStreamTpm.Pcr == 0 { - // Default PCR to use in the TPM. Values can be 0-23, and most platforms - // reserve values 0-12 for the OS, boot locker, disc encryption, etc. - // 16 used for debugging. In sticking to NATS tradition, we'll use 22 - // as the default with the option being configurable. - opts.JetStreamTpm.Pcr = 22 - } - - // Using the TPM to generate or get the encryption key and update the encryption options. - opts.JetStreamKey, err = tpm.LoadJetStreamEncryptionKeyFromTPM(opts.JetStreamTpm.SrkPassword, - opts.JetStreamTpm.KeysFile, opts.JetStreamTpm.KeyPassword, opts.JetStreamTpm.Pcr) - - return err -} - -// enableJetStream will start up the JetStream subsystem. -func (s *Server) enableJetStream(cfg JetStreamConfig) error { - js := &jetStream{srv: s, config: cfg, accounts: make(map[string]*jsAccount), apiSubs: NewSublistNoCache()} - s.gcbMu.Lock() - if s.gcbOutMax = s.getOpts().JetStreamMaxCatchup; s.gcbOutMax == 0 { - s.gcbOutMax = defaultMaxTotalCatchupOutBytes - } - s.gcbMu.Unlock() - - // TODO: Not currently reloadable. - atomic.StoreInt64(&js.queueLimit, s.getOpts().JetStreamRequestQueueLimit) - - s.js.Store(js) - - // FIXME(dlc) - Allow memory only operation? - if stat, err := os.Stat(cfg.StoreDir); os.IsNotExist(err) { - if err := os.MkdirAll(cfg.StoreDir, defaultDirPerms); err != nil { - return fmt.Errorf("could not create storage directory - %v", err) - } - } else { - // Make sure its a directory and that we can write to it. - if stat == nil || !stat.IsDir() { - return fmt.Errorf("storage directory is not a directory") - } - tmpfile, err := os.CreateTemp(cfg.StoreDir, "_test_") - if err != nil { - return fmt.Errorf("storage directory is not writable") - } - tmpfile.Close() - os.Remove(tmpfile.Name()) - } - - if err := s.initJetStreamEncryption(); err != nil { - return err - } - - // JetStream is an internal service so we need to make sure we have a system account. - // This system account will export the JetStream service endpoints. - if s.SystemAccount() == nil { - s.SetDefaultSystemAccount() - } - - opts := s.getOpts() - if !opts.DisableJetStreamBanner { - s.Noticef(" _ ___ _____ ___ _____ ___ ___ _ __ __") - s.Noticef(" _ | | __|_ _/ __|_ _| _ \\ __| /_\\ | \\/ |") - s.Noticef("| || | _| | | \\__ \\ | | | / _| / _ \\| |\\/| |") - s.Noticef(" \\__/|___| |_| |___/ |_| |_|_\\___/_/ \\_\\_| |_|") - s.Noticef("") - s.Noticef(" https://docs.nats.io/jetstream") - s.Noticef("") - } - s.Noticef("---------------- JETSTREAM ----------------") - - if cfg.Strict { - s.Noticef(" Strict: %t", cfg.Strict) - } - - s.Noticef(" Max Memory: %s", friendlyBytes(cfg.MaxMemory)) - s.Noticef(" Max Storage: %s", friendlyBytes(cfg.MaxStore)) - s.Noticef(" Store Directory: \"%s\"", cfg.StoreDir) - if cfg.Domain != _EMPTY_ { - s.Noticef(" Domain: %s", cfg.Domain) - } - - if ek := opts.JetStreamKey; ek != _EMPTY_ { - s.Noticef(" Encryption: %s", opts.JetStreamCipher) - } - if opts.JetStreamTpm.KeysFile != _EMPTY_ { - s.Noticef(" TPM File: %q, Pcr: %d", opts.JetStreamTpm.KeysFile, - opts.JetStreamTpm.Pcr) - } - s.Noticef(" API Level: %d", JSApiLevel) - s.Noticef("-------------------------------------------") - - // Setup our internal subscriptions. - if err := s.setJetStreamExportSubs(); err != nil { - return fmt.Errorf("setting up internal jetstream subscriptions failed: %v", err) - } - - // Setup our internal system exports. - s.Debugf(" Exports:") - s.Debugf(" %s", jsAllAPI) - s.setupJetStreamExports() - - standAlone, canExtend := s.standAloneMode(), s.canExtendOtherDomain() - if standAlone && canExtend && s.getOpts().JetStreamExtHint != jsWillExtend { - canExtend = false - s.Noticef("Standalone server started in clustered mode do not support extending domains") - s.Noticef(`Manually disable standalone mode by setting the JetStream Option "extension_hint: %s"`, jsWillExtend) - } - - // Indicate if we will be standalone for checking resource reservations, etc. - js.setJetStreamStandAlone(standAlone && !canExtend) - - // Enable accounts and restore state before starting clustering. - if err := s.enableJetStreamAccounts(); err != nil { - return err - } - - // If we are in clustered mode go ahead and start the meta controller. - if !standAlone || canExtend { - if err := s.enableJetStreamClustering(); err != nil { - return err - } - // Set our atomic bool to clustered. - s.jsClustered.Store(true) - } - - // Mark when we are up and running. - js.setStarted() - - return nil -} - -const jsNoExtend = "no_extend" -const jsWillExtend = "will_extend" - -// This will check if we have a solicited leafnode that shares the system account -// and extension is not manually disabled -func (s *Server) canExtendOtherDomain() bool { - opts := s.getOpts() - sysAcc := s.SystemAccount().GetName() - for _, r := range opts.LeafNode.Remotes { - if r.LocalAccount == sysAcc { - for _, denySub := range r.DenyImports { - if subjectIsSubsetMatch(denySub, raftAllSubj) { - return false - } - } - return true - } - } - return false -} - -func (s *Server) updateJetStreamInfoStatus(enabled bool) { - s.mu.Lock() - s.info.JetStream = enabled - s.mu.Unlock() -} - -// restartJetStream will try to re-enable JetStream during a reload if it had been disabled during runtime. -func (s *Server) restartJetStream() error { - opts := s.getOpts() - cfg := JetStreamConfig{ - StoreDir: opts.StoreDir, - SyncInterval: opts.SyncInterval, - SyncAlways: opts.SyncAlways, - MaxMemory: opts.JetStreamMaxMemory, - MaxStore: opts.JetStreamMaxStore, - Domain: opts.JetStreamDomain, - Strict: opts.JetStreamStrict, - } - s.Noticef("Restarting JetStream") - err := s.EnableJetStream(&cfg) - if err != nil { - s.Warnf("Can't start JetStream: %v", err) - return s.DisableJetStream() - } - s.updateJetStreamInfoStatus(true) - return nil -} - -// checkJetStreamExports will check if we have the JS exports setup -// on the system account, and if not go ahead and set them up. -func (s *Server) checkJetStreamExports() { - if sacc := s.SystemAccount(); sacc != nil { - sacc.mu.RLock() - se := sacc.getServiceExport(jsAllAPI) - sacc.mu.RUnlock() - if se == nil { - s.setupJetStreamExports() - } - } -} - -func (s *Server) setupJetStreamExports() { - // Setup our internal system export. - if err := s.SystemAccount().AddServiceExport(jsAllAPI, nil); err != nil { - s.Warnf("Error setting up jetstream service exports: %v", err) - } -} - -func (s *Server) jetStreamOOSPending() (wasPending bool) { - if js := s.getJetStream(); js != nil { - js.mu.Lock() - wasPending = js.oos - js.oos = true - js.mu.Unlock() - } - return wasPending -} - -func (s *Server) setJetStreamDisabled() { - if js := s.getJetStream(); js != nil { - js.disabled.Store(true) - } -} - -func (s *Server) handleOutOfSpace(mset *stream) { - if s.JetStreamEnabled() && !s.jetStreamOOSPending() { - var stream string - if mset != nil { - stream = mset.name() - s.Errorf("JetStream out of %s resources, will be DISABLED", mset.Store().Type()) - } else { - s.Errorf("JetStream out of resources, will be DISABLED") - } - - go s.DisableJetStream() - - adv := &JSServerOutOfSpaceAdvisory{ - TypedEvent: TypedEvent{ - Type: JSServerOutOfStorageAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Server: s.Name(), - ServerID: s.ID(), - Stream: stream, - Cluster: s.cachedClusterName(), - Domain: s.getOpts().JetStreamDomain, - } - s.publishAdvisory(nil, JSAdvisoryServerOutOfStorage, adv) - } -} - -// DisableJetStream will turn off JetStream and signals in clustered mode -// to have the metacontroller remove us from the peer list. -func (s *Server) DisableJetStream() error { - if !s.JetStreamEnabled() { - return nil - } - - s.setJetStreamDisabled() - - if s.JetStreamIsClustered() { - isLeader := s.JetStreamIsLeader() - js, cc := s.getJetStreamCluster() - if js == nil { - s.shutdownJetStream() - return nil - } - js.mu.RLock() - meta := cc.meta - js.mu.RUnlock() - - if meta != nil { - if isLeader { - s.Warnf("JetStream initiating meta leader transfer") - meta.StepDown() - select { - case <-s.quitCh: - return nil - case <-time.After(2 * time.Second): - } - if !s.JetStreamIsCurrent() { - s.Warnf("JetStream timeout waiting for meta leader transfer") - } - } - meta.Delete() - } - } - - // Update our info status. - s.updateJetStreamInfoStatus(false) - - // Normal shutdown. - s.shutdownJetStream() - - // Shut down the RAFT groups. - s.shutdownRaftNodes() - - return nil -} - -func (s *Server) enableJetStreamAccounts() error { - // If we have no configured accounts setup then setup imports on global account. - if s.globalAccountOnly() { - gacc := s.GlobalAccount() - gacc.mu.Lock() - if len(gacc.jsLimits) == 0 { - gacc.jsLimits = defaultJSAccountTiers - } - gacc.mu.Unlock() - if err := s.configJetStream(gacc); err != nil { - return err - } - } else if err := s.configAllJetStreamAccounts(); err != nil { - return fmt.Errorf("Error enabling jetstream on configured accounts: %v", err) - } - return nil -} - -// enableAllJetStreamServiceImportsAndMappings turns on all service imports and mappings for jetstream for this account. -func (a *Account) enableAllJetStreamServiceImportsAndMappings() error { - a.mu.RLock() - s := a.srv - a.mu.RUnlock() - - if s == nil { - return fmt.Errorf("jetstream account not registered") - } - - if !a.serviceImportExists(jsAllAPI) { - // Capture si so we can turn on implicit sharing with JetStream layer. - // Make sure to set "to" otherwise will incur performance slow down. - si, err := a.addServiceImport(s.SystemAccount(), jsAllAPI, jsAllAPI, nil) - if err != nil { - return fmt.Errorf("Error setting up jetstream service imports for account: %v", err) - } - a.mu.Lock() - si.share = true - a.mu.Unlock() - } - - // Check if we have a Domain specified. - // If so add in a subject mapping that will allow local connected clients to reach us here as well. - if opts := s.getOpts(); opts.JetStreamDomain != _EMPTY_ { - mappings := generateJSMappingTable(opts.JetStreamDomain) - a.mu.RLock() - for _, m := range a.mappings { - delete(mappings, m.src) - } - a.mu.RUnlock() - for src, dest := range mappings { - if err := a.AddMapping(src, dest); err != nil { - s.Errorf("Error adding JetStream domain mapping: %v", err) - } - } - } - - return nil -} - -// enableJetStreamInfoServiceImportOnly will enable the single service import responder. -// Should we do them all regardless? -func (a *Account) enableJetStreamInfoServiceImportOnly() error { - // Check if this import would be overshadowed. This can happen when accounts - // are importing from another account for JS access. - if a.serviceImportShadowed(JSApiAccountInfo) { - return nil - } - - return a.enableAllJetStreamServiceImportsAndMappings() -} - -func (s *Server) configJetStream(acc *Account) error { - if acc == nil { - return nil - } - acc.mu.RLock() - jsLimits := acc.jsLimits - acc.mu.RUnlock() - if jsLimits != nil { - // Check if already enabled. This can be during a reload. - if acc.JetStreamEnabled() { - if err := acc.enableAllJetStreamServiceImportsAndMappings(); err != nil { - return err - } - if err := acc.UpdateJetStreamLimits(jsLimits); err != nil { - return err - } - } else { - if err := acc.EnableJetStream(jsLimits); err != nil { - return err - } - if s.gateway.enabled { - s.switchAccountToInterestMode(acc.GetName()) - } - } - } else if acc != s.SystemAccount() { - if acc.JetStreamEnabled() { - acc.DisableJetStream() - } - // We will setup basic service imports to respond to - // requests if JS is enabled for this account. - if err := acc.enableJetStreamInfoServiceImportOnly(); err != nil { - return err - } - } - return nil -} - -// configAllJetStreamAccounts walk all configured accounts and turn on jetstream if requested. -func (s *Server) configAllJetStreamAccounts() error { - // Check to see if system account has been enabled. We could arrive here via reload and - // a non-default system account. - s.checkJetStreamExports() - - // Bail if server not enabled. If it was enabled and a reload turns it off - // that will be handled elsewhere. - js := s.getJetStream() - if js == nil { - return nil - } - - // Snapshot into our own list. Might not be needed. - s.mu.RLock() - if s.sys != nil { - // clustered stream removal will perform this cleanup as well - // this is mainly for initial cleanup - saccName := s.sys.account.Name - accStoreDirs, _ := os.ReadDir(js.config.StoreDir) - for _, acc := range accStoreDirs { - if accName := acc.Name(); accName != saccName { - // no op if not empty - accDir := filepath.Join(js.config.StoreDir, accName) - os.Remove(filepath.Join(accDir, streamsDir)) - os.Remove(accDir) - } - } - } - - var jsAccounts []*Account - s.accounts.Range(func(k, v any) bool { - jsAccounts = append(jsAccounts, v.(*Account)) - return true - }) - accounts := &s.accounts - s.mu.RUnlock() - - // Process any jetstream enabled accounts here. These will be accounts we are - // already aware of at startup etc. - for _, acc := range jsAccounts { - if err := s.configJetStream(acc); err != nil { - return err - } - } - - // Now walk all the storage we have and resolve any accounts that we did not process already. - // This is important in resolver/operator models. - fis, _ := os.ReadDir(js.config.StoreDir) - for _, fi := range fis { - if accName := fi.Name(); accName != _EMPTY_ { - // Only load up ones not already loaded since they are processed above. - if _, ok := accounts.Load(accName); !ok { - if acc, err := s.lookupAccount(accName); err != nil && acc != nil { - if err := s.configJetStream(acc); err != nil { - return err - } - } - } - } - } - - return nil -} - -// Mark our started time. -func (js *jetStream) setStarted() { - js.mu.Lock() - defer js.mu.Unlock() - js.started = time.Now() -} - -func (js *jetStream) isEnabled() bool { - if js == nil { - return false - } - return !js.disabled.Load() -} - -// Mark that we will be in standlone mode. -func (js *jetStream) setJetStreamStandAlone(isStandAlone bool) { - if js == nil { - return - } - js.mu.Lock() - defer js.mu.Unlock() - if js.standAlone = isStandAlone; js.standAlone { - // Update our server atomic. - js.srv.isMetaLeader.Store(true) - js.accountPurge, _ = js.srv.systemSubscribe(JSApiAccountPurge, _EMPTY_, false, nil, js.srv.jsLeaderAccountPurgeRequest) - } else if js.accountPurge != nil { - js.srv.sysUnsubscribe(js.accountPurge) - } -} - -// JetStreamEnabled reports if jetstream is enabled for this server. -func (s *Server) JetStreamEnabled() bool { - return s.getJetStream().isEnabled() -} - -// JetStreamEnabledForDomain will report if any servers have JetStream enabled within this domain. -func (s *Server) JetStreamEnabledForDomain() bool { - if s.JetStreamEnabled() { - return true - } - - var jsFound bool - // If we are here we do not have JetStream enabled for ourselves, but we need to check all connected servers. - // TODO(dlc) - Could optimize and memoize this. - s.nodeToInfo.Range(func(k, v any) bool { - // This should not be dependent on online status, so only check js. - if v.(nodeInfo).js { - jsFound = true - return false - } - return true - }) - - return jsFound -} - -// Will signal that all pull requests for consumers on this server are now invalid. -func (s *Server) signalPullConsumers() { - js := s.getJetStream() - if js == nil { - return - } - - js.mu.RLock() - defer js.mu.RUnlock() - - // In case we have stale pending requests. - const hdr = "NATS/1.0 409 Server Shutdown\r\n" + JSPullRequestPendingMsgs + ": %d\r\n" + JSPullRequestPendingBytes + ": %d\r\n\r\n" - var didSend bool - - for _, jsa := range js.accounts { - jsa.mu.RLock() - for _, stream := range jsa.streams { - stream.mu.RLock() - for _, o := range stream.consumers { - o.mu.RLock() - // Only signal on R1. - if o.cfg.Replicas <= 1 { - for reply, wr := range o.pendingRequests() { - shdr := fmt.Sprintf(hdr, wr.n, wr.b) - o.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, []byte(shdr), nil, nil, 0)) - didSend = true - } - } - o.mu.RUnlock() - } - stream.mu.RUnlock() - } - jsa.mu.RUnlock() - } - // Give time for migration information to make it out of our server. - if didSend { - time.Sleep(50 * time.Millisecond) - } -} - -// Helper for determining if we are shutting down. -func (js *jetStream) isShuttingDown() bool { - js.mu.RLock() - defer js.mu.RUnlock() - return js.shuttingDown -} - -// Shutdown jetstream for this server. -func (s *Server) shutdownJetStream() { - js := s.getJetStream() - if js == nil { - return - } - - s.Noticef("Initiating JetStream Shutdown...") - defer s.Noticef("JetStream Shutdown") - - // If we have folks blocked on sync requests, unblock. - // Send 1 is enough, but use select in case they were all present. - select { - case s.syncOutSem <- struct{}{}: - default: - } - - var _a [512]*Account - accounts := _a[:0] - - js.mu.Lock() - // Collect accounts. - for _, jsa := range js.accounts { - if a := jsa.acc(); a != nil { - accounts = append(accounts, a) - } - } - accPurgeSub := js.accountPurge - js.accountPurge = nil - // Signal we are shutting down. - js.shuttingDown = true - js.mu.Unlock() - - if accPurgeSub != nil { - s.sysUnsubscribe(accPurgeSub) - } - - for _, a := range accounts { - a.removeJetStream() - } - - s.js.Store(nil) - - js.mu.Lock() - js.accounts = nil - - var qch chan struct{} - - if cc := js.cluster; cc != nil { - if cc.qch != nil { - qch = cc.qch - cc.qch = nil - } - js.stopUpdatesSub() - if cc.c != nil { - cc.c.closeConnection(ClientClosed) - cc.c = nil - } - cc.meta = nil - // Set our atomic bool to false. - s.jsClustered.Store(false) - } - js.mu.Unlock() - - // If we were clustered signal the monitor cluster go routine. - // We will wait for a bit for it to close. - // Do this without the lock. - if qch != nil { - select { - case qch <- struct{}{}: - select { - case <-qch: - case <-time.After(2 * time.Second): - s.Warnf("Did not receive signal for successful shutdown of cluster routine") - } - default: - } - } -} - -// JetStreamConfig will return the current config. Useful if the system -// created a dynamic configuration. A copy is returned. -func (s *Server) JetStreamConfig() *JetStreamConfig { - var c *JetStreamConfig - if js := s.getJetStream(); js != nil { - copy := js.config - c = &(copy) - } - return c -} - -// StoreDir returns the current JetStream directory. -func (s *Server) StoreDir() string { - js := s.getJetStream() - if js == nil { - return _EMPTY_ - } - return js.config.StoreDir -} - -// JetStreamNumAccounts returns the number of enabled accounts this server is tracking. -func (s *Server) JetStreamNumAccounts() int { - js := s.getJetStream() - if js == nil { - return 0 - } - js.mu.Lock() - defer js.mu.Unlock() - return len(js.accounts) -} - -// JetStreamReservedResources returns the reserved resources if JetStream is enabled. -func (s *Server) JetStreamReservedResources() (int64, int64, error) { - js := s.getJetStream() - if js == nil { - return -1, -1, NewJSNotEnabledForAccountError() - } - js.mu.RLock() - defer js.mu.RUnlock() - return js.memReserved, js.storeReserved, nil -} - -func (s *Server) getJetStream() *jetStream { - return s.js.Load() -} - -func (a *Account) assignJetStreamLimits(limits map[string]JetStreamAccountLimits) { - a.mu.Lock() - a.jsLimits = limits - a.mu.Unlock() -} - -// EnableJetStream will enable JetStream on this account with the defined limits. -// This is a helper for JetStreamEnableAccount. -func (a *Account) EnableJetStream(limits map[string]JetStreamAccountLimits) error { - a.mu.RLock() - s := a.srv - a.mu.RUnlock() - - if s == nil { - return fmt.Errorf("jetstream account not registered") - } - - if s.SystemAccount() == a { - return fmt.Errorf("jetstream can not be enabled on the system account") - } - - s.mu.RLock() - if s.sys == nil { - s.mu.RUnlock() - return ErrServerNotRunning - } - sendq := s.sys.sendq - s.mu.RUnlock() - - // No limits means we dynamically set up limits. - // We also place limits here so we know that the account is configured for JetStream. - if len(limits) == 0 { - limits = defaultJSAccountTiers - } - - a.assignJetStreamLimits(limits) - - js := s.getJetStream() - if js == nil { - return NewJSNotEnabledError() - } - - js.mu.Lock() - if jsa, ok := js.accounts[a.Name]; ok { - a.mu.Lock() - a.js = jsa - a.mu.Unlock() - js.mu.Unlock() - return a.enableAllJetStreamServiceImportsAndMappings() - } - - // Check the limits against existing reservations. - if err := js.sufficientResources(limits); err != nil { - js.mu.Unlock() - return err - } - - sysNode := s.Node() - - jsa := &jsAccount{js: js, account: a, limits: limits, streams: make(map[string]*stream), sendq: sendq, usage: make(map[string]*jsaStorage)} - jsa.storeDir = filepath.Join(js.config.StoreDir, a.Name) - - // A single server does not need to do the account updates at this point. - if js.cluster != nil || !s.standAloneMode() { - jsa.usageMu.Lock() - jsa.utimer = time.AfterFunc(usageTick, jsa.sendClusterUsageUpdateTimer) - // Cluster mode updates to resource usage. System internal prevents echos. - jsa.updatesPub = fmt.Sprintf(jsaUpdatesPubT, a.Name, sysNode) - jsa.updatesSub, _ = s.sysSubscribe(fmt.Sprintf(jsaUpdatesSubT, a.Name), jsa.remoteUpdateUsage) - jsa.usageMu.Unlock() - } - - js.accounts[a.Name] = jsa - // Stamp inside account as well. Needs to be done under js's lock. - a.mu.Lock() - a.js = jsa - a.mu.Unlock() - js.mu.Unlock() - - // Create the proper imports here. - if err := a.enableAllJetStreamServiceImportsAndMappings(); err != nil { - return err - } - - s.Debugf("Enabled JetStream for account %q", a.Name) - if l, ok := limits[_EMPTY_]; ok { - s.Debugf(" Max Memory: %s", friendlyBytes(l.MaxMemory)) - s.Debugf(" Max Storage: %s", friendlyBytes(l.MaxStore)) - } else { - for t, l := range limits { - s.Debugf(" Tier: %s", t) - s.Debugf(" Max Memory: %s", friendlyBytes(l.MaxMemory)) - s.Debugf(" Max Storage: %s", friendlyBytes(l.MaxStore)) - } - } - - // Clean up any old snapshots that were orphaned while staging. - os.RemoveAll(filepath.Join(js.config.StoreDir, snapStagingDir)) - - sdir := filepath.Join(jsa.storeDir, streamsDir) - if _, err := os.Stat(sdir); os.IsNotExist(err) { - if err := os.MkdirAll(sdir, defaultDirPerms); err != nil { - return fmt.Errorf("could not create storage streams directory - %v", err) - } - // Just need to make sure we can write to the directory. - // Remove the directory will create later if needed. - os.RemoveAll(sdir) - // when empty remove parent directory, which may have been created as well - os.Remove(jsa.storeDir) - } else { - // Restore any state here. - s.Debugf("Recovering JetStream state for account %q", a.Name) - } - - // Check templates first since messsage sets will need proper ownership. - // FIXME(dlc) - Make this consistent. - tdir := filepath.Join(jsa.storeDir, tmplsDir) - if stat, err := os.Stat(tdir); err == nil && stat.IsDir() { - key := sha256.Sum256([]byte("templates")) - hh, err := highwayhash.New64(key[:]) - if err != nil { - return err - } - fis, _ := os.ReadDir(tdir) - for _, fi := range fis { - metafile := filepath.Join(tdir, fi.Name(), JetStreamMetaFile) - metasum := filepath.Join(tdir, fi.Name(), JetStreamMetaFileSum) - buf, err := os.ReadFile(metafile) - if err != nil { - s.Warnf(" Error reading StreamTemplate metafile %q: %v", metasum, err) - continue - } - if _, err := os.Stat(metasum); os.IsNotExist(err) { - s.Warnf(" Missing StreamTemplate checksum for %q", metasum) - continue - } - sum, err := os.ReadFile(metasum) - if err != nil { - s.Warnf(" Error reading StreamTemplate checksum %q: %v", metasum, err) - continue - } - hh.Reset() - hh.Write(buf) - checksum := hex.EncodeToString(hh.Sum(nil)) - if checksum != string(sum) { - s.Warnf(" StreamTemplate checksums do not match %q vs %q", sum, checksum) - continue - } - var cfg StreamTemplateConfig - if err := json.Unmarshal(buf, &cfg); err != nil { - s.Warnf(" Error unmarshalling StreamTemplate metafile: %v", err) - continue - } - cfg.Config.Name = _EMPTY_ - if _, err := a.addStreamTemplate(&cfg); err != nil { - s.Warnf(" Error recreating StreamTemplate %q: %v", cfg.Name, err) - continue - } - } - } - - // Collect consumers, do after all streams. - type ce struct { - mset *stream - odir string - } - var consumers []*ce - - // Collect any interest policy streams to check for - // https://github.com/nats-io/nats-server/issues/3612 - var ipstreams []*stream - - // Remember if we should be encrypted and what cipher we think we should use. - encrypted := s.getOpts().JetStreamKey != _EMPTY_ - plaintext := true - sc := s.getOpts().JetStreamCipher - - // Now recover the streams. - fis, _ := os.ReadDir(sdir) - for _, fi := range fis { - mdir := filepath.Join(sdir, fi.Name()) - // Check for partially deleted streams. They are marked with "." prefix. - if strings.HasPrefix(fi.Name(), tsep) { - go os.RemoveAll(mdir) - continue - } - key := sha256.Sum256([]byte(fi.Name())) - hh, err := highwayhash.New64(key[:]) - if err != nil { - return err - } - metafile := filepath.Join(mdir, JetStreamMetaFile) - metasum := filepath.Join(mdir, JetStreamMetaFileSum) - if _, err := os.Stat(metafile); os.IsNotExist(err) { - s.Warnf(" Missing stream metafile for %q", metafile) - continue - } - buf, err := os.ReadFile(metafile) - if err != nil { - s.Warnf(" Error reading metafile %q: %v", metafile, err) - continue - } - if _, err := os.Stat(metasum); os.IsNotExist(err) { - s.Warnf(" Missing stream checksum file %q", metasum) - continue - } - sum, err := os.ReadFile(metasum) - if err != nil { - s.Warnf(" Error reading Stream metafile checksum %q: %v", metasum, err) - continue - } - hh.Write(buf) - checksum := hex.EncodeToString(hh.Sum(nil)) - if checksum != string(sum) { - s.Warnf(" Stream metafile %q: checksums do not match %q vs %q", metafile, sum, checksum) - continue - } - - // Track if we are converting ciphers. - var convertingCiphers bool - - // Check if we are encrypted. - keyFile := filepath.Join(mdir, JetStreamMetaFileKey) - keyBuf, err := os.ReadFile(keyFile) - if err == nil { - s.Debugf(" Stream metafile is encrypted, reading encrypted keyfile") - if len(keyBuf) < minMetaKeySize { - s.Warnf(" Bad stream encryption key length of %d", len(keyBuf)) - continue - } - // Decode the buffer before proceeding. - var nbuf []byte - nbuf, convertingCiphers, err = s.decryptMeta(sc, keyBuf, buf, a.Name, fi.Name()) - if err != nil { - s.Warnf(" Error decrypting our stream metafile: %v", err) - continue - } - buf = nbuf - plaintext = false - } - - var cfg FileStreamInfo - if err := json.Unmarshal(buf, &cfg); err != nil { - s.Warnf(" Error unmarshalling stream metafile %q: %v", metafile, err) - continue - } - - if cfg.Template != _EMPTY_ { - if err := jsa.addStreamNameToTemplate(cfg.Template, cfg.Name); err != nil { - s.Warnf(" Error adding stream %q to template %q: %v", cfg.Name, cfg.Template, err) - } - } - - // We had a bug that set a default de dupe window on mirror, despite that being not a valid config - fixCfgMirrorWithDedupWindow(&cfg.StreamConfig) - - // We had a bug that could allow subjects in that had prefix or suffix spaces. We check for that here - // and will patch them on the fly for now. We will warn about them. - var hadSubjErr bool - for i, subj := range cfg.StreamConfig.Subjects { - if !IsValidSubject(subj) { - s.Warnf(" Detected bad subject %q while adding stream %q, will attempt to repair", subj, cfg.Name) - if nsubj := strings.TrimSpace(subj); IsValidSubject(nsubj) { - s.Warnf(" Bad subject %q repaired to %q", subj, nsubj) - cfg.StreamConfig.Subjects[i] = nsubj - } else { - s.Warnf(" Error recreating stream %q: %v", cfg.Name, "invalid subject") - hadSubjErr = true - break - } - } - } - if hadSubjErr { - continue - } - - // The other possible bug is assigning subjects to mirrors, so check for that and patch as well. - if cfg.StreamConfig.Mirror != nil && len(cfg.StreamConfig.Subjects) > 0 { - s.Warnf(" Detected subjects on a mirrored stream %q, will remove", cfg.Name) - cfg.StreamConfig.Subjects = nil - } - - s.Noticef(" Starting restore for stream '%s > %s'", a.Name, cfg.StreamConfig.Name) - rt := time.Now() - - // Log if we are converting from plaintext to encrypted. - if encrypted { - if plaintext { - s.Noticef(" Encrypting stream '%s > %s'", a.Name, cfg.StreamConfig.Name) - } else if convertingCiphers { - s.Noticef(" Converting to %s for stream '%s > %s'", sc, a.Name, cfg.StreamConfig.Name) - // Remove the key file to have system regenerate with the new cipher. - os.Remove(keyFile) - } - } - - // Add in the stream. - mset, err := a.addStream(&cfg.StreamConfig) - if err != nil { - s.Warnf(" Error recreating stream %q: %v", cfg.Name, err) - // If we removed a keyfile from above make sure to put it back. - if convertingCiphers { - err := os.WriteFile(keyFile, keyBuf, defaultFilePerms) - if err != nil { - s.Warnf(" Error replacing meta keyfile for stream %q: %v", cfg.Name, err) - } - } - continue - } - if !cfg.Created.IsZero() { - mset.setCreatedTime(cfg.Created) - } - - state := mset.state() - s.Noticef(" Restored %s messages for stream '%s > %s' in %v", - comma(int64(state.Msgs)), mset.accName(), mset.name(), time.Since(rt).Round(time.Millisecond)) - - // Collect to check for dangling messages. - // TODO(dlc) - Can be removed eventually. - if cfg.StreamConfig.Retention == InterestPolicy { - ipstreams = append(ipstreams, mset) - } - - // Now do the consumers. - odir := filepath.Join(sdir, fi.Name(), consumerDir) - consumers = append(consumers, &ce{mset, odir}) - } - - for _, e := range consumers { - ofis, _ := os.ReadDir(e.odir) - if len(ofis) > 0 { - s.Noticef(" Recovering %d consumers for stream - '%s > %s'", len(ofis), e.mset.accName(), e.mset.name()) - } - for _, ofi := range ofis { - metafile := filepath.Join(e.odir, ofi.Name(), JetStreamMetaFile) - metasum := filepath.Join(e.odir, ofi.Name(), JetStreamMetaFileSum) - if _, err := os.Stat(metafile); os.IsNotExist(err) { - s.Warnf(" Missing consumer metafile %q", metafile) - continue - } - buf, err := os.ReadFile(metafile) - if err != nil { - s.Warnf(" Error reading consumer metafile %q: %v", metafile, err) - continue - } - if _, err := os.Stat(metasum); os.IsNotExist(err) { - s.Warnf(" Missing consumer checksum for %q", metasum) - continue - } - - // Check if we are encrypted. - if key, err := os.ReadFile(filepath.Join(e.odir, ofi.Name(), JetStreamMetaFileKey)); err == nil { - s.Debugf(" Consumer metafile is encrypted, reading encrypted keyfile") - // Decode the buffer before proceeding. - ctxName := e.mset.name() + tsep + ofi.Name() - nbuf, _, err := s.decryptMeta(sc, key, buf, a.Name, ctxName) - if err != nil { - s.Warnf(" Error decrypting our consumer metafile: %v", err) - continue - } - buf = nbuf - } - - var cfg FileConsumerInfo - if err := json.Unmarshal(buf, &cfg); err != nil { - s.Warnf(" Error unmarshalling consumer metafile %q: %v", metafile, err) - continue - } - isEphemeral := !isDurableConsumer(&cfg.ConsumerConfig) - if isEphemeral { - // This is an ephermal consumer and this could fail on restart until - // the consumer can reconnect. We will create it as a durable and switch it. - cfg.ConsumerConfig.Durable = ofi.Name() - } - obs, err := e.mset.addConsumerWithAssignment(&cfg.ConsumerConfig, _EMPTY_, nil, true, ActionCreateOrUpdate, false) - if err != nil { - s.Warnf(" Error adding consumer %q: %v", cfg.Name, err) - continue - } - if isEphemeral { - obs.switchToEphemeral() - } - if !cfg.Created.IsZero() { - obs.setCreatedTime(cfg.Created) - } - if err != nil { - s.Warnf(" Error restoring consumer %q state: %v", cfg.Name, err) - } - } - } - - // Make sure to cleanup any old remaining snapshots. - os.RemoveAll(filepath.Join(jsa.storeDir, snapsDir)) - - // Check interest policy streams for auto cleanup. - for _, mset := range ipstreams { - mset.checkForOrphanMsgs() - mset.checkConsumerReplication() - } - - s.Debugf("JetStream state for account %q recovered", a.Name) - - return nil -} - -// Return whether we require MaxBytes to be set and if > 0 an upper limit for stream size exists -// Both limits are independent of each other. -func (a *Account) maxBytesLimits(cfg *StreamConfig) (bool, int64) { - a.mu.RLock() - jsa := a.js - a.mu.RUnlock() - if jsa == nil { - return false, 0 - } - jsa.usageMu.RLock() - var replicas int - if cfg != nil { - replicas = cfg.Replicas - } - selectedLimits, _, ok := jsa.selectLimits(replicas) - jsa.usageMu.RUnlock() - if !ok { - return false, 0 - } - maxStreamBytes := int64(0) - if cfg.Storage == MemoryStorage { - maxStreamBytes = selectedLimits.MemoryMaxStreamBytes - } else { - maxStreamBytes = selectedLimits.StoreMaxStreamBytes - } - return selectedLimits.MaxBytesRequired, maxStreamBytes -} - -// NumStreams will return how many streams we have. -func (a *Account) numStreams() int { - a.mu.RLock() - jsa := a.js - a.mu.RUnlock() - if jsa == nil { - return 0 - } - jsa.mu.Lock() - n := len(jsa.streams) - jsa.mu.Unlock() - return n -} - -// Streams will return all known streams. -func (a *Account) streams() []*stream { - return a.filteredStreams(_EMPTY_) -} - -func (a *Account) filteredStreams(filter string) []*stream { - a.mu.RLock() - jsa := a.js - a.mu.RUnlock() - - if jsa == nil { - return nil - } - - jsa.mu.RLock() - defer jsa.mu.RUnlock() - - var msets []*stream - for _, mset := range jsa.streams { - if filter != _EMPTY_ { - mset.cfgMu.RLock() - for _, subj := range mset.cfg.Subjects { - if SubjectsCollide(filter, subj) { - msets = append(msets, mset) - break - } - } - mset.cfgMu.RUnlock() - } else { - msets = append(msets, mset) - } - } - - return msets -} - -// lookupStream will lookup a stream by name. -func (a *Account) lookupStream(name string) (*stream, error) { - a.mu.RLock() - jsa := a.js - a.mu.RUnlock() - - if jsa == nil { - return nil, NewJSNotEnabledForAccountError() - } - jsa.mu.RLock() - defer jsa.mu.RUnlock() - - mset, ok := jsa.streams[name] - if !ok { - return nil, NewJSStreamNotFoundError() - } - return mset, nil -} - -// UpdateJetStreamLimits will update the account limits for a JetStream enabled account. -func (a *Account) UpdateJetStreamLimits(limits map[string]JetStreamAccountLimits) error { - a.mu.RLock() - s, jsa := a.srv, a.js - a.mu.RUnlock() - - if s == nil { - return fmt.Errorf("jetstream account not registered") - } - js := s.getJetStream() - if js == nil { - return NewJSNotEnabledError() - } - if jsa == nil { - return NewJSNotEnabledForAccountError() - } - - if len(limits) == 0 { - limits = defaultJSAccountTiers - } - - // Calculate the delta between what we have and what we want. - jsa.usageMu.RLock() - dl := diffCheckedLimits(jsa.limits, limits) - jsa.usageMu.RUnlock() - - js.mu.Lock() - // Check the limits against existing reservations. - if err := js.sufficientResources(dl); err != nil { - js.mu.Unlock() - return err - } - js.mu.Unlock() - - // Update - jsa.usageMu.Lock() - jsa.limits = limits - jsa.usageMu.Unlock() - - return nil -} - -func diffCheckedLimits(a, b map[string]JetStreamAccountLimits) map[string]JetStreamAccountLimits { - diff := map[string]JetStreamAccountLimits{} - for t, la := range a { - // in a, not in b will return 0 - lb := b[t] - diff[t] = JetStreamAccountLimits{ - MaxMemory: lb.MaxMemory - la.MaxMemory, - MaxStore: lb.MaxStore - la.MaxStore, - } - } - for t, lb := range b { - if la, ok := a[t]; !ok { - // only in b not in a. (in a and b already covered) - diff[t] = JetStreamAccountLimits{ - MaxMemory: lb.MaxMemory - la.MaxMemory, - MaxStore: lb.MaxStore - la.MaxStore, - } - } - } - return diff -} - -// Return reserved bytes for memory and store for this account on this server. -// Lock should be held. -func (jsa *jsAccount) reservedStorage(tier string) (mem, store uint64) { - for _, mset := range jsa.streams { - cfg := &mset.cfg - if tier == _EMPTY_ || tier == tierName(cfg.Replicas) && cfg.MaxBytes > 0 { - switch cfg.Storage { - case FileStorage: - store += uint64(cfg.MaxBytes) - case MemoryStorage: - mem += uint64(cfg.MaxBytes) - } - } - } - return mem, store -} - -// Return reserved bytes for memory and store for this account in clustered mode. -// js lock should be held. -func reservedStorage(sas map[string]*streamAssignment, tier string) (mem, store uint64) { - for _, sa := range sas { - cfg := sa.Config - if tier == _EMPTY_ || tier == tierName(cfg.Replicas) && cfg.MaxBytes > 0 { - switch cfg.Storage { - case FileStorage: - store += uint64(cfg.MaxBytes) - case MemoryStorage: - mem += uint64(cfg.MaxBytes) - } - } - } - return mem, store -} - -// JetStreamUsage reports on JetStream usage and limits for an account. -func (a *Account) JetStreamUsage() JetStreamAccountStats { - a.mu.RLock() - jsa, aname := a.js, a.Name - accJsLimits := a.jsLimits - a.mu.RUnlock() - - var stats JetStreamAccountStats - if jsa != nil { - js := jsa.js - js.mu.RLock() - cc := js.cluster - singleServer := cc == nil - jsa.mu.RLock() - jsa.usageMu.RLock() - stats.Memory, stats.Store = jsa.storageTotals() - stats.Domain = js.config.Domain - stats.API = JetStreamAPIStats{ - Level: JSApiLevel, - Total: jsa.apiTotal, - Errors: jsa.apiErrors, - } - if singleServer { - stats.ReservedMemory, stats.ReservedStore = jsa.reservedStorage(_EMPTY_) - } else { - stats.ReservedMemory, stats.ReservedStore = reservedStorage(cc.streams[aname], _EMPTY_) - } - l, defaultTier := jsa.limits[_EMPTY_] - if defaultTier { - stats.Limits = l - } else { - skipped := 0 - stats.Tiers = make(map[string]JetStreamTier) - for t, total := range jsa.usage { - if _, ok := jsa.limits[t]; !ok && (*total) == (jsaStorage{}) { - // skip tiers not present that don't contain a count - // In case this shows an empty stream, that tier will be added when iterating over streams - skipped++ - } else { - tier := JetStreamTier{ - Memory: uint64(total.total.mem), - Store: uint64(total.total.store), - Limits: jsa.limits[t], - } - if singleServer { - tier.ReservedMemory, tier.ReservedStore = jsa.reservedStorage(t) - } else { - tier.ReservedMemory, tier.ReservedStore = reservedStorage(cc.streams[aname], t) - } - stats.Tiers[t] = tier - } - } - if len(accJsLimits) != len(jsa.usage)-skipped { - // insert unused limits - for t, lim := range accJsLimits { - if _, ok := stats.Tiers[t]; !ok { - tier := JetStreamTier{Limits: lim} - if singleServer { - tier.ReservedMemory, tier.ReservedStore = jsa.reservedStorage(t) - } else { - tier.ReservedMemory, tier.ReservedStore = reservedStorage(cc.streams[aname], t) - } - stats.Tiers[t] = tier - } - } - } - } - jsa.usageMu.RUnlock() - - // Clustered - if cc := js.cluster; cc != nil { - sas := cc.streams[aname] - if defaultTier { - stats.Streams = len(sas) - stats.ReservedMemory, stats.ReservedStore = reservedStorage(sas, _EMPTY_) - } - for _, sa := range sas { - if defaultTier { - stats.Consumers += len(sa.consumers) - } else { - stats.Streams++ - streamTier := tierName(sa.Config.Replicas) - su, ok := stats.Tiers[streamTier] - if !ok { - su = JetStreamTier{} - } - su.Streams++ - stats.Tiers[streamTier] = su - - // Now consumers, check each since could be different tiers. - for _, ca := range sa.consumers { - stats.Consumers++ - consumerTier := tierName(ca.Config.replicas(sa.Config)) - cu, ok := stats.Tiers[consumerTier] - if !ok { - cu = JetStreamTier{} - } - cu.Consumers++ - stats.Tiers[consumerTier] = cu - } - } - } - } else { - if defaultTier { - stats.Streams = len(jsa.streams) - } - for _, mset := range jsa.streams { - consCount := mset.numConsumers() - stats.Consumers += consCount - if !defaultTier { - u, ok := stats.Tiers[mset.tier] - if !ok { - u = JetStreamTier{} - } - u.Streams++ - stats.Streams++ - u.Consumers += consCount - stats.Tiers[mset.tier] = u - } - } - } - jsa.mu.RUnlock() - js.mu.RUnlock() - } - return stats -} - -// DisableJetStream will disable JetStream for this account. -func (a *Account) DisableJetStream() error { - return a.removeJetStream() -} - -// removeJetStream is called when JetStream has been disabled for this account. -func (a *Account) removeJetStream() error { - a.mu.Lock() - s := a.srv - a.js = nil - a.mu.Unlock() - - if s == nil { - return fmt.Errorf("jetstream account not registered") - } - - js := s.getJetStream() - if js == nil { - return NewJSNotEnabledForAccountError() - } - - return js.disableJetStream(js.lookupAccount(a)) -} - -// Disable JetStream for the account. -func (js *jetStream) disableJetStream(jsa *jsAccount) error { - if jsa == nil || jsa.account == nil { - return NewJSNotEnabledForAccountError() - } - - js.mu.Lock() - delete(js.accounts, jsa.account.Name) - js.mu.Unlock() - - jsa.delete() - - return nil -} - -// jetStreamConfigured reports whether the account has JetStream configured, regardless of this -// servers JetStream status. -func (a *Account) jetStreamConfigured() bool { - if a == nil { - return false - } - a.mu.RLock() - defer a.mu.RUnlock() - return len(a.jsLimits) > 0 -} - -// JetStreamEnabled is a helper to determine if jetstream is enabled for an account. -func (a *Account) JetStreamEnabled() bool { - if a == nil { - return false - } - a.mu.RLock() - enabled := a.js != nil - a.mu.RUnlock() - return enabled -} - -func (jsa *jsAccount) remoteUpdateUsage(sub *subscription, c *client, _ *Account, subject, _ string, msg []byte) { - // jsa.js.srv is immutable and guaranteed to no be nil, so no lock needed. - s := jsa.js.srv - - jsa.usageMu.Lock() - defer jsa.usageMu.Unlock() - - if len(msg) < minUsageUpdateLen { - s.Warnf("Ignoring remote usage update with size too short") - return - } - var rnode string - if li := strings.LastIndexByte(subject, btsep); li > 0 && li < len(subject) { - rnode = subject[li+1:] - } - if rnode == _EMPTY_ { - s.Warnf("Received remote usage update with no remote node") - return - } - rUsage, ok := jsa.rusage[rnode] - if !ok { - if jsa.rusage == nil { - jsa.rusage = make(map[string]*remoteUsage) - } - rUsage = &remoteUsage{tiers: make(map[string]*jsaUsage)} - jsa.rusage[rnode] = rUsage - } - updateTotal := func(tierName string, memUsed, storeUsed int64) { - total, ok := jsa.usage[tierName] - if !ok { - total = &jsaStorage{} - jsa.usage[tierName] = total - } - // Update the usage for this remote. - if usage := rUsage.tiers[tierName]; usage != nil { - // Decrement our old values. - total.total.mem -= usage.mem - total.total.store -= usage.store - usage.mem, usage.store = memUsed, storeUsed - } else { - rUsage.tiers[tierName] = &jsaUsage{memUsed, storeUsed} - } - total.total.mem += memUsed - total.total.store += storeUsed - } - - var le = binary.LittleEndian - apiTotal, apiErrors := le.Uint64(msg[16:]), le.Uint64(msg[24:]) - memUsed, storeUsed := int64(le.Uint64(msg[0:])), int64(le.Uint64(msg[8:])) - - // We later extended the data structure to support multiple tiers - var excessRecordCnt uint32 - var tierName string - - if len(msg) >= usageMultiTiersLen { - excessRecordCnt = le.Uint32(msg[minUsageUpdateLen:]) - length := le.Uint64(msg[minUsageUpdateLen+4:]) - // Need to protect past this point in case this is wrong. - if uint64(len(msg)) < usageMultiTiersLen+length { - s.Warnf("Received corrupt remote usage update") - return - } - tierName = string(msg[usageMultiTiersLen : usageMultiTiersLen+length]) - msg = msg[usageMultiTiersLen+length:] - } - updateTotal(tierName, memUsed, storeUsed) - for ; excessRecordCnt > 0 && len(msg) >= usageRecordLen; excessRecordCnt-- { - memUsed, storeUsed := int64(le.Uint64(msg[0:])), int64(le.Uint64(msg[8:])) - length := le.Uint64(msg[16:]) - if uint64(len(msg)) < usageRecordLen+length { - s.Warnf("Received corrupt remote usage update on excess record") - return - } - tierName = string(msg[usageRecordLen : usageRecordLen+length]) - msg = msg[usageRecordLen+length:] - updateTotal(tierName, memUsed, storeUsed) - } - jsa.apiTotal -= rUsage.api - jsa.apiErrors -= rUsage.err - rUsage.api = apiTotal - rUsage.err = apiErrors - jsa.apiTotal += apiTotal - jsa.apiErrors += apiErrors -} - -// When we detect a skew of some sort this will verify the usage reporting is correct. -// No locks should be held. -func (jsa *jsAccount) checkAndSyncUsage(tierName string, storeType StorageType) { - // This will run in a separate go routine, so check that we are only running once. - if !jsa.sync.CompareAndSwap(false, true) { - return - } - defer jsa.sync.Store(false) - - // Hold the account read lock and the usage lock while we calculate. - // We scope by tier and storage type, but if R3 File has 200 streams etc. could - // show a pause. I did test with > 100 non-active streams and was 80-200ns or so. - // Should be rare this gets called as well. - jsa.mu.RLock() - defer jsa.mu.RUnlock() - js := jsa.js - if js == nil { - return - } - s := js.srv - - // We need to collect the stream stores before we acquire the usage lock since in storeUpdates the - // stream lock could be held if deletion are inline with storing a new message, e.g. via limits. - var stores []StreamStore - for _, mset := range jsa.streams { - mset.mu.RLock() - if mset.tier == tierName && mset.stype == storeType && mset.store != nil { - stores = append(stores, mset.store) - } - mset.mu.RUnlock() - } - - // Now range and qualify, hold usage lock to prevent updates. - jsa.usageMu.Lock() - defer jsa.usageMu.Unlock() - - usage, ok := jsa.usage[tierName] - if !ok { - return - } - - // Collect current total for all stream stores that matched. - var total int64 - var state StreamState - for _, store := range stores { - store.FastState(&state) - total += int64(state.Bytes) - } - - var needClusterUpdate bool - // If we do not match on our calculations compute delta and adjust. - if storeType == MemoryStorage { - if total != usage.local.mem { - s.Warnf("MemStore usage drift of %v vs %v detected for account %q", - friendlyBytes(total), friendlyBytes(usage.local.mem), jsa.account.GetName()) - delta := total - usage.local.mem - usage.local.mem += delta - usage.total.mem += delta - atomic.AddInt64(&js.memUsed, delta) - needClusterUpdate = true - } - } else { - if total != usage.local.store { - s.Warnf("FileStore usage drift of %v vs %v detected for account %q", - friendlyBytes(total), friendlyBytes(usage.local.store), jsa.account.GetName()) - delta := total - usage.local.store - usage.local.store += delta - usage.total.store += delta - atomic.AddInt64(&js.storeUsed, delta) - needClusterUpdate = true - } - } - - // Publish our local updates if in clustered mode. - if needClusterUpdate && js.isClusteredNoLock() { - jsa.sendClusterUsageUpdate() - } -} - -// Updates accounting on in use memory and storage. This is called from locally -// by the lower storage layers. -func (jsa *jsAccount) updateUsage(tierName string, storeType StorageType, delta int64) { - // jsa.js is immutable and cannot be nil, so ok w/o lock. - js := jsa.js - // updateUsage() may be invoked under the mset's lock, so we can't get - // the js' lock to check if clustered. So use this function that make - // use of an atomic to do the check without having data race reports. - isClustered := js.isClusteredNoLock() - - var needsCheck bool - jsa.usageMu.Lock() - s, ok := jsa.usage[tierName] - if !ok { - s = &jsaStorage{} - jsa.usage[tierName] = s - } - if storeType == MemoryStorage { - s.local.mem += delta - s.total.mem += delta - atomic.AddInt64(&js.memUsed, delta) - needsCheck = s.local.mem < 0 - } else { - s.local.store += delta - s.total.store += delta - atomic.AddInt64(&js.storeUsed, delta) - needsCheck = s.local.store < 0 - } - // Publish our local updates if in clustered mode. - if isClustered { - jsa.sendClusterUsageUpdate() - } - jsa.usageMu.Unlock() - - if needsCheck { - // We could be holding the stream lock from up in the stack, and this - // will want the jsa lock, which would violate locking order. - // So do this in a Go routine. The function will check if it is already running. - go jsa.checkAndSyncUsage(tierName, storeType) - } -} - -var usageTick = 1500 * time.Millisecond - -func (jsa *jsAccount) sendClusterUsageUpdateTimer() { - jsa.usageMu.Lock() - defer jsa.usageMu.Unlock() - jsa.sendClusterUsageUpdate() - if jsa.utimer != nil { - jsa.utimer.Reset(usageTick) - } -} - -// For usage fields. -const ( - minUsageUpdateLen = 32 - stackUsageUpdate = 72 - usageRecordLen = 24 - usageMultiTiersLen = 44 - apiStatsAndNumTiers = 20 - minUsageUpdateWindow = 250 * time.Millisecond -) - -// Send updates to our account usage for this server. -// jsa.usageMu lock should be held. -func (jsa *jsAccount) sendClusterUsageUpdate() { - // These values are absolute so we can limit send rates. - now := time.Now() - if now.Sub(jsa.lupdate) < minUsageUpdateWindow { - return - } - jsa.lupdate = now - - lenUsage := len(jsa.usage) - if lenUsage == 0 { - return - } - // every base record contains mem/store/len(tier) as well as the tier name - l := usageRecordLen * lenUsage - for tier := range jsa.usage { - l += len(tier) - } - // first record contains api/usage errors as well as count for extra base records - l += apiStatsAndNumTiers - - var raw [stackUsageUpdate]byte - var b []byte - if l > stackUsageUpdate { - b = make([]byte, l) - } else { - b = raw[:l] - } - - var i int - var le = binary.LittleEndian - for tier, usage := range jsa.usage { - le.PutUint64(b[i+0:], uint64(usage.local.mem)) - le.PutUint64(b[i+8:], uint64(usage.local.store)) - if i == 0 { - le.PutUint64(b[16:], jsa.usageApi) - le.PutUint64(b[24:], jsa.usageErr) - le.PutUint32(b[32:], uint32(len(jsa.usage)-1)) - le.PutUint64(b[36:], uint64(len(tier))) - copy(b[usageMultiTiersLen:], tier) - i = usageMultiTiersLen + len(tier) - } else { - le.PutUint64(b[i+16:], uint64(len(tier))) - copy(b[i+usageRecordLen:], tier) - i += usageRecordLen + len(tier) - } - } - jsa.sendq.push(newPubMsg(nil, jsa.updatesPub, _EMPTY_, nil, nil, b, noCompression, false, false)) -} - -func (js *jetStream) wouldExceedLimits(storeType StorageType, sz int) bool { - var ( - total *int64 - max int64 - ) - if storeType == MemoryStorage { - total, max = &js.memUsed, js.config.MaxMemory - } else { - total, max = &js.storeUsed, js.config.MaxStore - } - return (atomic.LoadInt64(total) + int64(sz)) > max -} - -func (js *jetStream) limitsExceeded(storeType StorageType) bool { - return js.wouldExceedLimits(storeType, 0) -} - -func tierName(replicas int) string { - // TODO (mh) this is where we could select based off a placement tag as well "qos:tier" - if replicas == 0 { - replicas = 1 - } - return fmt.Sprintf("R%d", replicas) -} - -func isSameTier(cfgA, cfgB *StreamConfig) bool { - // TODO (mh) this is where we could select based off a placement tag as well "qos:tier" - return cfgA.Replicas == cfgB.Replicas -} - -func (jsa *jsAccount) jetStreamAndClustered() (*jetStream, bool) { - jsa.mu.RLock() - js := jsa.js - jsa.mu.RUnlock() - return js, js.isClustered() -} - -// jsa.usageMu read lock should be held. -func (jsa *jsAccount) selectLimits(replicas int) (JetStreamAccountLimits, string, bool) { - if selectedLimits, ok := jsa.limits[_EMPTY_]; ok { - return selectedLimits, _EMPTY_, true - } - tier := tierName(replicas) - if selectedLimits, ok := jsa.limits[tier]; ok { - return selectedLimits, tier, true - } - return JetStreamAccountLimits{}, _EMPTY_, false -} - -// Lock should be held. -func (jsa *jsAccount) countStreams(tier string, cfg *StreamConfig) (streams int) { - for _, sa := range jsa.streams { - // Don't count the stream toward the limit if it already exists. - if (tier == _EMPTY_ || isSameTier(&sa.cfg, cfg)) && sa.cfg.Name != cfg.Name { - streams++ - } - } - return streams -} - -// jsa.usageMu read lock (at least) should be held. -func (jsa *jsAccount) storageTotals() (uint64, uint64) { - mem := uint64(0) - store := uint64(0) - for _, sa := range jsa.usage { - mem += uint64(sa.total.mem) - store += uint64(sa.total.store) - } - return mem, store -} - -func (jsa *jsAccount) limitsExceeded(storeType StorageType, tierName string, replicas int) (bool, *ApiError) { - return jsa.wouldExceedLimits(storeType, tierName, replicas, _EMPTY_, nil, nil) -} - -func (jsa *jsAccount) wouldExceedLimits(storeType StorageType, tierName string, replicas int, subj string, hdr, msg []byte) (bool, *ApiError) { - jsa.usageMu.RLock() - defer jsa.usageMu.RUnlock() - - selectedLimits, ok := jsa.limits[tierName] - if !ok { - return true, NewJSNoLimitsError() - } - inUse := jsa.usage[tierName] - if inUse == nil { - // Imply totals of 0 - return false, nil - } - r := int64(replicas) - // Make sure replicas is correct. - if r < 1 { - r = 1 - } - // This is for limits. If we have no tier, consider all to be flat, vs tiers like R3 where we want to scale limit by replication. - lr := r - if tierName == _EMPTY_ { - lr = 1 - } - - // Since tiers are flat we need to scale limit up by replicas when checking. - if storeType == MemoryStorage { - totalMem := inUse.total.mem + (int64(memStoreMsgSize(subj, hdr, msg)) * r) - if selectedLimits.MemoryMaxStreamBytes > 0 && totalMem > selectedLimits.MemoryMaxStreamBytes*lr { - return true, nil - } - if selectedLimits.MaxMemory >= 0 && totalMem > selectedLimits.MaxMemory*lr { - return true, nil - } - } else { - totalStore := inUse.total.store + (int64(fileStoreMsgSize(subj, hdr, msg)) * r) - if selectedLimits.StoreMaxStreamBytes > 0 && totalStore > selectedLimits.StoreMaxStreamBytes*lr { - return true, nil - } - if selectedLimits.MaxStore >= 0 && totalStore > selectedLimits.MaxStore*lr { - return true, nil - } - } - - return false, nil -} - -// Check account limits. -// Read Lock should be held -func (js *jetStream) checkAccountLimits(selected *JetStreamAccountLimits, config *StreamConfig, currentRes int64) error { - return js.checkLimits(selected, config, false, currentRes, 0) -} - -// Check account and server limits. -// Read Lock should be held -func (js *jetStream) checkAllLimits(selected *JetStreamAccountLimits, config *StreamConfig, currentRes, maxBytesOffset int64) error { - return js.checkLimits(selected, config, true, currentRes, maxBytesOffset) -} - -// Check if a new proposed msg set while exceed our account limits. -// Lock should be held. -func (js *jetStream) checkLimits(selected *JetStreamAccountLimits, config *StreamConfig, checkServer bool, currentRes, maxBytesOffset int64) error { - // Check MaxConsumers - if config.MaxConsumers > 0 && selected.MaxConsumers > 0 && config.MaxConsumers > selected.MaxConsumers { - return NewJSMaximumConsumersLimitError() - } - // stream limit is checked separately on stream create only! - // Check storage, memory or disk. - return js.checkBytesLimits(selected, config.MaxBytes, config.Storage, checkServer, currentRes, maxBytesOffset) -} - -// Check if additional bytes will exceed our account limits and optionally the server itself. -// Read Lock should be held. -func (js *jetStream) checkBytesLimits(selectedLimits *JetStreamAccountLimits, addBytes int64, storage StorageType, checkServer bool, currentRes, maxBytesOffset int64) error { - if addBytes < 0 { - addBytes = 1 - } - totalBytes := addBytes + maxBytesOffset - - switch storage { - case MemoryStorage: - // Account limits defined. - if selectedLimits.MaxMemory >= 0 && currentRes+totalBytes > selectedLimits.MaxMemory { - return NewJSMemoryResourcesExceededError() - } - // Check if this server can handle request. - if checkServer && js.memReserved+addBytes > js.config.MaxMemory { - return NewJSMemoryResourcesExceededError() - } - case FileStorage: - // Account limits defined. - if selectedLimits.MaxStore >= 0 && currentRes+totalBytes > selectedLimits.MaxStore { - return NewJSStorageResourcesExceededError() - } - // Check if this server can handle request. - if checkServer && js.storeReserved+addBytes > js.config.MaxStore { - return NewJSStorageResourcesExceededError() - } - } - - return nil -} - -func (jsa *jsAccount) acc() *Account { - return jsa.account -} - -// Delete the JetStream resources. -func (jsa *jsAccount) delete() { - var streams []*stream - var ts []string - - jsa.mu.Lock() - // The update timer and subs need to be protected by usageMu lock - jsa.usageMu.Lock() - if jsa.utimer != nil { - jsa.utimer.Stop() - jsa.utimer = nil - } - if jsa.updatesSub != nil && jsa.js.srv != nil { - s := jsa.js.srv - s.sysUnsubscribe(jsa.updatesSub) - jsa.updatesSub = nil - } - jsa.usageMu.Unlock() - - for _, ms := range jsa.streams { - streams = append(streams, ms) - } - acc := jsa.account - for _, t := range jsa.templates { - ts = append(ts, t.Name) - } - jsa.templates = nil - jsa.mu.Unlock() - - for _, mset := range streams { - mset.stop(false, false) - } - - for _, t := range ts { - acc.deleteStreamTemplate(t) - } -} - -// Lookup the jetstream account for a given account. -func (js *jetStream) lookupAccount(a *Account) *jsAccount { - if a == nil { - return nil - } - js.mu.RLock() - jsa := js.accounts[a.Name] - js.mu.RUnlock() - return jsa -} - -// Report on JetStream stats and usage for this server. -func (js *jetStream) usageStats() *JetStreamStats { - var stats JetStreamStats - js.mu.RLock() - stats.Accounts = len(js.accounts) - stats.ReservedMemory = uint64(js.memReserved) - stats.ReservedStore = uint64(js.storeReserved) - s := js.srv - js.mu.RUnlock() - stats.API.Level = JSApiLevel - stats.API.Total = uint64(atomic.LoadInt64(&js.apiTotal)) - stats.API.Errors = uint64(atomic.LoadInt64(&js.apiErrors)) - stats.API.Inflight = uint64(atomic.LoadInt64(&js.apiInflight)) - // Make sure we do not report negative. - used := atomic.LoadInt64(&js.memUsed) - if used < 0 { - used = 0 - } - stats.Memory = uint64(used) - used = atomic.LoadInt64(&js.storeUsed) - if used < 0 { - used = 0 - } - stats.Store = uint64(used) - stats.HAAssets = s.numRaftNodes() - return &stats -} - -// Check to see if we have enough system resources for this account. -// Lock should be held. -func (js *jetStream) sufficientResources(limits map[string]JetStreamAccountLimits) error { - // If we are clustered we do not really know how many resources will be ultimately available. - // This needs to be handled out of band. - // If we are a single server, we can make decisions here. - if limits == nil || !js.standAlone { - return nil - } - - totalMaxBytes := func(limits map[string]JetStreamAccountLimits) (int64, int64) { - totalMaxMemory := int64(0) - totalMaxStore := int64(0) - for _, l := range limits { - if l.MaxMemory > 0 { - totalMaxMemory += l.MaxMemory - } - if l.MaxStore > 0 { - totalMaxStore += l.MaxStore - } - } - return totalMaxMemory, totalMaxStore - } - - totalMaxMemory, totalMaxStore := totalMaxBytes(limits) - - // Reserved is now specific to the MaxBytes for streams. - if js.memReserved+totalMaxMemory > js.config.MaxMemory { - return NewJSMemoryResourcesExceededError() - } - if js.storeReserved+totalMaxStore > js.config.MaxStore { - return NewJSStorageResourcesExceededError() - } - - // Since we know if we are here we are single server mode, check the account reservations. - var storeReserved, memReserved int64 - for _, jsa := range js.accounts { - if jsa.account.IsExpired() { - continue - } - jsa.usageMu.RLock() - maxMemory, maxStore := totalMaxBytes(jsa.limits) - jsa.usageMu.RUnlock() - memReserved += maxMemory - storeReserved += maxStore - } - - if memReserved+totalMaxMemory > js.config.MaxMemory { - return NewJSMemoryResourcesExceededError() - } - if storeReserved+totalMaxStore > js.config.MaxStore { - return NewJSStorageResourcesExceededError() - } - - return nil -} - -// This will reserve the stream resources requested. -// This will spin off off of MaxBytes. -func (js *jetStream) reserveStreamResources(cfg *StreamConfig) { - if cfg == nil || cfg.MaxBytes <= 0 { - return - } - - js.mu.Lock() - switch cfg.Storage { - case MemoryStorage: - js.memReserved += cfg.MaxBytes - case FileStorage: - js.storeReserved += cfg.MaxBytes - } - s, clustered := js.srv, !js.standAlone - js.mu.Unlock() - // If clustered send an update to the system immediately. - if clustered { - s.sendStatszUpdate() - } -} - -// Release reserved resources held by a stream. -func (js *jetStream) releaseStreamResources(cfg *StreamConfig) { - if cfg == nil || cfg.MaxBytes <= 0 { - return - } - - js.mu.Lock() - switch cfg.Storage { - case MemoryStorage: - js.memReserved -= cfg.MaxBytes - case FileStorage: - js.storeReserved -= cfg.MaxBytes - } - s, clustered := js.srv, !js.standAlone - js.mu.Unlock() - // If clustered send an update to the system immediately. - if clustered { - s.sendStatszUpdate() - } -} - -const ( - // JetStreamStoreDir is the prefix we use. - JetStreamStoreDir = "jetstream" - // JetStreamMaxStoreDefault is the default disk storage limit. 1TB - JetStreamMaxStoreDefault = 1024 * 1024 * 1024 * 1024 - // JetStreamMaxMemDefault is only used when we can't determine system memory. 256MB - JetStreamMaxMemDefault = 1024 * 1024 * 256 - // snapshot staging for restores. - snapStagingDir = ".snap-staging" -) - -// Dynamically create a config with a tmp based directory (repeatable) and 75% of system memory. -func (s *Server) dynJetStreamConfig(storeDir string, maxStore, maxMem int64) *JetStreamConfig { - jsc := &JetStreamConfig{} - if storeDir != _EMPTY_ { - jsc.StoreDir = filepath.Join(storeDir, JetStreamStoreDir) - } else { - // Create one in tmp directory, but make it consistent for restarts. - jsc.StoreDir = filepath.Join(os.TempDir(), "nats", JetStreamStoreDir) - s.Warnf("Temporary storage directory used, data could be lost on system reboot") - } - - opts := s.getOpts() - - // Strict mode. - jsc.Strict = opts.JetStreamStrict - - // Sync options. - jsc.SyncInterval = opts.SyncInterval - jsc.SyncAlways = opts.SyncAlways - - if opts.maxStoreSet && maxStore >= 0 { - jsc.MaxStore = maxStore - } else { - jsc.MaxStore = diskAvailable(jsc.StoreDir) - } - - if opts.maxMemSet && maxMem >= 0 { - jsc.MaxMemory = maxMem - } else { - // Estimate to 75% of total memory if we can determine system memory. - if sysMem := sysmem.Memory(); sysMem > 0 { - // Check if we have been limited with GOMEMLIMIT and if lower use that value. - if gml := debug.SetMemoryLimit(-1); gml != math.MaxInt64 && gml < sysMem { - s.Debugf("JetStream detected GOMEMLIMIT of %v", friendlyBytes(gml)) - sysMem = gml - } - jsc.MaxMemory = sysMem / 4 * 3 - } else { - jsc.MaxMemory = JetStreamMaxMemDefault - } - } - - return jsc -} - -// Helper function. -func (a *Account) checkForJetStream() (*Server, *jsAccount, error) { - a.mu.RLock() - s := a.srv - jsa := a.js - a.mu.RUnlock() - - if s == nil || jsa == nil { - return nil, nil, NewJSNotEnabledForAccountError() - } - - return s, jsa, nil -} - -// StreamTemplateConfig allows a configuration to auto-create streams based on this template when a message -// is received that matches. Each new stream will use the config as the template config to create them. -type StreamTemplateConfig struct { - Name string `json:"name"` - Config *StreamConfig `json:"config"` - MaxStreams uint32 `json:"max_streams"` -} - -// StreamTemplateInfo -type StreamTemplateInfo struct { - Config *StreamTemplateConfig `json:"config"` - Streams []string `json:"streams"` -} - -// streamTemplate -type streamTemplate struct { - mu sync.Mutex - tc *client - jsa *jsAccount - *StreamTemplateConfig - streams []string -} - -func (t *StreamTemplateConfig) deepCopy() *StreamTemplateConfig { - copy := *t - cfg := *t.Config - copy.Config = &cfg - return © -} - -// addStreamTemplate will add a stream template to this account that allows auto-creation of streams. -func (a *Account) addStreamTemplate(tc *StreamTemplateConfig) (*streamTemplate, error) { - s, jsa, err := a.checkForJetStream() - if err != nil { - return nil, err - } - if tc.Config.Name != "" { - return nil, fmt.Errorf("template config name should be empty") - } - if len(tc.Name) > JSMaxNameLen { - return nil, fmt.Errorf("template name is too long, maximum allowed is %d", JSMaxNameLen) - } - - // FIXME(dlc) - Hacky - tcopy := tc.deepCopy() - tcopy.Config.Name = "_" - cfg, apiErr := s.checkStreamCfg(tcopy.Config, a, false) - if apiErr != nil { - return nil, apiErr - } - tcopy.Config = &cfg - t := &streamTemplate{ - StreamTemplateConfig: tcopy, - tc: s.createInternalJetStreamClient(), - jsa: jsa, - } - t.tc.registerWithAccount(a) - - jsa.mu.Lock() - if jsa.templates == nil { - jsa.templates = make(map[string]*streamTemplate) - // Create the appropriate store - if cfg.Storage == FileStorage { - jsa.store = newTemplateFileStore(jsa.storeDir) - } else { - jsa.store = newTemplateMemStore() - } - } else if _, ok := jsa.templates[tcopy.Name]; ok { - jsa.mu.Unlock() - return nil, fmt.Errorf("template with name %q already exists", tcopy.Name) - } - jsa.templates[tcopy.Name] = t - jsa.mu.Unlock() - - // FIXME(dlc) - we can not overlap subjects between templates. Need to have test. - - // Setup the internal subscriptions to trap the messages. - if err := t.createTemplateSubscriptions(); err != nil { - return nil, err - } - if err := jsa.store.Store(t); err != nil { - t.delete() - return nil, err - } - return t, nil -} - -func (t *streamTemplate) createTemplateSubscriptions() error { - if t == nil { - return fmt.Errorf("no template") - } - if t.tc == nil { - return fmt.Errorf("template not enabled") - } - c := t.tc - if !c.srv.EventsEnabled() { - return ErrNoSysAccount - } - sid := 1 - for _, subject := range t.Config.Subjects { - // Now create the subscription - if _, err := c.processSub([]byte(subject), nil, []byte(strconv.Itoa(sid)), t.processInboundTemplateMsg, false); err != nil { - c.acc.deleteStreamTemplate(t.Name) - return err - } - sid++ - } - return nil -} - -func (t *streamTemplate) processInboundTemplateMsg(_ *subscription, pc *client, acc *Account, subject, reply string, msg []byte) { - if t == nil || t.jsa == nil { - return - } - jsa := t.jsa - cn := canonicalName(subject) - - jsa.mu.Lock() - // If we already are registered then we can just return here. - if _, ok := jsa.streams[cn]; ok { - jsa.mu.Unlock() - return - } - jsa.mu.Unlock() - - // Check if we are at the maximum and grab some variables. - t.mu.Lock() - c := t.tc - cfg := *t.Config - cfg.Template = t.Name - atLimit := len(t.streams) >= int(t.MaxStreams) - if !atLimit { - t.streams = append(t.streams, cn) - } - t.mu.Unlock() - - if atLimit { - c.RateLimitWarnf("JetStream could not create stream for account %q on subject %q, at limit", acc.Name, subject) - return - } - - // We need to create the stream here. - // Change the config from the template and only use literal subject. - cfg.Name = cn - cfg.Subjects = []string{subject} - mset, err := acc.addStream(&cfg) - if err != nil { - acc.validateStreams(t) - c.RateLimitWarnf("JetStream could not create stream for account %q on subject %q: %v", acc.Name, subject, err) - return - } - - // Process this message directly by invoking mset. - mset.processInboundJetStreamMsg(nil, pc, acc, subject, reply, msg) -} - -// lookupStreamTemplate looks up the names stream template. -func (a *Account) lookupStreamTemplate(name string) (*streamTemplate, error) { - _, jsa, err := a.checkForJetStream() - if err != nil { - return nil, err - } - jsa.mu.Lock() - defer jsa.mu.Unlock() - if jsa.templates == nil { - return nil, fmt.Errorf("template not found") - } - t, ok := jsa.templates[name] - if !ok { - return nil, fmt.Errorf("template not found") - } - return t, nil -} - -// This function will check all named streams and make sure they are valid. -func (a *Account) validateStreams(t *streamTemplate) { - t.mu.Lock() - var vstreams []string - for _, sname := range t.streams { - if _, err := a.lookupStream(sname); err == nil { - vstreams = append(vstreams, sname) - } - } - t.streams = vstreams - t.mu.Unlock() -} - -func (t *streamTemplate) delete() error { - if t == nil { - return fmt.Errorf("nil stream template") - } - - t.mu.Lock() - jsa := t.jsa - c := t.tc - t.tc = nil - defer func() { - if c != nil { - c.closeConnection(ClientClosed) - } - }() - t.mu.Unlock() - - if jsa == nil { - return NewJSNotEnabledForAccountError() - } - - jsa.mu.Lock() - if jsa.templates == nil { - jsa.mu.Unlock() - return fmt.Errorf("template not found") - } - if _, ok := jsa.templates[t.Name]; !ok { - jsa.mu.Unlock() - return fmt.Errorf("template not found") - } - delete(jsa.templates, t.Name) - acc := jsa.account - jsa.mu.Unlock() - - // Remove streams associated with this template. - var streams []*stream - t.mu.Lock() - for _, name := range t.streams { - if mset, err := acc.lookupStream(name); err == nil { - streams = append(streams, mset) - } - } - t.mu.Unlock() - - if jsa.store != nil { - if err := jsa.store.Delete(t); err != nil { - return fmt.Errorf("error deleting template from store: %v", err) - } - } - - var lastErr error - for _, mset := range streams { - if err := mset.delete(); err != nil { - lastErr = err - } - } - return lastErr -} - -func (a *Account) deleteStreamTemplate(name string) error { - t, err := a.lookupStreamTemplate(name) - if err != nil { - return NewJSStreamTemplateNotFoundError() - } - return t.delete() -} - -func (a *Account) templates() []*streamTemplate { - var ts []*streamTemplate - _, jsa, err := a.checkForJetStream() - if err != nil { - return nil - } - - jsa.mu.Lock() - for _, t := range jsa.templates { - // FIXME(dlc) - Copy? - ts = append(ts, t) - } - jsa.mu.Unlock() - - return ts -} - -// Will add a stream to a template, this is for recovery. -func (jsa *jsAccount) addStreamNameToTemplate(tname, mname string) error { - if jsa.templates == nil { - return fmt.Errorf("template not found") - } - t, ok := jsa.templates[tname] - if !ok { - return fmt.Errorf("template not found") - } - // We found template. - t.mu.Lock() - t.streams = append(t.streams, mname) - t.mu.Unlock() - return nil -} - -// This will check if a template owns this stream. -// jsAccount lock should be held -func (jsa *jsAccount) checkTemplateOwnership(tname, sname string) bool { - if jsa.templates == nil { - return false - } - t, ok := jsa.templates[tname] - if !ok { - return false - } - // We found template, make sure we are in streams. - for _, streamName := range t.streams { - if sname == streamName { - return true - } - } - return false -} - -type Number interface { - int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64 -} - -// friendlyBytes returns a string with the given bytes int64 -// represented as a size, such as 1KB, 10MB, etc... -func friendlyBytes[T Number](bytes T) string { - fbytes := float64(bytes) - base := 1024 - pre := []string{"K", "M", "G", "T", "P", "E"} - if fbytes < float64(base) { - return fmt.Sprintf("%v B", fbytes) - } - exp := int(math.Log(fbytes) / math.Log(float64(base))) - index := exp - 1 - return fmt.Sprintf("%.2f %sB", fbytes/math.Pow(float64(base), float64(exp)), pre[index]) -} - -func isValidName(name string) bool { - if name == _EMPTY_ { - return false - } - return !strings.ContainsAny(name, " \t\r\n\f.*>") -} - -// CanonicalName will replace all token separators '.' with '_'. -// This can be used when naming streams or consumers with multi-token subjects. -func canonicalName(name string) string { - return strings.ReplaceAll(name, ".", "_") -} - -// To throttle the out of resources errors. -func (s *Server) resourcesExceededError() { - var didAlert bool - - s.rerrMu.Lock() - if now := time.Now(); now.Sub(s.rerrLast) > 10*time.Second { - s.Errorf("JetStream resource limits exceeded for server") - s.rerrLast = now - didAlert = true - } - s.rerrMu.Unlock() - - // If we are meta leader we should relinquish that here. - if didAlert { - if js := s.getJetStream(); js != nil { - js.mu.RLock() - if cc := js.cluster; cc != nil && cc.meta != nil { - cc.meta.StepDown() - } - js.mu.RUnlock() - } - } -} - -// For validating options. -func validateJetStreamOptions(o *Options) error { - // in non operator mode, the account names need to be configured - if len(o.JsAccDefaultDomain) > 0 { - if len(o.TrustedOperators) == 0 { - for a, domain := range o.JsAccDefaultDomain { - found := false - if isReservedAccount(a) { - found = true - } else { - for _, acc := range o.Accounts { - if a == acc.GetName() { - if len(acc.jsLimits) > 0 && domain != _EMPTY_ { - return fmt.Errorf("default_js_domain contains account name %q with enabled JetStream", a) - } - found = true - break - } - } - } - if !found { - return fmt.Errorf("in non operator mode, `default_js_domain` references non existing account %q", a) - } - } - } else { - for a := range o.JsAccDefaultDomain { - if !nkeys.IsValidPublicAccountKey(a) { - return fmt.Errorf("default_js_domain contains account name %q, which is not a valid public account nkey", a) - } - } - } - for a, d := range o.JsAccDefaultDomain { - sacc := DEFAULT_SYSTEM_ACCOUNT - if o.SystemAccount != _EMPTY_ { - sacc = o.SystemAccount - } - if a == sacc { - return fmt.Errorf("system account %q can not be in default_js_domain", a) - } - if d == _EMPTY_ { - continue - } - if sub := fmt.Sprintf(jsDomainAPI, d); !IsValidSubject(sub) { - return fmt.Errorf("default_js_domain contains account %q with invalid domain name %q", a, d) - } - } - } - if o.JetStreamDomain != _EMPTY_ { - if subj := fmt.Sprintf(jsDomainAPI, o.JetStreamDomain); !IsValidSubject(subj) { - return fmt.Errorf("invalid domain name: derived %q is not a valid subject", subj) - } - - if !isValidName(o.JetStreamDomain) { - return fmt.Errorf("invalid domain name: may not contain ., * or >") - } - } - // If not clustered no checks needed past here. - if !o.JetStream || o.Cluster.Port == 0 { - return nil - } - if o.ServerName == _EMPTY_ { - return fmt.Errorf("jetstream cluster requires `server_name` to be set") - } - if o.Cluster.Name == _EMPTY_ { - return fmt.Errorf("jetstream cluster requires `cluster.name` to be set") - } - - h := strings.ToLower(o.JetStreamExtHint) - switch h { - case jsWillExtend, jsNoExtend, _EMPTY_: - o.JetStreamExtHint = h - default: - return fmt.Errorf("expected 'no_extend' for string value, got '%s'", h) - } - - if o.JetStreamMaxCatchup < 0 { - return fmt.Errorf("jetstream max catchup cannot be negative") - } - return nil -} - -// We had a bug that set a default de dupe window on mirror, despite that being not a valid config -func fixCfgMirrorWithDedupWindow(cfg *StreamConfig) { - if cfg == nil || cfg.Mirror == nil { - return - } - if cfg.Duplicates != 0 { - cfg.Duplicates = 0 - } -} - -func (s *Server) handleWritePermissionError() { - //TODO Check if we should add s.jetStreamOOSPending in condition - if s.JetStreamEnabled() { - s.Errorf("File system permission denied while writing, disabling JetStream") - - go s.DisableJetStream() - - //TODO Send respective advisory if needed, same as in handleOutOfSpace - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_api.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_api.go deleted file mode 100644 index 3d9882ad..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_api.go +++ /dev/null @@ -1,5076 +0,0 @@ -// Copyright 2020-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "cmp" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "runtime" - "slices" - "strconv" - "strings" - "sync/atomic" - "time" - "unicode" - - "github.com/nats-io/nuid" -) - -// Request API subjects for JetStream. -const ( - // All API endpoints. - jsAllAPI = "$JS.API.>" - - // For constructing JetStream domain prefixes. - jsDomainAPI = "$JS.%s.API.>" - - JSApiPrefix = "$JS.API" - - // JSApiAccountInfo is for obtaining general information about JetStream for this account. - // Will return JSON response. - JSApiAccountInfo = "$JS.API.INFO" - - // JSApiTemplateCreate is the endpoint to create new stream templates. - // Will return JSON response. - JSApiTemplateCreate = "$JS.API.STREAM.TEMPLATE.CREATE.*" - JSApiTemplateCreateT = "$JS.API.STREAM.TEMPLATE.CREATE.%s" - - // JSApiTemplates is the endpoint to list all stream template names for this account. - // Will return JSON response. - JSApiTemplates = "$JS.API.STREAM.TEMPLATE.NAMES" - - // JSApiTemplateInfo is for obtaining general information about a named stream template. - // Will return JSON response. - JSApiTemplateInfo = "$JS.API.STREAM.TEMPLATE.INFO.*" - JSApiTemplateInfoT = "$JS.API.STREAM.TEMPLATE.INFO.%s" - - // JSApiTemplateDelete is the endpoint to delete stream templates. - // Will return JSON response. - JSApiTemplateDelete = "$JS.API.STREAM.TEMPLATE.DELETE.*" - JSApiTemplateDeleteT = "$JS.API.STREAM.TEMPLATE.DELETE.%s" - - // JSApiStreamCreate is the endpoint to create new streams. - // Will return JSON response. - JSApiStreamCreate = "$JS.API.STREAM.CREATE.*" - JSApiStreamCreateT = "$JS.API.STREAM.CREATE.%s" - - // JSApiStreamUpdate is the endpoint to update existing streams. - // Will return JSON response. - JSApiStreamUpdate = "$JS.API.STREAM.UPDATE.*" - JSApiStreamUpdateT = "$JS.API.STREAM.UPDATE.%s" - - // JSApiStreams is the endpoint to list all stream names for this account. - // Will return JSON response. - JSApiStreams = "$JS.API.STREAM.NAMES" - // JSApiStreamList is the endpoint that will return all detailed stream information - JSApiStreamList = "$JS.API.STREAM.LIST" - - // JSApiStreamInfo is for obtaining general information about a named stream. - // Will return JSON response. - JSApiStreamInfo = "$JS.API.STREAM.INFO.*" - JSApiStreamInfoT = "$JS.API.STREAM.INFO.%s" - - // JSApiStreamDelete is the endpoint to delete streams. - // Will return JSON response. - JSApiStreamDelete = "$JS.API.STREAM.DELETE.*" - JSApiStreamDeleteT = "$JS.API.STREAM.DELETE.%s" - - // JSApiStreamPurge is the endpoint to purge streams. - // Will return JSON response. - JSApiStreamPurge = "$JS.API.STREAM.PURGE.*" - JSApiStreamPurgeT = "$JS.API.STREAM.PURGE.%s" - - // JSApiStreamSnapshot is the endpoint to snapshot streams. - // Will return a stream of chunks with a nil chunk as EOF to - // the deliver subject. Caller should respond to each chunk - // with a nil body response for ack flow. - JSApiStreamSnapshot = "$JS.API.STREAM.SNAPSHOT.*" - JSApiStreamSnapshotT = "$JS.API.STREAM.SNAPSHOT.%s" - - // JSApiStreamRestore is the endpoint to restore a stream from a snapshot. - // Caller should respond to each chunk with a nil body response. - JSApiStreamRestore = "$JS.API.STREAM.RESTORE.*" - JSApiStreamRestoreT = "$JS.API.STREAM.RESTORE.%s" - - // JSApiMsgDelete is the endpoint to delete messages from a stream. - // Will return JSON response. - JSApiMsgDelete = "$JS.API.STREAM.MSG.DELETE.*" - JSApiMsgDeleteT = "$JS.API.STREAM.MSG.DELETE.%s" - - // JSApiMsgGet is the template for direct requests for a message by its stream sequence number. - // Will return JSON response. - JSApiMsgGet = "$JS.API.STREAM.MSG.GET.*" - JSApiMsgGetT = "$JS.API.STREAM.MSG.GET.%s" - - // JSDirectMsgGet is the template for non-api layer direct requests for a message by its stream sequence number or last by subject. - // Will return the message similar to how a consumer receives the message, no JSON processing. - // If the message can not be found we will use a status header of 404. If the stream does not exist the client will get a no-responders or timeout. - JSDirectMsgGet = "$JS.API.DIRECT.GET.*" - JSDirectMsgGetT = "$JS.API.DIRECT.GET.%s" - - // This is a direct version of get last by subject, which will be the dominant pattern for KV access once 2.9 is released. - // The stream and the key will be part of the subject to allow for no-marshal payloads and subject based security permissions. - JSDirectGetLastBySubject = "$JS.API.DIRECT.GET.*.>" - JSDirectGetLastBySubjectT = "$JS.API.DIRECT.GET.%s.%s" - - // jsDirectGetPre - jsDirectGetPre = "$JS.API.DIRECT.GET" - - // JSApiConsumerCreate is the endpoint to create consumers for streams. - // This was also the legacy endpoint for ephemeral consumers. - // It now can take consumer name and optional filter subject, which when part of the subject controls access. - // Will return JSON response. - JSApiConsumerCreate = "$JS.API.CONSUMER.CREATE.*" - JSApiConsumerCreateT = "$JS.API.CONSUMER.CREATE.%s" - JSApiConsumerCreateEx = "$JS.API.CONSUMER.CREATE.*.>" - JSApiConsumerCreateExT = "$JS.API.CONSUMER.CREATE.%s.%s.%s" - - // JSApiDurableCreate is the endpoint to create durable consumers for streams. - // You need to include the stream and consumer name in the subject. - JSApiDurableCreate = "$JS.API.CONSUMER.DURABLE.CREATE.*.*" - JSApiDurableCreateT = "$JS.API.CONSUMER.DURABLE.CREATE.%s.%s" - - // JSApiConsumers is the endpoint to list all consumer names for the stream. - // Will return JSON response. - JSApiConsumers = "$JS.API.CONSUMER.NAMES.*" - JSApiConsumersT = "$JS.API.CONSUMER.NAMES.%s" - - // JSApiConsumerList is the endpoint that will return all detailed consumer information - JSApiConsumerList = "$JS.API.CONSUMER.LIST.*" - JSApiConsumerListT = "$JS.API.CONSUMER.LIST.%s" - - // JSApiConsumerInfo is for obtaining general information about a consumer. - // Will return JSON response. - JSApiConsumerInfo = "$JS.API.CONSUMER.INFO.*.*" - JSApiConsumerInfoT = "$JS.API.CONSUMER.INFO.%s.%s" - - // JSApiConsumerDelete is the endpoint to delete consumers. - // Will return JSON response. - JSApiConsumerDelete = "$JS.API.CONSUMER.DELETE.*.*" - JSApiConsumerDeleteT = "$JS.API.CONSUMER.DELETE.%s.%s" - - // JSApiConsumerPause is the endpoint to pause or unpause consumers. - // Will return JSON response. - JSApiConsumerPause = "$JS.API.CONSUMER.PAUSE.*.*" - JSApiConsumerPauseT = "$JS.API.CONSUMER.PAUSE.%s.%s" - - // JSApiRequestNextT is the prefix for the request next message(s) for a consumer in worker/pull mode. - JSApiRequestNextT = "$JS.API.CONSUMER.MSG.NEXT.%s.%s" - - // JSApiConsumerUnpinT is the prefix for unpinning subscription for a given consumer. - JSApiConsumerUnpin = "$JS.API.CONSUMER.UNPIN.*.*" - JSApiConsumerUnpinT = "$JS.API.CONSUMER.UNPIN.%s.%s" - - // jsRequestNextPre - jsRequestNextPre = "$JS.API.CONSUMER.MSG.NEXT." - - // For snapshots and restores. The ack will have additional tokens. - jsSnapshotAckT = "$JS.SNAPSHOT.ACK.%s.%s" - jsRestoreDeliverT = "$JS.SNAPSHOT.RESTORE.%s.%s" - - // JSApiStreamRemovePeer is the endpoint to remove a peer from a clustered stream and its consumers. - // Will return JSON response. - JSApiStreamRemovePeer = "$JS.API.STREAM.PEER.REMOVE.*" - JSApiStreamRemovePeerT = "$JS.API.STREAM.PEER.REMOVE.%s" - - // JSApiStreamLeaderStepDown is the endpoint to have stream leader stepdown. - // Will return JSON response. - JSApiStreamLeaderStepDown = "$JS.API.STREAM.LEADER.STEPDOWN.*" - JSApiStreamLeaderStepDownT = "$JS.API.STREAM.LEADER.STEPDOWN.%s" - - // JSApiConsumerLeaderStepDown is the endpoint to have consumer leader stepdown. - // Will return JSON response. - JSApiConsumerLeaderStepDown = "$JS.API.CONSUMER.LEADER.STEPDOWN.*.*" - JSApiConsumerLeaderStepDownT = "$JS.API.CONSUMER.LEADER.STEPDOWN.%s.%s" - - // JSApiLeaderStepDown is the endpoint to have our metaleader stepdown. - // Only works from system account. - // Will return JSON response. - JSApiLeaderStepDown = "$JS.API.META.LEADER.STEPDOWN" - - // JSApiRemoveServer is the endpoint to remove a peer server from the cluster. - // Only works from system account. - // Will return JSON response. - JSApiRemoveServer = "$JS.API.SERVER.REMOVE" - - // JSApiAccountPurge is the endpoint to purge the js content of an account - // Only works from system account. - // Will return JSON response. - JSApiAccountPurge = "$JS.API.ACCOUNT.PURGE.*" - JSApiAccountPurgeT = "$JS.API.ACCOUNT.PURGE.%s" - - // JSApiServerStreamMove is the endpoint to move streams off a server - // Only works from system account. - // Will return JSON response. - JSApiServerStreamMove = "$JS.API.ACCOUNT.STREAM.MOVE.*.*" - JSApiServerStreamMoveT = "$JS.API.ACCOUNT.STREAM.MOVE.%s.%s" - - // JSApiServerStreamCancelMove is the endpoint to cancel a stream move - // Only works from system account. - // Will return JSON response. - JSApiServerStreamCancelMove = "$JS.API.ACCOUNT.STREAM.CANCEL_MOVE.*.*" - JSApiServerStreamCancelMoveT = "$JS.API.ACCOUNT.STREAM.CANCEL_MOVE.%s.%s" - - // The prefix for system level account API. - jsAPIAccountPre = "$JS.API.ACCOUNT." - - // jsAckT is the template for the ack message stream coming back from a consumer - // when they ACK/NAK, etc a message. - jsAckT = "$JS.ACK.%s.%s" - jsAckPre = "$JS.ACK." - jsAckPreLen = len(jsAckPre) - - // jsFlowControl is for flow control subjects. - jsFlowControlPre = "$JS.FC." - // jsFlowControl is for FC responses. - jsFlowControl = "$JS.FC.%s.%s.*" - - // JSAdvisoryPrefix is a prefix for all JetStream advisories. - JSAdvisoryPrefix = "$JS.EVENT.ADVISORY" - - // JSMetricPrefix is a prefix for all JetStream metrics. - JSMetricPrefix = "$JS.EVENT.METRIC" - - // JSMetricConsumerAckPre is a metric containing ack latency. - JSMetricConsumerAckPre = "$JS.EVENT.METRIC.CONSUMER.ACK" - - // JSAdvisoryConsumerMaxDeliveryExceedPre is a notification published when a message exceeds its delivery threshold. - JSAdvisoryConsumerMaxDeliveryExceedPre = "$JS.EVENT.ADVISORY.CONSUMER.MAX_DELIVERIES" - - // JSAdvisoryConsumerMsgNakPre is a notification published when a message has been naked - JSAdvisoryConsumerMsgNakPre = "$JS.EVENT.ADVISORY.CONSUMER.MSG_NAKED" - - // JSAdvisoryConsumerMsgTerminatedPre is a notification published when a message has been terminated. - JSAdvisoryConsumerMsgTerminatedPre = "$JS.EVENT.ADVISORY.CONSUMER.MSG_TERMINATED" - - // JSAdvisoryStreamCreatedPre notification that a stream was created. - JSAdvisoryStreamCreatedPre = "$JS.EVENT.ADVISORY.STREAM.CREATED" - - // JSAdvisoryStreamDeletedPre notification that a stream was deleted. - JSAdvisoryStreamDeletedPre = "$JS.EVENT.ADVISORY.STREAM.DELETED" - - // JSAdvisoryStreamUpdatedPre notification that a stream was updated. - JSAdvisoryStreamUpdatedPre = "$JS.EVENT.ADVISORY.STREAM.UPDATED" - - // JSAdvisoryConsumerCreatedPre notification that a consumer was created. - JSAdvisoryConsumerCreatedPre = "$JS.EVENT.ADVISORY.CONSUMER.CREATED" - - // JSAdvisoryConsumerDeletedPre notification that a consumer was deleted. - JSAdvisoryConsumerDeletedPre = "$JS.EVENT.ADVISORY.CONSUMER.DELETED" - - // JSAdvisoryConsumerPausePre notification that a consumer paused/unpaused. - JSAdvisoryConsumerPausePre = "$JS.EVENT.ADVISORY.CONSUMER.PAUSE" - - // JSAdvisoryConsumerPinnedPre notification that a consumer was pinned. - JSAdvisoryConsumerPinnedPre = "$JS.EVENT.ADVISORY.CONSUMER.PINNED" - - // JSAdvisoryConsumerUnpinnedPre notification that a consumer was unpinned. - JSAdvisoryConsumerUnpinnedPre = "$JS.EVENT.ADVISORY.CONSUMER.UNPINNED" - - // JSAdvisoryStreamSnapshotCreatePre notification that a snapshot was created. - JSAdvisoryStreamSnapshotCreatePre = "$JS.EVENT.ADVISORY.STREAM.SNAPSHOT_CREATE" - - // JSAdvisoryStreamSnapshotCompletePre notification that a snapshot was completed. - JSAdvisoryStreamSnapshotCompletePre = "$JS.EVENT.ADVISORY.STREAM.SNAPSHOT_COMPLETE" - - // JSAdvisoryStreamRestoreCreatePre notification that a restore was start. - JSAdvisoryStreamRestoreCreatePre = "$JS.EVENT.ADVISORY.STREAM.RESTORE_CREATE" - - // JSAdvisoryStreamRestoreCompletePre notification that a restore was completed. - JSAdvisoryStreamRestoreCompletePre = "$JS.EVENT.ADVISORY.STREAM.RESTORE_COMPLETE" - - // JSAdvisoryDomainLeaderElectedPre notification that a jetstream domain has elected a leader. - JSAdvisoryDomainLeaderElected = "$JS.EVENT.ADVISORY.DOMAIN.LEADER_ELECTED" - - // JSAdvisoryStreamLeaderElectedPre notification that a replicated stream has elected a leader. - JSAdvisoryStreamLeaderElectedPre = "$JS.EVENT.ADVISORY.STREAM.LEADER_ELECTED" - - // JSAdvisoryStreamQuorumLostPre notification that a stream and its consumers are stalled. - JSAdvisoryStreamQuorumLostPre = "$JS.EVENT.ADVISORY.STREAM.QUORUM_LOST" - - // JSAdvisoryConsumerLeaderElectedPre notification that a replicated consumer has elected a leader. - JSAdvisoryConsumerLeaderElectedPre = "$JS.EVENT.ADVISORY.CONSUMER.LEADER_ELECTED" - - // JSAdvisoryConsumerQuorumLostPre notification that a consumer is stalled. - JSAdvisoryConsumerQuorumLostPre = "$JS.EVENT.ADVISORY.CONSUMER.QUORUM_LOST" - - // JSAdvisoryServerOutOfStorage notification that a server has no more storage. - JSAdvisoryServerOutOfStorage = "$JS.EVENT.ADVISORY.SERVER.OUT_OF_STORAGE" - - // JSAdvisoryServerRemoved notification that a server has been removed from the system. - JSAdvisoryServerRemoved = "$JS.EVENT.ADVISORY.SERVER.REMOVED" - - // JSAdvisoryAPILimitReached notification that a server has reached the JS API hard limit. - JSAdvisoryAPILimitReached = "$JS.EVENT.ADVISORY.API.LIMIT_REACHED" - - // JSAuditAdvisory is a notification about JetStream API access. - // FIXME - Add in details about who.. - JSAuditAdvisory = "$JS.EVENT.ADVISORY.API" -) - -var denyAllClientJs = []string{jsAllAPI, "$KV.>", "$OBJ.>"} -var denyAllJs = []string{jscAllSubj, raftAllSubj, jsAllAPI, "$KV.>", "$OBJ.>"} - -func generateJSMappingTable(domain string) map[string]string { - mappings := map[string]string{} - // This set of mappings is very very very ugly. - // It is a consequence of what we defined the domain prefix to be "$JS.domain.API" and it's mapping to "$JS.API" - // For optics $KV and $OBJ where made to be independent subject spaces. - // As materialized views of JS, they did not simply extend that subject space to say "$JS.API.KV" "$JS.API.OBJ" - // This is very unfortunate!!! - // Furthermore, it seemed bad to require different domain prefixes for JS/KV/OBJ. - // Especially since the actual API for say KV, does use stream create from JS. - // To avoid overlaps KV and OBJ views append the prefix to their API. - // (Replacing $KV with the prefix allows users to create collisions with say the bucket name) - // This mapping therefore needs to have extra token so that the mapping can properly discern between $JS, $KV, $OBJ - for srcMappingSuffix, to := range map[string]string{ - "INFO": JSApiAccountInfo, - "STREAM.>": "$JS.API.STREAM.>", - "CONSUMER.>": "$JS.API.CONSUMER.>", - "DIRECT.>": "$JS.API.DIRECT.>", - "META.>": "$JS.API.META.>", - "SERVER.>": "$JS.API.SERVER.>", - "ACCOUNT.>": "$JS.API.ACCOUNT.>", - "$KV.>": "$KV.>", - "$OBJ.>": "$OBJ.>", - } { - mappings[fmt.Sprintf("$JS.%s.API.%s", domain, srcMappingSuffix)] = to - } - return mappings -} - -// JSMaxDescription is the maximum description length for streams and consumers. -const JSMaxDescriptionLen = 4 * 1024 - -// JSMaxMetadataLen is the maximum length for streams and consumers metadata map. -// It's calculated by summing length of all keys and values. -const JSMaxMetadataLen = 128 * 1024 - -// JSMaxNameLen is the maximum name lengths for streams, consumers and templates. -// Picked 255 as it seems to be a widely used file name limit -const JSMaxNameLen = 255 - -// JSDefaultRequestQueueLimit is the default number of entries that we will -// put on the global request queue before we react. -const JSDefaultRequestQueueLimit = 10_000 - -// Responses for API calls. - -// ApiResponse is a standard response from the JetStream JSON API -type ApiResponse struct { - Type string `json:"type"` - Error *ApiError `json:"error,omitempty"` -} - -const JSApiSystemResponseType = "io.nats.jetstream.api.v1.system_response" - -// When passing back to the clients generalize store failures. -var ( - errStreamStoreFailed = errors.New("error creating store for stream") - errConsumerStoreFailed = errors.New("error creating store for consumer") -) - -// ToError checks if the response has a error and if it does converts it to an error avoiding -// the pitfalls described by https://yourbasic.org/golang/gotcha-why-nil-error-not-equal-nil/ -func (r *ApiResponse) ToError() error { - if r.Error == nil { - return nil - } - - return r.Error -} - -const JSApiOverloadedType = "io.nats.jetstream.api.v1.system_overloaded" - -// ApiPaged includes variables used to create paged responses from the JSON API -type ApiPaged struct { - Total int `json:"total"` - Offset int `json:"offset"` - Limit int `json:"limit"` -} - -// ApiPagedRequest includes parameters allowing specific pages to be requests from APIs responding with ApiPaged -type ApiPagedRequest struct { - Offset int `json:"offset"` -} - -// JSApiAccountInfoResponse reports back information on jetstream for this account. -type JSApiAccountInfoResponse struct { - ApiResponse - *JetStreamAccountStats -} - -const JSApiAccountInfoResponseType = "io.nats.jetstream.api.v1.account_info_response" - -// JSApiStreamCreateResponse stream creation. -type JSApiStreamCreateResponse struct { - ApiResponse - *StreamInfo - DidCreate bool `json:"did_create,omitempty"` -} - -const JSApiStreamCreateResponseType = "io.nats.jetstream.api.v1.stream_create_response" - -// JSApiStreamDeleteResponse stream removal. -type JSApiStreamDeleteResponse struct { - ApiResponse - Success bool `json:"success,omitempty"` -} - -const JSApiStreamDeleteResponseType = "io.nats.jetstream.api.v1.stream_delete_response" - -// JSMaxSubjectDetails The limit of the number of subject details we will send in a stream info response. -const JSMaxSubjectDetails = 100_000 - -type JSApiStreamInfoRequest struct { - ApiPagedRequest - DeletedDetails bool `json:"deleted_details,omitempty"` - SubjectsFilter string `json:"subjects_filter,omitempty"` -} - -type JSApiStreamInfoResponse struct { - ApiResponse - ApiPaged - *StreamInfo -} - -const JSApiStreamInfoResponseType = "io.nats.jetstream.api.v1.stream_info_response" - -// JSApiNamesLimit is the maximum entries we will return for streams or consumers lists. -// TODO(dlc) - with header or request support could request chunked response. -const JSApiNamesLimit = 1024 -const JSApiListLimit = 256 - -type JSApiStreamNamesRequest struct { - ApiPagedRequest - // These are filters that can be applied to the list. - Subject string `json:"subject,omitempty"` -} - -// JSApiStreamNamesResponse list of streams. -// A nil request is valid and means all streams. -type JSApiStreamNamesResponse struct { - ApiResponse - ApiPaged - Streams []string `json:"streams"` -} - -const JSApiStreamNamesResponseType = "io.nats.jetstream.api.v1.stream_names_response" - -type JSApiStreamListRequest struct { - ApiPagedRequest - // These are filters that can be applied to the list. - Subject string `json:"subject,omitempty"` -} - -// JSApiStreamListResponse list of detailed stream information. -// A nil request is valid and means all streams. -type JSApiStreamListResponse struct { - ApiResponse - ApiPaged - Streams []*StreamInfo `json:"streams"` - Missing []string `json:"missing,omitempty"` -} - -const JSApiStreamListResponseType = "io.nats.jetstream.api.v1.stream_list_response" - -// JSApiStreamPurgeRequest is optional request information to the purge API. -// Subject will filter the purge request to only messages that match the subject, which can have wildcards. -// Sequence will purge up to but not including this sequence and can be combined with subject filtering. -// Keep will specify how many messages to keep. This can also be combined with subject filtering. -// Note that Sequence and Keep are mutually exclusive, so both can not be set at the same time. -type JSApiStreamPurgeRequest struct { - // Purge up to but not including sequence. - Sequence uint64 `json:"seq,omitempty"` - // Subject to match against messages for the purge command. - Subject string `json:"filter,omitempty"` - // Number of messages to keep. - Keep uint64 `json:"keep,omitempty"` -} - -type JSApiStreamPurgeResponse struct { - ApiResponse - Success bool `json:"success,omitempty"` - Purged uint64 `json:"purged"` -} - -const JSApiStreamPurgeResponseType = "io.nats.jetstream.api.v1.stream_purge_response" - -type JSApiConsumerUnpinRequest struct { - Group string `json:"group"` -} - -type JSApiConsumerUnpinResponse struct { - ApiResponse -} - -const JSApiConsumerUnpinResponseType = "io.nats.jetstream.api.v1.consumer_unpin_response" - -// JSApiStreamUpdateResponse for updating a stream. -type JSApiStreamUpdateResponse struct { - ApiResponse - *StreamInfo -} - -const JSApiStreamUpdateResponseType = "io.nats.jetstream.api.v1.stream_update_response" - -// JSApiMsgDeleteRequest delete message request. -type JSApiMsgDeleteRequest struct { - Seq uint64 `json:"seq"` - NoErase bool `json:"no_erase,omitempty"` -} - -type JSApiMsgDeleteResponse struct { - ApiResponse - Success bool `json:"success,omitempty"` -} - -const JSApiMsgDeleteResponseType = "io.nats.jetstream.api.v1.stream_msg_delete_response" - -type JSApiStreamSnapshotRequest struct { - // Subject to deliver the chunks to for the snapshot. - DeliverSubject string `json:"deliver_subject"` - // Do not include consumers in the snapshot. - NoConsumers bool `json:"no_consumers,omitempty"` - // Optional chunk size preference. - // Best to just let server select. - ChunkSize int `json:"chunk_size,omitempty"` - // Check all message's checksums prior to snapshot. - CheckMsgs bool `json:"jsck,omitempty"` -} - -// JSApiStreamSnapshotResponse is the direct response to the snapshot request. -type JSApiStreamSnapshotResponse struct { - ApiResponse - // Configuration of the given stream. - Config *StreamConfig `json:"config,omitempty"` - // Current State for the given stream. - State *StreamState `json:"state,omitempty"` -} - -const JSApiStreamSnapshotResponseType = "io.nats.jetstream.api.v1.stream_snapshot_response" - -// JSApiStreamRestoreRequest is the required restore request. -type JSApiStreamRestoreRequest struct { - // Configuration of the given stream. - Config StreamConfig `json:"config"` - // Current State for the given stream. - State StreamState `json:"state"` -} - -// JSApiStreamRestoreResponse is the direct response to the restore request. -type JSApiStreamRestoreResponse struct { - ApiResponse - // Subject to deliver the chunks to for the snapshot restore. - DeliverSubject string `json:"deliver_subject"` -} - -const JSApiStreamRestoreResponseType = "io.nats.jetstream.api.v1.stream_restore_response" - -// JSApiStreamRemovePeerRequest is the required remove peer request. -type JSApiStreamRemovePeerRequest struct { - // Server name of the peer to be removed. - Peer string `json:"peer"` -} - -// JSApiStreamRemovePeerResponse is the response to a remove peer request. -type JSApiStreamRemovePeerResponse struct { - ApiResponse - Success bool `json:"success,omitempty"` -} - -const JSApiStreamRemovePeerResponseType = "io.nats.jetstream.api.v1.stream_remove_peer_response" - -// JSApiStreamLeaderStepDownResponse is the response to a leader stepdown request. -type JSApiStreamLeaderStepDownResponse struct { - ApiResponse - Success bool `json:"success,omitempty"` -} - -const JSApiStreamLeaderStepDownResponseType = "io.nats.jetstream.api.v1.stream_leader_stepdown_response" - -// JSApiConsumerLeaderStepDownResponse is the response to a consumer leader stepdown request. -type JSApiConsumerLeaderStepDownResponse struct { - ApiResponse - Success bool `json:"success,omitempty"` -} - -const JSApiConsumerLeaderStepDownResponseType = "io.nats.jetstream.api.v1.consumer_leader_stepdown_response" - -// JSApiLeaderStepdownRequest allows placement control over the meta leader placement. -type JSApiLeaderStepdownRequest struct { - Placement *Placement `json:"placement,omitempty"` -} - -// JSApiLeaderStepDownResponse is the response to a meta leader stepdown request. -type JSApiLeaderStepDownResponse struct { - ApiResponse - Success bool `json:"success,omitempty"` -} - -const JSApiLeaderStepDownResponseType = "io.nats.jetstream.api.v1.meta_leader_stepdown_response" - -// JSApiMetaServerRemoveRequest will remove a peer from the meta group. -type JSApiMetaServerRemoveRequest struct { - // Server name of the peer to be removed. - Server string `json:"peer"` - // Peer ID of the peer to be removed. If specified this is used - // instead of the server name. - Peer string `json:"peer_id,omitempty"` -} - -// JSApiMetaServerRemoveResponse is the response to a peer removal request in the meta group. -type JSApiMetaServerRemoveResponse struct { - ApiResponse - Success bool `json:"success,omitempty"` -} - -const JSApiMetaServerRemoveResponseType = "io.nats.jetstream.api.v1.meta_server_remove_response" - -// JSApiMetaServerStreamMoveRequest will move a stream on a server to another -// response to this will come as JSApiStreamUpdateResponse/JSApiStreamUpdateResponseType -type JSApiMetaServerStreamMoveRequest struct { - // Server name of the peer to be evacuated. - Server string `json:"server,omitempty"` - // Cluster the server is in - Cluster string `json:"cluster,omitempty"` - // Domain the sever is in - Domain string `json:"domain,omitempty"` - // Ephemeral placement tags for the move - Tags []string `json:"tags,omitempty"` -} - -const JSApiAccountPurgeResponseType = "io.nats.jetstream.api.v1.account_purge_response" - -// JSApiAccountPurgeResponse is the response to a purge request in the meta group. -type JSApiAccountPurgeResponse struct { - ApiResponse - Initiated bool `json:"initiated,omitempty"` -} - -// JSApiMsgGetRequest get a message request. -type JSApiMsgGetRequest struct { - Seq uint64 `json:"seq,omitempty"` - LastFor string `json:"last_by_subj,omitempty"` - NextFor string `json:"next_by_subj,omitempty"` - - // Batch support. Used to request more then one msg at a time. - // Can be used with simple starting seq, but also NextFor with wildcards. - Batch int `json:"batch,omitempty"` - // This will make sure we limit how much data we blast out. If not set we will - // inherit the slow consumer default max setting of the server. Default is MAX_PENDING_SIZE. - MaxBytes int `json:"max_bytes,omitempty"` - // Return messages as of this start time. - StartTime *time.Time `json:"start_time,omitempty"` - - // Multiple response support. Will get the last msgs matching the subjects. These can include wildcards. - MultiLastFor []string `json:"multi_last,omitempty"` - // Only return messages up to this sequence. If not set, will be last sequence for the stream. - UpToSeq uint64 `json:"up_to_seq,omitempty"` - // Only return messages up to this time. - UpToTime *time.Time `json:"up_to_time,omitempty"` -} - -type JSApiMsgGetResponse struct { - ApiResponse - Message *StoredMsg `json:"message,omitempty"` -} - -const JSApiMsgGetResponseType = "io.nats.jetstream.api.v1.stream_msg_get_response" - -// JSWaitQueueDefaultMax is the default max number of outstanding requests for pull consumers. -const JSWaitQueueDefaultMax = 512 - -type JSApiConsumerCreateResponse struct { - ApiResponse - *ConsumerInfo -} - -const JSApiConsumerCreateResponseType = "io.nats.jetstream.api.v1.consumer_create_response" - -type JSApiConsumerDeleteResponse struct { - ApiResponse - Success bool `json:"success,omitempty"` -} - -const JSApiConsumerDeleteResponseType = "io.nats.jetstream.api.v1.consumer_delete_response" - -type JSApiConsumerPauseRequest struct { - PauseUntil time.Time `json:"pause_until,omitempty"` -} - -const JSApiConsumerPauseResponseType = "io.nats.jetstream.api.v1.consumer_pause_response" - -type JSApiConsumerPauseResponse struct { - ApiResponse - Paused bool `json:"paused"` - PauseUntil time.Time `json:"pause_until"` - PauseRemaining time.Duration `json:"pause_remaining,omitempty"` -} - -type JSApiConsumerInfoResponse struct { - ApiResponse - *ConsumerInfo -} - -const JSApiConsumerInfoResponseType = "io.nats.jetstream.api.v1.consumer_info_response" - -type JSApiConsumersRequest struct { - ApiPagedRequest -} - -type JSApiConsumerNamesResponse struct { - ApiResponse - ApiPaged - Consumers []string `json:"consumers"` -} - -const JSApiConsumerNamesResponseType = "io.nats.jetstream.api.v1.consumer_names_response" - -type JSApiConsumerListResponse struct { - ApiResponse - ApiPaged - Consumers []*ConsumerInfo `json:"consumers"` - Missing []string `json:"missing,omitempty"` -} - -const JSApiConsumerListResponseType = "io.nats.jetstream.api.v1.consumer_list_response" - -// JSApiConsumerGetNextRequest is for getting next messages for pull based consumers. -type JSApiConsumerGetNextRequest struct { - Expires time.Duration `json:"expires,omitempty"` - Batch int `json:"batch,omitempty"` - MaxBytes int `json:"max_bytes,omitempty"` - NoWait bool `json:"no_wait,omitempty"` - Heartbeat time.Duration `json:"idle_heartbeat,omitempty"` - PriorityGroup -} - -// JSApiStreamTemplateCreateResponse for creating templates. -type JSApiStreamTemplateCreateResponse struct { - ApiResponse - *StreamTemplateInfo -} - -const JSApiStreamTemplateCreateResponseType = "io.nats.jetstream.api.v1.stream_template_create_response" - -type JSApiStreamTemplateDeleteResponse struct { - ApiResponse - Success bool `json:"success,omitempty"` -} - -const JSApiStreamTemplateDeleteResponseType = "io.nats.jetstream.api.v1.stream_template_delete_response" - -// JSApiStreamTemplateInfoResponse for information about stream templates. -type JSApiStreamTemplateInfoResponse struct { - ApiResponse - *StreamTemplateInfo -} - -const JSApiStreamTemplateInfoResponseType = "io.nats.jetstream.api.v1.stream_template_info_response" - -type JSApiStreamTemplatesRequest struct { - ApiPagedRequest -} - -// JSApiStreamTemplateNamesResponse list of templates -type JSApiStreamTemplateNamesResponse struct { - ApiResponse - ApiPaged - Templates []string `json:"streams"` -} - -const JSApiStreamTemplateNamesResponseType = "io.nats.jetstream.api.v1.stream_template_names_response" - -// Structure that holds state for a JetStream API request that is processed -// in a separate long-lived go routine. This is to avoid possibly blocking -// ROUTE and GATEWAY connections. -type jsAPIRoutedReq struct { - jsub *subscription - sub *subscription - acc *Account - subject string - reply string - msg []byte - pa pubArg -} - -func (js *jetStream) apiDispatch(sub *subscription, c *client, acc *Account, subject, reply string, rmsg []byte) { - // Ignore system level directives meta stepdown and peer remove requests here. - if subject == JSApiLeaderStepDown || - subject == JSApiRemoveServer || - strings.HasPrefix(subject, jsAPIAccountPre) { - return - } - // No lock needed, those are immutable. - s, rr := js.srv, js.apiSubs.Match(subject) - - hdr, msg := c.msgParts(rmsg) - if len(sliceHeader(ClientInfoHdr, hdr)) == 0 { - // Check if this is the system account. We will let these through for the account info only. - sacc := s.SystemAccount() - if sacc != acc { - return - } - if subject != JSApiAccountInfo { - // Only respond from the initial server entry to the NATS system. - if c.kind == CLIENT || c.kind == LEAF { - var resp = ApiResponse{ - Type: JSApiSystemResponseType, - Error: NewJSNotEnabledForAccountError(), - } - s.sendAPIErrResponse(nil, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - } - - // Short circuit for no interest. - if len(rr.psubs)+len(rr.qsubs) == 0 { - if (c.kind == CLIENT || c.kind == LEAF) && acc != s.SystemAccount() { - ci, acc, _, _, _ := s.getRequestInfo(c, rmsg) - var resp = ApiResponse{ - Type: JSApiSystemResponseType, - Error: NewJSBadRequestError(), - } - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - // We should only have psubs and only 1 per result. - if len(rr.psubs) != 1 { - s.Warnf("Malformed JetStream API Request: [%s] %q", subject, rmsg) - if c.kind == CLIENT || c.kind == LEAF { - ci, acc, _, _, _ := s.getRequestInfo(c, rmsg) - var resp = ApiResponse{ - Type: JSApiSystemResponseType, - Error: NewJSBadRequestError(), - } - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - jsub := rr.psubs[0] - - // If this is directly from a client connection ok to do in place. - if c.kind != ROUTER && c.kind != GATEWAY && c.kind != LEAF { - start := time.Now() - jsub.icb(sub, c, acc, subject, reply, rmsg) - if dur := time.Since(start); dur >= readLoopReportThreshold { - s.Warnf("Internal subscription on %q took too long: %v", subject, dur) - } - return - } - - // If we are here we have received this request over a non-client connection. - // We need to make sure not to block. We will send the request to a long-lived - // pool of go routines. - - // Increment inflight. Do this before queueing. - atomic.AddInt64(&js.apiInflight, 1) - - // Copy the state. Note the JSAPI only uses the hdr index to piece apart the - // header from the msg body. No other references are needed. - // Check pending and warn if getting backed up. - pending, _ := s.jsAPIRoutedReqs.push(&jsAPIRoutedReq{jsub, sub, acc, subject, reply, copyBytes(rmsg), c.pa}) - limit := atomic.LoadInt64(&js.queueLimit) - if pending >= int(limit) { - s.rateLimitFormatWarnf("JetStream API queue limit reached, dropping %d requests", pending) - drained := int64(s.jsAPIRoutedReqs.drain()) - atomic.AddInt64(&js.apiInflight, -drained) - - s.publishAdvisory(nil, JSAdvisoryAPILimitReached, JSAPILimitReachedAdvisory{ - TypedEvent: TypedEvent{ - Type: JSAPILimitReachedAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Server: s.Name(), - Domain: js.config.Domain, - Dropped: drained, - }) - } -} - -func (s *Server) processJSAPIRoutedRequests() { - defer s.grWG.Done() - - s.mu.RLock() - queue := s.jsAPIRoutedReqs - client := &client{srv: s, kind: JETSTREAM} - s.mu.RUnlock() - - js := s.getJetStream() - - for { - select { - case <-queue.ch: - // Only pop one item at a time here, otherwise if the system is recovering - // from queue buildup, then one worker will pull off all the tasks and the - // others will be starved of work. - for r, ok := queue.popOne(); ok && r != nil; r, ok = queue.popOne() { - client.pa = r.pa - start := time.Now() - r.jsub.icb(r.sub, client, r.acc, r.subject, r.reply, r.msg) - if dur := time.Since(start); dur >= readLoopReportThreshold { - s.Warnf("Internal subscription on %q took too long: %v", r.subject, dur) - } - atomic.AddInt64(&js.apiInflight, -1) - } - case <-s.quitCh: - return - } - } -} - -func (s *Server) setJetStreamExportSubs() error { - js := s.getJetStream() - if js == nil { - return NewJSNotEnabledError() - } - - // Start the go routine that will process API requests received by the - // subscription below when they are coming from routes, etc.. - const maxProcs = 16 - mp := runtime.GOMAXPROCS(0) - // Cap at 16 max for now on larger core setups. - if mp > maxProcs { - mp = maxProcs - } - s.jsAPIRoutedReqs = newIPQueue[*jsAPIRoutedReq](s, "Routed JS API Requests") - for i := 0; i < mp; i++ { - s.startGoRoutine(s.processJSAPIRoutedRequests) - } - - // This is the catch all now for all JetStream API calls. - if _, err := s.sysSubscribe(jsAllAPI, js.apiDispatch); err != nil { - return err - } - - if err := s.SystemAccount().AddServiceExport(jsAllAPI, nil); err != nil { - s.Warnf("Error setting up jetstream service exports: %v", err) - return err - } - - // API handles themselves. - pairs := []struct { - subject string - handler msgHandler - }{ - {JSApiAccountInfo, s.jsAccountInfoRequest}, - {JSApiTemplateCreate, s.jsTemplateCreateRequest}, - {JSApiTemplates, s.jsTemplateNamesRequest}, - {JSApiTemplateInfo, s.jsTemplateInfoRequest}, - {JSApiTemplateDelete, s.jsTemplateDeleteRequest}, - {JSApiStreamCreate, s.jsStreamCreateRequest}, - {JSApiStreamUpdate, s.jsStreamUpdateRequest}, - {JSApiStreams, s.jsStreamNamesRequest}, - {JSApiStreamList, s.jsStreamListRequest}, - {JSApiStreamInfo, s.jsStreamInfoRequest}, - {JSApiStreamDelete, s.jsStreamDeleteRequest}, - {JSApiStreamPurge, s.jsStreamPurgeRequest}, - {JSApiStreamSnapshot, s.jsStreamSnapshotRequest}, - {JSApiStreamRestore, s.jsStreamRestoreRequest}, - {JSApiStreamRemovePeer, s.jsStreamRemovePeerRequest}, - {JSApiStreamLeaderStepDown, s.jsStreamLeaderStepDownRequest}, - {JSApiConsumerLeaderStepDown, s.jsConsumerLeaderStepDownRequest}, - {JSApiMsgDelete, s.jsMsgDeleteRequest}, - {JSApiMsgGet, s.jsMsgGetRequest}, - {JSApiConsumerCreateEx, s.jsConsumerCreateRequest}, - {JSApiConsumerCreate, s.jsConsumerCreateRequest}, - {JSApiDurableCreate, s.jsConsumerCreateRequest}, - {JSApiConsumers, s.jsConsumerNamesRequest}, - {JSApiConsumerList, s.jsConsumerListRequest}, - {JSApiConsumerInfo, s.jsConsumerInfoRequest}, - {JSApiConsumerDelete, s.jsConsumerDeleteRequest}, - {JSApiConsumerPause, s.jsConsumerPauseRequest}, - {JSApiConsumerUnpin, s.jsConsumerUnpinRequest}, - } - - js.mu.Lock() - defer js.mu.Unlock() - - for _, p := range pairs { - sub := &subscription{subject: []byte(p.subject), icb: p.handler} - if err := js.apiSubs.Insert(sub); err != nil { - return err - } - } - - return nil -} - -func (s *Server) sendAPIResponse(ci *ClientInfo, acc *Account, subject, reply, request, response string) { - acc.trackAPI() - if reply != _EMPTY_ { - s.sendInternalAccountMsg(nil, reply, response) - } - s.sendJetStreamAPIAuditAdvisory(ci, acc, subject, request, response) -} - -func (s *Server) sendAPIErrResponse(ci *ClientInfo, acc *Account, subject, reply, request, response string) { - acc.trackAPIErr() - if reply != _EMPTY_ { - s.sendInternalAccountMsg(nil, reply, response) - } - s.sendJetStreamAPIAuditAdvisory(ci, acc, subject, request, response) -} - -const errRespDelay = 500 * time.Millisecond - -type delayedAPIResponse struct { - ci *ClientInfo - acc *Account - subject string - reply string - request string - response string - rg *raftGroup - deadline time.Time - next *delayedAPIResponse -} - -// Add `r` in the list that is maintained ordered by the `delayedAPIResponse.deadline` time. -func addDelayedResponse(head, tail **delayedAPIResponse, r *delayedAPIResponse) { - // Check if list empty. - if *head == nil { - *head, *tail = r, r - return - } - // Check if it should be added at the end, which is if after or equal to the tail. - if r.deadline.After((*tail).deadline) || r.deadline.Equal((*tail).deadline) { - (*tail).next, *tail = r, r - return - } - // Find its spot in the list. - var prev *delayedAPIResponse - for c := *head; c != nil; c = c.next { - // We insert only if we are stricly before the current `c`. - if r.deadline.Before(c.deadline) { - r.next = c - if prev != nil { - prev.next = r - } else { - *head = r - } - return - } - prev = c - } -} - -func (s *Server) delayedAPIResponder() { - defer s.grWG.Done() - var ( - head, tail *delayedAPIResponse // Linked list. - r *delayedAPIResponse // Updated by calling next(). - rqch <-chan struct{} // Quit channel of the Raft group (if present). - tm = time.NewTimer(time.Hour) - ) - next := func() { - r, rqch = nil, nil - // Check that JetStream is still on. Do not exit the go routine - // since JS can be enabled/disabled. The go routine will exit - // only if server is shutdown. - js := s.getJetStream() - if js == nil { - // Reset head and tail here. Also drain the ipQueue. - head, tail = nil, nil - s.delayedAPIResponses.drain() - // Fall back into next "if" that resets timer. - } - // If there are no delayed messages then delay the timer for - // a while. - if head == nil { - tm.Reset(time.Hour) - return - } - // Get the first expected message and then reset the timer. - r = head - js.mu.RLock() - if r.rg != nil && r.rg.node != nil { - // If there's an attached Raft group to the delayed response - // then pull out the quit channel, so that we don't bother - // sending responses for entities which are now no longer - // running. - rqch = r.rg.node.QuitC() - } - js.mu.RUnlock() - tm.Reset(time.Until(r.deadline)) - } - pop := func() { - if head == nil { - return - } - head = head.next - if head == nil { - tail = nil - } - } - for { - select { - case <-s.delayedAPIResponses.ch: - v, ok := s.delayedAPIResponses.popOne() - if !ok { - continue - } - // Add it to the list, and if ends up being the head, set things up. - addDelayedResponse(&head, &tail, v) - if v == head { - next() - } - case <-s.quitCh: - return - case <-rqch: - // If we were the head, drop and setup things for next. - if r != nil && r == head { - pop() - } - next() - case <-tm.C: - if r != nil { - s.sendAPIErrResponse(r.ci, r.acc, r.subject, r.reply, r.request, r.response) - pop() - } - next() - } - } -} - -func (s *Server) sendDelayedAPIErrResponse(ci *ClientInfo, acc *Account, subject, reply, request, response string, rg *raftGroup, duration time.Duration) { - s.delayedAPIResponses.push(&delayedAPIResponse{ - ci, acc, subject, reply, request, response, rg, time.Now().Add(duration), nil, - }) -} - -func (s *Server) getRequestInfo(c *client, raw []byte) (pci *ClientInfo, acc *Account, hdr, msg []byte, err error) { - hdr, msg = c.msgParts(raw) - var ci ClientInfo - - if len(hdr) > 0 { - if err := json.Unmarshal(sliceHeader(ClientInfoHdr, hdr), &ci); err != nil { - return nil, nil, nil, nil, err - } - } - - if ci.Service != _EMPTY_ { - acc, _ = s.LookupAccount(ci.Service) - } else if ci.Account != _EMPTY_ { - acc, _ = s.LookupAccount(ci.Account) - } else { - // Direct $SYS access. - acc = c.acc - if acc == nil { - acc = s.SystemAccount() - } - } - if acc == nil { - return nil, nil, nil, nil, ErrMissingAccount - } - return &ci, acc, hdr, msg, nil -} - -func (s *Server) unmarshalRequest(c *client, acc *Account, subject string, msg []byte, v any) error { - decoder := json.NewDecoder(bytes.NewReader(msg)) - decoder.DisallowUnknownFields() - - for { - if err := decoder.Decode(v); err != nil { - if err == io.EOF { - return nil - } - - var syntaxErr *json.SyntaxError - if errors.As(err, &syntaxErr) { - err = fmt.Errorf("%w at offset %d", err, syntaxErr.Offset) - } - - c.RateLimitWarnf("Invalid JetStream request '%s > %s': %s", acc, subject, err) - - if s.JetStreamConfig().Strict { - return err - } - - return json.Unmarshal(msg, v) - } - } -} - -func (a *Account) trackAPI() { - a.mu.RLock() - jsa := a.js - a.mu.RUnlock() - if jsa != nil { - jsa.usageMu.Lock() - jsa.usageApi++ - jsa.apiTotal++ - jsa.sendClusterUsageUpdate() - atomic.AddInt64(&jsa.js.apiTotal, 1) - jsa.usageMu.Unlock() - } -} - -func (a *Account) trackAPIErr() { - a.mu.RLock() - jsa := a.js - a.mu.RUnlock() - if jsa != nil { - jsa.usageMu.Lock() - jsa.usageApi++ - jsa.apiTotal++ - jsa.usageErr++ - jsa.apiErrors++ - jsa.sendClusterUsageUpdate() - atomic.AddInt64(&jsa.js.apiTotal, 1) - atomic.AddInt64(&jsa.js.apiErrors, 1) - jsa.usageMu.Unlock() - } -} - -const badAPIRequestT = "Malformed JetStream API Request: %q" - -// Helper function to check on JetStream being enabled but also on status of leafnodes -// If the local account is not enabled but does have leafnode connectivity we will not -// want to error immediately and let the other side decide. -func (a *Account) checkJetStream() (enabled, shouldError bool) { - a.mu.RLock() - defer a.mu.RUnlock() - return a.js != nil, a.nleafs+a.nrleafs == 0 -} - -// Request for current usage and limits for this account. -func (s *Server) jsAccountInfoRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiAccountInfoResponse{ApiResponse: ApiResponse{Type: JSApiAccountInfoResponseType}} - - // Determine if we should proceed here when we are in clustered mode. - if s.JetStreamIsClustered() { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - // Make sure we are meta leader. - if !s.JetStreamIsLeader() { - return - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if !doErr { - return - } - resp.Error = NewJSNotEnabledForAccountError() - } else { - stats := acc.JetStreamUsage() - resp.JetStreamAccountStats = &stats - } - b, err := json.Marshal(resp) - if err != nil { - return - } - - s.sendAPIResponse(ci, acc, subject, reply, string(msg), string(b)) -} - -// Helpers for token extraction. -func templateNameFromSubject(subject string) string { - return tokenAt(subject, 6) -} - -func streamNameFromSubject(subject string) string { - return tokenAt(subject, 5) -} - -func consumerNameFromSubject(subject string) string { - return tokenAt(subject, 6) -} - -// Request to create a new template. -func (s *Server) jsTemplateCreateRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiStreamTemplateCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamTemplateCreateResponseType}} - if !acc.JetStreamEnabled() { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // Not supported for now. - if s.JetStreamIsClustered() { - resp.Error = NewJSClusterUnSupportFeatureError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - var cfg StreamTemplateConfig - if err := s.unmarshalRequest(c, acc, subject, msg, &cfg); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - templateName := templateNameFromSubject(subject) - if templateName != cfg.Name { - resp.Error = NewJSTemplateNameNotMatchSubjectError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - t, err := acc.addStreamTemplate(&cfg) - if err != nil { - resp.Error = NewJSStreamTemplateCreateError(err, Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - t.mu.Lock() - tcfg := t.StreamTemplateConfig.deepCopy() - streams := t.streams - if streams == nil { - streams = []string{} - } - t.mu.Unlock() - resp.StreamTemplateInfo = &StreamTemplateInfo{Config: tcfg, Streams: streams} - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request for the list of all template names. -func (s *Server) jsTemplateNamesRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiStreamTemplateNamesResponse{ApiResponse: ApiResponse{Type: JSApiStreamTemplateNamesResponseType}} - if !acc.JetStreamEnabled() { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // Not supported for now. - if s.JetStreamIsClustered() { - resp.Error = NewJSClusterUnSupportFeatureError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - var offset int - if isJSONObjectOrArray(msg) { - var req JSApiStreamTemplatesRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - offset = req.Offset - } - - ts := acc.templates() - slices.SortFunc(ts, func(i, j *streamTemplate) int { - return cmp.Compare(i.StreamTemplateConfig.Name, j.StreamTemplateConfig.Name) - }) - - tcnt := len(ts) - if offset > tcnt { - offset = tcnt - } - - for _, t := range ts[offset:] { - t.mu.Lock() - name := t.Name - t.mu.Unlock() - resp.Templates = append(resp.Templates, name) - if len(resp.Templates) >= JSApiNamesLimit { - break - } - } - resp.Total = tcnt - resp.Limit = JSApiNamesLimit - resp.Offset = offset - if resp.Templates == nil { - resp.Templates = []string{} - } - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request for information about a stream template. -func (s *Server) jsTemplateInfoRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiStreamTemplateInfoResponse{ApiResponse: ApiResponse{Type: JSApiStreamTemplateInfoResponseType}} - if !acc.JetStreamEnabled() { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - if !isEmptyRequest(msg) { - resp.Error = NewJSNotEmptyRequestError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - name := templateNameFromSubject(subject) - t, err := acc.lookupStreamTemplate(name) - if err != nil { - resp.Error = NewJSStreamTemplateNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - t.mu.Lock() - cfg := t.StreamTemplateConfig.deepCopy() - streams := t.streams - if streams == nil { - streams = []string{} - } - t.mu.Unlock() - - resp.StreamTemplateInfo = &StreamTemplateInfo{Config: cfg, Streams: streams} - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request to delete a stream template. -func (s *Server) jsTemplateDeleteRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiStreamTemplateDeleteResponse{ApiResponse: ApiResponse{Type: JSApiStreamTemplateDeleteResponseType}} - if !acc.JetStreamEnabled() { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - if !isEmptyRequest(msg) { - resp.Error = NewJSNotEmptyRequestError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - name := templateNameFromSubject(subject) - err = acc.deleteStreamTemplate(name) - if err != nil { - resp.Error = NewJSStreamTemplateDeleteError(err, Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - resp.Success = true - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -func (s *Server) jsonResponse(v any) string { - b, err := json.Marshal(v) - if err != nil { - s.Warnf("Problem marshaling JSON for JetStream API:", err) - return "" - } - return string(b) -} - -// Read lock must be held -func (jsa *jsAccount) tieredReservation(tier string, cfg *StreamConfig) int64 { - reservation := int64(0) - if tier == _EMPTY_ { - for _, sa := range jsa.streams { - if sa.cfg.MaxBytes > 0 { - if sa.cfg.Storage == cfg.Storage && sa.cfg.Name != cfg.Name { - reservation += (int64(sa.cfg.Replicas) * sa.cfg.MaxBytes) - } - } - } - } else { - for _, sa := range jsa.streams { - if sa.cfg.Replicas == cfg.Replicas { - if sa.cfg.MaxBytes > 0 { - if isSameTier(&sa.cfg, cfg) && sa.cfg.Name != cfg.Name { - reservation += (int64(sa.cfg.Replicas) * sa.cfg.MaxBytes) - } - } - } - } - } - return reservation -} - -// Request to create a stream. -func (s *Server) jsStreamCreateRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiStreamCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamCreateResponseType}} - - // Determine if we should proceed here when we are in clustered mode. - if s.JetStreamIsClustered() { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - // Make sure we are meta leader. - if !s.JetStreamIsLeader() { - return - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - var cfg StreamConfigRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &cfg); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // Initialize asset version metadata. - setStaticStreamMetadata(&cfg.StreamConfig) - - streamName := streamNameFromSubject(subject) - if streamName != cfg.Name { - resp.Error = NewJSStreamMismatchError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // Check for path like separators in the name. - if strings.ContainsAny(streamName, `\/`) { - resp.Error = NewJSStreamNameContainsPathSeparatorsError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // Can't create a stream with a sealed state. - if cfg.Sealed { - resp.Error = NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration for create can not be sealed")) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // If we are told to do mirror direct but are not mirroring, error. - if cfg.MirrorDirect && cfg.Mirror == nil { - resp.Error = NewJSStreamInvalidConfigError(fmt.Errorf("stream has no mirror but does have mirror direct")) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // Hand off to cluster for processing. - if s.JetStreamIsClustered() { - s.jsClusteredStreamRequest(ci, acc, subject, reply, rmsg, &cfg) - return - } - - if err := acc.jsNonClusteredStreamLimitsCheck(&cfg.StreamConfig); err != nil { - resp.Error = err - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - mset, err := acc.addStreamPedantic(&cfg.StreamConfig, cfg.Pedantic) - if err != nil { - if IsNatsErr(err, JSStreamStoreFailedF) { - s.Warnf("Stream create failed for '%s > %s': %v", acc, streamName, err) - err = errStreamStoreFailed - } - resp.Error = NewJSStreamCreateError(err, Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - msetCfg := mset.config() - resp.StreamInfo = &StreamInfo{ - Created: mset.createdTime(), - State: mset.state(), - Config: *setDynamicStreamMetadata(&msetCfg), - TimeStamp: time.Now().UTC(), - Mirror: mset.mirrorInfo(), - Sources: mset.sourcesInfo(), - } - resp.DidCreate = true - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request to update a stream. -func (s *Server) jsStreamUpdateRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiStreamUpdateResponse{ApiResponse: ApiResponse{Type: JSApiStreamUpdateResponseType}} - - // Determine if we should proceed here when we are in clustered mode. - if s.JetStreamIsClustered() { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - // Make sure we are meta leader. - if !s.JetStreamIsLeader() { - return - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - var ncfg StreamConfigRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &ncfg); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - cfg, apiErr := s.checkStreamCfg(&ncfg.StreamConfig, acc, ncfg.Pedantic) - if apiErr != nil { - resp.Error = apiErr - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - streamName := streamNameFromSubject(subject) - if streamName != cfg.Name { - resp.Error = NewJSStreamMismatchError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // Handle clustered version here. - if s.JetStreamIsClustered() { - // Always do in separate Go routine. - go s.jsClusteredStreamUpdateRequest(ci, acc, subject, reply, copyBytes(rmsg), &cfg, nil, ncfg.Pedantic) - return - } - - mset, err := acc.lookupStream(streamName) - if err != nil { - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // Update asset version metadata. - setStaticStreamMetadata(&cfg) - - if err := mset.updatePedantic(&cfg, ncfg.Pedantic); err != nil { - resp.Error = NewJSStreamUpdateError(err, Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - msetCfg := mset.config() - resp.StreamInfo = &StreamInfo{ - Created: mset.createdTime(), - State: mset.state(), - Config: *setDynamicStreamMetadata(&msetCfg), - Domain: s.getOpts().JetStreamDomain, - Mirror: mset.mirrorInfo(), - Sources: mset.sourcesInfo(), - TimeStamp: time.Now().UTC(), - } - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request for the list of all stream names. -func (s *Server) jsStreamNamesRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiStreamNamesResponse{ApiResponse: ApiResponse{Type: JSApiStreamNamesResponseType}} - - // Determine if we should proceed here when we are in clustered mode. - if s.JetStreamIsClustered() { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - // Make sure we are meta leader. - if !s.JetStreamIsLeader() { - return - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - var offset int - var filter string - - if isJSONObjectOrArray(msg) { - var req JSApiStreamNamesRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - offset = req.Offset - if req.Subject != _EMPTY_ { - filter = req.Subject - } - } - - // TODO(dlc) - Maybe hold these results for large results that we expect to be paged. - // TODO(dlc) - If this list is long maybe do this in a Go routine? - var numStreams int - if s.JetStreamIsClustered() { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - // TODO(dlc) - Debug or Warn? - return - } - js.mu.RLock() - for stream, sa := range cc.streams[acc.Name] { - if IsNatsErr(sa.err, JSClusterNotAssignedErr) { - continue - } - if filter != _EMPTY_ { - // These could not have subjects auto-filled in since they are raw and unprocessed. - if len(sa.Config.Subjects) == 0 { - if SubjectsCollide(filter, sa.Config.Name) { - resp.Streams = append(resp.Streams, stream) - } - } else { - for _, subj := range sa.Config.Subjects { - if SubjectsCollide(filter, subj) { - resp.Streams = append(resp.Streams, stream) - break - } - } - } - } else { - resp.Streams = append(resp.Streams, stream) - } - } - js.mu.RUnlock() - if len(resp.Streams) > 1 { - slices.Sort(resp.Streams) - } - numStreams = len(resp.Streams) - if offset > numStreams { - offset = numStreams - } - if offset > 0 { - resp.Streams = resp.Streams[offset:] - } - if len(resp.Streams) > JSApiNamesLimit { - resp.Streams = resp.Streams[:JSApiNamesLimit] - } - } else { - msets := acc.filteredStreams(filter) - // Since we page results order matters. - if len(msets) > 1 { - slices.SortFunc(msets, func(i, j *stream) int { return cmp.Compare(i.cfg.Name, j.cfg.Name) }) - } - - numStreams = len(msets) - if offset > numStreams { - offset = numStreams - } - - for _, mset := range msets[offset:] { - resp.Streams = append(resp.Streams, mset.cfg.Name) - if len(resp.Streams) >= JSApiNamesLimit { - break - } - } - } - resp.Total = numStreams - resp.Limit = JSApiNamesLimit - resp.Offset = offset - - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request for the list of all detailed stream info. -// TODO(dlc) - combine with above long term -func (s *Server) jsStreamListRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiStreamListResponse{ - ApiResponse: ApiResponse{Type: JSApiStreamListResponseType}, - Streams: []*StreamInfo{}, - } - - // Determine if we should proceed here when we are in clustered mode. - if s.JetStreamIsClustered() { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - // Make sure we are meta leader. - if !s.JetStreamIsLeader() { - return - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - var offset int - var filter string - - if isJSONObjectOrArray(msg) { - var req JSApiStreamListRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - offset = req.Offset - if req.Subject != _EMPTY_ { - filter = req.Subject - } - } - - // Clustered mode will invoke a scatter and gather. - if s.JetStreamIsClustered() { - // Need to copy these off before sending.. don't move this inside startGoRoutine!!! - msg = copyBytes(msg) - s.startGoRoutine(func() { s.jsClusteredStreamListRequest(acc, ci, filter, offset, subject, reply, msg) }) - return - } - - // TODO(dlc) - Maybe hold these results for large results that we expect to be paged. - // TODO(dlc) - If this list is long maybe do this in a Go routine? - var msets []*stream - if filter == _EMPTY_ { - msets = acc.streams() - } else { - msets = acc.filteredStreams(filter) - } - - slices.SortFunc(msets, func(i, j *stream) int { return cmp.Compare(i.cfg.Name, j.cfg.Name) }) - - scnt := len(msets) - if offset > scnt { - offset = scnt - } - - for _, mset := range msets[offset:] { - config := mset.config() - resp.Streams = append(resp.Streams, &StreamInfo{ - Created: mset.createdTime(), - State: mset.state(), - Config: config, - Domain: s.getOpts().JetStreamDomain, - Mirror: mset.mirrorInfo(), - Sources: mset.sourcesInfo(), - TimeStamp: time.Now().UTC(), - }) - if len(resp.Streams) >= JSApiListLimit { - break - } - } - resp.Total = scnt - resp.Limit = JSApiListLimit - resp.Offset = offset - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request for information about a stream. -func (s *Server) jsStreamInfoRequest(sub *subscription, c *client, a *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, hdr, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - streamName := streamNameFromSubject(subject) - - var resp = JSApiStreamInfoResponse{ApiResponse: ApiResponse{Type: JSApiStreamInfoResponseType}} - - // If someone creates a duplicate stream that is identical we will get this request forwarded to us. - // Make sure the response type is for a create call. - if rt := getHeader(JSResponseType, hdr); len(rt) > 0 && string(rt) == jsCreateResponse { - resp.ApiResponse.Type = JSApiStreamCreateResponseType - } - - var clusterWideConsCount int - - js, cc := s.getJetStreamCluster() - if js == nil { - return - } - // If we are in clustered mode we need to be the stream leader to proceed. - if cc != nil { - // Check to make sure the stream is assigned. - js.mu.RLock() - isLeader, sa := cc.isLeader(), js.streamAssignment(acc.Name, streamName) - var offline bool - if sa != nil { - clusterWideConsCount = len(sa.consumers) - offline = s.allPeersOffline(sa.Group) - } - js.mu.RUnlock() - - if isLeader && sa == nil { - // We can't find the stream, so mimic what would be the errors below. - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - // No stream present. - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } else if sa == nil { - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - // Delaying an error response gives the leader a chance to respond before us - s.sendDelayedAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp), nil, errRespDelay) - } - return - } else if isLeader && offline { - resp.Error = NewJSStreamOfflineError() - s.sendDelayedAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp), nil, errRespDelay) - return - } - - // Check to see if we are a member of the group and if the group has no leader. - isLeaderless := js.isGroupLeaderless(sa.Group) - - // We have the stream assigned and a leader, so only the stream leader should answer. - if !acc.JetStreamIsStreamLeader(streamName) && !isLeaderless { - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - // Delaying an error response gives the leader a chance to respond before us - s.sendDelayedAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp), sa.Group, errRespDelay) - return - } - - // We may be in process of electing a leader, but if this is a scale up from 1 we will still be the state leader - // while the new members work through the election and catchup process. - // Double check for that instead of exiting here and being silent. e.g. nats stream update test --replicas=3 - js.mu.RLock() - rg := sa.Group - var ourID string - if cc.meta != nil { - ourID = cc.meta.ID() - } - // We have seen cases where rg is nil at this point, - // so check explicitly and bail if that is the case. - bail := rg == nil || !rg.isMember(ourID) - if !bail { - // We know we are a member here, if this group is new and we are preferred allow us to answer. - // Also, we have seen cases where rg.node is nil at this point, - // so check explicitly and bail if that is the case. - bail = rg.Preferred != ourID || (rg.node != nil && time.Since(rg.node.Created()) > lostQuorumIntervalDefault) - } - js.mu.RUnlock() - if bail { - return - } - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - var details bool - var subjects string - var offset int - if isJSONObjectOrArray(msg) { - var req JSApiStreamInfoRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - details, subjects = req.DeletedDetails, req.SubjectsFilter - offset = req.Offset - } - - mset, err := acc.lookupStream(streamName) - // Error is not to be expected at this point, but could happen if same stream trying to be created. - if err != nil { - if cc != nil { - // This could be inflight, pause for a short bit and try again. - // This will not be inline, so ok. - time.Sleep(10 * time.Millisecond) - mset, err = acc.lookupStream(streamName) - } - // Check again. - if err != nil { - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - } - - config := mset.config() - resp.StreamInfo = &StreamInfo{ - Created: mset.createdTime(), - State: mset.stateWithDetail(details), - Config: *setDynamicStreamMetadata(&config), - Domain: s.getOpts().JetStreamDomain, - Cluster: js.clusterInfo(mset.raftGroup()), - Mirror: mset.mirrorInfo(), - Sources: mset.sourcesInfo(), - Alternates: js.streamAlternates(ci, config.Name), - TimeStamp: time.Now().UTC(), - } - if clusterWideConsCount > 0 { - resp.StreamInfo.State.Consumers = clusterWideConsCount - } - - // Check if they have asked for subject details. - if subjects != _EMPTY_ { - st := mset.store.SubjectsTotals(subjects) - if lst := len(st); lst > 0 { - // Common for both cases. - resp.Offset = offset - resp.Limit = JSMaxSubjectDetails - resp.Total = lst - - if offset == 0 && lst <= JSMaxSubjectDetails { - resp.StreamInfo.State.Subjects = st - } else { - // Here we have to filter list due to offset or maximum constraints. - subjs := make([]string, 0, len(st)) - for subj := range st { - subjs = append(subjs, subj) - } - // Sort it - slices.Sort(subjs) - - if offset > len(subjs) { - offset = len(subjs) - } - - end := offset + JSMaxSubjectDetails - if end > len(subjs) { - end = len(subjs) - } - actualSize := end - offset - var sd map[string]uint64 - - if actualSize > 0 { - sd = make(map[string]uint64, actualSize) - for _, ss := range subjs[offset:end] { - sd[ss] = st[ss] - } - } - resp.StreamInfo.State.Subjects = sd - } - } - } - // Check for out of band catchups. - if mset.hasCatchupPeers() { - mset.checkClusterInfo(resp.StreamInfo.Cluster) - } - - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request to have a stream leader stepdown. -func (s *Server) jsStreamLeaderStepDownRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - // Have extra token for this one. - name := tokenAt(subject, 6) - - var resp = JSApiStreamLeaderStepDownResponse{ApiResponse: ApiResponse{Type: JSApiStreamLeaderStepDownResponseType}} - - // If we are not in clustered mode this is a failed request. - if !s.JetStreamIsClustered() { - resp.Error = NewJSClusterRequiredError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // If we are here we are clustered. See if we are the stream leader in order to proceed. - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - js.mu.RLock() - isLeader, sa := cc.isLeader(), js.streamAssignment(acc.Name, name) - js.mu.RUnlock() - - if isLeader && sa == nil { - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } else if sa == nil { - return - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - // Check to see if we are a member of the group and if the group has no leader. - if js.isGroupLeaderless(sa.Group) { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // We have the stream assigned and a leader, so only the stream leader should answer. - if !acc.JetStreamIsStreamLeader(name) { - return - } - - mset, err := acc.lookupStream(name) - if err != nil { - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if mset == nil { - resp.Success = true - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) - return - } - - node := mset.raftNode() - if node == nil { - resp.Success = true - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) - return - } - - var preferredLeader string - if isJSONObjectOrArray(msg) { - var req JSApiLeaderStepdownRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - if preferredLeader, resp.Error = s.getStepDownPreferredPlacement(node, req.Placement); resp.Error != nil { - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - } - - // Call actual stepdown. - err = node.StepDown(preferredLeader) - if err != nil { - resp.Error = NewJSRaftGeneralError(err, Unless(err)) - } else { - resp.Success = true - } - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request to have a consumer leader stepdown. -func (s *Server) jsConsumerLeaderStepDownRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiConsumerLeaderStepDownResponse{ApiResponse: ApiResponse{Type: JSApiConsumerLeaderStepDownResponseType}} - - // If we are not in clustered mode this is a failed request. - if !s.JetStreamIsClustered() { - resp.Error = NewJSClusterRequiredError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // If we are here we are clustered. See if we are the stream leader in order to proceed. - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // Have extra token for this one. - stream := tokenAt(subject, 6) - consumer := tokenAt(subject, 7) - - js.mu.RLock() - isLeader, sa := cc.isLeader(), js.streamAssignment(acc.Name, stream) - js.mu.RUnlock() - - if isLeader && sa == nil { - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } else if sa == nil { - return - } - var ca *consumerAssignment - if sa.consumers != nil { - ca = sa.consumers[consumer] - } - if ca == nil { - resp.Error = NewJSConsumerNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - // Check to see if we are a member of the group and if the group has no leader. - if js.isGroupLeaderless(ca.Group) { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if !acc.JetStreamIsConsumerLeader(stream, consumer) { - return - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - mset, err := acc.lookupStream(stream) - if err != nil { - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - o := mset.lookupConsumer(consumer) - if o == nil { - resp.Error = NewJSConsumerNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - n := o.raftNode() - if n == nil { - resp.Success = true - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) - return - } - - var preferredLeader string - if isJSONObjectOrArray(msg) { - var req JSApiLeaderStepdownRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - if preferredLeader, resp.Error = s.getStepDownPreferredPlacement(n, req.Placement); resp.Error != nil { - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - } - - // Call actual stepdown. - err = n.StepDown(preferredLeader) - if err != nil { - resp.Error = NewJSRaftGeneralError(err, Unless(err)) - } else { - resp.Success = true - } - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request to remove a peer from a clustered stream. -func (s *Server) jsStreamRemovePeerRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - // Have extra token for this one. - name := tokenAt(subject, 6) - - var resp = JSApiStreamRemovePeerResponse{ApiResponse: ApiResponse{Type: JSApiStreamRemovePeerResponseType}} - - // If we are not in clustered mode this is a failed request. - if !s.JetStreamIsClustered() { - resp.Error = NewJSClusterRequiredError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // If we are here we are clustered. See if we are the stream leader in order to proceed. - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - js.mu.RLock() - isLeader, sa := cc.isLeader(), js.streamAssignment(acc.Name, name) - js.mu.RUnlock() - - // Make sure we are meta leader. - if !isLeader { - return - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - if isEmptyRequest(msg) { - resp.Error = NewJSBadRequestError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - var req JSApiStreamRemovePeerRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - if req.Peer == _EMPTY_ { - resp.Error = NewJSBadRequestError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if sa == nil { - // No stream present. - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // Check to see if we are a member of the group and if the group has no leader. - // Peers here is a server name, convert to node name. - nodeName := getHash(req.Peer) - - js.mu.RLock() - rg := sa.Group - isMember := rg.isMember(nodeName) - js.mu.RUnlock() - - // Make sure we are a member. - if !isMember { - resp.Error = NewJSClusterPeerNotMemberError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // If we are here we have a valid peer member set for removal. - if !js.removePeerFromStream(sa, nodeName) { - resp.Error = NewJSPeerRemapError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - resp.Success = true - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request to have the metaleader remove a peer from the system. -func (s *Server) jsLeaderServerRemoveRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil || cc.meta == nil { - return - } - - // Extra checks here but only leader is listening. - js.mu.RLock() - isLeader := cc.isLeader() - js.mu.RUnlock() - - if !isLeader { - return - } - - var resp = JSApiMetaServerRemoveResponse{ApiResponse: ApiResponse{Type: JSApiMetaServerRemoveResponseType}} - - if isEmptyRequest(msg) { - resp.Error = NewJSBadRequestError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - var req JSApiMetaServerRemoveRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - var found string - js.mu.RLock() - for _, p := range cc.meta.Peers() { - // If Peer is specified, it takes precedence - if req.Peer != _EMPTY_ { - if p.ID == req.Peer { - found = req.Peer - break - } - continue - } - si, ok := s.nodeToInfo.Load(p.ID) - if ok && si.(nodeInfo).name == req.Server { - found = p.ID - break - } - } - js.mu.RUnlock() - - if found == _EMPTY_ { - resp.Error = NewJSClusterServerNotMemberError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // So we have a valid peer. - js.mu.Lock() - cc.meta.ProposeRemovePeer(found) - js.mu.Unlock() - - resp.Success = true - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) -} - -func (s *Server) peerSetToNames(ps []string) []string { - names := make([]string, len(ps)) - for i := 0; i < len(ps); i++ { - if si, ok := s.nodeToInfo.Load(ps[i]); !ok { - names[i] = ps[i] - } else { - names[i] = si.(nodeInfo).name - } - } - return names -} - -// looks up the peer id for a given server name. Cluster and domain name are optional filter criteria -func (s *Server) nameToPeer(js *jetStream, serverName, clusterName, domainName string) string { - js.mu.RLock() - defer js.mu.RUnlock() - if cc := js.cluster; cc != nil { - for _, p := range cc.meta.Peers() { - si, ok := s.nodeToInfo.Load(p.ID) - if ok && si.(nodeInfo).name == serverName { - if clusterName == _EMPTY_ || clusterName == si.(nodeInfo).cluster { - if domainName == _EMPTY_ || domainName == si.(nodeInfo).domain { - return p.ID - } - } - } - } - } - return _EMPTY_ -} - -// Request to have the metaleader move a stream on a peer to another -func (s *Server) jsLeaderServerStreamMoveRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil || cc.meta == nil { - return - } - - // Extra checks here but only leader is listening. - js.mu.RLock() - isLeader := cc.isLeader() - js.mu.RUnlock() - - if !isLeader { - return - } - - accName := tokenAt(subject, 6) - streamName := tokenAt(subject, 7) - - var resp = JSApiStreamUpdateResponse{ApiResponse: ApiResponse{Type: JSApiStreamUpdateResponseType}} - - var req JSApiMetaServerStreamMoveRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - srcPeer := _EMPTY_ - if req.Server != _EMPTY_ { - srcPeer = s.nameToPeer(js, req.Server, req.Cluster, req.Domain) - } - - targetAcc, ok := s.accounts.Load(accName) - if !ok { - resp.Error = NewJSNoAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - var streamFound bool - cfg := StreamConfig{} - currPeers := []string{} - currCluster := _EMPTY_ - js.mu.Lock() - streams, ok := cc.streams[accName] - if ok { - sa, ok := streams[streamName] - if ok { - cfg = *sa.Config.clone() - streamFound = true - currPeers = sa.Group.Peers - currCluster = sa.Group.Cluster - } - } - js.mu.Unlock() - - if !streamFound { - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // if server was picked, make sure src peer exists and move it to first position. - // removal will drop peers from the left - if req.Server != _EMPTY_ { - if srcPeer == _EMPTY_ { - resp.Error = NewJSClusterServerNotMemberError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - var peerFound bool - for i := 0; i < len(currPeers); i++ { - if currPeers[i] == srcPeer { - copy(currPeers[1:], currPeers[:i]) - currPeers[0] = srcPeer - peerFound = true - break - } - } - if !peerFound { - resp.Error = NewJSClusterPeerNotMemberError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - } - - // make sure client is scoped to requested account - ciNew := *(ci) - ciNew.Account = accName - - // backup placement such that peers can be looked up with modified tag list - var origPlacement *Placement - if cfg.Placement != nil { - tmp := *cfg.Placement - origPlacement = &tmp - } - - if len(req.Tags) > 0 { - if cfg.Placement == nil { - cfg.Placement = &Placement{} - } - cfg.Placement.Tags = append(cfg.Placement.Tags, req.Tags...) - } - - peers, e := cc.selectPeerGroup(cfg.Replicas+1, currCluster, &cfg, currPeers, 1, nil) - if len(peers) <= cfg.Replicas { - // since expanding in the same cluster did not yield a result, try in different cluster - peers = nil - - clusters := map[string]struct{}{} - s.nodeToInfo.Range(func(_, ni any) bool { - if currCluster != ni.(nodeInfo).cluster { - clusters[ni.(nodeInfo).cluster] = struct{}{} - } - return true - }) - errs := &selectPeerError{} - errs.accumulate(e) - for cluster := range clusters { - newPeers, e := cc.selectPeerGroup(cfg.Replicas, cluster, &cfg, nil, 0, nil) - if len(newPeers) >= cfg.Replicas { - peers = append([]string{}, currPeers...) - peers = append(peers, newPeers[:cfg.Replicas]...) - break - } - errs.accumulate(e) - } - if peers == nil { - resp.Error = NewJSClusterNoPeersError(errs) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - } - - cfg.Placement = origPlacement - - s.Noticef("Requested move for stream '%s > %s' R=%d from %+v to %+v", - accName, streamName, cfg.Replicas, s.peerSetToNames(currPeers), s.peerSetToNames(peers)) - - // We will always have peers and therefore never do a callout, therefore it is safe to call inline - // We should be fine ignoring pedantic mode here. as we do not touch configuration. - s.jsClusteredStreamUpdateRequest(&ciNew, targetAcc.(*Account), subject, reply, rmsg, &cfg, peers, false) -} - -// Request to have the metaleader move a stream on a peer to another -func (s *Server) jsLeaderServerStreamCancelMoveRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil || cc.meta == nil { - return - } - - // Extra checks here but only leader is listening. - js.mu.RLock() - isLeader := cc.isLeader() - js.mu.RUnlock() - - if !isLeader { - return - } - - var resp = JSApiStreamUpdateResponse{ApiResponse: ApiResponse{Type: JSApiStreamUpdateResponseType}} - - accName := tokenAt(subject, 6) - streamName := tokenAt(subject, 7) - - targetAcc, ok := s.accounts.Load(accName) - if !ok { - resp.Error = NewJSNoAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - streamFound := false - cfg := StreamConfig{} - currPeers := []string{} - js.mu.Lock() - streams, ok := cc.streams[accName] - if ok { - sa, ok := streams[streamName] - if ok { - cfg = *sa.Config.clone() - streamFound = true - currPeers = sa.Group.Peers - } - } - js.mu.Unlock() - - if !streamFound { - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if len(currPeers) <= cfg.Replicas { - resp.Error = NewJSStreamMoveNotInProgressError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // make sure client is scoped to requested account - ciNew := *(ci) - ciNew.Account = accName - - peers := currPeers[:cfg.Replicas] - - // Remove placement in case tags don't match - // This can happen if the move was initiated by modifying the tags. - // This is an account operation. - // This can NOT happen when the move was initiated by the system account. - // There move honors the original tag list. - if cfg.Placement != nil && len(cfg.Placement.Tags) != 0 { - FOR_TAGCHECK: - for _, peer := range peers { - si, ok := s.nodeToInfo.Load(peer) - if !ok { - // can't verify tags, do the safe thing and error - resp.Error = NewJSStreamGeneralError( - fmt.Errorf("peer %s not present for tag validation", peer)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - nodeTags := si.(nodeInfo).tags - for _, tag := range cfg.Placement.Tags { - if !nodeTags.Contains(tag) { - // clear placement as tags don't match - cfg.Placement = nil - break FOR_TAGCHECK - } - } - - } - } - - s.Noticef("Requested cancel of move: R=%d '%s > %s' to peer set %+v and restore previous peer set %+v", - cfg.Replicas, accName, streamName, s.peerSetToNames(currPeers), s.peerSetToNames(peers)) - - // We will always have peers and therefore never do a callout, therefore it is safe to call inline - s.jsClusteredStreamUpdateRequest(&ciNew, targetAcc.(*Account), subject, reply, rmsg, &cfg, peers, false) -} - -// Request to have an account purged -func (s *Server) jsLeaderAccountPurgeRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - js := s.getJetStream() - if js == nil { - return - } - - accName := tokenAt(subject, 5) - - var resp = JSApiAccountPurgeResponse{ApiResponse: ApiResponse{Type: JSApiAccountPurgeResponseType}} - - if !s.JetStreamIsClustered() { - var streams []*stream - var ac *Account - if ac, err = s.lookupAccount(accName); err == nil && ac != nil { - streams = ac.streams() - } - - s.Noticef("Purge request for account %s (streams: %d, hasAccount: %t)", - accName, len(streams), ac != nil) - - for _, mset := range streams { - err := mset.delete() - if err != nil { - resp.Error = NewJSStreamDeleteError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - } - if err := os.RemoveAll(filepath.Join(js.config.StoreDir, accName)); err != nil { - resp.Error = NewJSStreamGeneralError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - resp.Initiated = true - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - _, cc := s.getJetStreamCluster() - if cc == nil || cc.meta == nil || !cc.isLeader() { - return - } - - if js.isMetaRecovering() { - // While in recovery mode, the data structures are not fully initialized - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - js.mu.RLock() - ns, nc := 0, 0 - streams, hasAccount := cc.streams[accName] - for _, osa := range streams { - for _, oca := range osa.consumers { - oca.deleted = true - ca := &consumerAssignment{Group: oca.Group, Stream: oca.Stream, Name: oca.Name, Config: oca.Config, Subject: subject, Client: oca.Client} - cc.meta.Propose(encodeDeleteConsumerAssignment(ca)) - nc++ - } - sa := &streamAssignment{Group: osa.Group, Config: osa.Config, Subject: subject, Client: osa.Client} - cc.meta.Propose(encodeDeleteStreamAssignment(sa)) - ns++ - } - js.mu.RUnlock() - - s.Noticef("Purge request for account %s (streams: %d, consumer: %d, hasAccount: %t)", accName, ns, nc, hasAccount) - - resp.Initiated = true - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) -} - -// Request to have the meta leader stepdown. -// These will only be received by the meta leader, so less checking needed. -func (s *Server) jsLeaderStepDownRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - // This should only be coming from the System Account. - if acc != s.SystemAccount() { - s.RateLimitWarnf("JetStream API stepdown request from non-system account: %q user: %q", ci.serviceAccount(), ci.User) - return - } - - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil || cc.meta == nil { - return - } - - // Extra checks here but only leader is listening. - js.mu.RLock() - isLeader := cc.isLeader() - js.mu.RUnlock() - - if !isLeader { - return - } - - var preferredLeader string - var resp = JSApiLeaderStepDownResponse{ApiResponse: ApiResponse{Type: JSApiLeaderStepDownResponseType}} - - if isJSONObjectOrArray(msg) { - var req JSApiLeaderStepdownRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - if preferredLeader, resp.Error = s.getStepDownPreferredPlacement(cc.meta, req.Placement); resp.Error != nil { - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - } - - // Call actual stepdown. - err = cc.meta.StepDown(preferredLeader) - if err != nil { - resp.Error = NewJSRaftGeneralError(err, Unless(err)) - } else { - resp.Success = true - } - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Check if given []bytes is a JSON Object or Array. -// Technically, valid JSON can also be a plain string or number, but for our use case, -// we care only for JSON objects or arrays which starts with `[` or `{`. -// This function does not have to ensure valid JSON in its entirety. It is used merely -// to hint the codepath if it should attempt to parse the request as JSON or not. -func isJSONObjectOrArray(req []byte) bool { - // Skip leading JSON whitespace (space, tab, newline, carriage return) - i := 0 - for i < len(req) && (req[i] == ' ' || req[i] == '\t' || req[i] == '\n' || req[i] == '\r') { - i++ - } - // Check for empty input after trimming - if i >= len(req) { - return false - } - // Check if the first non-whitespace character is '{' or '[' - return req[i] == '{' || req[i] == '[' -} - -func isEmptyRequest(req []byte) bool { - if len(req) == 0 { - return true - } - if bytes.Equal(req, []byte("{}")) { - return true - } - // If we are here we didn't get our simple match, but still could be valid. - var v any - if err := json.Unmarshal(req, &v); err != nil { - return false - } - vm, ok := v.(map[string]any) - if !ok { - return false - } - return len(vm) == 0 -} - -// getStepDownPreferredPlacement attempts to work out what the best placement is -// for a stepdown request. The preferred server name always takes precedence, but -// if not specified, the placement will be used to filter by cluster. The caller -// should check for return API errors and return those to the requestor if needed. -func (s *Server) getStepDownPreferredPlacement(group RaftNode, placement *Placement) (string, *ApiError) { - if placement == nil { - return _EMPTY_, nil - } - var preferredLeader string - if placement.Preferred != _EMPTY_ { - for _, p := range group.Peers() { - si, ok := s.nodeToInfo.Load(p.ID) - if !ok || si == nil { - continue - } - if si.(nodeInfo).name == placement.Preferred { - preferredLeader = p.ID - break - } - } - if preferredLeader == group.ID() { - return _EMPTY_, NewJSClusterNoPeersError(fmt.Errorf("preferred server %q is already leader", placement.Preferred)) - } - if preferredLeader == _EMPTY_ { - return _EMPTY_, NewJSClusterNoPeersError(fmt.Errorf("preferred server %q not known", placement.Preferred)) - } - } else { - possiblePeers := make(map[*Peer]nodeInfo, len(group.Peers())) - ourID := group.ID() - for _, p := range group.Peers() { - if p == nil { - continue // ... shouldn't happen. - } - si, ok := s.nodeToInfo.Load(p.ID) - if !ok || si == nil { - continue - } - ni := si.(nodeInfo) - if ni.offline || p.ID == ourID { - continue - } - possiblePeers[p] = ni - } - // If cluster is specified, filter out anything not matching the cluster name. - if placement.Cluster != _EMPTY_ { - for p, si := range possiblePeers { - if si.cluster != placement.Cluster { - delete(possiblePeers, p) - } - } - } - // If tags are specified, filter out anything not matching all supplied tags. - if len(placement.Tags) > 0 { - for p, si := range possiblePeers { - matchesAll := true - for _, tag := range placement.Tags { - if matchesAll = matchesAll && si.tags.Contains(tag); !matchesAll { - break - } - } - if !matchesAll { - delete(possiblePeers, p) - } - } - } - // If there are no possible peers, return an error. - if len(possiblePeers) == 0 { - return _EMPTY_, NewJSClusterNoPeersError(fmt.Errorf("no replacement peer connected")) - } - // Take advantage of random map iteration order to select the preferred. - for p := range possiblePeers { - preferredLeader = p.ID - break - } - } - return preferredLeader, nil -} - -// Request to delete a stream. -func (s *Server) jsStreamDeleteRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiStreamDeleteResponse{ApiResponse: ApiResponse{Type: JSApiStreamDeleteResponseType}} - - // Determine if we should proceed here when we are in clustered mode. - if s.JetStreamIsClustered() { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - // Make sure we are meta leader. - if !s.JetStreamIsLeader() { - return - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - if !isEmptyRequest(msg) { - resp.Error = NewJSNotEmptyRequestError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - stream := streamNameFromSubject(subject) - - // Clustered. - if s.JetStreamIsClustered() { - s.jsClusteredStreamDeleteRequest(ci, acc, stream, subject, reply, msg) - return - } - - mset, err := acc.lookupStream(stream) - if err != nil { - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if err := mset.delete(); err != nil { - resp.Error = NewJSStreamDeleteError(err, Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - resp.Success = true - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request to delete a message. -// This expects a stream sequence number as the msg body. -func (s *Server) jsMsgDeleteRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - stream := tokenAt(subject, 6) - - var resp = JSApiMsgDeleteResponse{ApiResponse: ApiResponse{Type: JSApiMsgDeleteResponseType}} - - // If we are in clustered mode we need to be the stream leader to proceed. - if s.JetStreamIsClustered() { - // Check to make sure the stream is assigned. - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - js.mu.RLock() - isLeader, sa := cc.isLeader(), js.streamAssignment(acc.Name, stream) - js.mu.RUnlock() - - if isLeader && sa == nil { - // We can't find the stream, so mimic what would be the errors below. - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - // No stream present. - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } else if sa == nil { - return - } - - // Check to see if we are a member of the group and if the group has no leader. - if js.isGroupLeaderless(sa.Group) { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // We have the stream assigned and a leader, so only the stream leader should answer. - if !acc.JetStreamIsStreamLeader(stream) { - return - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - if isEmptyRequest(msg) { - resp.Error = NewJSBadRequestError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - var req JSApiMsgDeleteRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - mset, err := acc.lookupStream(stream) - if err != nil { - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - if mset.cfg.Sealed { - resp.Error = NewJSStreamSealedError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - if mset.cfg.DenyDelete { - resp.Error = NewJSStreamMsgDeleteFailedError(errors.New("message delete not permitted")) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if s.JetStreamIsClustered() { - s.jsClusteredMsgDeleteRequest(ci, acc, mset, stream, subject, reply, &req, rmsg) - return - } - - var removed bool - if req.NoErase { - removed, err = mset.removeMsg(req.Seq) - } else { - removed, err = mset.eraseMsg(req.Seq) - } - if err != nil { - resp.Error = NewJSStreamMsgDeleteFailedError(err, Unless(err)) - } else if !removed { - resp.Error = NewJSSequenceNotFoundError(req.Seq) - } else { - resp.Success = true - } - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request to get a raw stream message. -func (s *Server) jsMsgGetRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - stream := tokenAt(subject, 6) - - var resp = JSApiMsgGetResponse{ApiResponse: ApiResponse{Type: JSApiMsgGetResponseType}} - - // If we are in clustered mode we need to be the stream leader to proceed. - if s.JetStreamIsClustered() { - // Check to make sure the stream is assigned. - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - js.mu.RLock() - isLeader, sa := cc.isLeader(), js.streamAssignment(acc.Name, stream) - js.mu.RUnlock() - - if isLeader && sa == nil { - // We can't find the stream, so mimic what would be the errors below. - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - // No stream present. - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } else if sa == nil { - return - } - - // Check to see if we are a member of the group and if the group has no leader. - if js.isGroupLeaderless(sa.Group) { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // We have the stream assigned and a leader, so only the stream leader should answer. - if !acc.JetStreamIsStreamLeader(stream) { - return - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - if isEmptyRequest(msg) { - resp.Error = NewJSBadRequestError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - var req JSApiMsgGetRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // This version does not support batch. - if req.Batch > 0 || req.MaxBytes > 0 { - resp.Error = NewJSBadRequestError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // Validate non-conflicting options. Seq, LastFor, and AsOfTime are mutually exclusive. - // NextFor can be paired with Seq or AsOfTime indicating a filter subject. - if (req.Seq > 0 && req.LastFor != _EMPTY_) || - (req.Seq == 0 && req.LastFor == _EMPTY_ && req.NextFor == _EMPTY_ && req.StartTime == nil) || - (req.Seq > 0 && req.StartTime != nil) || - (req.StartTime != nil && req.LastFor != _EMPTY_) || - (req.LastFor != _EMPTY_ && req.NextFor != _EMPTY_) { - resp.Error = NewJSBadRequestError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - mset, err := acc.lookupStream(stream) - if err != nil { - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - var svp StoreMsg - var sm *StoreMsg - - // If AsOfTime is set, perform this first to get the sequence. - var seq uint64 - if req.StartTime != nil { - seq = mset.store.GetSeqFromTime(*req.StartTime) - } else { - seq = req.Seq - } - - if seq > 0 && req.NextFor == _EMPTY_ { - sm, err = mset.store.LoadMsg(seq, &svp) - } else if req.NextFor != _EMPTY_ { - sm, _, err = mset.store.LoadNextMsg(req.NextFor, subjectHasWildcard(req.NextFor), seq, &svp) - } else { - sm, err = mset.store.LoadLastMsg(req.LastFor, &svp) - } - if err != nil { - resp.Error = NewJSNoMessageFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - resp.Message = &StoredMsg{ - Subject: sm.subj, - Sequence: sm.seq, - Header: sm.hdr, - Data: sm.msg, - Time: time.Unix(0, sm.ts).UTC(), - } - - // Don't send response through API layer for this call. - s.sendInternalAccountMsg(nil, reply, s.jsonResponse(resp)) -} - -func (s *Server) jsConsumerUnpinRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - stream := streamNameFromSubject(subject) - consumer := consumerNameFromSubject(subject) - - var req JSApiConsumerUnpinRequest - var resp = JSApiConsumerUnpinResponse{ApiResponse: ApiResponse{Type: JSApiConsumerUnpinResponseType}} - - if err := json.Unmarshal(msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if req.Group == _EMPTY_ { - resp.Error = NewJSInvalidJSONError(errors.New("consumer group not specified")) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if !validGroupName.MatchString(req.Group) { - resp.Error = NewJSConsumerInvalidGroupNameError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - if s.JetStreamIsClustered() { - // Check to make sure the stream is assigned. - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - - // First check if the stream and consumer is there. - js.mu.RLock() - sa := js.streamAssignment(acc.Name, stream) - if sa == nil { - js.mu.RUnlock() - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - ca, ok := sa.consumers[consumer] - if !ok || ca == nil { - js.mu.RUnlock() - resp.Error = NewJSConsumerNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - js.mu.RUnlock() - - // Then check if we are the leader. - mset, err := acc.lookupStream(stream) - if err != nil { - return - } - - o := mset.lookupConsumer(consumer) - if o == nil { - return - } - if !o.isLeader() { - return - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - mset, err := acc.lookupStream(stream) - if err != nil { - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - o := mset.lookupConsumer(consumer) - if o == nil { - resp.Error = NewJSConsumerNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - var foundPriority bool - for _, group := range o.config().PriorityGroups { - if group == req.Group { - foundPriority = true - break - } - } - if !foundPriority { - resp.Error = NewJSConsumerInvalidPriorityGroupError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - o.mu.Lock() - o.currentPinId = _EMPTY_ - o.sendUnpinnedAdvisoryLocked(req.Group, "admin") - o.mu.Unlock() - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request to purge a stream. -func (s *Server) jsStreamPurgeRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - stream := streamNameFromSubject(subject) - - var resp = JSApiStreamPurgeResponse{ApiResponse: ApiResponse{Type: JSApiStreamPurgeResponseType}} - - // If we are in clustered mode we need to be the stream leader to proceed. - if s.JetStreamIsClustered() { - // Check to make sure the stream is assigned. - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - - js.mu.RLock() - isLeader, sa := cc.isLeader(), js.streamAssignment(acc.Name, stream) - js.mu.RUnlock() - - if isLeader && sa == nil { - // We can't find the stream, so mimic what would be the errors below. - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - // No stream present. - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } else if sa == nil { - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - // Check to see if we are a member of the group and if the group has no leader. - if js.isGroupLeaderless(sa.Group) { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // We have the stream assigned and a leader, so only the stream leader should answer. - if !acc.JetStreamIsStreamLeader(stream) { - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - var purgeRequest *JSApiStreamPurgeRequest - if isJSONObjectOrArray(msg) { - var req JSApiStreamPurgeRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - if req.Sequence > 0 && req.Keep > 0 { - resp.Error = NewJSBadRequestError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - purgeRequest = &req - } - - mset, err := acc.lookupStream(stream) - if err != nil { - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - if mset.cfg.Sealed { - resp.Error = NewJSStreamSealedError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - if mset.cfg.DenyPurge { - resp.Error = NewJSStreamPurgeFailedError(errors.New("stream purge not permitted")) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if s.JetStreamIsClustered() { - s.jsClusteredStreamPurgeRequest(ci, acc, mset, stream, subject, reply, rmsg, purgeRequest) - return - } - - purged, err := mset.purge(purgeRequest) - if err != nil { - resp.Error = NewJSStreamGeneralError(err, Unless(err)) - } else { - resp.Purged = purged - resp.Success = true - } - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -func (acc *Account) jsNonClusteredStreamLimitsCheck(cfg *StreamConfig) *ApiError { - var replicas int - if cfg != nil { - replicas = cfg.Replicas - } - selectedLimits, tier, jsa, apiErr := acc.selectLimits(replicas) - if apiErr != nil { - return apiErr - } - jsa.mu.RLock() - defer jsa.mu.RUnlock() - if selectedLimits.MaxStreams > 0 && jsa.countStreams(tier, cfg) >= selectedLimits.MaxStreams { - return NewJSMaximumStreamsLimitError() - } - reserved := jsa.tieredReservation(tier, cfg) - if err := jsa.js.checkAllLimits(selectedLimits, cfg, reserved, 0); err != nil { - return NewJSStreamLimitsError(err, Unless(err)) - } - return nil -} - -// Request to restore a stream. -func (s *Server) jsStreamRestoreRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamIsLeader() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiStreamRestoreResponse{ApiResponse: ApiResponse{Type: JSApiStreamRestoreResponseType}} - if !acc.JetStreamEnabled() { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - if isEmptyRequest(msg) { - resp.Error = NewJSBadRequestError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - var req JSApiStreamRestoreRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - stream := streamNameFromSubject(subject) - - if stream != req.Config.Name && req.Config.Name == _EMPTY_ { - req.Config.Name = stream - } - - // check stream config at the start of the restore process, not at the end - cfg, apiErr := s.checkStreamCfg(&req.Config, acc, false) - if apiErr != nil { - resp.Error = apiErr - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if s.JetStreamIsClustered() { - s.jsClusteredStreamRestoreRequest(ci, acc, &req, subject, reply, rmsg) - return - } - - if err := acc.jsNonClusteredStreamLimitsCheck(&cfg); err != nil { - resp.Error = err - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if _, err := acc.lookupStream(stream); err == nil { - resp.Error = NewJSStreamNameExistRestoreFailedError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - s.processStreamRestore(ci, acc, &req.Config, subject, reply, string(msg)) -} - -func (s *Server) processStreamRestore(ci *ClientInfo, acc *Account, cfg *StreamConfig, subject, reply, msg string) <-chan error { - js := s.getJetStream() - - var resp = JSApiStreamRestoreResponse{ApiResponse: ApiResponse{Type: JSApiStreamRestoreResponseType}} - - snapDir := filepath.Join(js.config.StoreDir, snapStagingDir) - if _, err := os.Stat(snapDir); os.IsNotExist(err) { - if err := os.MkdirAll(snapDir, defaultDirPerms); err != nil { - resp.Error = &ApiError{Code: 503, Description: "JetStream unable to create temp storage for restore"} - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return nil - } - } - - tfile, err := os.CreateTemp(snapDir, "js-restore-") - if err != nil { - resp.Error = NewJSTempStorageFailedError() - s.sendAPIErrResponse(ci, acc, subject, reply, msg, s.jsonResponse(&resp)) - return nil - } - - streamName := cfg.Name - s.Noticef("Starting restore for stream '%s > %s'", acc.Name, streamName) - - start := time.Now().UTC() - domain := s.getOpts().JetStreamDomain - s.publishAdvisory(acc, JSAdvisoryStreamRestoreCreatePre+"."+streamName, &JSRestoreCreateAdvisory{ - TypedEvent: TypedEvent{ - Type: JSRestoreCreateAdvisoryType, - ID: nuid.Next(), - Time: start, - }, - Stream: streamName, - Client: ci.forAdvisory(), - Domain: domain, - }) - - // Create our internal subscription to accept the snapshot. - restoreSubj := fmt.Sprintf(jsRestoreDeliverT, streamName, nuid.Next()) - - type result struct { - err error - reply string - } - - // For signaling to upper layers. - resultCh := make(chan result, 1) - activeQ := newIPQueue[int](s, fmt.Sprintf("[ACC:%s] stream '%s' restore", acc.Name, streamName)) // of int - - var total int - - // FIXME(dlc) - Probably take out of network path eventually due to disk I/O? - processChunk := func(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) { - // We require reply subjects to communicate back failures, flow etc. If they do not have one log and cancel. - if reply == _EMPTY_ { - sub.client.processUnsub(sub.sid) - resultCh <- result{ - fmt.Errorf("restore for stream '%s > %s' requires reply subject for each chunk", acc.Name, streamName), - reply, - } - return - } - // Account client messages have \r\n on end. This is an error. - if len(msg) < LEN_CR_LF { - sub.client.processUnsub(sub.sid) - resultCh <- result{ - fmt.Errorf("restore for stream '%s > %s' received short chunk", acc.Name, streamName), - reply, - } - return - } - // Adjust. - msg = msg[:len(msg)-LEN_CR_LF] - - // This means we are complete with our transfer from the client. - if len(msg) == 0 { - s.Debugf("Finished staging restore for stream '%s > %s'", acc.Name, streamName) - resultCh <- result{err, reply} - return - } - - // We track total and check on server limits. - // TODO(dlc) - We could check apriori and cancel initial request if we know it won't fit. - total += len(msg) - if js.wouldExceedLimits(FileStorage, total) { - s.resourcesExceededError() - resultCh <- result{NewJSInsufficientResourcesError(), reply} - return - } - - // Append chunk to temp file. Mark as issue if we encounter an error. - if n, err := tfile.Write(msg); n != len(msg) || err != nil { - resultCh <- result{err, reply} - if reply != _EMPTY_ { - s.sendInternalAccountMsg(acc, reply, "-ERR 'storage failure during restore'") - } - return - } - - activeQ.push(len(msg)) - - s.sendInternalAccountMsg(acc, reply, nil) - } - - sub, err := acc.subscribeInternal(restoreSubj, processChunk) - if err != nil { - tfile.Close() - os.Remove(tfile.Name()) - resp.Error = NewJSRestoreSubscribeFailedError(err, restoreSubj) - s.sendAPIErrResponse(ci, acc, subject, reply, msg, s.jsonResponse(&resp)) - return nil - } - - // Mark the subject so the end user knows where to send the snapshot chunks. - resp.DeliverSubject = restoreSubj - s.sendAPIResponse(ci, acc, subject, reply, msg, s.jsonResponse(resp)) - - doneCh := make(chan error, 1) - - // Monitor the progress from another Go routine. - s.startGoRoutine(func() { - defer s.grWG.Done() - defer func() { - tfile.Close() - os.Remove(tfile.Name()) - sub.client.processUnsub(sub.sid) - activeQ.unregister() - }() - - const activityInterval = 5 * time.Second - notActive := time.NewTimer(activityInterval) - defer notActive.Stop() - - total := 0 - for { - select { - case result := <-resultCh: - err := result.err - var mset *stream - - // If we staged properly go ahead and do restore now. - if err == nil { - s.Debugf("Finalizing restore for stream '%s > %s'", acc.Name, streamName) - tfile.Seek(0, 0) - mset, err = acc.RestoreStream(cfg, tfile) - } else { - errStr := err.Error() - tmp := []rune(errStr) - tmp[0] = unicode.ToUpper(tmp[0]) - s.Warnf(errStr) - } - - end := time.Now().UTC() - - // TODO(rip) - Should this have the error code in it?? - s.publishAdvisory(acc, JSAdvisoryStreamRestoreCompletePre+"."+streamName, &JSRestoreCompleteAdvisory{ - TypedEvent: TypedEvent{ - Type: JSRestoreCompleteAdvisoryType, - ID: nuid.Next(), - Time: end, - }, - Stream: streamName, - Start: start, - End: end, - Bytes: int64(total), - Client: ci.forAdvisory(), - Domain: domain, - }) - - var resp = JSApiStreamCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamCreateResponseType}} - - if err != nil { - resp.Error = NewJSStreamRestoreError(err, Unless(err)) - s.Warnf("Restore failed for %s for stream '%s > %s' in %v", - friendlyBytes(int64(total)), acc.Name, streamName, end.Sub(start)) - } else { - msetCfg := mset.config() - resp.StreamInfo = &StreamInfo{ - Created: mset.createdTime(), - State: mset.state(), - Config: *setDynamicStreamMetadata(&msetCfg), - TimeStamp: time.Now().UTC(), - } - s.Noticef("Completed restore of %s for stream '%s > %s' in %v", - friendlyBytes(int64(total)), acc.Name, streamName, end.Sub(start).Round(time.Millisecond)) - } - - // On the last EOF, send back the stream info or error status. - s.sendInternalAccountMsg(acc, result.reply, s.jsonResponse(&resp)) - // Signal to the upper layers. - doneCh <- err - return - case <-activeQ.ch: - if n, ok := activeQ.popOne(); ok { - total += n - notActive.Reset(activityInterval) - } - case <-notActive.C: - err := fmt.Errorf("restore for stream '%s > %s' is stalled", acc, streamName) - doneCh <- err - return - } - } - }) - - return doneCh -} - -// Process a snapshot request. -func (s *Server) jsStreamSnapshotRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - smsg := string(msg) - stream := streamNameFromSubject(subject) - - // If we are in clustered mode we need to be the stream leader to proceed. - if s.JetStreamIsClustered() && !acc.JetStreamIsStreamLeader(stream) { - return - } - - var resp = JSApiStreamSnapshotResponse{ApiResponse: ApiResponse{Type: JSApiStreamSnapshotResponseType}} - if !acc.JetStreamEnabled() { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, smsg, s.jsonResponse(&resp)) - return - } - if isEmptyRequest(msg) { - resp.Error = NewJSBadRequestError() - s.sendAPIErrResponse(ci, acc, subject, reply, smsg, s.jsonResponse(&resp)) - return - } - - mset, err := acc.lookupStream(stream) - if err != nil { - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, smsg, s.jsonResponse(&resp)) - return - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, smsg, s.jsonResponse(&resp)) - } - return - } - - var req JSApiStreamSnapshotRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, smsg, s.jsonResponse(&resp)) - return - } - if !IsValidSubject(req.DeliverSubject) { - resp.Error = NewJSSnapshotDeliverSubjectInvalidError() - s.sendAPIErrResponse(ci, acc, subject, reply, smsg, s.jsonResponse(&resp)) - return - } - - // We will do the snapshot in a go routine as well since check msgs may - // stall this go routine. - go func() { - if req.CheckMsgs { - s.Noticef("Starting health check and snapshot for stream '%s > %s'", mset.jsa.account.Name, mset.name()) - } else { - s.Noticef("Starting snapshot for stream '%s > %s'", mset.jsa.account.Name, mset.name()) - } - - start := time.Now().UTC() - - sr, err := mset.snapshot(0, req.CheckMsgs, !req.NoConsumers) - if err != nil { - s.Warnf("Snapshot of stream '%s > %s' failed: %v", mset.jsa.account.Name, mset.name(), err) - resp.Error = NewJSStreamSnapshotError(err, Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, smsg, s.jsonResponse(&resp)) - return - } - - config := mset.config() - resp.State = &sr.State - resp.Config = &config - - s.sendAPIResponse(ci, acc, subject, reply, smsg, s.jsonResponse(resp)) - - s.publishAdvisory(acc, JSAdvisoryStreamSnapshotCreatePre+"."+mset.name(), &JSSnapshotCreateAdvisory{ - TypedEvent: TypedEvent{ - Type: JSSnapshotCreatedAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Stream: mset.name(), - State: sr.State, - Client: ci.forAdvisory(), - Domain: s.getOpts().JetStreamDomain, - }) - - // Now do the real streaming. - s.streamSnapshot(acc, mset, sr, &req) - - end := time.Now().UTC() - - s.publishAdvisory(acc, JSAdvisoryStreamSnapshotCompletePre+"."+mset.name(), &JSSnapshotCompleteAdvisory{ - TypedEvent: TypedEvent{ - Type: JSSnapshotCompleteAdvisoryType, - ID: nuid.Next(), - Time: end, - }, - Stream: mset.name(), - Start: start, - End: end, - Client: ci.forAdvisory(), - Domain: s.getOpts().JetStreamDomain, - }) - - s.Noticef("Completed snapshot of %s for stream '%s > %s' in %v", - friendlyBytes(int64(sr.State.Bytes)), - mset.jsa.account.Name, - mset.name(), - end.Sub(start)) - }() -} - -// Default chunk size for now. -const defaultSnapshotChunkSize = 128 * 1024 -const defaultSnapshotWindowSize = 8 * 1024 * 1024 // 8MB - -// streamSnapshot will stream out our snapshot to the reply subject. -func (s *Server) streamSnapshot(acc *Account, mset *stream, sr *SnapshotResult, req *JSApiStreamSnapshotRequest) { - chunkSize := req.ChunkSize - if chunkSize == 0 { - chunkSize = defaultSnapshotChunkSize - } - // Setup for the chunk stream. - reply := req.DeliverSubject - r := sr.Reader - defer r.Close() - - // Check interest for the snapshot deliver subject. - inch := make(chan bool, 1) - acc.sl.RegisterNotification(req.DeliverSubject, inch) - defer acc.sl.ClearNotification(req.DeliverSubject, inch) - hasInterest := <-inch - if !hasInterest { - // Allow 2 seconds or so for interest to show up. - select { - case <-inch: - case <-time.After(2 * time.Second): - } - } - - // Create our ack flow handler. - // This is very simple for now. - ackSize := defaultSnapshotWindowSize / chunkSize - if ackSize < 8 { - ackSize = 8 - } else if ackSize > 8*1024 { - ackSize = 8 * 1024 - } - acks := make(chan struct{}, ackSize) - acks <- struct{}{} - - // Track bytes outstanding. - var out int32 - - // We will place sequence number and size of chunk sent in the reply. - ackSubj := fmt.Sprintf(jsSnapshotAckT, mset.name(), nuid.Next()) - ackSub, _ := mset.subscribeInternal(ackSubj+".>", func(_ *subscription, _ *client, _ *Account, subject, _ string, _ []byte) { - cs, _ := strconv.Atoi(tokenAt(subject, 6)) - // This is very crude and simple, but ok for now. - // This only matters when sending multiple chunks. - if atomic.AddInt32(&out, int32(-cs)) < defaultSnapshotWindowSize { - select { - case acks <- struct{}{}: - default: - } - } - }) - defer mset.unsubscribe(ackSub) - - // TODO(dlc) - Add in NATS-Chunked-Sequence header - var hdr []byte - for index := 1; ; index++ { - chunk := make([]byte, chunkSize) - n, err := r.Read(chunk) - chunk = chunk[:n] - if err != nil { - if n > 0 { - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, chunk, nil, 0)) - } - break - } - - // Wait on acks for flow control if past our window size. - // Wait up to 10ms for now if no acks received. - if atomic.LoadInt32(&out) > defaultSnapshotWindowSize { - select { - case <-acks: - // ok to proceed. - case <-inch: - // Lost interest - hdr = []byte("NATS/1.0 408 No Interest\r\n\r\n") - goto done - case <-time.After(2 * time.Second): - hdr = []byte("NATS/1.0 408 No Flow Response\r\n\r\n") - goto done - } - } - ackReply := fmt.Sprintf("%s.%d.%d", ackSubj, len(chunk), index) - if hdr == nil { - hdr = []byte("NATS/1.0 204\r\n\r\n") - } - mset.outq.send(newJSPubMsg(reply, _EMPTY_, ackReply, nil, chunk, nil, 0)) - atomic.AddInt32(&out, int32(len(chunk))) - } - - if err := <-sr.errCh; err != _EMPTY_ { - hdr = []byte(fmt.Sprintf("NATS/1.0 500 %s\r\n\r\n", err)) - } - -done: - // Send last EOF - // TODO(dlc) - place hash in header - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) -} - -// For determining consumer request type. -type ccReqType uint8 - -const ( - ccNew = iota - ccLegacyEphemeral - ccLegacyDurable -) - -// Request to create a consumer where stream and optional consumer name are part of the subject, and optional -// filtered subjects can be at the tail end. -// Assumes stream and consumer names are single tokens. -func (s *Server) jsConsumerCreateRequest(sub *subscription, c *client, a *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiConsumerCreateResponse{ApiResponse: ApiResponse{Type: JSApiConsumerCreateResponseType}} - - var req CreateConsumerRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - var js *jetStream - isClustered := s.JetStreamIsClustered() - - // Determine if we should proceed here when we are in clustered mode. - if isClustered { - if req.Config.Direct { - // Check to see if we have this stream and are the stream leader. - if !acc.JetStreamIsStreamLeader(streamNameFromSubject(subject)) { - return - } - } else { - var cc *jetStreamCluster - js, cc = s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - // Make sure we are meta leader. - if !s.JetStreamIsLeader() { - return - } - } - } - - var streamName, consumerName, filteredSubject string - var rt ccReqType - - if n := numTokens(subject); n < 5 { - s.Warnf(badAPIRequestT, msg) - return - } else if n == 5 { - // Legacy ephemeral. - rt = ccLegacyEphemeral - streamName = streamNameFromSubject(subject) - } else { - // New style and durable legacy. - if tokenAt(subject, 4) == "DURABLE" { - rt = ccLegacyDurable - if n != 7 { - resp.Error = NewJSConsumerDurableNameNotInSubjectError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - streamName = tokenAt(subject, 6) - consumerName = tokenAt(subject, 7) - } else { - streamName = streamNameFromSubject(subject) - consumerName = consumerNameFromSubject(subject) - // New has optional filtered subject as part of main subject.. - if n > 6 { - tokens := strings.Split(subject, tsep) - filteredSubject = strings.Join(tokens[6:], tsep) - } - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - if streamName != req.Stream { - resp.Error = NewJSStreamMismatchError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if consumerName != _EMPTY_ { - // Check for path like separators in the name. - if strings.ContainsAny(consumerName, `\/`) { - resp.Error = NewJSConsumerNameContainsPathSeparatorsError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - } - - // Should we expect a durable name - if rt == ccLegacyDurable { - if numTokens(subject) < 7 { - resp.Error = NewJSConsumerDurableNameNotInSubjectError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - // Now check on requirements for durable request. - if req.Config.Durable == _EMPTY_ { - resp.Error = NewJSConsumerDurableNameNotSetError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - if consumerName != req.Config.Durable { - resp.Error = NewJSConsumerDurableNameNotMatchSubjectError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - } - // If new style and durable set make sure they match. - if rt == ccNew { - if req.Config.Durable != _EMPTY_ { - if consumerName != req.Config.Durable { - resp.Error = NewJSConsumerDurableNameNotMatchSubjectError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - } - // New style ephemeral so we need to honor the name. - req.Config.Name = consumerName - } - // Check for legacy ephemeral mis-configuration. - if rt == ccLegacyEphemeral && req.Config.Durable != _EMPTY_ { - resp.Error = NewJSConsumerEphemeralWithDurableNameError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // in case of multiple filters provided, error if new API is used. - if filteredSubject != _EMPTY_ && len(req.Config.FilterSubjects) != 0 { - resp.Error = NewJSConsumerMultipleFiltersNotAllowedError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // Check for a filter subject. - if filteredSubject != _EMPTY_ && req.Config.FilterSubject != filteredSubject { - resp.Error = NewJSConsumerCreateFilterSubjectMismatchError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if isClustered && !req.Config.Direct { - // If we are inline with client, we still may need to do a callout for consumer info - // during this call, so place in Go routine to not block client. - // Router and Gateway API calls already in separate context. - if c.kind != ROUTER && c.kind != GATEWAY { - go s.jsClusteredConsumerRequest(ci, acc, subject, reply, rmsg, req.Stream, &req.Config, req.Action, req.Pedantic) - } else { - s.jsClusteredConsumerRequest(ci, acc, subject, reply, rmsg, req.Stream, &req.Config, req.Action, req.Pedantic) - } - return - } - - // If we are here we are single server mode. - if req.Config.Replicas > 1 { - resp.Error = NewJSStreamReplicasNotSupportedError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - stream, err := acc.lookupStream(req.Stream) - if err != nil { - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if o := stream.lookupConsumer(consumerName); o != nil { - // If the consumer already exists then don't allow updating the PauseUntil, just set - // it back to whatever the current configured value is. - req.Config.PauseUntil = o.cfg.PauseUntil - } - - // Initialize/update asset version metadata. - setStaticConsumerMetadata(&req.Config) - - o, err := stream.addConsumerWithAction(&req.Config, req.Action, req.Pedantic) - - if err != nil { - if IsNatsErr(err, JSConsumerStoreFailedErrF) { - cname := req.Config.Durable // Will be empty if ephemeral. - s.Warnf("Consumer create failed for '%s > %s > %s': %v", acc, req.Stream, cname, err) - err = errConsumerStoreFailed - } - resp.Error = NewJSConsumerCreateError(err, Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - resp.ConsumerInfo = setDynamicConsumerInfoMetadata(o.initialInfo()) - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) - - if o.cfg.PauseUntil != nil && !o.cfg.PauseUntil.IsZero() && time.Now().Before(*o.cfg.PauseUntil) { - o.sendPauseAdvisoryLocked(&o.cfg) - } -} - -// Request for the list of all consumer names. -func (s *Server) jsConsumerNamesRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiConsumerNamesResponse{ - ApiResponse: ApiResponse{Type: JSApiConsumerNamesResponseType}, - Consumers: []string{}, - } - - // Determine if we should proceed here when we are in clustered mode. - if s.JetStreamIsClustered() { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - // Make sure we are meta leader. - if !s.JetStreamIsLeader() { - return - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - var offset int - if isJSONObjectOrArray(msg) { - var req JSApiConsumersRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - offset = req.Offset - } - - streamName := streamNameFromSubject(subject) - var numConsumers int - - if s.JetStreamIsClustered() { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - // TODO(dlc) - Debug or Warn? - return - } - js.mu.RLock() - sas := cc.streams[acc.Name] - if sas == nil { - js.mu.RUnlock() - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - sa := sas[streamName] - if sa == nil || sa.err != nil { - js.mu.RUnlock() - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - for consumer := range sa.consumers { - resp.Consumers = append(resp.Consumers, consumer) - } - if len(resp.Consumers) > 1 { - slices.Sort(resp.Consumers) - } - numConsumers = len(resp.Consumers) - if offset > numConsumers { - offset = numConsumers - } - resp.Consumers = resp.Consumers[offset:] - if len(resp.Consumers) > JSApiNamesLimit { - resp.Consumers = resp.Consumers[:JSApiNamesLimit] - } - js.mu.RUnlock() - - } else { - mset, err := acc.lookupStream(streamName) - if err != nil { - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - obs := mset.getPublicConsumers() - slices.SortFunc(obs, func(i, j *consumer) int { return cmp.Compare(i.name, j.name) }) - - numConsumers = len(obs) - if offset > numConsumers { - offset = numConsumers - } - - for _, o := range obs[offset:] { - resp.Consumers = append(resp.Consumers, o.String()) - if len(resp.Consumers) >= JSApiNamesLimit { - break - } - } - } - resp.Total = numConsumers - resp.Limit = JSApiNamesLimit - resp.Offset = offset - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request for the list of all detailed consumer information. -func (s *Server) jsConsumerListRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiConsumerListResponse{ - ApiResponse: ApiResponse{Type: JSApiConsumerListResponseType}, - Consumers: []*ConsumerInfo{}, - } - - // Determine if we should proceed here when we are in clustered mode. - if s.JetStreamIsClustered() { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - // Make sure we are meta leader. - if !s.JetStreamIsLeader() { - return - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - var offset int - if isJSONObjectOrArray(msg) { - var req JSApiConsumersRequest - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - offset = req.Offset - } - - streamName := streamNameFromSubject(subject) - - // Clustered mode will invoke a scatter and gather. - if s.JetStreamIsClustered() { - // Need to copy these off before sending.. don't move this inside startGoRoutine!!! - msg = copyBytes(msg) - s.startGoRoutine(func() { - s.jsClusteredConsumerListRequest(acc, ci, offset, streamName, subject, reply, msg) - }) - return - } - - mset, err := acc.lookupStream(streamName) - if err != nil { - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - obs := mset.getPublicConsumers() - slices.SortFunc(obs, func(i, j *consumer) int { return cmp.Compare(i.name, j.name) }) - - ocnt := len(obs) - if offset > ocnt { - offset = ocnt - } - - for _, o := range obs[offset:] { - if cinfo := o.info(); cinfo != nil { - resp.Consumers = append(resp.Consumers, cinfo) - } - if len(resp.Consumers) >= JSApiListLimit { - break - } - } - resp.Total = ocnt - resp.Limit = JSApiListLimit - resp.Offset = offset - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request for information about an consumer. -func (s *Server) jsConsumerInfoRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - streamName := streamNameFromSubject(subject) - consumerName := consumerNameFromSubject(subject) - - var resp = JSApiConsumerInfoResponse{ApiResponse: ApiResponse{Type: JSApiConsumerInfoResponseType}} - - if !isEmptyRequest(msg) { - resp.Error = NewJSNotEmptyRequestError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // If we are in clustered mode we need to be the consumer leader to proceed. - if s.JetStreamIsClustered() { - // Check to make sure the consumer is assigned. - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - - js.mu.RLock() - meta := cc.meta - js.mu.RUnlock() - - // Since these could wait on the Raft group lock, don't do so under the JS lock. - ourID := meta.ID() - groupLeaderless := meta.Leaderless() - groupCreated := meta.Created() - - js.mu.RLock() - isLeader, sa, ca := cc.isLeader(), js.streamAssignment(acc.Name, streamName), js.consumerAssignment(acc.Name, streamName, consumerName) - var rg *raftGroup - var offline, isMember bool - if ca != nil { - if rg = ca.Group; rg != nil { - offline = s.allPeersOffline(rg) - isMember = rg.isMember(ourID) - } - } - // Capture consumer leader here. - isConsumerLeader := cc.isConsumerLeader(acc.Name, streamName, consumerName) - // Also capture if we think there is no meta leader. - var isLeaderLess bool - if !isLeader { - isLeaderLess = groupLeaderless && time.Since(groupCreated) > lostQuorumIntervalDefault - } - js.mu.RUnlock() - - if isLeader && ca == nil { - // We can't find the consumer, so mimic what would be the errors below. - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - if sa == nil { - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - // If we are here the consumer is not present. - resp.Error = NewJSConsumerNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } else if ca == nil { - if isLeaderLess { - resp.Error = NewJSClusterNotAvailError() - // Delaying an error response gives the leader a chance to respond before us - s.sendDelayedAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp), nil, errRespDelay) - } - return - } else if isLeader && offline { - resp.Error = NewJSConsumerOfflineError() - s.sendDelayedAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp), nil, errRespDelay) - return - } - - // Check to see if we are a member of the group and if the group has no leader. - if isMember && js.isGroupLeaderless(ca.Group) { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - // We have the consumer assigned and a leader, so only the consumer leader should answer. - if !isConsumerLeader { - if isLeaderLess { - resp.Error = NewJSClusterNotAvailError() - // Delaying an error response gives the leader a chance to respond before us - s.sendDelayedAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp), ca.Group, errRespDelay) - return - } - - var node RaftNode - var leaderNotPartOfGroup bool - - // We have a consumer assignment. - if isMember { - js.mu.RLock() - if rg != nil && rg.node != nil { - node = rg.node - if gl := node.GroupLeader(); gl != _EMPTY_ && !rg.isMember(gl) { - leaderNotPartOfGroup = true - } - } - js.mu.RUnlock() - } - - // Check if we should ignore all together. - if node == nil { - // We have been assigned but have not created a node yet. If we are a member return - // our config and defaults for state and no cluster info. - if isMember { - // Since we access consumerAssignment, need js lock. - js.mu.RLock() - resp.ConsumerInfo = &ConsumerInfo{ - Stream: ca.Stream, - Name: ca.Name, - Created: ca.Created, - Config: setDynamicConsumerMetadata(ca.Config), - TimeStamp: time.Now().UTC(), - } - b := s.jsonResponse(resp) - js.mu.RUnlock() - s.sendAPIResponse(ci, acc, subject, reply, string(msg), b) - } - return - } - // If we are a member and we have a group leader or we had a previous leader consider bailing out. - if !node.Leaderless() || node.HadPreviousLeader() || (rg != nil && rg.Preferred != _EMPTY_ && rg.Preferred != ourID) { - if leaderNotPartOfGroup { - resp.Error = NewJSConsumerOfflineError() - s.sendDelayedAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp), nil, errRespDelay) - } - return - } - // If we are here we are a member and this is just a new consumer that does not have a (preferred) leader yet. - // Will fall through and return what we have. All consumers can respond but this should be very rare - // but makes more sense to clients when they try to create, get a consumer exists, and then do consumer info. - } - } - - if !acc.JetStreamEnabled() { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - mset, err := acc.lookupStream(streamName) - if err != nil { - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - obs := mset.lookupConsumer(consumerName) - if obs == nil { - resp.Error = NewJSConsumerNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - if resp.ConsumerInfo = setDynamicConsumerInfoMetadata(obs.info()); resp.ConsumerInfo == nil { - // This consumer returned nil which means it's closed. Respond with not found. - resp.Error = NewJSConsumerNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request to delete an Consumer. -func (s *Server) jsConsumerDeleteRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var resp = JSApiConsumerDeleteResponse{ApiResponse: ApiResponse{Type: JSApiConsumerDeleteResponseType}} - - // Determine if we should proceed here when we are in clustered mode. - if s.JetStreamIsClustered() { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - // Make sure we are meta leader. - if !s.JetStreamIsLeader() { - return - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - if !isEmptyRequest(msg) { - resp.Error = NewJSNotEmptyRequestError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - stream := streamNameFromSubject(subject) - consumer := consumerNameFromSubject(subject) - - if s.JetStreamIsClustered() { - s.jsClusteredConsumerDeleteRequest(ci, acc, stream, consumer, subject, reply, rmsg) - return - } - - mset, err := acc.lookupStream(stream) - if err != nil { - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - obs := mset.lookupConsumer(consumer) - if obs == nil { - resp.Error = NewJSConsumerNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - if err := obs.delete(); err != nil { - resp.Error = NewJSStreamGeneralError(err, Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - resp.Success = true - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// Request to pause or unpause a Consumer. -func (s *Server) jsConsumerPauseRequest(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if c == nil || !s.JetStreamEnabled() { - return - } - ci, acc, _, msg, err := s.getRequestInfo(c, rmsg) - if err != nil { - s.Warnf(badAPIRequestT, msg) - return - } - - var req JSApiConsumerPauseRequest - var resp = JSApiConsumerPauseResponse{ApiResponse: ApiResponse{Type: JSApiConsumerPauseResponseType}} - - if isJSONObjectOrArray(msg) { - if err := s.unmarshalRequest(c, acc, subject, msg, &req); err != nil { - resp.Error = NewJSInvalidJSONError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - } - - // Determine if we should proceed here when we are in clustered mode. - isClustered := s.JetStreamIsClustered() - js, cc := s.getJetStreamCluster() - if isClustered { - if js == nil || cc == nil { - return - } - if js.isLeaderless() { - resp.Error = NewJSClusterNotAvailError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - // Make sure we are meta leader. - if !s.JetStreamIsLeader() { - return - } - } - - if hasJS, doErr := acc.checkJetStream(); !hasJS { - if doErr { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - } - return - } - - stream := streamNameFromSubject(subject) - consumer := consumerNameFromSubject(subject) - - if isClustered { - js.mu.RLock() - sa := js.streamAssignment(acc.Name, stream) - if sa == nil { - js.mu.RUnlock() - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - ca, ok := sa.consumers[consumer] - if !ok || ca == nil { - js.mu.RUnlock() - resp.Error = NewJSConsumerNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - nca := *ca - ncfg := *ca.Config - nca.Config = &ncfg - js.mu.RUnlock() - pauseUTC := req.PauseUntil.UTC() - if !pauseUTC.IsZero() { - nca.Config.PauseUntil = &pauseUTC - } else { - nca.Config.PauseUntil = nil - } - - // Update asset version metadata due to updating pause/resume. - // Only PauseUntil is updated above, so reuse config for both. - setStaticConsumerMetadata(nca.Config) - - eca := encodeAddConsumerAssignment(&nca) - cc.meta.Propose(eca) - - resp.PauseUntil = pauseUTC - if resp.Paused = time.Now().Before(pauseUTC); resp.Paused { - resp.PauseRemaining = time.Until(pauseUTC) - } - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) - return - } - - mset, err := acc.lookupStream(stream) - if err != nil { - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - obs := mset.lookupConsumer(consumer) - if obs == nil { - resp.Error = NewJSConsumerNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - ncfg := obs.cfg - pauseUTC := req.PauseUntil.UTC() - if !pauseUTC.IsZero() { - ncfg.PauseUntil = &pauseUTC - } else { - ncfg.PauseUntil = nil - } - - // Update asset version metadata due to updating pause/resume. - setStaticConsumerMetadata(&ncfg) - - if err := obs.updateConfig(&ncfg); err != nil { - // The only type of error that should be returned here is from o.store, - // so use a store failed error type. - resp.Error = NewJSConsumerStoreFailedError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(&resp)) - return - } - - resp.PauseUntil = pauseUTC - if resp.Paused = time.Now().Before(pauseUTC); resp.Paused { - resp.PauseRemaining = time.Until(pauseUTC) - } - s.sendAPIResponse(ci, acc, subject, reply, string(msg), s.jsonResponse(resp)) -} - -// sendJetStreamAPIAuditAdvisory will send the audit event for a given event. -func (s *Server) sendJetStreamAPIAuditAdvisory(ci *ClientInfo, acc *Account, subject, request, response string) { - s.publishAdvisory(acc, JSAuditAdvisory, JSAPIAudit{ - TypedEvent: TypedEvent{ - Type: JSAPIAuditType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Server: s.Name(), - Client: ci.forAdvisory(), - Subject: subject, - Request: request, - Response: response, - Domain: s.getOpts().JetStreamDomain, - }) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go deleted file mode 100644 index 83100e23..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_cluster.go +++ /dev/null @@ -1,9309 +0,0 @@ -// Copyright 2020-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "cmp" - crand "crypto/rand" - "encoding/binary" - "encoding/json" - "errors" - "fmt" - "math" - "math/rand" - "os" - "path/filepath" - "reflect" - "slices" - "strconv" - "strings" - "sync/atomic" - "time" - - "github.com/klauspost/compress/s2" - "github.com/minio/highwayhash" - "github.com/nats-io/nuid" -) - -// jetStreamCluster holds information about the meta group and stream assignments. -type jetStreamCluster struct { - // The metacontroller raftNode. - meta RaftNode - // For stream and consumer assignments. All servers will have this be the same. - // ACCOUNT -> STREAM -> Stream Assignment -> Consumers - streams map[string]map[string]*streamAssignment - // These are inflight proposals and used to apply limits when there are - // concurrent requests that would otherwise be accepted. - // We also record the group for the stream. This is needed since if we have - // concurrent requests for same account and stream we need to let it process to get - // a response but they need to be same group, peers etc. and sync subjects. - inflight map[string]map[string]*inflightInfo - // Signals meta-leader should check the stream assignments. - streamsCheck bool - // Server. - s *Server - // Internal client. - c *client - // Processing assignment results. - streamResults *subscription - consumerResults *subscription - // System level request to have the leader stepdown. - stepdown *subscription - // System level requests to remove a peer. - peerRemove *subscription - // System level request to move a stream - peerStreamMove *subscription - // System level request to cancel a stream move - peerStreamCancelMove *subscription - // To pop out the monitorCluster before the raft layer. - qch chan struct{} -} - -// Used to track inflight stream add requests to properly re-use same group and sync subject. -type inflightInfo struct { - rg *raftGroup - sync string -} - -// Used to guide placement of streams and meta controllers in clustered JetStream. -type Placement struct { - Cluster string `json:"cluster,omitempty"` - Tags []string `json:"tags,omitempty"` - Preferred string `json:"preferred,omitempty"` -} - -// Define types of the entry. -type entryOp uint8 - -// ONLY ADD TO THE END, DO NOT INSERT IN BETWEEN WILL BREAK SERVER INTEROP. -const ( - // Meta ops. - assignStreamOp entryOp = iota - assignConsumerOp - removeStreamOp - removeConsumerOp - // Stream ops. - streamMsgOp - purgeStreamOp - deleteMsgOp - // Consumer ops. - updateDeliveredOp - updateAcksOp - // Compressed consumer assignments. - assignCompressedConsumerOp - // Filtered Consumer skip. - updateSkipOp - // Update Stream. - updateStreamOp - // For updating information on pending pull requests. - addPendingRequest - removePendingRequest - // For sending compressed streams, either through RAFT or catchup. - compressedStreamMsgOp - // For sending deleted gaps on catchups for replicas. - deleteRangeOp -) - -// raftGroups are controlled by the metagroup controller. -// The raftGroups will house streams and consumers. -type raftGroup struct { - Name string `json:"name"` - Peers []string `json:"peers"` - Storage StorageType `json:"store"` - Cluster string `json:"cluster,omitempty"` - Preferred string `json:"preferred,omitempty"` - // Internal - node RaftNode -} - -// streamAssignment is what the meta controller uses to assign streams to peers. -type streamAssignment struct { - Client *ClientInfo `json:"client,omitempty"` - Created time.Time `json:"created"` - Config *StreamConfig `json:"stream"` - Group *raftGroup `json:"group"` - Sync string `json:"sync"` - Subject string `json:"subject,omitempty"` - Reply string `json:"reply,omitempty"` - Restore *StreamState `json:"restore_state,omitempty"` - // Internal - consumers map[string]*consumerAssignment - responded bool - recovering bool - reassigning bool // i.e. due to placement issues, lack of resources, etc. - resetting bool // i.e. there was an error, and we're stopping and starting the stream - err error -} - -// consumerAssignment is what the meta controller uses to assign consumers to streams. -type consumerAssignment struct { - Client *ClientInfo `json:"client,omitempty"` - Created time.Time `json:"created"` - Name string `json:"name"` - Stream string `json:"stream"` - Config *ConsumerConfig `json:"consumer"` - Group *raftGroup `json:"group"` - Subject string `json:"subject,omitempty"` - Reply string `json:"reply,omitempty"` - State *ConsumerState `json:"state,omitempty"` - // Internal - responded bool - recovering bool - pending bool - deleted bool - err error -} - -// streamPurge is what the stream leader will replicate when purging a stream. -type streamPurge struct { - Client *ClientInfo `json:"client,omitempty"` - Stream string `json:"stream"` - LastSeq uint64 `json:"last_seq"` - Subject string `json:"subject"` - Reply string `json:"reply"` - Request *JSApiStreamPurgeRequest `json:"request,omitempty"` -} - -// streamMsgDelete is what the stream leader will replicate when deleting a message. -type streamMsgDelete struct { - Client *ClientInfo `json:"client,omitempty"` - Stream string `json:"stream"` - Seq uint64 `json:"seq"` - NoErase bool `json:"no_erase,omitempty"` - Subject string `json:"subject"` - Reply string `json:"reply"` -} - -const ( - defaultStoreDirName = "_js_" - defaultMetaGroupName = "_meta_" - defaultMetaFSBlkSize = 1024 * 1024 - jsExcludePlacement = "!jetstream" -) - -// Returns information useful in mixed mode. -func (s *Server) trackedJetStreamServers() (js, total int) { - s.mu.RLock() - defer s.mu.RUnlock() - if !s.isRunning() || !s.eventsEnabled() { - return -1, -1 - } - s.nodeToInfo.Range(func(k, v any) bool { - si := v.(nodeInfo) - if si.js { - js++ - } - total++ - return true - }) - return js, total -} - -func (s *Server) getJetStreamCluster() (*jetStream, *jetStreamCluster) { - if s.isShuttingDown() { - return nil, nil - } - - js := s.getJetStream() - if js == nil { - return nil, nil - } - - // Only set once, do not need a lock. - return js, js.cluster -} - -func (s *Server) JetStreamIsClustered() bool { - return s.jsClustered.Load() -} - -func (s *Server) JetStreamIsLeader() bool { - return s.isMetaLeader.Load() -} - -func (s *Server) JetStreamIsCurrent() bool { - js := s.getJetStream() - if js == nil { - return false - } - // Grab what we need and release js lock. - js.mu.RLock() - var meta RaftNode - cc := js.cluster - if cc != nil { - meta = cc.meta - } - js.mu.RUnlock() - - if cc == nil { - // Non-clustered mode - return true - } - return meta.Current() -} - -func (s *Server) JetStreamSnapshotMeta() error { - js := s.getJetStream() - if js == nil { - return NewJSNotEnabledError() - } - js.mu.RLock() - cc := js.cluster - isLeader := cc.isLeader() - meta := cc.meta - js.mu.RUnlock() - - if !isLeader { - return errNotLeader - } - - snap, err := js.metaSnapshot() - if err != nil { - return err - } - - return meta.InstallSnapshot(snap) -} - -func (s *Server) JetStreamStepdownStream(account, stream string) error { - js, cc := s.getJetStreamCluster() - if js == nil { - return NewJSNotEnabledError() - } - if cc == nil { - return NewJSClusterNotActiveError() - } - // Grab account - acc, err := s.LookupAccount(account) - if err != nil { - return err - } - // Grab stream - mset, err := acc.lookupStream(stream) - if err != nil { - return err - } - - if node := mset.raftNode(); node != nil { - node.StepDown() - } - - return nil -} - -func (s *Server) JetStreamStepdownConsumer(account, stream, consumer string) error { - js, cc := s.getJetStreamCluster() - if js == nil { - return NewJSNotEnabledError() - } - if cc == nil { - return NewJSClusterNotActiveError() - } - // Grab account - acc, err := s.LookupAccount(account) - if err != nil { - return err - } - // Grab stream - mset, err := acc.lookupStream(stream) - if err != nil { - return err - } - - o := mset.lookupConsumer(consumer) - if o == nil { - return NewJSConsumerNotFoundError() - } - - if node := o.raftNode(); node != nil { - node.StepDown() - } - - return nil -} - -func (s *Server) JetStreamSnapshotStream(account, stream string) error { - js, cc := s.getJetStreamCluster() - if js == nil { - return NewJSNotEnabledForAccountError() - } - if cc == nil { - return NewJSClusterNotActiveError() - } - // Grab account - acc, err := s.LookupAccount(account) - if err != nil { - return err - } - // Grab stream - mset, err := acc.lookupStream(stream) - if err != nil { - return err - } - - // Hold lock when installing snapshot. - mset.mu.Lock() - if mset.node == nil { - mset.mu.Unlock() - return nil - } - err = mset.node.InstallSnapshot(mset.stateSnapshotLocked()) - mset.mu.Unlock() - - return err -} - -func (s *Server) JetStreamClusterPeers() []string { - js := s.getJetStream() - if js == nil { - return nil - } - js.mu.RLock() - defer js.mu.RUnlock() - - cc := js.cluster - if !cc.isLeader() || cc.meta == nil { - return nil - } - peers := cc.meta.Peers() - var nodes []string - for _, p := range peers { - si, ok := s.nodeToInfo.Load(p.ID) - if !ok || si == nil { - continue - } - ni := si.(nodeInfo) - // Ignore if offline, no JS, or no current stats have been received. - if ni.offline || !ni.js || ni.stats == nil { - continue - } - nodes = append(nodes, si.(nodeInfo).name) - } - return nodes -} - -// Read lock should be held. -func (cc *jetStreamCluster) isLeader() bool { - if cc == nil { - // Non-clustered mode - return true - } - return cc.meta != nil && cc.meta.Leader() -} - -// isStreamCurrent will determine if the stream is up to date. -// For R1 it will make sure the stream is present on this server. -// Read lock should be held. -func (cc *jetStreamCluster) isStreamCurrent(account, stream string) bool { - if cc == nil { - // Non-clustered mode - return true - } - as := cc.streams[account] - if as == nil { - return false - } - sa := as[stream] - if sa == nil { - return false - } - rg := sa.Group - if rg == nil { - return false - } - - if rg.node == nil || rg.node.Current() { - // Check if we are processing a snapshot and are catching up. - acc, err := cc.s.LookupAccount(account) - if err != nil { - return false - } - mset, err := acc.lookupStream(stream) - if err != nil { - return false - } - if mset.isCatchingUp() { - return false - } - // Success. - return true - } - - return false -} - -// isStreamHealthy will determine if the stream is up to date or very close. -// For R1 it will make sure the stream is present on this server. -func (js *jetStream) isStreamHealthy(acc *Account, sa *streamAssignment) error { - js.mu.RLock() - s, cc := js.srv, js.cluster - if cc == nil { - // Non-clustered mode - js.mu.RUnlock() - return nil - } - if sa == nil || sa.Group == nil { - js.mu.RUnlock() - return errors.New("stream assignment or group missing") - } - streamName := sa.Config.Name - node := sa.Group.node - js.mu.RUnlock() - - // First lookup stream and make sure its there. - mset, err := acc.lookupStream(streamName) - if err != nil { - return errors.New("stream not found") - } - - switch { - case mset.cfg.Replicas <= 1: - return nil // No further checks for R=1 streams - - case node == nil: - return errors.New("group node missing") - - case node != mset.raftNode(): - s.Warnf("Detected stream cluster node skew '%s > %s'", acc.GetName(), streamName) - node.Delete() - mset.resetClusteredState(nil) - return errors.New("cluster node skew detected") - - case !mset.isMonitorRunning(): - return errors.New("monitor goroutine not running") - - case !node.Healthy(): - return errors.New("group node unhealthy") - - case mset.isCatchingUp(): - return errors.New("stream catching up") - - default: - return nil - } -} - -// isConsumerHealthy will determine if the consumer is up to date. -// For R1 it will make sure the consunmer is present on this server. -func (js *jetStream) isConsumerHealthy(mset *stream, consumer string, ca *consumerAssignment) error { - if mset == nil { - return errors.New("stream missing") - } - js.mu.RLock() - s, cc := js.srv, js.cluster - if cc == nil { - // Non-clustered mode - js.mu.RUnlock() - return nil - } - if ca == nil || ca.Group == nil { - js.mu.RUnlock() - return errors.New("consumer assignment or group missing") - } - node := ca.Group.node - js.mu.RUnlock() - - // Check if not running at all. - o := mset.lookupConsumer(consumer) - if o == nil { - return errors.New("consumer not found") - } - - rc, _ := o.replica() - switch { - case rc <= 1: - return nil // No further checks for R=1 consumers - - case node == nil: - return errors.New("group node missing") - - case node != o.raftNode(): - mset.mu.RLock() - accName, streamName := mset.acc.GetName(), mset.cfg.Name - mset.mu.RUnlock() - s.Warnf("Detected consumer cluster node skew '%s > %s > %s'", accName, streamName, consumer) - node.Delete() - o.deleteWithoutAdvisory() - - // When we try to restart we nil out the node and reprocess the consumer assignment. - js.mu.Lock() - ca.Group.node = nil - js.mu.Unlock() - js.processConsumerAssignment(ca) - return errors.New("cluster node skew detected") - - case !o.isMonitorRunning(): - return errors.New("monitor goroutine not running") - - case !node.Healthy(): - return errors.New("group node unhealthy") - - default: - return nil - } -} - -// subjectsOverlap checks all existing stream assignments for the account cross-cluster for subject overlap -// Use only for clustered JetStream -// Read lock should be held. -func (jsc *jetStreamCluster) subjectsOverlap(acc string, subjects []string, osa *streamAssignment) bool { - asa := jsc.streams[acc] - for _, sa := range asa { - // can't overlap yourself, assume osa pre-checked for deep equal if passed - if osa != nil && sa == osa { - continue - } - for _, subj := range sa.Config.Subjects { - for _, tsubj := range subjects { - if SubjectsCollide(tsubj, subj) { - return true - } - } - } - } - return false -} - -func (a *Account) getJetStreamFromAccount() (*Server, *jetStream, *jsAccount) { - a.mu.RLock() - jsa := a.js - a.mu.RUnlock() - if jsa == nil { - return nil, nil, nil - } - jsa.mu.RLock() - js := jsa.js - jsa.mu.RUnlock() - if js == nil { - return nil, nil, nil - } - // Lock not needed, set on creation. - s := js.srv - return s, js, jsa -} - -func (s *Server) JetStreamIsStreamLeader(account, stream string) bool { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return false - } - js.mu.RLock() - defer js.mu.RUnlock() - return cc.isStreamLeader(account, stream) -} - -func (a *Account) JetStreamIsStreamLeader(stream string) bool { - s, js, jsa := a.getJetStreamFromAccount() - if s == nil || js == nil || jsa == nil { - return false - } - js.mu.RLock() - defer js.mu.RUnlock() - return js.cluster.isStreamLeader(a.Name, stream) -} - -func (s *Server) JetStreamIsStreamCurrent(account, stream string) bool { - js, cc := s.getJetStreamCluster() - if js == nil { - return false - } - js.mu.RLock() - defer js.mu.RUnlock() - return cc.isStreamCurrent(account, stream) -} - -func (a *Account) JetStreamIsConsumerLeader(stream, consumer string) bool { - s, js, jsa := a.getJetStreamFromAccount() - if s == nil || js == nil || jsa == nil { - return false - } - js.mu.RLock() - defer js.mu.RUnlock() - return js.cluster.isConsumerLeader(a.Name, stream, consumer) -} - -func (s *Server) JetStreamIsConsumerLeader(account, stream, consumer string) bool { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return false - } - js.mu.RLock() - defer js.mu.RUnlock() - return cc.isConsumerLeader(account, stream, consumer) -} - -func (s *Server) enableJetStreamClustering() error { - if !s.isRunning() { - return nil - } - js := s.getJetStream() - if js == nil { - return NewJSNotEnabledForAccountError() - } - // Already set. - if js.cluster != nil { - return nil - } - - s.Noticef("Starting JetStream cluster") - // We need to determine if we have a stable cluster name and expected number of servers. - s.Debugf("JetStream cluster checking for stable cluster name and peers") - - hasLeafNodeSystemShare := s.canExtendOtherDomain() - if s.isClusterNameDynamic() && !hasLeafNodeSystemShare { - return errors.New("JetStream cluster requires cluster name") - } - if s.configuredRoutes() == 0 && !hasLeafNodeSystemShare { - return errors.New("JetStream cluster requires configured routes or solicited leafnode for the system account") - } - - return js.setupMetaGroup() -} - -// isClustered returns if we are clustered. -// Lock should not be held. -func (js *jetStream) isClustered() bool { - // This is only ever set, no need for lock here. - return js.cluster != nil -} - -// isClusteredNoLock returns if we are clustered, but unlike isClustered() does -// not use the jetstream's lock, instead, uses an atomic operation. -// There are situations where some code wants to know if we are clustered but -// can't use js.isClustered() without causing a lock inversion. -func (js *jetStream) isClusteredNoLock() bool { - return atomic.LoadInt32(&js.clustered) == 1 -} - -func (js *jetStream) setupMetaGroup() error { - s := js.srv - s.Noticef("Creating JetStream metadata controller") - - // Setup our WAL for the metagroup. - sysAcc := s.SystemAccount() - if sysAcc == nil { - return ErrNoSysAccount - } - storeDir := filepath.Join(js.config.StoreDir, sysAcc.Name, defaultStoreDirName, defaultMetaGroupName) - - js.srv.optsMu.RLock() - syncAlways := js.srv.opts.SyncAlways - syncInterval := js.srv.opts.SyncInterval - js.srv.optsMu.RUnlock() - fs, err := newFileStoreWithCreated( - FileStoreConfig{StoreDir: storeDir, BlockSize: defaultMetaFSBlkSize, AsyncFlush: false, SyncAlways: syncAlways, SyncInterval: syncInterval, srv: s}, - StreamConfig{Name: defaultMetaGroupName, Storage: FileStorage}, - time.Now().UTC(), - s.jsKeyGen(s.getOpts().JetStreamKey, defaultMetaGroupName), - s.jsKeyGen(s.getOpts().JetStreamOldKey, defaultMetaGroupName), - ) - if err != nil { - s.Errorf("Error creating filestore: %v", err) - return err - } - - cfg := &RaftConfig{Name: defaultMetaGroupName, Store: storeDir, Log: fs} - - // If we are soliciting leafnode connections and we are sharing a system account and do not disable it with a hint, - // we want to move to observer mode so that we extend the solicited cluster or supercluster but do not form our own. - cfg.Observer = s.canExtendOtherDomain() && s.getOpts().JetStreamExtHint != jsNoExtend - - var bootstrap bool - if ps, err := readPeerState(storeDir); err != nil { - s.Noticef("JetStream cluster bootstrapping") - bootstrap = true - peers := s.ActivePeers() - s.Debugf("JetStream cluster initial peers: %+v", peers) - if err := s.bootstrapRaftNode(cfg, peers, false); err != nil { - return err - } - if cfg.Observer { - s.Noticef("Turning JetStream metadata controller Observer Mode on") - } - } else { - s.Noticef("JetStream cluster recovering state") - // correlate the value of observer with observations from a previous run. - if cfg.Observer { - switch ps.domainExt { - case extExtended: - s.Noticef("Keeping JetStream metadata controller Observer Mode on - due to previous contact") - case extNotExtended: - s.Noticef("Turning JetStream metadata controller Observer Mode off - due to previous contact") - cfg.Observer = false - case extUndetermined: - s.Noticef("Turning JetStream metadata controller Observer Mode on - no previous contact") - s.Noticef("In cases where JetStream will not be extended") - s.Noticef("and waiting for leader election until first contact is not acceptable,") - s.Noticef(`manually disable Observer Mode by setting the JetStream Option "extension_hint: %s"`, jsNoExtend) - } - } else { - // To track possible configuration changes, responsible for an altered value of cfg.Observer, - // set extension state to undetermined. - ps.domainExt = extUndetermined - if err := writePeerState(storeDir, ps); err != nil { - return err - } - } - } - - // Start up our meta node. - n, err := s.startRaftNode(sysAcc.GetName(), cfg, pprofLabels{ - "type": "metaleader", - "account": sysAcc.Name, - }) - if err != nil { - s.Warnf("Could not start metadata controller: %v", err) - return err - } - - // If we are bootstrapped with no state, start campaign early. - if bootstrap { - n.Campaign() - } - - c := s.createInternalJetStreamClient() - - js.mu.Lock() - defer js.mu.Unlock() - js.cluster = &jetStreamCluster{ - meta: n, - streams: make(map[string]map[string]*streamAssignment), - s: s, - c: c, - qch: make(chan struct{}), - } - atomic.StoreInt32(&js.clustered, 1) - c.registerWithAccount(sysAcc) - - // Set to true before we start. - js.metaRecovering = true - js.srv.startGoRoutine( - js.monitorCluster, - pprofLabels{ - "type": "metaleader", - "account": sysAcc.Name, - }, - ) - return nil -} - -func (js *jetStream) getMetaGroup() RaftNode { - js.mu.RLock() - defer js.mu.RUnlock() - if js.cluster == nil { - return nil - } - return js.cluster.meta -} - -func (js *jetStream) server() *Server { - // Lock not needed, only set once on creation. - return js.srv -} - -// Will respond if we do not think we have a metacontroller leader. -func (js *jetStream) isLeaderless() bool { - js.mu.RLock() - cc := js.cluster - if cc == nil || cc.meta == nil { - js.mu.RUnlock() - return false - } - meta := cc.meta - js.mu.RUnlock() - - // If we don't have a leader. - // Make sure we have been running for enough time. - if meta.Leaderless() && time.Since(meta.Created()) > lostQuorumIntervalDefault { - return true - } - return false -} - -// Will respond iff we are a member and we know we have no leader. -func (js *jetStream) isGroupLeaderless(rg *raftGroup) bool { - if rg == nil || js == nil { - return false - } - js.mu.RLock() - cc := js.cluster - started := js.started - - // If we are not a member we can not say.. - if cc.meta == nil { - js.mu.RUnlock() - return false - } - if !rg.isMember(cc.meta.ID()) { - js.mu.RUnlock() - return false - } - // Single peer groups always have a leader if we are here. - if rg.node == nil { - js.mu.RUnlock() - return false - } - node := rg.node - js.mu.RUnlock() - // If we don't have a leader. - if node.Leaderless() { - // Threshold for jetstream startup. - const startupThreshold = 10 * time.Second - - if node.HadPreviousLeader() { - // Make sure we have been running long enough to intelligently determine this. - if time.Since(started) > startupThreshold { - return true - } - } - // Make sure we have been running for enough time. - if time.Since(node.Created()) > lostQuorumIntervalDefault { - return true - } - } - - return false -} - -func (s *Server) JetStreamIsStreamAssigned(account, stream string) bool { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return false - } - acc, _ := s.LookupAccount(account) - if acc == nil { - return false - } - js.mu.RLock() - assigned := cc.isStreamAssigned(acc, stream) - js.mu.RUnlock() - return assigned -} - -// streamAssigned informs us if this server has this stream assigned. -func (jsa *jsAccount) streamAssigned(stream string) bool { - jsa.mu.RLock() - js, acc := jsa.js, jsa.account - jsa.mu.RUnlock() - - if js == nil { - return false - } - js.mu.RLock() - assigned := js.cluster.isStreamAssigned(acc, stream) - js.mu.RUnlock() - return assigned -} - -// Read lock should be held. -func (cc *jetStreamCluster) isStreamAssigned(a *Account, stream string) bool { - // Non-clustered mode always return true. - if cc == nil { - return true - } - if cc.meta == nil { - return false - } - as := cc.streams[a.Name] - if as == nil { - return false - } - sa := as[stream] - if sa == nil { - return false - } - rg := sa.Group - if rg == nil { - return false - } - // Check if we are the leader of this raftGroup assigned to the stream. - ourID := cc.meta.ID() - for _, peer := range rg.Peers { - if peer == ourID { - return true - } - } - return false -} - -// Read lock should be held. -func (cc *jetStreamCluster) isStreamLeader(account, stream string) bool { - // Non-clustered mode always return true. - if cc == nil { - return true - } - if cc.meta == nil { - return false - } - - var sa *streamAssignment - if as := cc.streams[account]; as != nil { - sa = as[stream] - } - if sa == nil { - return false - } - rg := sa.Group - if rg == nil { - return false - } - // Check if we are the leader of this raftGroup assigned to the stream. - ourID := cc.meta.ID() - for _, peer := range rg.Peers { - if peer == ourID { - if len(rg.Peers) == 1 || (rg.node != nil && rg.node.Leader()) { - return true - } - } - } - return false -} - -// Read lock should be held. -func (cc *jetStreamCluster) isConsumerLeader(account, stream, consumer string) bool { - // Non-clustered mode always return true. - if cc == nil { - return true - } - if cc.meta == nil { - return false - } - - var sa *streamAssignment - if as := cc.streams[account]; as != nil { - sa = as[stream] - } - if sa == nil { - return false - } - // Check if we are the leader of this raftGroup assigned to this consumer. - ca := sa.consumers[consumer] - if ca == nil { - return false - } - rg := ca.Group - ourID := cc.meta.ID() - for _, peer := range rg.Peers { - if peer == ourID { - if len(rg.Peers) == 1 || (rg.node != nil && rg.node.Leader()) { - return true - } - } - } - return false -} - -// Remove the stream `streamName` for the account `accName` from the inflight -// proposals map. This is done on success (processStreamAssignment) or on -// failure (processStreamAssignmentResults). -// (Write) Lock held on entry. -func (cc *jetStreamCluster) removeInflightProposal(accName, streamName string) { - streams, ok := cc.inflight[accName] - if !ok { - return - } - delete(streams, streamName) - if len(streams) == 0 { - delete(cc.inflight, accName) - } -} - -// Return the cluster quit chan. -func (js *jetStream) clusterQuitC() chan struct{} { - js.mu.RLock() - defer js.mu.RUnlock() - if js.cluster != nil { - return js.cluster.qch - } - return nil -} - -// Mark that the meta layer is recovering. -func (js *jetStream) setMetaRecovering() { - js.mu.Lock() - defer js.mu.Unlock() - if js.cluster != nil { - // metaRecovering - js.metaRecovering = true - } -} - -// Mark that the meta layer is no longer recovering. -func (js *jetStream) clearMetaRecovering() { - js.mu.Lock() - defer js.mu.Unlock() - js.metaRecovering = false -} - -// Return whether the meta layer is recovering. -func (js *jetStream) isMetaRecovering() bool { - js.mu.RLock() - defer js.mu.RUnlock() - return js.metaRecovering -} - -// During recovery track any stream and consumer delete and update operations. -type recoveryUpdates struct { - removeStreams map[string]*streamAssignment - removeConsumers map[string]map[string]*consumerAssignment - addStreams map[string]*streamAssignment - updateStreams map[string]*streamAssignment - updateConsumers map[string]map[string]*consumerAssignment -} - -// Called after recovery of the cluster on startup to check for any orphans. -// Streams and consumers are recovered from disk, and the meta layer's mappings -// should clean them up, but under crash scenarios there could be orphans. -func (js *jetStream) checkForOrphans() { - // Can not hold jetstream lock while trying to delete streams or consumers. - js.mu.Lock() - s, cc := js.srv, js.cluster - s.Debugf("JetStream cluster checking for orphans") - - // We only want to cleanup any orphans if we know we are current with the meta-leader. - meta := cc.meta - if meta == nil || meta.Leaderless() { - js.mu.Unlock() - s.Debugf("JetStream cluster skipping check for orphans, no meta-leader") - return - } - if !meta.Healthy() { - js.mu.Unlock() - s.Debugf("JetStream cluster skipping check for orphans, not current with the meta-leader") - return - } - - var streams []*stream - var consumers []*consumer - - for accName, jsa := range js.accounts { - asa := cc.streams[accName] - jsa.mu.RLock() - for stream, mset := range jsa.streams { - if sa := asa[stream]; sa == nil { - streams = append(streams, mset) - } else { - // This one is good, check consumers now. - for _, o := range mset.getConsumers() { - if sa.consumers[o.String()] == nil { - consumers = append(consumers, o) - } - } - } - } - jsa.mu.RUnlock() - } - js.mu.Unlock() - - for _, mset := range streams { - mset.mu.RLock() - accName, stream := mset.acc.Name, mset.cfg.Name - mset.mu.RUnlock() - s.Warnf("Detected orphaned stream '%s > %s', will cleanup", accName, stream) - if err := mset.delete(); err != nil { - s.Warnf("Deleting stream encountered an error: %v", err) - } - } - for _, o := range consumers { - o.mu.RLock() - accName, mset, consumer := o.acc.Name, o.mset, o.name - o.mu.RUnlock() - stream := "N/A" - if mset != nil { - mset.mu.RLock() - stream = mset.cfg.Name - mset.mu.RUnlock() - } - if o.isDurable() { - s.Warnf("Detected orphaned durable consumer '%s > %s > %s', will cleanup", accName, stream, consumer) - } else { - s.Debugf("Detected orphaned consumer '%s > %s > %s', will cleanup", accName, stream, consumer) - } - - if err := o.delete(); err != nil { - s.Warnf("Deleting consumer encountered an error: %v", err) - } - } -} - -func (js *jetStream) monitorCluster() { - s, n := js.server(), js.getMetaGroup() - qch, rqch, lch, aq := js.clusterQuitC(), n.QuitC(), n.LeadChangeC(), n.ApplyQ() - - defer s.grWG.Done() - - s.Debugf("Starting metadata monitor") - defer s.Debugf("Exiting metadata monitor") - - // Make sure to stop the raft group on exit to prevent accidental memory bloat. - defer n.Stop() - defer s.isMetaLeader.Store(false) - - const compactInterval = time.Minute - t := time.NewTicker(compactInterval) - defer t.Stop() - - // Used to check cold boot cluster when possibly in mixed mode. - const leaderCheckInterval = time.Second - lt := time.NewTicker(leaderCheckInterval) - defer lt.Stop() - - // Check the general health once an hour. - const healthCheckInterval = 1 * time.Hour - ht := time.NewTicker(healthCheckInterval) - defer ht.Stop() - - // Utility to check health. - checkHealth := func() { - if hs := s.healthz(nil); hs.Error != _EMPTY_ { - s.Warnf("%v", hs.Error) - } - } - - var ( - isLeader bool - lastSnapTime time.Time - compactSizeMin = uint64(8 * 1024 * 1024) // 8MB - minSnapDelta = 30 * time.Second - ) - - // Highwayhash key for generating hashes. - key := make([]byte, 32) - crand.Read(key) - - // Set to true to start. - js.setMetaRecovering() - - // Snapshotting function. - doSnapshot := func() { - // Suppress during recovery. - if js.isMetaRecovering() { - return - } - // For the meta layer we want to snapshot when asked if we need one or have any entries that we can compact. - if ne, _ := n.Size(); ne > 0 || n.NeedSnapshot() { - snap, err := js.metaSnapshot() - if err != nil { - s.Warnf("Error generating JetStream cluster snapshot: %v", err) - } else if err = n.InstallSnapshot(snap); err == nil { - lastSnapTime = time.Now() - } else if err != errNoSnapAvailable && err != errNodeClosed { - s.Warnf("Error snapshotting JetStream cluster state: %v", err) - } - } - } - - ru := &recoveryUpdates{ - removeStreams: make(map[string]*streamAssignment), - removeConsumers: make(map[string]map[string]*consumerAssignment), - addStreams: make(map[string]*streamAssignment), - updateStreams: make(map[string]*streamAssignment), - updateConsumers: make(map[string]map[string]*consumerAssignment), - } - - // Make sure to cancel any pending checkForOrphans calls if the - // monitor goroutine exits. - var oc *time.Timer - defer stopAndClearTimer(&oc) - - for { - select { - case <-s.quitCh: - // Server shutting down, but we might receive this before qch, so try to snapshot. - doSnapshot() - return - case <-rqch: - return - case <-qch: - // Clean signal from shutdown routine so do best effort attempt to snapshot meta layer. - doSnapshot() - // Return the signal back since shutdown will be waiting. - close(qch) - return - case <-aq.ch: - ces := aq.pop() - for _, ce := range ces { - if ce == nil { - // Process any removes that are still valid after recovery. - for _, cas := range ru.removeConsumers { - for _, ca := range cas { - js.processConsumerRemoval(ca) - } - } - for _, sa := range ru.removeStreams { - js.processStreamRemoval(sa) - } - // Process stream additions. - for _, sa := range ru.addStreams { - js.processStreamAssignment(sa) - } - // Process pending updates. - for _, sa := range ru.updateStreams { - js.processUpdateStreamAssignment(sa) - } - // Now consumers. - for _, cas := range ru.updateConsumers { - for _, ca := range cas { - js.processConsumerAssignment(ca) - } - } - // Signals we have replayed all of our metadata. - js.clearMetaRecovering() - // Clear. - ru = nil - s.Debugf("Recovered JetStream cluster metadata") - oc = time.AfterFunc(30*time.Second, js.checkForOrphans) - // Do a health check here as well. - go checkHealth() - continue - } - if didSnap, didStreamRemoval, _, err := js.applyMetaEntries(ce.Entries, ru); err == nil { - var nb uint64 - // Some entries can fail without an error when shutting down, don't move applied forward. - if !js.isShuttingDown() { - _, nb = n.Applied(ce.Index) - } - if js.hasPeerEntries(ce.Entries) || didStreamRemoval || (didSnap && !isLeader) { - doSnapshot() - } else if nb > compactSizeMin && time.Since(lastSnapTime) > minSnapDelta { - doSnapshot() - } - ce.ReturnToPool() - } else { - s.Warnf("Error applying JetStream cluster entries: %v", err) - } - } - aq.recycle(&ces) - - case isLeader = <-lch: - // Process the change. - js.processLeaderChange(isLeader) - if isLeader { - s.sendInternalMsgLocked(serverStatsPingReqSubj, _EMPTY_, nil, nil) - // Install a snapshot as we become leader. - js.checkClusterSize() - doSnapshot() - } - - case <-t.C: - doSnapshot() - // Periodically check the cluster size. - if n.Leader() { - js.checkClusterSize() - } - case <-ht.C: - // Do this in a separate go routine. - go checkHealth() - - case <-lt.C: - s.Debugf("Checking JetStream cluster state") - // If we have a current leader or had one in the past we can cancel this here since the metaleader - // will be in charge of all peer state changes. - // For cold boot only. - if !n.Leaderless() || n.HadPreviousLeader() { - lt.Stop() - continue - } - // If we are here we do not have a leader and we did not have a previous one, so cold start. - // Check to see if we can adjust our cluster size down iff we are in mixed mode and we have - // seen a total that is what our original estimate was. - cs := n.ClusterSize() - if js, total := s.trackedJetStreamServers(); js < total && total >= cs && js != cs { - s.Noticef("Adjusting JetStream expected peer set size to %d from original %d", js, cs) - n.AdjustBootClusterSize(js) - } - } - } -} - -// This is called on first leader transition to double check the peers and cluster set size. -func (js *jetStream) checkClusterSize() { - s, n := js.server(), js.getMetaGroup() - if n == nil { - return - } - // We will check that we have a correct cluster set size by checking for any non-js servers - // which can happen in mixed mode. - ps := n.(*raft).currentPeerState() - if len(ps.knownPeers) >= ps.clusterSize { - return - } - - // Grab our active peers. - peers := s.ActivePeers() - - // If we have not registered all of our peers yet we can't do - // any adjustments based on a mixed mode. We will periodically check back. - if len(peers) < ps.clusterSize { - return - } - - s.Debugf("Checking JetStream cluster size") - - // If we are here our known set as the leader is not the same as the cluster size. - // Check to see if we have a mixed mode setup. - var totalJS int - for _, p := range peers { - if si, ok := s.nodeToInfo.Load(p); ok && si != nil { - if si.(nodeInfo).js { - totalJS++ - } - } - } - // If we have less then our cluster size adjust that here. Can not do individual peer removals since - // they will not be in the tracked peers. - if totalJS < ps.clusterSize { - s.Debugf("Adjusting JetStream cluster size from %d to %d", ps.clusterSize, totalJS) - if err := n.AdjustClusterSize(totalJS); err != nil { - s.Warnf("Error adjusting JetStream cluster size: %v", err) - } - } -} - -// Represents our stable meta state that we can write out. -type writeableStreamAssignment struct { - Client *ClientInfo `json:"client,omitempty"` - Created time.Time `json:"created"` - Config *StreamConfig `json:"stream"` - Group *raftGroup `json:"group"` - Sync string `json:"sync"` - Consumers []*consumerAssignment -} - -func (js *jetStream) clusterStreamConfig(accName, streamName string) (StreamConfig, bool) { - js.mu.RLock() - defer js.mu.RUnlock() - if sa, ok := js.cluster.streams[accName][streamName]; ok { - return *sa.Config, true - } - return StreamConfig{}, false -} - -func (js *jetStream) metaSnapshot() ([]byte, error) { - start := time.Now() - js.mu.RLock() - s := js.srv - cc := js.cluster - nsa := 0 - nca := 0 - for _, asa := range cc.streams { - nsa += len(asa) - } - streams := make([]writeableStreamAssignment, 0, nsa) - for _, asa := range cc.streams { - for _, sa := range asa { - wsa := writeableStreamAssignment{ - Client: sa.Client.forAssignmentSnap(), - Created: sa.Created, - Config: sa.Config, - Group: sa.Group, - Sync: sa.Sync, - Consumers: make([]*consumerAssignment, 0, len(sa.consumers)), - } - for _, ca := range sa.consumers { - // Skip if the consumer is pending, we can't include it in our snapshot. - // If the proposal fails after we marked it pending, it would result in a ghost consumer. - if ca.pending { - continue - } - cca := *ca - cca.Stream = wsa.Config.Name // Needed for safe roll-backs. - cca.Client = cca.Client.forAssignmentSnap() - cca.Subject, cca.Reply = _EMPTY_, _EMPTY_ - wsa.Consumers = append(wsa.Consumers, &cca) - nca++ - } - streams = append(streams, wsa) - } - } - - if len(streams) == 0 { - js.mu.RUnlock() - return nil, nil - } - - // Track how long it took to marshal the JSON - mstart := time.Now() - b, err := json.Marshal(streams) - mend := time.Since(mstart) - - js.mu.RUnlock() - - // Must not be possible for a JSON marshaling error to result - // in an empty snapshot. - if err != nil { - return nil, err - } - - // Track how long it took to compress the JSON - cstart := time.Now() - snap := s2.Encode(nil, b) - cend := time.Since(cstart) - - if took := time.Since(start); took > time.Second { - s.rateLimitFormatWarnf("Metalayer snapshot took %.3fs (streams: %d, consumers: %d, marshal: %.3fs, s2: %.3fs, uncompressed: %d, compressed: %d)", - took.Seconds(), nsa, nca, mend.Seconds(), cend.Seconds(), len(b), len(snap)) - } - return snap, nil -} - -func (js *jetStream) applyMetaSnapshot(buf []byte, ru *recoveryUpdates, isRecovering bool) error { - var wsas []writeableStreamAssignment - if len(buf) > 0 { - jse, err := s2.Decode(nil, buf) - if err != nil { - return err - } - if err = json.Unmarshal(jse, &wsas); err != nil { - return err - } - } - - // Build our new version here outside of js. - streams := make(map[string]map[string]*streamAssignment) - for _, wsa := range wsas { - fixCfgMirrorWithDedupWindow(wsa.Config) - as := streams[wsa.Client.serviceAccount()] - if as == nil { - as = make(map[string]*streamAssignment) - streams[wsa.Client.serviceAccount()] = as - } - sa := &streamAssignment{Client: wsa.Client, Created: wsa.Created, Config: wsa.Config, Group: wsa.Group, Sync: wsa.Sync} - if len(wsa.Consumers) > 0 { - sa.consumers = make(map[string]*consumerAssignment) - for _, ca := range wsa.Consumers { - if ca.Stream == _EMPTY_ { - ca.Stream = sa.Config.Name // Rehydrate from the stream name. - } - sa.consumers[ca.Name] = ca - } - } - as[wsa.Config.Name] = sa - } - - js.mu.Lock() - cc := js.cluster - - var saAdd, saDel, saChk []*streamAssignment - // Walk through the old list to generate the delete list. - for account, asa := range cc.streams { - nasa := streams[account] - for sn, sa := range asa { - if nsa := nasa[sn]; nsa == nil { - saDel = append(saDel, sa) - } else { - saChk = append(saChk, nsa) - } - } - } - // Walk through the new list to generate the add list. - for account, nasa := range streams { - asa := cc.streams[account] - for sn, sa := range nasa { - if asa[sn] == nil { - saAdd = append(saAdd, sa) - } - } - } - - // Now walk the ones to check and process consumers. - var caAdd, caDel []*consumerAssignment - for _, sa := range saChk { - // Make sure to add in all the new ones from sa. - for _, ca := range sa.consumers { - caAdd = append(caAdd, ca) - } - if osa := js.streamAssignment(sa.Client.serviceAccount(), sa.Config.Name); osa != nil { - for _, ca := range osa.consumers { - // Consumer was either removed, or recreated with a different raft group. - if nca := sa.consumers[ca.Name]; nca == nil { - caDel = append(caDel, ca) - } else if nca.Group != nil && ca.Group != nil && nca.Group.Name != ca.Group.Name { - caDel = append(caDel, ca) - } - } - } - } - js.mu.Unlock() - - // Do removals first. - for _, sa := range saDel { - js.setStreamAssignmentRecovering(sa) - if isRecovering { - key := sa.recoveryKey() - ru.removeStreams[key] = sa - delete(ru.addStreams, key) - delete(ru.updateStreams, key) - delete(ru.updateConsumers, key) - delete(ru.removeConsumers, key) - } else { - js.processStreamRemoval(sa) - } - } - // Now do add for the streams. Also add in all consumers. - for _, sa := range saAdd { - js.setStreamAssignmentRecovering(sa) - js.processStreamAssignment(sa) - - // We can simply process the consumers. - for _, ca := range sa.consumers { - js.setConsumerAssignmentRecovering(ca) - js.processConsumerAssignment(ca) - } - } - - // Perform updates on those in saChk. These were existing so make - // sure to process any changes. - for _, sa := range saChk { - js.setStreamAssignmentRecovering(sa) - if isRecovering { - key := sa.recoveryKey() - ru.updateStreams[key] = sa - delete(ru.addStreams, key) - delete(ru.removeStreams, key) - } else { - js.processUpdateStreamAssignment(sa) - } - } - - // Now do the deltas for existing stream's consumers. - for _, ca := range caDel { - js.setConsumerAssignmentRecovering(ca) - if isRecovering { - key := ca.recoveryKey() - skey := ca.streamRecoveryKey() - if _, ok := ru.removeConsumers[skey]; !ok { - ru.removeConsumers[skey] = map[string]*consumerAssignment{} - } - ru.removeConsumers[skey][key] = ca - if consumers, ok := ru.updateConsumers[skey]; ok { - delete(consumers, key) - } - } else { - js.processConsumerRemoval(ca) - } - } - for _, ca := range caAdd { - js.setConsumerAssignmentRecovering(ca) - if isRecovering { - key := ca.recoveryKey() - skey := ca.streamRecoveryKey() - if consumers, ok := ru.removeConsumers[skey]; ok { - delete(consumers, key) - } - if _, ok := ru.updateConsumers[skey]; !ok { - ru.updateConsumers[skey] = map[string]*consumerAssignment{} - } - ru.updateConsumers[skey][key] = ca - } else { - js.processConsumerAssignment(ca) - } - } - - return nil -} - -// Called on recovery to make sure we do not process like original. -func (js *jetStream) setStreamAssignmentRecovering(sa *streamAssignment) { - js.mu.Lock() - defer js.mu.Unlock() - sa.responded = true - sa.recovering = true - sa.Restore = nil - if sa.Group != nil { - sa.Group.Preferred = _EMPTY_ - } -} - -// Called on recovery to make sure we do not process like original. -func (js *jetStream) setConsumerAssignmentRecovering(ca *consumerAssignment) { - js.mu.Lock() - defer js.mu.Unlock() - ca.responded = true - ca.recovering = true - if ca.Group != nil { - ca.Group.Preferred = _EMPTY_ - } -} - -// Just copies over and changes out the group so it can be encoded. -// Lock should be held. -func (sa *streamAssignment) copyGroup() *streamAssignment { - csa, cg := *sa, *sa.Group - csa.Group = &cg - csa.Group.Peers = copyStrings(sa.Group.Peers) - return &csa -} - -// Just copies over and changes out the group so it can be encoded. -// Lock should be held. -func (ca *consumerAssignment) copyGroup() *consumerAssignment { - cca, cg := *ca, *ca.Group - cca.Group = &cg - cca.Group.Peers = copyStrings(ca.Group.Peers) - return &cca -} - -// Lock should be held. -func (sa *streamAssignment) missingPeers() bool { - return len(sa.Group.Peers) < sa.Config.Replicas -} - -// Called when we detect a new peer. Only the leader will process checking -// for any streams, and consequently any consumers. -func (js *jetStream) processAddPeer(peer string) { - js.mu.Lock() - defer js.mu.Unlock() - - s, cc := js.srv, js.cluster - if cc == nil || cc.meta == nil { - return - } - isLeader := cc.isLeader() - - // Now check if we are meta-leader. We will check for any re-assignments. - if !isLeader { - return - } - - sir, ok := s.nodeToInfo.Load(peer) - if !ok || sir == nil { - return - } - si := sir.(nodeInfo) - - for _, asa := range cc.streams { - for _, sa := range asa { - if sa.missingPeers() { - // Make sure the right cluster etc. - if si.cluster != sa.Client.Cluster { - continue - } - // If we are here we can add in this peer. - csa := sa.copyGroup() - csa.Group.Peers = append(csa.Group.Peers, peer) - // Send our proposal for this csa. Also use same group definition for all the consumers as well. - cc.meta.Propose(encodeAddStreamAssignment(csa)) - for _, ca := range sa.consumers { - // Ephemerals are R=1, so only auto-remap durables, or R>1. - if ca.Config.Durable != _EMPTY_ || len(ca.Group.Peers) > 1 { - cca := ca.copyGroup() - cca.Group.Peers = csa.Group.Peers - cc.meta.Propose(encodeAddConsumerAssignment(cca)) - } - } - } - } - } -} - -func (js *jetStream) processRemovePeer(peer string) { - // We may be already disabled. - if js == nil || js.disabled.Load() { - return - } - - js.mu.Lock() - s, cc := js.srv, js.cluster - if cc == nil || cc.meta == nil { - js.mu.Unlock() - return - } - isLeader := cc.isLeader() - // All nodes will check if this is them. - isUs := cc.meta.ID() == peer - js.mu.Unlock() - - if isUs { - s.Errorf("JetStream being DISABLED, our server was removed from the cluster") - adv := &JSServerRemovedAdvisory{ - TypedEvent: TypedEvent{ - Type: JSServerRemovedAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Server: s.Name(), - ServerID: s.ID(), - Cluster: s.cachedClusterName(), - Domain: s.getOpts().JetStreamDomain, - } - s.publishAdvisory(nil, JSAdvisoryServerRemoved, adv) - - go s.DisableJetStream() - } - - // Now check if we are meta-leader. We will attempt re-assignment. - if !isLeader { - return - } - - js.mu.Lock() - defer js.mu.Unlock() - - for _, asa := range cc.streams { - for _, sa := range asa { - if rg := sa.Group; rg.isMember(peer) { - js.removePeerFromStreamLocked(sa, peer) - } - } - } -} - -// Assumes all checks have already been done. -func (js *jetStream) removePeerFromStream(sa *streamAssignment, peer string) bool { - js.mu.Lock() - defer js.mu.Unlock() - return js.removePeerFromStreamLocked(sa, peer) -} - -// Lock should be held. -func (js *jetStream) removePeerFromStreamLocked(sa *streamAssignment, peer string) bool { - if rg := sa.Group; !rg.isMember(peer) { - return false - } - - s, cc, csa := js.srv, js.cluster, sa.copyGroup() - if cc == nil || cc.meta == nil { - return false - } - replaced := cc.remapStreamAssignment(csa, peer) - if !replaced { - s.Warnf("JetStream cluster could not replace peer for stream '%s > %s'", sa.Client.serviceAccount(), sa.Config.Name) - } - - // Send our proposal for this csa. Also use same group definition for all the consumers as well. - cc.meta.Propose(encodeAddStreamAssignment(csa)) - rg := csa.Group - for _, ca := range sa.consumers { - // Ephemerals are R=1, so only auto-remap durables, or R>1. - if ca.Config.Durable != _EMPTY_ { - cca := ca.copyGroup() - cca.Group.Peers, cca.Group.Preferred = rg.Peers, _EMPTY_ - cc.meta.Propose(encodeAddConsumerAssignment(cca)) - } else if ca.Group.isMember(peer) { - // These are ephemerals. Check to see if we deleted this peer. - cc.meta.Propose(encodeDeleteConsumerAssignment(ca)) - } - } - return replaced -} - -// Check if we have peer related entries. -func (js *jetStream) hasPeerEntries(entries []*Entry) bool { - for _, e := range entries { - if e.Type == EntryRemovePeer || e.Type == EntryAddPeer { - return true - } - } - return false -} - -const ksep = ":" - -func (sa *streamAssignment) recoveryKey() string { - if sa == nil { - return _EMPTY_ - } - return sa.Client.serviceAccount() + ksep + sa.Config.Name -} - -func (ca *consumerAssignment) streamRecoveryKey() string { - if ca == nil { - return _EMPTY_ - } - return ca.Client.serviceAccount() + ksep + ca.Stream -} - -func (ca *consumerAssignment) recoveryKey() string { - if ca == nil { - return _EMPTY_ - } - return ca.Client.serviceAccount() + ksep + ca.Stream + ksep + ca.Name -} - -func (js *jetStream) applyMetaEntries(entries []*Entry, ru *recoveryUpdates) (bool, bool, bool, error) { - var didSnap, didRemoveStream, didRemoveConsumer bool - isRecovering := js.isMetaRecovering() - - for _, e := range entries { - if e.Type == EntrySnapshot { - js.applyMetaSnapshot(e.Data, ru, isRecovering) - didSnap = true - } else if e.Type == EntryRemovePeer { - if !isRecovering { - js.processRemovePeer(string(e.Data)) - } - } else if e.Type == EntryAddPeer { - if !isRecovering { - js.processAddPeer(string(e.Data)) - } - } else { - buf := e.Data - switch entryOp(buf[0]) { - case assignStreamOp: - sa, err := decodeStreamAssignment(buf[1:]) - if err != nil { - js.srv.Errorf("JetStream cluster failed to decode stream assignment: %q", buf[1:]) - return didSnap, didRemoveStream, didRemoveConsumer, err - } - if isRecovering { - js.setStreamAssignmentRecovering(sa) - key := sa.recoveryKey() - ru.addStreams[key] = sa - delete(ru.removeStreams, key) - } else if js.processStreamAssignment(sa) { - didRemoveStream = true - } - case removeStreamOp: - sa, err := decodeStreamAssignment(buf[1:]) - if err != nil { - js.srv.Errorf("JetStream cluster failed to decode stream assignment: %q", buf[1:]) - return didSnap, didRemoveStream, didRemoveConsumer, err - } - if isRecovering { - js.setStreamAssignmentRecovering(sa) - key := sa.recoveryKey() - ru.removeStreams[key] = sa - delete(ru.addStreams, key) - delete(ru.updateStreams, key) - delete(ru.updateConsumers, key) - delete(ru.removeConsumers, key) - } else { - js.processStreamRemoval(sa) - didRemoveStream = true - } - case assignConsumerOp: - ca, err := decodeConsumerAssignment(buf[1:]) - if err != nil { - js.srv.Errorf("JetStream cluster failed to decode consumer assignment: %q", buf[1:]) - return didSnap, didRemoveStream, didRemoveConsumer, err - } - if isRecovering { - js.setConsumerAssignmentRecovering(ca) - key := ca.recoveryKey() - skey := ca.streamRecoveryKey() - if consumers, ok := ru.removeConsumers[skey]; ok { - delete(consumers, key) - } - if _, ok := ru.updateConsumers[skey]; !ok { - ru.updateConsumers[skey] = map[string]*consumerAssignment{} - } - ru.updateConsumers[skey][key] = ca - } else { - js.processConsumerAssignment(ca) - } - case assignCompressedConsumerOp: - ca, err := decodeConsumerAssignmentCompressed(buf[1:]) - if err != nil { - js.srv.Errorf("JetStream cluster failed to decode compressed consumer assignment: %q", buf[1:]) - return didSnap, didRemoveStream, didRemoveConsumer, err - } - if isRecovering { - js.setConsumerAssignmentRecovering(ca) - key := ca.recoveryKey() - skey := ca.streamRecoveryKey() - if consumers, ok := ru.removeConsumers[skey]; ok { - delete(consumers, key) - } - if _, ok := ru.updateConsumers[skey]; !ok { - ru.updateConsumers[skey] = map[string]*consumerAssignment{} - } - ru.updateConsumers[skey][key] = ca - } else { - js.processConsumerAssignment(ca) - } - case removeConsumerOp: - ca, err := decodeConsumerAssignment(buf[1:]) - if err != nil { - js.srv.Errorf("JetStream cluster failed to decode consumer assignment: %q", buf[1:]) - return didSnap, didRemoveStream, didRemoveConsumer, err - } - if isRecovering { - js.setConsumerAssignmentRecovering(ca) - key := ca.recoveryKey() - skey := ca.streamRecoveryKey() - if _, ok := ru.removeConsumers[skey]; !ok { - ru.removeConsumers[skey] = map[string]*consumerAssignment{} - } - ru.removeConsumers[skey][key] = ca - if consumers, ok := ru.updateConsumers[skey]; ok { - delete(consumers, key) - } - } else { - js.processConsumerRemoval(ca) - didRemoveConsumer = true - } - case updateStreamOp: - sa, err := decodeStreamAssignment(buf[1:]) - if err != nil { - js.srv.Errorf("JetStream cluster failed to decode stream assignment: %q", buf[1:]) - return didSnap, didRemoveStream, didRemoveConsumer, err - } - if isRecovering { - js.setStreamAssignmentRecovering(sa) - key := sa.recoveryKey() - ru.updateStreams[key] = sa - delete(ru.addStreams, key) - delete(ru.removeStreams, key) - } else { - js.processUpdateStreamAssignment(sa) - // Since an update can be lowering replica count, we want upper layer to treat - // similar to a removal and snapshot to collapse old entries. - didRemoveStream = true - } - default: - panic(fmt.Sprintf("JetStream Cluster Unknown meta entry op type: %v", entryOp(buf[0]))) - } - } - } - return didSnap, didRemoveStream, didRemoveConsumer, nil -} - -func (rg *raftGroup) isMember(id string) bool { - if rg == nil { - return false - } - for _, peer := range rg.Peers { - if peer == id { - return true - } - } - return false -} - -func (rg *raftGroup) setPreferred() { - if rg == nil || len(rg.Peers) == 0 { - return - } - if len(rg.Peers) == 1 { - rg.Preferred = rg.Peers[0] - } else { - // For now just randomly select a peer for the preferred. - pi := rand.Int31n(int32(len(rg.Peers))) - rg.Preferred = rg.Peers[pi] - } -} - -// createRaftGroup is called to spin up this raft group if needed. -func (js *jetStream) createRaftGroup(accName string, rg *raftGroup, storage StorageType, labels pprofLabels) error { - js.mu.Lock() - s, cc := js.srv, js.cluster - if cc == nil || cc.meta == nil { - js.mu.Unlock() - return NewJSClusterNotActiveError() - } - - // If this is a single peer raft group or we are not a member return. - if len(rg.Peers) <= 1 || !rg.isMember(cc.meta.ID()) { - js.mu.Unlock() - // Nothing to do here. - return nil - } - - // Check if we already have this assigned. -retry: - if node := s.lookupRaftNode(rg.Name); node != nil { - if node.State() == Closed { - // We're waiting for this node to finish shutting down before we replace it. - js.mu.Unlock() - node.WaitForStop() - js.mu.Lock() - goto retry - } - s.Debugf("JetStream cluster already has raft group %q assigned", rg.Name) - // Check and see if the group has the same peers. If not then we - // will update the known peers, which will send a peerstate if leader. - groupPeerIDs := append([]string{}, rg.Peers...) - var samePeers bool - if nodePeers := node.Peers(); len(rg.Peers) == len(nodePeers) { - nodePeerIDs := make([]string, 0, len(nodePeers)) - for _, n := range nodePeers { - nodePeerIDs = append(nodePeerIDs, n.ID) - } - slices.Sort(groupPeerIDs) - slices.Sort(nodePeerIDs) - samePeers = slices.Equal(groupPeerIDs, nodePeerIDs) - } - if !samePeers { - node.UpdateKnownPeers(groupPeerIDs) - } - rg.node = node - js.mu.Unlock() - return nil - } - - s.Debugf("JetStream cluster creating raft group:%+v", rg) - js.mu.Unlock() - - sysAcc := s.SystemAccount() - if sysAcc == nil { - s.Debugf("JetStream cluster detected shutdown processing raft group: %+v", rg) - return errors.New("shutting down") - } - - // Check here to see if we have a max HA Assets limit set. - if maxHaAssets := s.getOpts().JetStreamLimits.MaxHAAssets; maxHaAssets > 0 { - if s.numRaftNodes() > maxHaAssets { - s.Warnf("Maximum HA Assets limit reached: %d", maxHaAssets) - // Since the meta leader assigned this, send a statsz update to them to get them up to date. - go s.sendStatszUpdate() - return errors.New("system limit reached") - } - } - - storeDir := filepath.Join(js.config.StoreDir, sysAcc.Name, defaultStoreDirName, rg.Name) - var store StreamStore - if storage == FileStorage { - // If the server is set to sync always, do the same for the Raft log. - js.srv.optsMu.RLock() - syncAlways := js.srv.opts.SyncAlways - syncInterval := js.srv.opts.SyncInterval - js.srv.optsMu.RUnlock() - fs, err := newFileStoreWithCreated( - FileStoreConfig{StoreDir: storeDir, BlockSize: defaultMediumBlockSize, AsyncFlush: false, SyncAlways: syncAlways, SyncInterval: syncInterval, srv: s}, - StreamConfig{Name: rg.Name, Storage: FileStorage, Metadata: labels}, - time.Now().UTC(), - s.jsKeyGen(s.getOpts().JetStreamKey, rg.Name), - s.jsKeyGen(s.getOpts().JetStreamOldKey, rg.Name), - ) - if err != nil { - s.Errorf("Error creating filestore WAL: %v", err) - return err - } - store = fs - } else { - ms, err := newMemStore(&StreamConfig{Name: rg.Name, Storage: MemoryStorage}) - if err != nil { - s.Errorf("Error creating memstore WAL: %v", err) - return err - } - store = ms - } - - cfg := &RaftConfig{Name: rg.Name, Store: storeDir, Log: store, Track: true} - - if _, err := readPeerState(storeDir); err != nil { - s.bootstrapRaftNode(cfg, rg.Peers, true) - } - - n, err := s.startRaftNode(accName, cfg, labels) - if err != nil || n == nil { - s.Debugf("Error creating raft group: %v", err) - return err - } - // Need locking here for the assignment to avoid data-race reports - js.mu.Lock() - rg.node = n - // See if we are preferred and should start campaign immediately. - if n.ID() == rg.Preferred && n.Term() == 0 { - n.Campaign() - } - js.mu.Unlock() - return nil -} - -func (mset *stream) raftGroup() *raftGroup { - if mset == nil { - return nil - } - mset.mu.RLock() - defer mset.mu.RUnlock() - if mset.sa == nil { - return nil - } - return mset.sa.Group -} - -func (mset *stream) raftNode() RaftNode { - if mset == nil { - return nil - } - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.node -} - -func (mset *stream) removeNode() { - mset.mu.Lock() - defer mset.mu.Unlock() - if n := mset.node; n != nil { - n.Delete() - mset.node = nil - } -} - -// Helper function to generate peer info. -// lists and sets for old and new. -func genPeerInfo(peers []string, split int) (newPeers, oldPeers []string, newPeerSet, oldPeerSet map[string]bool) { - newPeers = peers[split:] - oldPeers = peers[:split] - newPeerSet = make(map[string]bool, len(newPeers)) - oldPeerSet = make(map[string]bool, len(oldPeers)) - for i, peer := range peers { - if i < split { - oldPeerSet[peer] = true - } else { - newPeerSet[peer] = true - } - } - return -} - -// This will wait for a period of time until all consumers are registered and have -// their consumer assignments assigned. -// Should only be called from monitorStream. -func (mset *stream) waitOnConsumerAssignments() { - mset.mu.RLock() - s, js, acc, sa, name, replicas := mset.srv, mset.js, mset.acc, mset.sa, mset.cfg.Name, mset.cfg.Replicas - mset.mu.RUnlock() - - if s == nil || js == nil || acc == nil || sa == nil { - return - } - - js.mu.RLock() - numExpectedConsumers := len(sa.consumers) - js.mu.RUnlock() - - // Max to wait. - const maxWaitTime = 10 * time.Second - const sleepTime = 500 * time.Millisecond - - // Wait up to 10s - timeout := time.Now().Add(maxWaitTime) - for time.Now().Before(timeout) { - var numReady int - for _, o := range mset.getConsumers() { - // Make sure we are registered with our consumer assignment. - if ca := o.consumerAssignment(); ca != nil { - if replicas > 1 && !o.isMonitorRunning() { - break - } - numReady++ - } else { - break - } - } - // Check if we are good. - if numReady >= numExpectedConsumers { - break - } - - s.Debugf("Waiting for consumers for interest based stream '%s > %s'", acc.Name, name) - select { - case <-s.quitCh: - return - case <-mset.monitorQuitC(): - return - case <-time.After(sleepTime): - } - } - - if actual := mset.numConsumers(); actual < numExpectedConsumers { - s.Warnf("All consumers not online for '%s > %s': expected %d but only have %d", acc.Name, name, numExpectedConsumers, actual) - } -} - -// Monitor our stream node for this stream. -func (js *jetStream) monitorStream(mset *stream, sa *streamAssignment, sendSnapshot bool) { - s, cc := js.server(), js.cluster - defer s.grWG.Done() - if mset != nil { - defer mset.monitorWg.Done() - } - js.mu.RLock() - n := sa.Group.node - meta := cc.meta - js.mu.RUnlock() - - if n == nil || meta == nil { - s.Warnf("No RAFT group for '%s > %s'", sa.Client.serviceAccount(), sa.Config.Name) - return - } - - // Make sure only one is running. - if mset != nil { - if mset.checkInMonitor() { - return - } - defer mset.clearMonitorRunning() - } - - // Make sure to stop the raft group on exit to prevent accidental memory bloat. - // This should be below the checkInMonitor call though to avoid stopping it out - // from underneath the one that is running since it will be the same raft node. - defer func() { - // We might be closing during shutdown, don't pre-emptively stop here since we'll still want to install snapshots. - if mset != nil && !mset.closed.Load() { - n.Stop() - } - }() - - qch, mqch, lch, aq, uch, ourPeerId := n.QuitC(), mset.monitorQuitC(), n.LeadChangeC(), n.ApplyQ(), mset.updateC(), meta.ID() - - s.Debugf("Starting stream monitor for '%s > %s' [%s]", sa.Client.serviceAccount(), sa.Config.Name, n.Group()) - defer s.Debugf("Exiting stream monitor for '%s > %s' [%s]", sa.Client.serviceAccount(), sa.Config.Name, n.Group()) - - // Make sure we do not leave the apply channel to fill up and block the raft layer. - defer func() { - if n.State() == Closed { - return - } - n.StepDown() - // Drain the commit queue... - aq.drain() - }() - - const ( - compactInterval = 2 * time.Minute - compactSizeMin = 8 * 1024 * 1024 - compactNumMin = 65536 - ) - - // Spread these out for large numbers on server restart. - rci := time.Duration(rand.Int63n(int64(time.Minute))) - t := time.NewTicker(compactInterval + rci) - defer t.Stop() - - js.mu.RLock() - isLeader := cc.isStreamLeader(sa.Client.serviceAccount(), sa.Config.Name) - isRestore := sa.Restore != nil - js.mu.RUnlock() - - acc, err := s.LookupAccount(sa.Client.serviceAccount()) - if err != nil { - s.Warnf("Could not retrieve account for stream '%s > %s'", sa.Client.serviceAccount(), sa.Config.Name) - return - } - accName := acc.GetName() - - // Used to represent how we can detect a changed state quickly and without representing - // a complete and detailed state which could be costly in terms of memory, cpu and GC. - // This only entails how many messages, and the first and last sequence of the stream. - // This is all that is needed to detect a change, and we can get this from FilteredState() - // with an empty filter. - var lastState SimpleState - - // Don't allow the upper layer to install snapshots until we have - // fully recovered from disk. - isRecovering := true - - doSnapshot := func() { - if mset == nil || isRecovering || isRestore { - return - } - - // Before we actually calculate the detailed state and encode it, let's check the - // simple state to detect any changes. - curState := mset.store.FilteredState(0, _EMPTY_) - - // If the state hasn't changed but the log has gone way over - // the compaction size then we will want to compact anyway. - // This shouldn't happen for streams like it can for pull - // consumers on idle streams but better to be safe than sorry! - ne, nb := n.Size() - if curState == lastState && ne < compactNumMin && nb < compactSizeMin { - return - } - - if err := n.InstallSnapshot(mset.stateSnapshot()); err == nil { - lastState = curState - } else if err != errNoSnapAvailable && err != errNodeClosed && err != errCatchupsRunning { - s.RateLimitWarnf("Failed to install snapshot for '%s > %s' [%s]: %v", mset.acc.Name, mset.name(), n.Group(), err) - } - } - - // We will establish a restoreDoneCh no matter what. Will never be triggered unless - // we replace with the restore chan. - restoreDoneCh := make(<-chan error) - - // For migration tracking. - var mmt *time.Ticker - var mmtc <-chan time.Time - - startMigrationMonitoring := func() { - if mmt == nil { - mmt = time.NewTicker(500 * time.Millisecond) - mmtc = mmt.C - } - } - - stopMigrationMonitoring := func() { - if mmt != nil { - mmt.Stop() - mmt, mmtc = nil, nil - } - } - defer stopMigrationMonitoring() - - // This is to optionally track when we are ready as a non-leader for direct access participation. - // Either direct or if we are a direct mirror, or both. - var dat *time.Ticker - var datc <-chan time.Time - - startDirectAccessMonitoring := func() { - if dat == nil { - dat = time.NewTicker(2 * time.Second) - datc = dat.C - } - } - - stopDirectMonitoring := func() { - if dat != nil { - dat.Stop() - dat, datc = nil, nil - } - } - defer stopDirectMonitoring() - - // For checking interest state if applicable. - var cist *time.Ticker - var cistc <-chan time.Time - - checkInterestInterval := checkInterestStateT + time.Duration(rand.Intn(checkInterestStateJ))*time.Second - - if mset != nil && mset.isInterestRetention() { - // Wait on our consumers to be assigned and running before proceeding. - // This can become important when a server has lots of assets - // since we process streams first then consumers as an asset class. - mset.waitOnConsumerAssignments() - // Setup our periodic check here. We will check once we have restored right away. - cist = time.NewTicker(checkInterestInterval) - cistc = cist.C - } - - // This is triggered during a scale up from R1 to clustered mode. We need the new followers to catchup, - // similar to how we trigger the catchup mechanism post a backup/restore. - // We can arrive here NOT being the leader, so we send the snapshot only if we are, and in this case - // reset the notion that we need to send the snapshot. If we are not, then the first time the server - // will switch to leader (in the loop below), we will send the snapshot. - if sendSnapshot && isLeader && mset != nil && n != nil && !isRecovering { - n.SendSnapshot(mset.stateSnapshot()) - sendSnapshot = false - } - - for { - select { - case <-s.quitCh: - // Server shutting down, but we might receive this before qch, so try to snapshot. - doSnapshot() - return - case <-mqch: - return - case <-qch: - // Clean signal from shutdown routine so do best effort attempt to snapshot. - doSnapshot() - return - case <-aq.ch: - var ne, nb uint64 - // If we bump clfs we will want to write out snapshot if within our time window. - pclfs := mset.getCLFS() - - ces := aq.pop() - for _, ce := range ces { - // No special processing needed for when we are caught up on restart. - if ce == nil { - isRecovering = false - // If we are interest based make sure to check consumers if interest retention policy. - // This is to make sure we process any outstanding acks from all consumers. - if mset != nil && mset.isInterestRetention() { - fire := time.Duration(rand.Intn(5)+5) * time.Second - time.AfterFunc(fire, mset.checkInterestState) - } - // If we became leader during this time and we need to send a snapshot to our - // followers, i.e. as a result of a scale-up from R1, do it now. - if sendSnapshot && isLeader && mset != nil && n != nil { - n.SendSnapshot(mset.stateSnapshot()) - sendSnapshot = false - } - continue - } - - // Apply our entries. - if err := js.applyStreamEntries(mset, ce, isRecovering); err == nil { - // Update our applied. - ne, nb = n.Applied(ce.Index) - ce.ReturnToPool() - } else { - // Our stream was closed out from underneath of us, simply return here. - if err == errStreamClosed || err == errCatchupStreamStopped || err == ErrServerNotRunning { - aq.recycle(&ces) - return - } - s.Warnf("Error applying entries to '%s > %s': %v", accName, sa.Config.Name, err) - if isClusterResetErr(err) { - if mset.isMirror() && mset.IsLeader() { - mset.retryMirrorConsumer() - continue - } - // We will attempt to reset our cluster state. - if mset.resetClusteredState(err) { - aq.recycle(&ces) - return - } - } else if isOutOfSpaceErr(err) { - // If applicable this will tear all of this down, but don't assume so and return. - s.handleOutOfSpace(mset) - } - } - } - aq.recycle(&ces) - - // Check about snapshotting - // If we have at least min entries to compact, go ahead and try to snapshot/compact. - if ne >= compactNumMin || nb > compactSizeMin || mset.getCLFS() > pclfs { - doSnapshot() - } - - case isLeader = <-lch: - if isLeader { - if mset != nil && n != nil && sendSnapshot && !isRecovering { - // If we *are* recovering at the time then this will get done when the apply queue - // handles the nil guard to show the catchup ended. - n.SendSnapshot(mset.stateSnapshot()) - sendSnapshot = false - } - if isRestore { - acc, _ := s.LookupAccount(sa.Client.serviceAccount()) - restoreDoneCh = s.processStreamRestore(sa.Client, acc, sa.Config, _EMPTY_, sa.Reply, _EMPTY_) - continue - } else if n != nil && n.NeedSnapshot() { - doSnapshot() - } - // Always cancel if this was running. - stopDirectMonitoring() - - } else if !n.Leaderless() { - js.setStreamAssignmentRecovering(sa) - } - - // Process our leader change. - js.processStreamLeaderChange(mset, isLeader) - - // We may receive a leader change after the stream assignment which would cancel us - // monitoring for this closely. So re-assess our state here as well. - // Or the old leader is no longer part of the set and transferred leadership - // for this leader to resume with removal - migrating := mset.isMigrating() - - // Check for migrations here. We set the state on the stream assignment update below. - if isLeader && migrating { - startMigrationMonitoring() - } - - // Here we are checking if we are not the leader but we have been asked to allow - // direct access. We now allow non-leaders to participate in the queue group. - if !isLeader && mset != nil { - mset.mu.RLock() - ad, md := mset.cfg.AllowDirect, mset.cfg.MirrorDirect - mset.mu.RUnlock() - if ad || md { - startDirectAccessMonitoring() - } - } - - case <-cistc: - cist.Reset(checkInterestInterval) - // We may be adjusting some things with consumers so do this in its own go routine. - go mset.checkInterestState() - - case <-datc: - if mset == nil || isRecovering { - continue - } - // If we are leader we can stop, we know this is setup now. - if isLeader { - stopDirectMonitoring() - continue - } - - mset.mu.Lock() - ad, md, current := mset.cfg.AllowDirect, mset.cfg.MirrorDirect, mset.isCurrent() - if !current { - const syncThreshold = 90.0 - // We are not current, but current means exactly caught up. Under heavy publish - // loads we may never reach this, so check if we are within 90% caught up. - _, c, a := mset.node.Progress() - if c == 0 { - mset.mu.Unlock() - continue - } - if p := float64(a) / float64(c) * 100.0; p < syncThreshold { - mset.mu.Unlock() - continue - } else { - s.Debugf("Stream '%s > %s' enabling direct gets at %.0f%% synchronized", - sa.Client.serviceAccount(), sa.Config.Name, p) - } - } - // We are current, cancel monitoring and create the direct subs as needed. - if ad { - mset.subscribeToDirect() - } - if md { - mset.subscribeToMirrorDirect() - } - mset.mu.Unlock() - // Stop direct monitoring. - stopDirectMonitoring() - - case <-t.C: - doSnapshot() - - case <-uch: - // keep stream assignment current - sa = mset.streamAssignment() - - // We get this when we have a new stream assignment caused by an update. - // We want to know if we are migrating. - if migrating := mset.isMigrating(); migrating { - if isLeader && mmtc == nil { - startMigrationMonitoring() - } - } else { - stopMigrationMonitoring() - } - case <-mmtc: - if !isLeader { - // We are no longer leader, so not our job. - stopMigrationMonitoring() - continue - } - - // Check to see where we are.. - rg := mset.raftGroup() - - // Track the new peers and check the ones that are current. - mset.mu.RLock() - replicas := mset.cfg.Replicas - mset.mu.RUnlock() - if len(rg.Peers) <= replicas { - // Migration no longer happening, so not our job anymore - stopMigrationMonitoring() - continue - } - - // Make sure we have correct cluster information on the other peers. - ci := js.clusterInfo(rg) - mset.checkClusterInfo(ci) - - newPeers, oldPeers, newPeerSet, oldPeerSet := genPeerInfo(rg.Peers, len(rg.Peers)-replicas) - - // If we are part of the new peerset and we have been passed the baton. - // We will handle scale down. - if newPeerSet[ourPeerId] { - // First need to check on any consumers and make sure they have moved properly before scaling down ourselves. - js.mu.RLock() - var needToWait bool - for name, c := range sa.consumers { - for _, peer := range c.Group.Peers { - // If we have peers still in the old set block. - if oldPeerSet[peer] { - s.Debugf("Scale down of '%s > %s' blocked by consumer '%s'", accName, sa.Config.Name, name) - needToWait = true - break - } - } - if needToWait { - break - } - } - js.mu.RUnlock() - if needToWait { - continue - } - - // We are good to go, can scale down here. - for _, p := range oldPeers { - n.ProposeRemovePeer(p) - } - - csa := sa.copyGroup() - csa.Group.Peers = newPeers - csa.Group.Preferred = ourPeerId - csa.Group.Cluster = s.cachedClusterName() - cc.meta.ForwardProposal(encodeUpdateStreamAssignment(csa)) - s.Noticef("Scaling down '%s > %s' to %+v", accName, sa.Config.Name, s.peerSetToNames(newPeers)) - } else { - // We are the old leader here, from the original peer set. - // We are simply waiting on the new peerset to be caught up so we can transfer leadership. - var newLeaderPeer, newLeader string - neededCurrent, current := replicas/2+1, 0 - - for _, r := range ci.Replicas { - if r.Current && newPeerSet[r.Peer] { - current++ - if newLeader == _EMPTY_ { - newLeaderPeer, newLeader = r.Peer, r.Name - } - } - } - // Check if we have a quorom. - if current >= neededCurrent { - s.Noticef("Transfer of stream leader for '%s > %s' to '%s'", accName, sa.Config.Name, newLeader) - n.ProposeKnownPeers(newPeers) - n.StepDown(newLeaderPeer) - } - } - - case err := <-restoreDoneCh: - // We have completed a restore from snapshot on this server. The stream assignment has - // already been assigned but the replicas will need to catch up out of band. Consumers - // will need to be assigned by forwarding the proposal and stamping the initial state. - s.Debugf("Stream restore for '%s > %s' completed", sa.Client.serviceAccount(), sa.Config.Name) - if err != nil { - s.Debugf("Stream restore failed: %v", err) - } - isRestore = false - sa.Restore = nil - // If we were successful lookup up our stream now. - if err == nil { - if mset, err = acc.lookupStream(sa.Config.Name); mset != nil { - mset.monitorWg.Add(1) - defer mset.monitorWg.Done() - mset.setStreamAssignment(sa) - // Make sure to update our updateC which would have been nil. - uch = mset.updateC() - // Also update our mqch - mqch = mset.monitorQuitC() - // Setup a periodic check here if we are interest based as well. - if mset.isInterestRetention() { - cist = time.NewTicker(checkInterestInterval) - cistc = cist.C - } - } - } - if err != nil { - if mset != nil { - mset.delete() - } - js.mu.Lock() - sa.err = err - if n != nil { - n.Delete() - } - result := &streamAssignmentResult{ - Account: sa.Client.serviceAccount(), - Stream: sa.Config.Name, - Restore: &JSApiStreamRestoreResponse{ApiResponse: ApiResponse{Type: JSApiStreamRestoreResponseType}}, - } - result.Restore.Error = NewJSStreamAssignmentError(err, Unless(err)) - js.mu.Unlock() - // Send response to the metadata leader. They will forward to the user as needed. - s.sendInternalMsgLocked(streamAssignmentSubj, _EMPTY_, nil, result) - return - } - - if !isLeader { - panic("Finished restore but not leader") - } - // Trigger the stream followers to catchup. - if n = mset.raftNode(); n != nil { - n.SendSnapshot(mset.stateSnapshot()) - } - js.processStreamLeaderChange(mset, isLeader) - - // Check to see if we have restored consumers here. - // These are not currently assigned so we will need to do so here. - if consumers := mset.getPublicConsumers(); len(consumers) > 0 { - for _, o := range consumers { - name, cfg := o.String(), o.config() - rg := cc.createGroupForConsumer(&cfg, sa) - // Pick a preferred leader. - rg.setPreferred() - - // Place our initial state here as well for assignment distribution. - state, _ := o.store.State() - ca := &consumerAssignment{ - Group: rg, - Stream: sa.Config.Name, - Name: name, - Config: &cfg, - Client: sa.Client, - Created: o.createdTime(), - State: state, - } - - // We make these compressed in case state is complex. - addEntry := encodeAddConsumerAssignmentCompressed(ca) - cc.meta.ForwardProposal(addEntry) - - // Check to make sure we see the assignment. - go func() { - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - for range ticker.C { - js.mu.RLock() - ca, meta := js.consumerAssignment(ca.Client.serviceAccount(), sa.Config.Name, name), cc.meta - js.mu.RUnlock() - if ca == nil { - s.Warnf("Consumer assignment has not been assigned, retrying") - if meta != nil { - meta.ForwardProposal(addEntry) - } else { - return - } - } else { - return - } - } - }() - } - } - } - } -} - -// Determine if we are migrating -func (mset *stream) isMigrating() bool { - if mset == nil { - return false - } - - mset.mu.RLock() - js, sa := mset.js, mset.sa - mset.mu.RUnlock() - - js.mu.RLock() - defer js.mu.RUnlock() - - // During migration we will always be R>1, even when we start R1. - // So if we do not have a group or node we no we are not migrating. - if sa == nil || sa.Group == nil || sa.Group.node == nil { - return false - } - // The sign of migration is if our group peer count != configured replica count. - if sa.Config.Replicas == len(sa.Group.Peers) { - return false - } - return true -} - -// resetClusteredState is called when a clustered stream had an error (e.g sequence mismatch, bad snapshot) and needs to be reset. -func (mset *stream) resetClusteredState(err error) bool { - mset.mu.RLock() - s, js, jsa, sa, acc, node := mset.srv, mset.js, mset.jsa, mset.sa, mset.acc, mset.node - stype, tierName, replicas := mset.cfg.Storage, mset.tier, mset.cfg.Replicas - mset.mu.RUnlock() - - // Stepdown regardless if we are the leader here. - if node != nil { - node.StepDown() - } - - // If we detect we are shutting down just return. - if js != nil && js.isShuttingDown() { - s.Debugf("Will not reset stream, JetStream shutting down") - return false - } - - // Server - if js.limitsExceeded(stype) { - s.Warnf("Will not reset stream, server resources exceeded") - return false - } - - // Account - if exceeded, _ := jsa.limitsExceeded(stype, tierName, replicas); exceeded { - s.Warnf("stream '%s > %s' errored, account resources exceeded", acc, mset.name()) - return false - } - - if node != nil { - if errors.Is(err, errCatchupAbortedNoLeader) || err == errCatchupTooManyRetries { - // Don't delete all state, could've just been temporarily unable to reach the leader. - node.Stop() - } else { - // We delete our raft state. Will recreate. - node.Delete() - } - } - - // Preserve our current state and messages unless we have a first sequence mismatch. - shouldDelete := err == errFirstSequenceMismatch - - // Need to do the rest in a separate Go routine. - go func() { - mset.monitorWg.Wait() - mset.resetAndWaitOnConsumers() - // Stop our stream. - mset.stop(shouldDelete, false) - - if sa != nil { - js.mu.Lock() - if js.shuttingDown { - js.mu.Unlock() - return - } - - s.Warnf("Resetting stream cluster state for '%s > %s'", sa.Client.serviceAccount(), sa.Config.Name) - // Mark stream assignment as resetting, so we don't double-account reserved resources. - // But only if we're not also releasing the resources as part of the delete. - sa.resetting = !shouldDelete - // Now wipe groups from assignments. - sa.Group.node = nil - var consumers []*consumerAssignment - if cc := js.cluster; cc != nil && cc.meta != nil { - ourID := cc.meta.ID() - for _, ca := range sa.consumers { - if rg := ca.Group; rg != nil && rg.isMember(ourID) { - rg.node = nil // Erase group raft/node state. - consumers = append(consumers, ca) - } - } - } - js.mu.Unlock() - - // This will reset the stream and consumers. - // Reset stream. - js.processClusterCreateStream(acc, sa) - // Reset consumers. - for _, ca := range consumers { - js.processClusterCreateConsumer(ca, nil, false) - } - } - }() - - return true -} - -func isControlHdr(hdr []byte) bool { - return bytes.HasPrefix(hdr, []byte("NATS/1.0 100 ")) -} - -// Apply our stream entries. -func (js *jetStream) applyStreamEntries(mset *stream, ce *CommittedEntry, isRecovering bool) error { - for _, e := range ce.Entries { - if e.Type == EntryNormal { - buf, op := e.Data, entryOp(e.Data[0]) - switch op { - case streamMsgOp, compressedStreamMsgOp: - if mset == nil { - continue - } - s := js.srv - - mbuf := buf[1:] - if op == compressedStreamMsgOp { - var err error - mbuf, err = s2.Decode(nil, mbuf) - if err != nil { - panic(err.Error()) - } - } - - subject, reply, hdr, msg, lseq, ts, sourced, err := decodeStreamMsg(mbuf) - if err != nil { - if node := mset.raftNode(); node != nil { - s.Errorf("JetStream cluster could not decode stream msg for '%s > %s' [%s]", - mset.account(), mset.name(), node.Group()) - } - panic(err.Error()) - } - - // Check for flowcontrol here. - if len(msg) == 0 && len(hdr) > 0 && reply != _EMPTY_ && isControlHdr(hdr) { - if !isRecovering { - mset.sendFlowControlReply(reply) - } - continue - } - - // Grab last sequence and CLFS. - last, clfs := mset.lastSeqAndCLFS() - - // We can skip if we know this is less than what we already have. - if lseq-clfs < last { - s.Debugf("Apply stream entries for '%s > %s' skipping message with sequence %d with last of %d", - mset.account(), mset.name(), lseq+1-clfs, last) - mset.mu.Lock() - // Check for any preAcks in case we are interest based. - mset.clearAllPreAcks(lseq + 1 - clfs) - mset.mu.Unlock() - continue - } - - // Skip by hand here since first msg special case. - // Reason is sequence is unsigned and for lseq being 0 - // the lseq under stream would have to be -1. - if lseq == 0 && last != 0 { - continue - } - - // Messages to be skipped have no subject or timestamp or msg or hdr. - if subject == _EMPTY_ && ts == 0 && len(msg) == 0 && len(hdr) == 0 { - // Skip and update our lseq. - last := mset.store.SkipMsg() - mset.mu.Lock() - mset.setLastSeq(last) - mset.clearAllPreAcks(last) - mset.mu.Unlock() - continue - } - - var mt *msgTrace - // If not recovering, see if we find a message trace object for this - // sequence. Only the leader that has proposed this entry will have - // stored the trace info. - if !isRecovering { - mt = mset.getAndDeleteMsgTrace(lseq) - } - // Process the actual message here. - err = mset.processJetStreamMsg(subject, reply, hdr, msg, lseq, ts, mt, sourced) - - // If we have inflight make sure to clear after processing. - // TODO(dlc) - technically check on inflight != nil could cause datarace. - // But do not want to acquire lock since tracking this will be rare. - if mset.inflight != nil { - mset.clMu.Lock() - delete(mset.inflight, lseq) - mset.clMu.Unlock() - } - - // Clear expected per subject state after processing. - if mset.expectedPerSubjectSequence != nil { - mset.clMu.Lock() - if subj, found := mset.expectedPerSubjectSequence[lseq]; found { - delete(mset.expectedPerSubjectSequence, lseq) - delete(mset.expectedPerSubjectInProcess, subj) - } - mset.clMu.Unlock() - } - - if err != nil { - if err == errLastSeqMismatch { - - var state StreamState - mset.store.FastState(&state) - - // If we have no msgs and the other side is delivering us a sequence past where we - // should be reset. This is possible if the other side has a stale snapshot and no longer - // has those messages. So compact and retry to reset. - if state.Msgs == 0 { - mset.store.Compact(lseq + 1) - // Retry - err = mset.processJetStreamMsg(subject, reply, hdr, msg, lseq, ts, mt, sourced) - } - // FIXME(dlc) - We could just run a catchup with a request defining the span between what we expected - // and what we got. - } - - // Only return in place if we are going to reset our stream or we are out of space, or we are closed. - if isClusterResetErr(err) || isOutOfSpaceErr(err) || err == errStreamClosed { - return err - } - s.Debugf("Apply stream entries for '%s > %s' got error processing message: %v", - mset.account(), mset.name(), err) - } - - case deleteMsgOp: - md, err := decodeMsgDelete(buf[1:]) - if err != nil { - if node := mset.raftNode(); node != nil { - s := js.srv - s.Errorf("JetStream cluster could not decode delete msg for '%s > %s' [%s]", - mset.account(), mset.name(), node.Group()) - } - panic(err.Error()) - } - s, cc := js.server(), js.cluster - - var removed bool - if md.NoErase { - removed, err = mset.removeMsg(md.Seq) - } else { - removed, err = mset.eraseMsg(md.Seq) - } - - // Cluster reset error. - if err == ErrStoreEOF { - return err - } - - if err != nil && !isRecovering { - s.Debugf("JetStream cluster failed to delete stream msg %d from '%s > %s': %v", - md.Seq, md.Client.serviceAccount(), md.Stream, err) - } - - js.mu.RLock() - isLeader := cc.isStreamLeader(md.Client.serviceAccount(), md.Stream) - js.mu.RUnlock() - - if isLeader && !isRecovering { - var resp = JSApiMsgDeleteResponse{ApiResponse: ApiResponse{Type: JSApiMsgDeleteResponseType}} - if err != nil { - resp.Error = NewJSStreamMsgDeleteFailedError(err, Unless(err)) - s.sendAPIErrResponse(md.Client, mset.account(), md.Subject, md.Reply, _EMPTY_, s.jsonResponse(resp)) - } else if !removed { - resp.Error = NewJSSequenceNotFoundError(md.Seq) - s.sendAPIErrResponse(md.Client, mset.account(), md.Subject, md.Reply, _EMPTY_, s.jsonResponse(resp)) - } else { - resp.Success = true - s.sendAPIResponse(md.Client, mset.account(), md.Subject, md.Reply, _EMPTY_, s.jsonResponse(resp)) - } - } - case purgeStreamOp: - sp, err := decodeStreamPurge(buf[1:]) - if err != nil { - if node := mset.raftNode(); node != nil { - s := js.srv - s.Errorf("JetStream cluster could not decode purge msg for '%s > %s' [%s]", - mset.account(), mset.name(), node.Group()) - } - panic(err.Error()) - } - // If no explicit request, fill in with leader stamped last sequence to protect ourselves on replay during server start. - if sp.Request == nil || sp.Request.Sequence == 0 { - purgeSeq := sp.LastSeq + 1 - if sp.Request == nil { - sp.Request = &JSApiStreamPurgeRequest{Sequence: purgeSeq} - } else if sp.Request.Keep == 0 { - sp.Request.Sequence = purgeSeq - } else if isRecovering { - continue - } - } - - s := js.server() - purged, err := mset.purge(sp.Request) - if err != nil { - s.Warnf("JetStream cluster failed to purge stream %q for account %q: %v", sp.Stream, sp.Client.serviceAccount(), err) - } - - js.mu.RLock() - isLeader := js.cluster.isStreamLeader(sp.Client.serviceAccount(), sp.Stream) - js.mu.RUnlock() - - if isLeader && !isRecovering { - var resp = JSApiStreamPurgeResponse{ApiResponse: ApiResponse{Type: JSApiStreamPurgeResponseType}} - if err != nil { - resp.Error = NewJSStreamGeneralError(err, Unless(err)) - s.sendAPIErrResponse(sp.Client, mset.account(), sp.Subject, sp.Reply, _EMPTY_, s.jsonResponse(resp)) - } else { - resp.Purged = purged - resp.Success = true - s.sendAPIResponse(sp.Client, mset.account(), sp.Subject, sp.Reply, _EMPTY_, s.jsonResponse(resp)) - } - } - default: - panic(fmt.Sprintf("JetStream Cluster Unknown group entry op type: %v", op)) - } - } else if e.Type == EntrySnapshot { - if mset == nil { - continue - } - - // Everything operates on new replicated state. Will convert legacy snapshots to this for processing. - var ss *StreamReplicatedState - - onBadState := func(err error) { - // If we are the leader or recovering, meaning we own the snapshot, - // we should stepdown and clear our raft state since our snapshot is bad. - if isRecovering || mset.IsLeader() { - mset.mu.RLock() - s, accName, streamName := mset.srv, mset.acc.GetName(), mset.cfg.Name - mset.mu.RUnlock() - s.Warnf("Detected bad stream state, resetting '%s > %s'", accName, streamName) - mset.resetClusteredState(err) - } - } - - // Check if we are the new binary encoding. - if IsEncodedStreamState(e.Data) { - var err error - ss, err = DecodeStreamState(e.Data) - if err != nil { - onBadState(err) - return err - } - } else { - var snap streamSnapshot - if err := json.Unmarshal(e.Data, &snap); err != nil { - onBadState(err) - return err - } - // Convert over to StreamReplicatedState - ss = &StreamReplicatedState{ - Msgs: snap.Msgs, - Bytes: snap.Bytes, - FirstSeq: snap.FirstSeq, - LastSeq: snap.LastSeq, - Failed: snap.Failed, - } - if len(snap.Deleted) > 0 { - ss.Deleted = append(ss.Deleted, DeleteSlice(snap.Deleted)) - } - } - - if isRecovering || !mset.IsLeader() { - if err := mset.processSnapshot(ss, ce.Index); err != nil { - return err - } - } - } else if e.Type == EntryRemovePeer { - js.mu.RLock() - var ourID string - if js.cluster != nil && js.cluster.meta != nil { - ourID = js.cluster.meta.ID() - } - js.mu.RUnlock() - // We only need to do processing if this is us. - if peer := string(e.Data); peer == ourID && mset != nil { - // Double check here with the registered stream assignment. - shouldRemove := true - if sa := mset.streamAssignment(); sa != nil && sa.Group != nil { - js.mu.RLock() - shouldRemove = !sa.Group.isMember(ourID) - js.mu.RUnlock() - } - if shouldRemove { - mset.stop(true, false) - } - } - } - } - return nil -} - -// Returns the PeerInfo for all replicas of a raft node. This is different than node.Peers() -// and is used for external facing advisories. -func (s *Server) replicas(node RaftNode) []*PeerInfo { - now := time.Now() - var replicas []*PeerInfo - for _, rp := range node.Peers() { - if sir, ok := s.nodeToInfo.Load(rp.ID); ok && sir != nil { - si := sir.(nodeInfo) - pi := &PeerInfo{Peer: rp.ID, Name: si.name, Current: rp.Current, Active: now.Sub(rp.Last), Offline: si.offline, Lag: rp.Lag} - replicas = append(replicas, pi) - } - } - return replicas -} - -// Process a leader change for the clustered stream. -func (js *jetStream) processStreamLeaderChange(mset *stream, isLeader bool) { - if mset == nil { - return - } - sa := mset.streamAssignment() - if sa == nil { - return - } - - // Clear inflight dedupe IDs, where seq=0. - mset.mu.Lock() - var removed int - for i := len(mset.ddarr) - 1; i >= mset.ddindex; i-- { - dde := mset.ddarr[i] - if dde.seq != 0 { - break - } - removed++ - delete(mset.ddmap, dde.id) - } - if removed > 0 { - if len(mset.ddmap) > 0 { - mset.ddarr = mset.ddarr[:len(mset.ddarr)-removed] - } else { - mset.ddmap = nil - mset.ddarr = nil - mset.ddindex = 0 - } - } - mset.mu.Unlock() - - mset.clMu.Lock() - // Clear inflight if we have it. - mset.inflight = nil - // Clear expected per subject state. - mset.expectedPerSubjectSequence = nil - mset.expectedPerSubjectInProcess = nil - mset.clMu.Unlock() - - js.mu.Lock() - s, account, err := js.srv, sa.Client.serviceAccount(), sa.err - client, subject, reply := sa.Client, sa.Subject, sa.Reply - hasResponded := sa.responded - sa.responded = true - js.mu.Unlock() - - streamName := mset.name() - - if isLeader { - s.Noticef("JetStream cluster new stream leader for '%s > %s'", account, streamName) - s.sendStreamLeaderElectAdvisory(mset) - } else { - // We are stepping down. - // Make sure if we are doing so because we have lost quorum that we send the appropriate advisories. - if node := mset.raftNode(); node != nil && !node.Quorum() && time.Since(node.Created()) > 5*time.Second { - s.sendStreamLostQuorumAdvisory(mset) - } - - // Clear clseq. If we become leader again, it will be fixed up - // automatically on the next processClusteredInboundMsg call. - mset.clMu.Lock() - if mset.clseq > 0 { - mset.clseq = 0 - } - mset.clMu.Unlock() - } - - // Tell stream to switch leader status. - mset.setLeader(isLeader) - - if !isLeader || hasResponded { - return - } - - acc, _ := s.LookupAccount(account) - if acc == nil { - return - } - - // Send our response. - var resp = JSApiStreamCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamCreateResponseType}} - if err != nil { - resp.Error = NewJSStreamCreateError(err, Unless(err)) - s.sendAPIErrResponse(client, acc, subject, reply, _EMPTY_, s.jsonResponse(&resp)) - } else { - msetCfg := mset.config() - resp.StreamInfo = &StreamInfo{ - Created: mset.createdTime(), - State: mset.state(), - Config: *setDynamicStreamMetadata(&msetCfg), - Cluster: js.clusterInfo(mset.raftGroup()), - Sources: mset.sourcesInfo(), - Mirror: mset.mirrorInfo(), - TimeStamp: time.Now().UTC(), - } - resp.DidCreate = true - s.sendAPIResponse(client, acc, subject, reply, _EMPTY_, s.jsonResponse(&resp)) - if node := mset.raftNode(); node != nil { - mset.sendCreateAdvisory() - } - } -} - -// Fixed value ok for now. -const lostQuorumAdvInterval = 10 * time.Second - -// Determines if we should send lost quorum advisory. We throttle these after first one. -func (mset *stream) shouldSendLostQuorum() bool { - mset.mu.Lock() - defer mset.mu.Unlock() - if time.Since(mset.lqsent) >= lostQuorumAdvInterval { - mset.lqsent = time.Now() - return true - } - return false -} - -func (s *Server) sendStreamLostQuorumAdvisory(mset *stream) { - if mset == nil { - return - } - node, stream, acc := mset.raftNode(), mset.name(), mset.account() - if node == nil { - return - } - if !mset.shouldSendLostQuorum() { - return - } - - s.Warnf("JetStream cluster stream '%s > %s' has NO quorum, stalled", acc.GetName(), stream) - - subj := JSAdvisoryStreamQuorumLostPre + "." + stream - adv := &JSStreamQuorumLostAdvisory{ - TypedEvent: TypedEvent{ - Type: JSStreamQuorumLostAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Stream: stream, - Replicas: s.replicas(node), - Domain: s.getOpts().JetStreamDomain, - } - - // Send to the user's account if not the system account. - if acc != s.SystemAccount() { - s.publishAdvisory(acc, subj, adv) - } - // Now do system level one. Place account info in adv, and nil account means system. - adv.Account = acc.GetName() - s.publishAdvisory(nil, subj, adv) -} - -func (s *Server) sendStreamLeaderElectAdvisory(mset *stream) { - if mset == nil { - return - } - node, stream, acc := mset.raftNode(), mset.name(), mset.account() - if node == nil { - return - } - subj := JSAdvisoryStreamLeaderElectedPre + "." + stream - adv := &JSStreamLeaderElectedAdvisory{ - TypedEvent: TypedEvent{ - Type: JSStreamLeaderElectedAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Stream: stream, - Leader: s.serverNameForNode(node.GroupLeader()), - Replicas: s.replicas(node), - Domain: s.getOpts().JetStreamDomain, - } - - // Send to the user's account if not the system account. - if acc != s.SystemAccount() { - s.publishAdvisory(acc, subj, adv) - } - // Now do system level one. Place account info in adv, and nil account means system. - adv.Account = acc.GetName() - s.publishAdvisory(nil, subj, adv) -} - -// Will lookup a stream assignment. -// Lock should be held. -func (js *jetStream) streamAssignment(account, stream string) (sa *streamAssignment) { - cc := js.cluster - if cc == nil { - return nil - } - - if as := cc.streams[account]; as != nil { - sa = as[stream] - } - return sa -} - -// processStreamAssignment is called when followers have replicated an assignment. -func (js *jetStream) processStreamAssignment(sa *streamAssignment) bool { - js.mu.Lock() - s, cc := js.srv, js.cluster - accName, stream := sa.Client.serviceAccount(), sa.Config.Name - noMeta := cc == nil || cc.meta == nil - var ourID string - if !noMeta { - ourID = cc.meta.ID() - } - var isMember bool - if sa.Group != nil && ourID != _EMPTY_ { - isMember = sa.Group.isMember(ourID) - } - - // Remove this stream from the inflight proposals - cc.removeInflightProposal(accName, sa.Config.Name) - - if s == nil || noMeta { - js.mu.Unlock() - return false - } - - accStreams := cc.streams[accName] - if accStreams == nil { - accStreams = make(map[string]*streamAssignment) - } else if osa := accStreams[stream]; osa != nil && osa != sa { - // Copy over private existing state from former SA. - if sa.Group != nil { - sa.Group.node = osa.Group.node - } - sa.consumers = osa.consumers - sa.responded = osa.responded - sa.err = osa.err - } - - // Update our state. - accStreams[stream] = sa - cc.streams[accName] = accStreams - hasResponded := sa.responded - js.mu.Unlock() - - acc, err := s.LookupAccount(accName) - if err != nil { - ll := fmt.Sprintf("Account [%s] lookup for stream create failed: %v", accName, err) - if isMember { - if !hasResponded { - // If we can not lookup the account and we are a member, send this result back to the metacontroller leader. - result := &streamAssignmentResult{ - Account: accName, - Stream: stream, - Response: &JSApiStreamCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamCreateResponseType}}, - } - result.Response.Error = NewJSNoAccountError() - s.sendInternalMsgLocked(streamAssignmentSubj, _EMPTY_, nil, result) - } - s.Warnf(ll) - } else { - s.Debugf(ll) - } - return false - } - - var didRemove bool - - // Check if this is for us.. - if isMember { - js.processClusterCreateStream(acc, sa) - } else if mset, _ := acc.lookupStream(sa.Config.Name); mset != nil { - // We have one here even though we are not a member. This can happen on re-assignment. - s.removeStream(mset, sa) - } - - // If this stream assignment does not have a sync subject (bug) set that the meta-leader should check when elected. - if sa.Sync == _EMPTY_ { - js.mu.Lock() - cc.streamsCheck = true - js.mu.Unlock() - return false - } - - return didRemove -} - -// processUpdateStreamAssignment is called when followers have replicated an updated assignment. -func (js *jetStream) processUpdateStreamAssignment(sa *streamAssignment) { - js.mu.RLock() - s, cc := js.srv, js.cluster - js.mu.RUnlock() - if s == nil || cc == nil { - // TODO(dlc) - debug at least - return - } - - accName := sa.Client.serviceAccount() - stream := sa.Config.Name - - js.mu.Lock() - if cc.meta == nil { - js.mu.Unlock() - return - } - ourID := cc.meta.ID() - - var isMember bool - if sa.Group != nil { - isMember = sa.Group.isMember(ourID) - } - - accStreams := cc.streams[accName] - if accStreams == nil { - js.mu.Unlock() - return - } - osa := accStreams[stream] - if osa == nil { - js.mu.Unlock() - return - } - - // Copy over private existing state from former SA. - if sa.Group != nil { - sa.Group.node = osa.Group.node - } - sa.consumers = osa.consumers - sa.err = osa.err - - // If we detect we are scaling down to 1, non-clustered, and we had a previous node, clear it here. - if sa.Config.Replicas == 1 && sa.Group.node != nil { - sa.Group.node = nil - } - - // Update our state. - accStreams[stream] = sa - cc.streams[accName] = accStreams - - // Make sure we respond if we are a member. - if isMember { - sa.responded = false - } else { - // Make sure to clean up any old node in case this stream moves back here. - if sa.Group != nil { - sa.Group.node = nil - } - } - js.mu.Unlock() - - acc, err := s.LookupAccount(accName) - if err != nil { - s.Warnf("Update Stream Account %s, error on lookup: %v", accName, err) - return - } - - // Check if this is for us.. - if isMember { - js.processClusterUpdateStream(acc, osa, sa) - } else if mset, _ := acc.lookupStream(sa.Config.Name); mset != nil { - // We have one here even though we are not a member. This can happen on re-assignment. - s.removeStream(mset, sa) - } -} - -// Common function to remove ourselves from this server. -// This can happen on re-assignment, move, etc -func (s *Server) removeStream(mset *stream, nsa *streamAssignment) { - if mset == nil { - return - } - // Make sure to use the new stream assignment, not our own. - s.Debugf("JetStream removing stream '%s > %s' from this server", nsa.Client.serviceAccount(), nsa.Config.Name) - if node := mset.raftNode(); node != nil { - node.StepDown(nsa.Group.Preferred) - // shutdown monitor by shutting down raft. - node.Delete() - } - - var isShuttingDown bool - // Make sure this node is no longer attached to our stream assignment. - if js, _ := s.getJetStreamCluster(); js != nil { - js.mu.Lock() - nsa.Group.node = nil - isShuttingDown = js.shuttingDown - js.mu.Unlock() - } - - if !isShuttingDown { - // wait for monitor to be shutdown. - mset.monitorWg.Wait() - } - mset.stop(true, false) -} - -// processClusterUpdateStream is called when we have a stream assignment that -// has been updated for an existing assignment and we are a member. -func (js *jetStream) processClusterUpdateStream(acc *Account, osa, sa *streamAssignment) { - if sa == nil { - return - } - - js.mu.Lock() - s, rg := js.srv, sa.Group - client, subject, reply := sa.Client, sa.Subject, sa.Reply - alreadyRunning, numReplicas := osa.Group.node != nil, len(rg.Peers) - needsNode := rg.node == nil - storage, cfg := sa.Config.Storage, sa.Config - hasResponded := sa.responded - sa.responded = true - recovering := sa.recovering - js.mu.Unlock() - - mset, err := acc.lookupStream(cfg.Name) - if err == nil && mset != nil { - // Make sure we have not had a new group assigned to us. - if osa.Group.Name != sa.Group.Name { - s.Warnf("JetStream cluster detected stream remapping for '%s > %s' from %q to %q", - acc, cfg.Name, osa.Group.Name, sa.Group.Name) - mset.removeNode() - alreadyRunning, needsNode = false, true - // Make sure to clear from original. - js.mu.Lock() - osa.Group.node = nil - js.mu.Unlock() - } - - if !alreadyRunning && numReplicas > 1 { - if needsNode { - // Since we are scaling up we want to make sure our sync subject - // is registered before we start our raft node. - mset.mu.Lock() - mset.startClusterSubs() - mset.mu.Unlock() - - js.createRaftGroup(acc.GetName(), rg, storage, pprofLabels{ - "type": "stream", - "account": mset.accName(), - "stream": mset.name(), - }) - } - mset.monitorWg.Add(1) - // Start monitoring.. - s.startGoRoutine( - func() { js.monitorStream(mset, sa, needsNode) }, - pprofLabels{ - "type": "stream", - "account": mset.accName(), - "stream": mset.name(), - }, - ) - } else if numReplicas == 1 && alreadyRunning { - // We downgraded to R1. Make sure we cleanup the raft node and the stream monitor. - mset.removeNode() - // In case we need to shutdown the cluster specific subs, etc. - mset.mu.Lock() - // Stop responding to sync requests. - mset.stopClusterSubs() - // Clear catchup state - mset.clearAllCatchupPeers() - mset.mu.Unlock() - // Remove from meta layer. - js.mu.Lock() - rg.node = nil - js.mu.Unlock() - } - // Set the new stream assignment. - mset.setStreamAssignment(sa) - - // Call update. - if err = mset.updateWithAdvisory(cfg, !recovering, false); err != nil { - s.Warnf("JetStream cluster error updating stream %q for account %q: %v", cfg.Name, acc.Name, err) - } - } - - // If not found we must be expanding into this node since if we are here we know we are a member. - if err == ErrJetStreamStreamNotFound { - js.processStreamAssignment(sa) - return - } - - if err != nil { - js.mu.Lock() - sa.err = err - result := &streamAssignmentResult{ - Account: sa.Client.serviceAccount(), - Stream: sa.Config.Name, - Response: &JSApiStreamCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamCreateResponseType}}, - Update: true, - } - result.Response.Error = NewJSStreamGeneralError(err, Unless(err)) - js.mu.Unlock() - - // Send response to the metadata leader. They will forward to the user as needed. - s.sendInternalMsgLocked(streamAssignmentSubj, _EMPTY_, nil, result) - return - } - - isLeader := mset.IsLeader() - - // Check for missing syncSubject bug. - if isLeader && osa != nil && osa.Sync == _EMPTY_ { - if node := mset.raftNode(); node != nil { - node.StepDown() - } - return - } - - // If we were a single node being promoted assume leadership role for purpose of responding. - if !hasResponded && !isLeader && !alreadyRunning { - isLeader = true - } - - // Check if we should bail. - if !isLeader || hasResponded || recovering { - return - } - - // Send our response. - var resp = JSApiStreamUpdateResponse{ApiResponse: ApiResponse{Type: JSApiStreamUpdateResponseType}} - msetCfg := mset.config() - resp.StreamInfo = &StreamInfo{ - Created: mset.createdTime(), - State: mset.state(), - Config: *setDynamicStreamMetadata(&msetCfg), - Cluster: js.clusterInfo(mset.raftGroup()), - Mirror: mset.mirrorInfo(), - Sources: mset.sourcesInfo(), - TimeStamp: time.Now().UTC(), - } - - s.sendAPIResponse(client, acc, subject, reply, _EMPTY_, s.jsonResponse(&resp)) -} - -// processClusterCreateStream is called when we have a stream assignment that -// has been committed and this server is a member of the peer group. -func (js *jetStream) processClusterCreateStream(acc *Account, sa *streamAssignment) { - if sa == nil { - return - } - - js.mu.RLock() - s, rg := js.srv, sa.Group - alreadyRunning := rg.node != nil - storage := sa.Config.Storage - restore := sa.Restore - js.mu.RUnlock() - - // Process the raft group and make sure it's running if needed. - err := js.createRaftGroup(acc.GetName(), rg, storage, pprofLabels{ - "type": "stream", - "account": acc.Name, - "stream": sa.Config.Name, - }) - - // If we are restoring, create the stream if we are R>1 and not the preferred who handles the - // receipt of the snapshot itself. - shouldCreate := true - if restore != nil { - if len(rg.Peers) == 1 || rg.node != nil && rg.node.ID() == rg.Preferred { - shouldCreate = false - } else { - js.mu.Lock() - sa.Restore = nil - js.mu.Unlock() - } - } - - // Our stream. - var mset *stream - - // Process here if not restoring or not the leader. - if shouldCreate && err == nil { - // Go ahead and create or update the stream. - mset, err = acc.lookupStream(sa.Config.Name) - if err == nil && mset != nil { - osa := mset.streamAssignment() - // If we already have a stream assignment and they are the same exact config, short circuit here. - if osa != nil { - if reflect.DeepEqual(osa.Config, sa.Config) { - if sa.Group.Name == osa.Group.Name && reflect.DeepEqual(sa.Group.Peers, osa.Group.Peers) { - // Since this already exists we know it succeeded, just respond to this caller. - js.mu.RLock() - client, subject, reply, recovering := sa.Client, sa.Subject, sa.Reply, sa.recovering - js.mu.RUnlock() - - if !recovering { - var resp = JSApiStreamCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamCreateResponseType}} - msetCfg := mset.config() - resp.StreamInfo = &StreamInfo{ - Created: mset.createdTime(), - State: mset.state(), - Config: *setDynamicStreamMetadata(&msetCfg), - Cluster: js.clusterInfo(mset.raftGroup()), - Sources: mset.sourcesInfo(), - Mirror: mset.mirrorInfo(), - TimeStamp: time.Now().UTC(), - } - s.sendAPIResponse(client, acc, subject, reply, _EMPTY_, s.jsonResponse(&resp)) - } - return - } else { - // We had a bug where we could have multiple assignments for the same - // stream but with different group assignments, including multiple raft - // groups. So check for that here. We can only bet on the last one being - // consistent in the long run, so let it continue if we see this condition. - s.Warnf("JetStream cluster detected duplicate assignment for stream %q for account %q", sa.Config.Name, acc.Name) - if osa.Group.node != nil && osa.Group.node != sa.Group.node { - osa.Group.node.Delete() - osa.Group.node = nil - } - } - } - } - mset.setStreamAssignment(sa) - // Check if our config has really been updated. - cfg := mset.config() - if !reflect.DeepEqual(&cfg, sa.Config) { - if err = mset.updateWithAdvisory(sa.Config, false, false); err != nil { - s.Warnf("JetStream cluster error updating stream %q for account %q: %v", sa.Config.Name, acc.Name, err) - if osa != nil { - // Process the raft group and make sure it's running if needed. - js.createRaftGroup(acc.GetName(), osa.Group, storage, pprofLabels{ - "type": "stream", - "account": mset.accName(), - "stream": mset.name(), - }) - mset.setStreamAssignment(osa) - } - if rg.node != nil { - rg.node.Delete() - rg.node = nil - } - } - } - } else if err == NewJSStreamNotFoundError() { - // Add in the stream here. - mset, err = acc.addStreamWithAssignment(sa.Config, nil, sa, false) - } - if mset != nil { - mset.setCreatedTime(sa.Created) - } - } - - // This is an error condition. - if err != nil { - // If we're shutting down we could get a variety of errors, for example: - // 'JetStream not enabled for account' when looking up the stream. - // Normally we can continue and delete state, but need to be careful when shutting down. - if js.isShuttingDown() { - s.Debugf("Could not create stream, JetStream shutting down") - return - } - - if IsNatsErr(err, JSStreamStoreFailedF) { - s.Warnf("Stream create failed for '%s > %s': %v", sa.Client.serviceAccount(), sa.Config.Name, err) - err = errStreamStoreFailed - } - js.mu.Lock() - - sa.err = err - hasResponded := sa.responded - - // If out of space do nothing for now. - if isOutOfSpaceErr(err) { - hasResponded = true - } - - if rg.node != nil { - rg.node.Delete() - } - - var result *streamAssignmentResult - if !hasResponded { - result = &streamAssignmentResult{ - Account: sa.Client.serviceAccount(), - Stream: sa.Config.Name, - Response: &JSApiStreamCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamCreateResponseType}}, - } - result.Response.Error = NewJSStreamCreateError(err, Unless(err)) - } - js.mu.Unlock() - - // Send response to the metadata leader. They will forward to the user as needed. - if result != nil { - s.sendInternalMsgLocked(streamAssignmentSubj, _EMPTY_, nil, result) - } - return - } - - // Re-capture node. - js.mu.RLock() - node := rg.node - js.mu.RUnlock() - - // Start our monitoring routine. - if node != nil { - if !alreadyRunning { - if mset != nil { - mset.monitorWg.Add(1) - } - s.startGoRoutine( - func() { js.monitorStream(mset, sa, false) }, - pprofLabels{ - "type": "stream", - "account": mset.accName(), - "stream": mset.name(), - }, - ) - } - } else { - // Single replica stream, process manually here. - // If we are restoring, process that first. - if sa.Restore != nil { - // We are restoring a stream here. - restoreDoneCh := s.processStreamRestore(sa.Client, acc, sa.Config, _EMPTY_, sa.Reply, _EMPTY_) - s.startGoRoutine(func() { - defer s.grWG.Done() - select { - case err := <-restoreDoneCh: - if err == nil { - mset, err = acc.lookupStream(sa.Config.Name) - if mset != nil { - mset.setStreamAssignment(sa) - mset.setCreatedTime(sa.Created) - } - } - if err != nil { - if mset != nil { - mset.delete() - } - js.mu.Lock() - sa.err = err - result := &streamAssignmentResult{ - Account: sa.Client.serviceAccount(), - Stream: sa.Config.Name, - Restore: &JSApiStreamRestoreResponse{ApiResponse: ApiResponse{Type: JSApiStreamRestoreResponseType}}, - } - result.Restore.Error = NewJSStreamRestoreError(err, Unless(err)) - js.mu.Unlock() - // Send response to the metadata leader. They will forward to the user as needed. - b, _ := json.Marshal(result) // Avoids auto-processing and doing fancy json with newlines. - s.sendInternalMsgLocked(streamAssignmentSubj, _EMPTY_, nil, b) - return - } - js.processStreamLeaderChange(mset, true) - - // Check to see if we have restored consumers here. - // These are not currently assigned so we will need to do so here. - if consumers := mset.getPublicConsumers(); len(consumers) > 0 { - js.mu.RLock() - cc := js.cluster - js.mu.RUnlock() - - for _, o := range consumers { - name, cfg := o.String(), o.config() - rg := cc.createGroupForConsumer(&cfg, sa) - - // Place our initial state here as well for assignment distribution. - ca := &consumerAssignment{ - Group: rg, - Stream: sa.Config.Name, - Name: name, - Config: &cfg, - Client: sa.Client, - Created: o.createdTime(), - } - - addEntry := encodeAddConsumerAssignment(ca) - cc.meta.ForwardProposal(addEntry) - - // Check to make sure we see the assignment. - go func() { - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - for range ticker.C { - js.mu.RLock() - ca, meta := js.consumerAssignment(ca.Client.serviceAccount(), sa.Config.Name, name), cc.meta - js.mu.RUnlock() - if ca == nil { - s.Warnf("Consumer assignment has not been assigned, retrying") - if meta != nil { - meta.ForwardProposal(addEntry) - } else { - return - } - } else { - return - } - } - }() - } - } - case <-s.quitCh: - return - } - }) - } else { - js.processStreamLeaderChange(mset, true) - } - } -} - -// processStreamRemoval is called when followers have replicated an assignment. -func (js *jetStream) processStreamRemoval(sa *streamAssignment) { - js.mu.Lock() - s, cc := js.srv, js.cluster - if s == nil || cc == nil || cc.meta == nil { - // TODO(dlc) - debug at least - js.mu.Unlock() - return - } - stream := sa.Config.Name - isMember := sa.Group.isMember(cc.meta.ID()) - wasLeader := cc.isStreamLeader(sa.Client.serviceAccount(), stream) - - // Check if we already have this assigned. - accStreams := cc.streams[sa.Client.serviceAccount()] - needDelete := accStreams != nil && accStreams[stream] != nil - if needDelete { - delete(accStreams, stream) - if len(accStreams) == 0 { - delete(cc.streams, sa.Client.serviceAccount()) - } - } - js.mu.Unlock() - - if needDelete { - js.processClusterDeleteStream(sa, isMember, wasLeader) - } -} - -func (js *jetStream) processClusterDeleteStream(sa *streamAssignment, isMember, wasLeader bool) { - if sa == nil { - return - } - js.mu.RLock() - s := js.srv - node := sa.Group.node - hadLeader := node == nil || !node.Leaderless() - offline := s.allPeersOffline(sa.Group) - var isMetaLeader bool - if cc := js.cluster; cc != nil { - isMetaLeader = cc.isLeader() - } - recovering := sa.recovering - js.mu.RUnlock() - - stopped := false - var resp = JSApiStreamDeleteResponse{ApiResponse: ApiResponse{Type: JSApiStreamDeleteResponseType}} - var err error - var acc *Account - - // Go ahead and delete the stream if we have it and the account here. - if acc, _ = s.LookupAccount(sa.Client.serviceAccount()); acc != nil { - if mset, _ := acc.lookupStream(sa.Config.Name); mset != nil { - // shut down monitor by shutting down raft - if n := mset.raftNode(); n != nil { - n.Delete() - } - // wait for monitor to be shut down - mset.monitorWg.Wait() - err = mset.stop(true, wasLeader) - stopped = true - } else if isMember { - s.Warnf("JetStream failed to lookup running stream while removing stream '%s > %s' from this server", - sa.Client.serviceAccount(), sa.Config.Name) - } - } else if isMember { - s.Warnf("JetStream failed to lookup account while removing stream '%s > %s' from this server", sa.Client.serviceAccount(), sa.Config.Name) - } - - // Always delete the node if present. - if node != nil { - node.Delete() - } - - // This is a stop gap cleanup in case - // 1) the account does not exist (and mset couldn't be stopped) and/or - // 2) node was nil (and couldn't be deleted) - if !stopped || node == nil { - if sacc := s.SystemAccount(); sacc != nil { - saccName := sacc.GetName() - os.RemoveAll(filepath.Join(js.config.StoreDir, saccName, defaultStoreDirName, sa.Group.Name)) - // cleanup dependent consumer groups - if !stopped { - for _, ca := range sa.consumers { - // Make sure we cleanup any possible running nodes for the consumers. - if isMember && ca.Group != nil && ca.Group.node != nil { - ca.Group.node.Delete() - } - os.RemoveAll(filepath.Join(js.config.StoreDir, saccName, defaultStoreDirName, ca.Group.Name)) - } - } - } - } - accDir := filepath.Join(js.config.StoreDir, sa.Client.serviceAccount()) - streamDir := filepath.Join(accDir, streamsDir) - os.RemoveAll(filepath.Join(streamDir, sa.Config.Name)) - - // no op if not empty - os.Remove(streamDir) - os.Remove(accDir) - - // Normally we want only the leader to respond here, but if we had no leader then all members will respond to make - // sure we get feedback to the user. - if !isMember || (hadLeader && !wasLeader) { - // If all the peers are offline and we are the meta leader we will also respond, so suppress returning here. - if !(offline && isMetaLeader) { - return - } - } - - // Do not respond if the account does not exist any longer - if acc == nil || recovering { - return - } - - if err != nil { - resp.Error = NewJSStreamGeneralError(err, Unless(err)) - s.sendAPIErrResponse(sa.Client, acc, sa.Subject, sa.Reply, _EMPTY_, s.jsonResponse(resp)) - } else { - resp.Success = true - s.sendAPIResponse(sa.Client, acc, sa.Subject, sa.Reply, _EMPTY_, s.jsonResponse(resp)) - } -} - -// processConsumerAssignment is called when followers have replicated an assignment for a consumer. -func (js *jetStream) processConsumerAssignment(ca *consumerAssignment) { - js.mu.RLock() - s, cc := js.srv, js.cluster - accName, stream, consumerName := ca.Client.serviceAccount(), ca.Stream, ca.Name - noMeta := cc == nil || cc.meta == nil - shuttingDown := js.shuttingDown - var ourID string - if !noMeta { - ourID = cc.meta.ID() - } - var isMember bool - if ca.Group != nil && ourID != _EMPTY_ { - isMember = ca.Group.isMember(ourID) - } - js.mu.RUnlock() - - if s == nil || noMeta || shuttingDown { - return - } - - js.mu.Lock() - sa := js.streamAssignment(accName, stream) - if sa == nil { - js.mu.Unlock() - s.Debugf("Consumer create failed, could not locate stream '%s > %s'", accName, stream) - return - } - - // Might need this below. - numReplicas := sa.Config.Replicas - - // Track if this existed already. - var wasExisting bool - - // Check if we have an existing consumer assignment. - if sa.consumers == nil { - sa.consumers = make(map[string]*consumerAssignment) - } else if oca := sa.consumers[ca.Name]; oca != nil { - wasExisting = true - // Copy over private existing state from former CA. - if ca.Group != nil { - ca.Group.node = oca.Group.node - } - ca.responded = oca.responded - ca.err = oca.err - } - - // Capture the optional state. We will pass it along if we are a member to apply. - // This is only applicable when restoring a stream with consumers. - state := ca.State - ca.State = nil - - // Place into our internal map under the stream assignment. - // Ok to replace an existing one, we check on process call below. - sa.consumers[ca.Name] = ca - ca.pending = false - js.mu.Unlock() - - acc, err := s.LookupAccount(accName) - if err != nil { - ll := fmt.Sprintf("Account [%s] lookup for consumer create failed: %v", accName, err) - if isMember { - if !js.isMetaRecovering() { - // If we can not lookup the account and we are a member, send this result back to the metacontroller leader. - result := &consumerAssignmentResult{ - Account: accName, - Stream: stream, - Consumer: consumerName, - Response: &JSApiConsumerCreateResponse{ApiResponse: ApiResponse{Type: JSApiConsumerCreateResponseType}}, - } - result.Response.Error = NewJSNoAccountError() - s.sendInternalMsgLocked(consumerAssignmentSubj, _EMPTY_, nil, result) - } - s.Warnf(ll) - } else { - s.Debugf(ll) - } - return - } - - // Check if this is for us.. - if isMember { - js.processClusterCreateConsumer(ca, state, wasExisting) - } else { - // We need to be removed here, we are no longer assigned. - // Grab consumer if we have it. - var o *consumer - if mset, _ := acc.lookupStream(sa.Config.Name); mset != nil { - o = mset.lookupConsumer(ca.Name) - } - - // Check if we have a raft node running, meaning we are no longer part of the group but were. - js.mu.Lock() - if node := ca.Group.node; node != nil { - // We have one here even though we are not a member. This can happen on re-assignment. - s.Debugf("JetStream removing consumer '%s > %s > %s' from this server", sa.Client.serviceAccount(), sa.Config.Name, ca.Name) - if node.Leader() { - s.Debugf("JetStream consumer '%s > %s > %s' is being removed and was the leader, will perform stepdown", - sa.Client.serviceAccount(), sa.Config.Name, ca.Name) - - peers, cn := node.Peers(), s.cachedClusterName() - migrating := numReplicas != len(peers) - - // Select a new peer to transfer to. If we are a migrating make sure its from the new cluster. - var npeer string - for _, r := range peers { - if !r.Current { - continue - } - if !migrating { - npeer = r.ID - break - } else if sir, ok := s.nodeToInfo.Load(r.ID); ok && sir != nil { - si := sir.(nodeInfo) - if si.cluster != cn { - npeer = r.ID - break - } - } - } - // Clear the raftnode from our consumer so that a subsequent o.delete will not also issue a stepdown. - if o != nil { - o.clearRaftNode() - } - // Manually handle the stepdown and deletion of the node. - node.UpdateKnownPeers(ca.Group.Peers) - node.StepDown(npeer) - node.Delete() - } else { - node.UpdateKnownPeers(ca.Group.Peers) - } - } - // Always clear the old node. - ca.Group.node = nil - ca.err = nil - js.mu.Unlock() - - if o != nil { - o.deleteWithoutAdvisory() - } - } -} - -func (js *jetStream) processConsumerRemoval(ca *consumerAssignment) { - js.mu.Lock() - s, cc := js.srv, js.cluster - if s == nil || cc == nil || cc.meta == nil { - // TODO(dlc) - debug at least - js.mu.Unlock() - return - } - wasLeader := cc.isConsumerLeader(ca.Client.serviceAccount(), ca.Stream, ca.Name) - - // Delete from our state. - var needDelete bool - if accStreams := cc.streams[ca.Client.serviceAccount()]; accStreams != nil { - if sa := accStreams[ca.Stream]; sa != nil && sa.consumers != nil && sa.consumers[ca.Name] != nil { - oca := sa.consumers[ca.Name] - // Make sure this removal is for what we have, otherwise ignore. - if ca.Group != nil && oca.Group != nil && ca.Group.Name == oca.Group.Name { - needDelete = true - oca.deleted = true - delete(sa.consumers, ca.Name) - } - } - } - js.mu.Unlock() - - if needDelete { - js.processClusterDeleteConsumer(ca, wasLeader) - } -} - -type consumerAssignmentResult struct { - Account string `json:"account"` - Stream string `json:"stream"` - Consumer string `json:"consumer"` - Response *JSApiConsumerCreateResponse `json:"response,omitempty"` -} - -// processClusterCreateConsumer is when we are a member of the group and need to create the consumer. -func (js *jetStream) processClusterCreateConsumer(ca *consumerAssignment, state *ConsumerState, wasExisting bool) { - if ca == nil { - return - } - js.mu.RLock() - s := js.srv - rg := ca.Group - alreadyRunning := rg != nil && rg.node != nil - accName, stream, consumer := ca.Client.serviceAccount(), ca.Stream, ca.Name - js.mu.RUnlock() - - acc, err := s.LookupAccount(accName) - if err != nil { - s.Warnf("JetStream cluster failed to lookup axccount %q: %v", accName, err) - return - } - - // Go ahead and create or update the consumer. - mset, err := acc.lookupStream(stream) - if err != nil { - if !js.isMetaRecovering() { - js.mu.Lock() - s.Warnf("Consumer create failed, could not locate stream '%s > %s > %s'", ca.Client.serviceAccount(), ca.Stream, ca.Name) - ca.err = NewJSStreamNotFoundError() - result := &consumerAssignmentResult{ - Account: ca.Client.serviceAccount(), - Stream: ca.Stream, - Consumer: ca.Name, - Response: &JSApiConsumerCreateResponse{ApiResponse: ApiResponse{Type: JSApiConsumerCreateResponseType}}, - } - result.Response.Error = NewJSStreamNotFoundError() - s.sendInternalMsgLocked(consumerAssignmentSubj, _EMPTY_, nil, result) - js.mu.Unlock() - } - return - } - - // Check if we already have this consumer running. - o := mset.lookupConsumer(consumer) - - if !alreadyRunning { - // Process the raft group and make sure its running if needed. - storage := mset.config().Storage - if ca.Config.MemoryStorage { - storage = MemoryStorage - } - // No-op if R1. - js.createRaftGroup(accName, rg, storage, pprofLabels{ - "type": "consumer", - "account": mset.accName(), - "stream": ca.Stream, - "consumer": ca.Name, - }) - } else { - // If we are clustered update the known peers. - js.mu.RLock() - node := rg.node - js.mu.RUnlock() - if node != nil { - node.UpdateKnownPeers(ca.Group.Peers) - } - } - - // Check if we already have this consumer running. - var didCreate, isConfigUpdate, needsLocalResponse bool - if o == nil { - // Add in the consumer if needed. - if o, err = mset.addConsumerWithAssignment(ca.Config, ca.Name, ca, js.isMetaRecovering(), ActionCreateOrUpdate, false); err == nil { - didCreate = true - } - } else { - // This consumer exists. - // Only update if config is really different. - cfg := o.config() - if isConfigUpdate = !reflect.DeepEqual(&cfg, ca.Config); isConfigUpdate { - // Call into update, ignore consumer exists error here since this means an old deliver subject is bound - // which can happen on restart etc. - // JS lock needed as this can mutate the consumer assignments and race with updateInactivityThreshold. - js.mu.Lock() - err := o.updateConfig(ca.Config) - js.mu.Unlock() - if err != nil && err != NewJSConsumerNameExistError() { - // This is essentially an update that has failed. Respond back to metaleader if we are not recovering. - js.mu.RLock() - if !js.metaRecovering { - result := &consumerAssignmentResult{ - Account: accName, - Stream: stream, - Consumer: consumer, - Response: &JSApiConsumerCreateResponse{ApiResponse: ApiResponse{Type: JSApiConsumerCreateResponseType}}, - } - result.Response.Error = NewJSConsumerNameExistError() - s.sendInternalMsgLocked(consumerAssignmentSubj, _EMPTY_, nil, result) - } - s.Warnf("Consumer create failed during update for '%s > %s > %s': %v", ca.Client.serviceAccount(), ca.Stream, ca.Name, err) - js.mu.RUnlock() - return - } - } - - var sendState bool - js.mu.RLock() - n := rg.node - // Check if we already had a consumer assignment and its still pending. - cca, oca := ca, o.consumerAssignment() - if oca != nil { - if !oca.responded { - // We can't override info for replying here otherwise leader once elected can not respond. - // So copy over original client and the reply from the old ca. - cac := *ca - cac.Client = oca.Client - cac.Reply = oca.Reply - cca = &cac - needsLocalResponse = true - } - // If we look like we are scaling up, let's send our current state to the group. - sendState = len(ca.Group.Peers) > len(oca.Group.Peers) && o.IsLeader() && n != nil - // Signal that this is an update - if ca.Reply != _EMPTY_ { - isConfigUpdate = true - } - } - js.mu.RUnlock() - - if sendState { - if snap, err := o.store.EncodedState(); err == nil { - n.SendSnapshot(snap) - } - } - - // Set CA for our consumer. - o.setConsumerAssignment(cca) - s.Debugf("JetStream cluster, consumer '%s > %s > %s' was already running", ca.Client.serviceAccount(), ca.Stream, ca.Name) - } - - // If we have an initial state set apply that now. - if state != nil && o != nil { - o.mu.Lock() - err = o.setStoreState(state) - o.mu.Unlock() - } - - if err != nil { - // If we're shutting down we could get a variety of errors. - // Normally we can continue and delete state, but need to be careful when shutting down. - if js.isShuttingDown() { - s.Debugf("Could not create consumer, JetStream shutting down") - return - } - - if IsNatsErr(err, JSConsumerStoreFailedErrF) { - s.Warnf("Consumer create failed for '%s > %s > %s': %v", ca.Client.serviceAccount(), ca.Stream, ca.Name, err) - err = errConsumerStoreFailed - } - - js.mu.Lock() - - ca.err = err - hasResponded := ca.responded - - // If out of space do nothing for now. - if isOutOfSpaceErr(err) { - hasResponded = true - } - - if rg.node != nil { - rg.node.Delete() - // Clear the node here. - rg.node = nil - } - - // If we did seem to create a consumer make sure to stop it. - if o != nil { - o.stop() - } - - var result *consumerAssignmentResult - if !hasResponded && !js.metaRecovering { - result = &consumerAssignmentResult{ - Account: ca.Client.serviceAccount(), - Stream: ca.Stream, - Consumer: ca.Name, - Response: &JSApiConsumerCreateResponse{ApiResponse: ApiResponse{Type: JSApiConsumerCreateResponseType}}, - } - result.Response.Error = NewJSConsumerCreateError(err, Unless(err)) - } else if err == errNoInterest { - // This is a stranded ephemeral, let's clean this one up. - subject := fmt.Sprintf(JSApiConsumerDeleteT, ca.Stream, ca.Name) - mset.outq.send(newJSPubMsg(subject, _EMPTY_, _EMPTY_, nil, nil, nil, 0)) - } - js.mu.Unlock() - - if result != nil { - // Send response to the metadata leader. They will forward to the user as needed. - b, _ := json.Marshal(result) // Avoids auto-processing and doing fancy json with newlines. - s.sendInternalMsgLocked(consumerAssignmentSubj, _EMPTY_, nil, b) - } - } else { - js.mu.RLock() - node := rg.node - js.mu.RUnlock() - - if didCreate { - o.setCreatedTime(ca.Created) - } else { - // Check for scale down to 1.. - if node != nil && len(rg.Peers) == 1 { - o.clearNode() - o.setLeader(true) - // Need to clear from rg too. - js.mu.Lock() - rg.node = nil - client, subject, reply := ca.Client, ca.Subject, ca.Reply - js.mu.Unlock() - var resp = JSApiConsumerCreateResponse{ApiResponse: ApiResponse{Type: JSApiConsumerCreateResponseType}} - resp.ConsumerInfo = setDynamicConsumerInfoMetadata(o.info()) - s.sendAPIResponse(client, acc, subject, reply, _EMPTY_, s.jsonResponse(&resp)) - return - } - } - - if node == nil { - // Single replica consumer, process manually here. - js.mu.Lock() - // Force response in case we think this is an update. - if !js.metaRecovering && isConfigUpdate { - ca.responded = false - } - js.mu.Unlock() - js.processConsumerLeaderChange(o, true) - } else { - // Clustered consumer. - // Start our monitoring routine if needed. - if !alreadyRunning && o.shouldStartMonitor() { - s.startGoRoutine( - func() { js.monitorConsumer(o, ca) }, - pprofLabels{ - "type": "consumer", - "account": mset.accName(), - "stream": mset.name(), - "consumer": ca.Name, - }, - ) - } - // For existing consumer, only send response if not recovering. - if wasExisting && !js.isMetaRecovering() { - if o.IsLeader() || (!didCreate && needsLocalResponse) { - // Process if existing as an update. Double check that this is not recovered. - js.mu.RLock() - client, subject, reply, recovering := ca.Client, ca.Subject, ca.Reply, ca.recovering - js.mu.RUnlock() - if !recovering { - var resp = JSApiConsumerCreateResponse{ApiResponse: ApiResponse{Type: JSApiConsumerCreateResponseType}} - resp.ConsumerInfo = setDynamicConsumerInfoMetadata(o.info()) - s.sendAPIResponse(client, acc, subject, reply, _EMPTY_, s.jsonResponse(&resp)) - } - } - } - } - } -} - -func (js *jetStream) processClusterDeleteConsumer(ca *consumerAssignment, wasLeader bool) { - if ca == nil { - return - } - js.mu.RLock() - s := js.srv - node := ca.Group.node - offline := s.allPeersOffline(ca.Group) - var isMetaLeader bool - if cc := js.cluster; cc != nil { - isMetaLeader = cc.isLeader() - } - recovering := ca.recovering - js.mu.RUnlock() - - var resp = JSApiConsumerDeleteResponse{ApiResponse: ApiResponse{Type: JSApiConsumerDeleteResponseType}} - var err error - var acc *Account - - // Go ahead and delete the consumer if we have it and the account. - if acc, _ = s.LookupAccount(ca.Client.serviceAccount()); acc != nil { - if mset, _ := acc.lookupStream(ca.Stream); mset != nil { - if o := mset.lookupConsumer(ca.Name); o != nil { - err = o.stopWithFlags(true, false, true, wasLeader) - } - } - } else if ca.Group != nil { - // We have a missing account, see if we can cleanup. - if sacc := s.SystemAccount(); sacc != nil { - os.RemoveAll(filepath.Join(js.config.StoreDir, sacc.GetName(), defaultStoreDirName, ca.Group.Name)) - } - } - - // Always delete the node if present. - if node != nil { - node.Delete() - } - - if !wasLeader || ca.Reply == _EMPTY_ { - if !(offline && isMetaLeader) { - return - } - } - - // Do not respond if the account does not exist any longer or this is during recovery. - if acc == nil || recovering { - return - } - - if err != nil { - resp.Error = NewJSStreamNotFoundError(Unless(err)) - s.sendAPIErrResponse(ca.Client, acc, ca.Subject, ca.Reply, _EMPTY_, s.jsonResponse(resp)) - } else { - resp.Success = true - s.sendAPIResponse(ca.Client, acc, ca.Subject, ca.Reply, _EMPTY_, s.jsonResponse(resp)) - } -} - -// Returns the consumer assignment, or nil if not present. -// Lock should be held. -func (js *jetStream) consumerAssignment(account, stream, consumer string) *consumerAssignment { - if sa := js.streamAssignment(account, stream); sa != nil { - return sa.consumers[consumer] - } - return nil -} - -// consumerAssigned informs us if this server has this consumer assigned. -func (jsa *jsAccount) consumerAssigned(stream, consumer string) bool { - jsa.mu.RLock() - js, acc := jsa.js, jsa.account - jsa.mu.RUnlock() - - if js == nil { - return false - } - js.mu.RLock() - defer js.mu.RUnlock() - return js.cluster.isConsumerAssigned(acc, stream, consumer) -} - -// Read lock should be held. -func (cc *jetStreamCluster) isConsumerAssigned(a *Account, stream, consumer string) bool { - // Non-clustered mode always return true. - if cc == nil { - return true - } - if cc.meta == nil { - return false - } - var sa *streamAssignment - accStreams := cc.streams[a.Name] - if accStreams != nil { - sa = accStreams[stream] - } - if sa == nil { - // TODO(dlc) - This should not happen. - return false - } - ca := sa.consumers[consumer] - if ca == nil { - return false - } - rg := ca.Group - // Check if we are the leader of this raftGroup assigned to the stream. - ourID := cc.meta.ID() - for _, peer := range rg.Peers { - if peer == ourID { - return true - } - } - return false -} - -// Returns our stream and underlying raft node. -func (o *consumer) streamAndNode() (*stream, RaftNode) { - if o == nil { - return nil, nil - } - o.mu.RLock() - defer o.mu.RUnlock() - return o.mset, o.node -} - -// Return the replica count for this consumer. If the consumer has been -// stopped, this will return an error. -func (o *consumer) replica() (int, error) { - o.mu.RLock() - oCfg := o.cfg - mset := o.mset - o.mu.RUnlock() - if mset == nil { - return 0, errBadConsumer - } - sCfg := mset.config() - return oCfg.replicas(&sCfg), nil -} - -func (o *consumer) raftGroup() *raftGroup { - if o == nil { - return nil - } - o.mu.RLock() - defer o.mu.RUnlock() - if o.ca == nil { - return nil - } - return o.ca.Group -} - -func (o *consumer) clearRaftNode() { - if o == nil { - return - } - o.mu.Lock() - defer o.mu.Unlock() - o.node = nil -} - -func (o *consumer) raftNode() RaftNode { - if o == nil { - return nil - } - o.mu.RLock() - defer o.mu.RUnlock() - return o.node -} - -func (js *jetStream) monitorConsumer(o *consumer, ca *consumerAssignment) { - s, n, cc := js.server(), o.raftNode(), js.cluster - defer s.grWG.Done() - - defer o.clearMonitorRunning() - - if n == nil { - s.Warnf("No RAFT group for '%s > %s > %s'", o.acc.Name, ca.Stream, ca.Name) - return - } - - // Make sure to stop the raft group on exit to prevent accidental memory bloat. - // This should be below the checkInMonitor call though to avoid stopping it out - // from underneath the one that is running since it will be the same raft node. - defer n.Stop() - - qch, lch, aq, uch, ourPeerId := n.QuitC(), n.LeadChangeC(), n.ApplyQ(), o.updateC(), cc.meta.ID() - - s.Debugf("Starting consumer monitor for '%s > %s > %s' [%s]", o.acc.Name, ca.Stream, ca.Name, n.Group()) - defer s.Debugf("Exiting consumer monitor for '%s > %s > %s' [%s]", o.acc.Name, ca.Stream, ca.Name, n.Group()) - - const ( - compactInterval = 2 * time.Minute - compactSizeMin = 64 * 1024 // What is stored here is always small for consumers. - compactNumMin = 1024 - minSnapDelta = 10 * time.Second - ) - - // Spread these out for large numbers on server restart. - rci := time.Duration(rand.Int63n(int64(time.Minute))) - t := time.NewTicker(compactInterval + rci) - defer t.Stop() - - // Highwayhash key for generating hashes. - key := make([]byte, 32) - crand.Read(key) - - // Hash of the last snapshot (fixed size in memory). - var lastSnap []byte - var lastSnapTime time.Time - - // Don't allow the upper layer to install snapshots until we have - // fully recovered from disk. - recovering := true - - doSnapshot := func(force bool) { - // Bail if trying too fast and not in a forced situation. - if recovering || (!force && time.Since(lastSnapTime) < minSnapDelta) { - return - } - - // Check several things to see if we need a snapshot. - ne, nb := n.Size() - if !n.NeedSnapshot() { - // Check if we should compact etc. based on size of log. - if !force && ne < compactNumMin && nb < compactSizeMin { - return - } - } - - if snap, err := o.store.EncodedState(); err == nil { - hash := highwayhash.Sum(snap, key) - // If the state hasn't changed but the log has gone way over - // the compaction size then we will want to compact anyway. - // This can happen for example when a pull consumer fetches a - // lot on an idle stream, log entries get distributed but the - // state never changes, therefore the log never gets compacted. - if !bytes.Equal(hash[:], lastSnap) || ne >= compactNumMin || nb >= compactSizeMin { - if err := n.InstallSnapshot(snap); err == nil { - lastSnap, lastSnapTime = hash[:], time.Now() - } else if err != errNoSnapAvailable && err != errNodeClosed && err != errCatchupsRunning { - s.RateLimitWarnf("Failed to install snapshot for '%s > %s > %s' [%s]: %v", o.acc.Name, ca.Stream, ca.Name, n.Group(), err) - } - } - } - } - - // For migration tracking. - var mmt *time.Ticker - var mmtc <-chan time.Time - - startMigrationMonitoring := func() { - if mmt == nil { - mmt = time.NewTicker(500 * time.Millisecond) - mmtc = mmt.C - } - } - - stopMigrationMonitoring := func() { - if mmt != nil { - mmt.Stop() - mmt, mmtc = nil, nil - } - } - defer stopMigrationMonitoring() - - // Track if we are leader. - var isLeader bool - - for { - select { - case <-s.quitCh: - // Server shutting down, but we might receive this before qch, so try to snapshot. - doSnapshot(false) - return - case <-qch: - // Clean signal from shutdown routine so do best effort attempt to snapshot. - doSnapshot(false) - return - case <-aq.ch: - ces := aq.pop() - for _, ce := range ces { - // No special processing needed for when we are caught up on restart. - if ce == nil { - recovering = false - if n.NeedSnapshot() { - doSnapshot(true) - } - } else if err := js.applyConsumerEntries(o, ce, isLeader); err == nil { - var ne, nb uint64 - // We can't guarantee writes are flushed while we're shutting down. Just rely on replay during recovery. - if !js.isShuttingDown() { - ne, nb = n.Applied(ce.Index) - } - ce.ReturnToPool() - // If we have at least min entries to compact, go ahead and snapshot/compact. - if nb > 0 && ne >= compactNumMin || nb > compactSizeMin { - doSnapshot(false) - } - } else if err != errConsumerClosed { - s.Warnf("Error applying consumer entries to '%s > %s'", ca.Client.serviceAccount(), ca.Name) - } - } - aq.recycle(&ces) - - case isLeader = <-lch: - if recovering && !isLeader { - js.setConsumerAssignmentRecovering(ca) - } - - // Process the change. - if err := js.processConsumerLeaderChange(o, isLeader); err == nil { - // Check our state if we are under an interest based stream. - if mset := o.getStream(); mset != nil { - var ss StreamState - mset.store.FastState(&ss) - o.checkStateForInterestStream(&ss) - } - } - - // We may receive a leader change after the consumer assignment which would cancel us - // monitoring for this closely. So re-assess our state here as well. - // Or the old leader is no longer part of the set and transferred leadership - // for this leader to resume with removal - rg := o.raftGroup() - - // Check for migrations (peer count and replica count differ) here. - // We set the state on the stream assignment update below. - replicas, err := o.replica() - if err != nil { - continue - } - if isLeader && len(rg.Peers) != replicas { - startMigrationMonitoring() - } else { - stopMigrationMonitoring() - } - case <-uch: - // keep consumer assignment current - ca = o.consumerAssignment() - // We get this when we have a new consumer assignment caused by an update. - // We want to know if we are migrating. - rg := o.raftGroup() - // If we are migrating, monitor for the new peers to be caught up. - replicas, err := o.replica() - if err != nil { - continue - } - if isLeader && len(rg.Peers) != replicas { - startMigrationMonitoring() - } else { - stopMigrationMonitoring() - } - case <-mmtc: - if !isLeader { - // We are no longer leader, so not our job. - stopMigrationMonitoring() - continue - } - rg := o.raftGroup() - ci := js.clusterInfo(rg) - replicas, err := o.replica() - if err != nil { - continue - } - if len(rg.Peers) <= replicas { - // Migration no longer happening, so not our job anymore - stopMigrationMonitoring() - continue - } - newPeers, oldPeers, newPeerSet, _ := genPeerInfo(rg.Peers, len(rg.Peers)-replicas) - - // If we are part of the new peerset and we have been passed the baton. - // We will handle scale down. - if newPeerSet[ourPeerId] { - for _, p := range oldPeers { - n.ProposeRemovePeer(p) - } - cca := ca.copyGroup() - cca.Group.Peers = newPeers - cca.Group.Cluster = s.cachedClusterName() - cc.meta.ForwardProposal(encodeAddConsumerAssignment(cca)) - s.Noticef("Scaling down '%s > %s > %s' to %+v", ca.Client.serviceAccount(), ca.Stream, ca.Name, s.peerSetToNames(newPeers)) - - } else { - var newLeaderPeer, newLeader, newCluster string - neededCurrent, current := replicas/2+1, 0 - for _, r := range ci.Replicas { - if r.Current && newPeerSet[r.Peer] { - current++ - if newCluster == _EMPTY_ { - newLeaderPeer, newLeader, newCluster = r.Peer, r.Name, r.cluster - } - } - } - - // Check if we have a quorom - if current >= neededCurrent { - s.Noticef("Transfer of consumer leader for '%s > %s > %s' to '%s'", ca.Client.serviceAccount(), ca.Stream, ca.Name, newLeader) - n.StepDown(newLeaderPeer) - } - } - - case <-t.C: - doSnapshot(false) - } - } -} - -func (js *jetStream) applyConsumerEntries(o *consumer, ce *CommittedEntry, isLeader bool) error { - for _, e := range ce.Entries { - if e.Type == EntrySnapshot { - if !isLeader { - // No-op needed? - state, err := decodeConsumerState(e.Data) - if err != nil { - if mset, node := o.streamAndNode(); mset != nil && node != nil { - s := js.srv - s.Errorf("JetStream cluster could not decode consumer snapshot for '%s > %s > %s' [%s]", - mset.account(), mset.name(), o, node.Group()) - } - panic(err.Error()) - } - - if err = o.store.Update(state); err != nil { - o.mu.RLock() - s, acc, mset, name := o.srv, o.acc, o.mset, o.name - o.mu.RUnlock() - if s != nil && mset != nil { - s.Warnf("Consumer '%s > %s > %s' error on store update from snapshot entry: %v", acc, mset.name(), name, err) - } - } - // Check our interest state if applicable. - if mset := o.getStream(); mset != nil { - var ss StreamState - mset.store.FastState(&ss) - // We used to register preacks here if our ack floor was higher than the last sequence. - // Now when streams catch up they properly call checkInterestState() and periodically run this as well. - // If our states drift this could have allocated lots of pre-acks. - o.checkStateForInterestStream(&ss) - } - } - - } else if e.Type == EntryRemovePeer { - js.mu.RLock() - var ourID string - if js.cluster != nil && js.cluster.meta != nil { - ourID = js.cluster.meta.ID() - } - js.mu.RUnlock() - if peer := string(e.Data); peer == ourID { - shouldRemove := true - if mset := o.getStream(); mset != nil { - if sa := mset.streamAssignment(); sa != nil && sa.Group != nil { - js.mu.RLock() - shouldRemove = !sa.Group.isMember(ourID) - js.mu.RUnlock() - } - } - if shouldRemove { - o.stopWithFlags(true, false, false, false) - } - } - } else if e.Type == EntryAddPeer { - // Ignore for now. - } else { - buf := e.Data - switch entryOp(buf[0]) { - case updateDeliveredOp: - dseq, sseq, dc, ts, err := decodeDeliveredUpdate(buf[1:]) - if err != nil { - if mset, node := o.streamAndNode(); mset != nil && node != nil { - s := js.srv - s.Errorf("JetStream cluster could not decode consumer delivered update for '%s > %s > %s' [%s]", - mset.account(), mset.name(), o, node.Group()) - } - panic(err.Error()) - } - // Make sure to update delivered under the lock. - o.mu.Lock() - err = o.store.UpdateDelivered(dseq, sseq, dc, ts) - o.ldt = time.Now() - o.mu.Unlock() - if err != nil { - panic(err.Error()) - } - case updateAcksOp: - dseq, sseq, err := decodeAckUpdate(buf[1:]) - if err != nil { - if mset, node := o.streamAndNode(); mset != nil && node != nil { - s := js.srv - s.Errorf("JetStream cluster could not decode consumer ack update for '%s > %s > %s' [%s]", - mset.account(), mset.name(), o, node.Group()) - } - panic(err.Error()) - } - if err := o.processReplicatedAck(dseq, sseq); err == errConsumerClosed { - return err - } - case updateSkipOp: - o.mu.Lock() - var le = binary.LittleEndian - sseq := le.Uint64(buf[1:]) - if !o.isLeader() && sseq > o.sseq { - o.sseq = sseq - } - if o.store != nil { - o.store.UpdateStarting(sseq - 1) - } - o.mu.Unlock() - case addPendingRequest: - o.mu.Lock() - if !o.isLeader() { - if o.prm == nil { - o.prm = make(map[string]struct{}) - } - o.prm[string(buf[1:])] = struct{}{} - } - o.mu.Unlock() - case removePendingRequest: - o.mu.Lock() - if !o.isLeader() { - if o.prm != nil { - delete(o.prm, string(buf[1:])) - } - } - o.mu.Unlock() - default: - panic(fmt.Sprintf("JetStream Cluster Unknown group entry op type: %v", entryOp(buf[0]))) - } - } - } - return nil -} - -var errConsumerClosed = errors.New("consumer closed") - -func (o *consumer) processReplicatedAck(dseq, sseq uint64) error { - o.mu.Lock() - // Update activity. - o.lat = time.Now() - - var sagap uint64 - if o.cfg.AckPolicy == AckAll { - // Always use the store state, as o.asflr is skipped ahead already. - // Capture before updating store. - state, err := o.store.BorrowState() - if err == nil { - sagap = sseq - state.AckFloor.Stream - } - } - - // Do actual ack update to store. - // Always do this to have it recorded. - o.store.UpdateAcks(dseq, sseq) - - mset := o.mset - if o.closed || mset == nil { - o.mu.Unlock() - return errConsumerClosed - } - if mset.closed.Load() { - o.mu.Unlock() - return errStreamClosed - } - - // Check if we have a reply that was requested. - if reply := o.replies[sseq]; reply != _EMPTY_ { - o.outq.sendMsg(reply, nil) - delete(o.replies, sseq) - } - - if o.retention == LimitsPolicy { - o.mu.Unlock() - return nil - } - o.mu.Unlock() - - if sagap > 1 { - // FIXME(dlc) - This is very inefficient, will need to fix. - for seq := sseq; seq > sseq-sagap; seq-- { - mset.ackMsg(o, seq) - } - } else { - mset.ackMsg(o, sseq) - } - return nil -} - -var errBadAckUpdate = errors.New("jetstream cluster bad replicated ack update") -var errBadDeliveredUpdate = errors.New("jetstream cluster bad replicated delivered update") - -func decodeAckUpdate(buf []byte) (dseq, sseq uint64, err error) { - var bi, n int - if dseq, n = binary.Uvarint(buf); n < 0 { - return 0, 0, errBadAckUpdate - } - bi += n - if sseq, n = binary.Uvarint(buf[bi:]); n < 0 { - return 0, 0, errBadAckUpdate - } - return dseq, sseq, nil -} - -func decodeDeliveredUpdate(buf []byte) (dseq, sseq, dc uint64, ts int64, err error) { - var bi, n int - if dseq, n = binary.Uvarint(buf); n < 0 { - return 0, 0, 0, 0, errBadDeliveredUpdate - } - bi += n - if sseq, n = binary.Uvarint(buf[bi:]); n < 0 { - return 0, 0, 0, 0, errBadDeliveredUpdate - } - bi += n - if dc, n = binary.Uvarint(buf[bi:]); n < 0 { - return 0, 0, 0, 0, errBadDeliveredUpdate - } - bi += n - if ts, n = binary.Varint(buf[bi:]); n < 0 { - return 0, 0, 0, 0, errBadDeliveredUpdate - } - return dseq, sseq, dc, ts, nil -} - -func (js *jetStream) processConsumerLeaderChange(o *consumer, isLeader bool) error { - stepDownIfLeader := func() error { - if node := o.raftNode(); node != nil && isLeader { - node.StepDown() - } - return errors.New("failed to update consumer leader status") - } - - if o == nil || o.isClosed() { - return stepDownIfLeader() - } - - ca := o.consumerAssignment() - if ca == nil { - return stepDownIfLeader() - } - js.mu.Lock() - s, account, err := js.srv, ca.Client.serviceAccount(), ca.err - client, subject, reply, streamName, consumerName := ca.Client, ca.Subject, ca.Reply, ca.Stream, ca.Name - hasResponded := ca.responded - ca.responded = true - js.mu.Unlock() - - acc, _ := s.LookupAccount(account) - if acc == nil { - return stepDownIfLeader() - } - - if isLeader { - s.Noticef("JetStream cluster new consumer leader for '%s > %s > %s'", ca.Client.serviceAccount(), streamName, consumerName) - s.sendConsumerLeaderElectAdvisory(o) - } else { - // We are stepping down. - // Make sure if we are doing so because we have lost quorum that we send the appropriate advisories. - if node := o.raftNode(); node != nil && !node.Quorum() && time.Since(node.Created()) > 5*time.Second { - s.sendConsumerLostQuorumAdvisory(o) - } - } - - // Tell consumer to switch leader status. - o.setLeader(isLeader) - - if !isLeader || hasResponded { - if isLeader { - o.clearInitialInfo() - } - return nil - } - - var resp = JSApiConsumerCreateResponse{ApiResponse: ApiResponse{Type: JSApiConsumerCreateResponseType}} - if err != nil { - resp.Error = NewJSConsumerCreateError(err, Unless(err)) - s.sendAPIErrResponse(client, acc, subject, reply, _EMPTY_, s.jsonResponse(&resp)) - } else { - resp.ConsumerInfo = setDynamicConsumerInfoMetadata(o.initialInfo()) - s.sendAPIResponse(client, acc, subject, reply, _EMPTY_, s.jsonResponse(&resp)) - o.sendCreateAdvisory() - } - - // Only send a pause advisory on consumer create if we're - // actually paused. The timer would have been kicked by now - // by the call to o.setLeader() above. - if isLeader && o.cfg.PauseUntil != nil && !o.cfg.PauseUntil.IsZero() && time.Now().Before(*o.cfg.PauseUntil) { - o.sendPauseAdvisoryLocked(&o.cfg) - } - - return nil -} - -// Determines if we should send lost quorum advisory. We throttle these after first one. -func (o *consumer) shouldSendLostQuorum() bool { - o.mu.Lock() - defer o.mu.Unlock() - if time.Since(o.lqsent) >= lostQuorumAdvInterval { - o.lqsent = time.Now() - return true - } - return false -} - -func (s *Server) sendConsumerLostQuorumAdvisory(o *consumer) { - if o == nil { - return - } - node, stream, consumer, acc := o.raftNode(), o.streamName(), o.String(), o.account() - if node == nil { - return - } - if !o.shouldSendLostQuorum() { - return - } - - s.Warnf("JetStream cluster consumer '%s > %s > %s' has NO quorum, stalled.", acc.GetName(), stream, consumer) - - subj := JSAdvisoryConsumerQuorumLostPre + "." + stream + "." + consumer - adv := &JSConsumerQuorumLostAdvisory{ - TypedEvent: TypedEvent{ - Type: JSConsumerQuorumLostAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Stream: stream, - Consumer: consumer, - Replicas: s.replicas(node), - Domain: s.getOpts().JetStreamDomain, - } - - // Send to the user's account if not the system account. - if acc != s.SystemAccount() { - s.publishAdvisory(acc, subj, adv) - } - // Now do system level one. Place account info in adv, and nil account means system. - adv.Account = acc.GetName() - s.publishAdvisory(nil, subj, adv) -} - -func (s *Server) sendConsumerLeaderElectAdvisory(o *consumer) { - if o == nil { - return - } - node, stream, consumer, acc := o.raftNode(), o.streamName(), o.String(), o.account() - if node == nil { - return - } - - subj := JSAdvisoryConsumerLeaderElectedPre + "." + stream + "." + consumer - adv := &JSConsumerLeaderElectedAdvisory{ - TypedEvent: TypedEvent{ - Type: JSConsumerLeaderElectedAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Stream: stream, - Consumer: consumer, - Leader: s.serverNameForNode(node.GroupLeader()), - Replicas: s.replicas(node), - Domain: s.getOpts().JetStreamDomain, - } - - // Send to the user's account if not the system account. - if acc != s.SystemAccount() { - s.publishAdvisory(acc, subj, adv) - } - // Now do system level one. Place account info in adv, and nil account means system. - adv.Account = acc.GetName() - s.publishAdvisory(nil, subj, adv) -} - -type streamAssignmentResult struct { - Account string `json:"account"` - Stream string `json:"stream"` - Response *JSApiStreamCreateResponse `json:"create_response,omitempty"` - Restore *JSApiStreamRestoreResponse `json:"restore_response,omitempty"` - Update bool `json:"is_update,omitempty"` -} - -// Determine if this is an insufficient resources' error type. -func isInsufficientResourcesErr(resp *JSApiStreamCreateResponse) bool { - return resp != nil && resp.Error != nil && IsNatsErr(resp.Error, JSInsufficientResourcesErr, JSMemoryResourcesExceededErr, JSStorageResourcesExceededErr) -} - -// Process error results of stream and consumer assignments. -// Success will be handled by stream leader. -func (js *jetStream) processStreamAssignmentResults(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) { - var result streamAssignmentResult - if err := json.Unmarshal(msg, &result); err != nil { - // TODO(dlc) - log - return - } - acc, _ := js.srv.LookupAccount(result.Account) - if acc == nil { - // TODO(dlc) - log - return - } - - js.mu.Lock() - defer js.mu.Unlock() - - s, cc := js.srv, js.cluster - if cc == nil || cc.meta == nil { - return - } - - // This should have been done already in processStreamAssignment, but in - // case we have a code path that gets here with no processStreamAssignment, - // then we will do the proper thing. Otherwise will be a no-op. - cc.removeInflightProposal(result.Account, result.Stream) - - if sa := js.streamAssignment(result.Account, result.Stream); sa != nil && !sa.reassigning { - canDelete := !result.Update && time.Since(sa.Created) < 5*time.Second - - // See if we should retry in case this cluster is full but there are others. - if cfg, ci := sa.Config, sa.Client; cfg != nil && ci != nil && isInsufficientResourcesErr(result.Response) && canDelete { - // If cluster is defined we can not retry. - if cfg.Placement == nil || cfg.Placement.Cluster == _EMPTY_ { - // If we have additional clusters to try we can retry. - // We have already verified that ci != nil. - if len(ci.Alternates) > 0 { - if rg, err := js.createGroupForStream(ci, cfg); err != nil { - s.Warnf("Retrying cluster placement for stream '%s > %s' failed due to placement error: %+v", result.Account, result.Stream, err) - } else { - if org := sa.Group; org != nil && len(org.Peers) > 0 { - s.Warnf("Retrying cluster placement for stream '%s > %s' due to insufficient resources in cluster %q", - result.Account, result.Stream, s.clusterNameForNode(org.Peers[0])) - } else { - s.Warnf("Retrying cluster placement for stream '%s > %s' due to insufficient resources", result.Account, result.Stream) - } - // Pick a new preferred leader. - rg.setPreferred() - // Get rid of previous attempt. - cc.meta.Propose(encodeDeleteStreamAssignment(sa)) - // Propose new. - sa.Group, sa.err = rg, nil - cc.meta.Propose(encodeAddStreamAssignment(sa)) - // When the new stream assignment is processed, sa.reassigning will be - // automatically set back to false. Until then, don't process any more - // assignment results. - sa.reassigning = true - return - } - } - } - } - - // Respond to the user here. - var resp string - if result.Response != nil { - resp = s.jsonResponse(result.Response) - } else if result.Restore != nil { - resp = s.jsonResponse(result.Restore) - } - if !sa.responded || result.Update { - sa.responded = true - js.srv.sendAPIErrResponse(sa.Client, acc, sa.Subject, sa.Reply, _EMPTY_, resp) - } - // Remove this assignment if possible. - if canDelete { - sa.err = NewJSClusterNotAssignedError() - cc.meta.Propose(encodeDeleteStreamAssignment(sa)) - } - } -} - -func (js *jetStream) processConsumerAssignmentResults(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) { - var result consumerAssignmentResult - if err := json.Unmarshal(msg, &result); err != nil { - // TODO(dlc) - log - return - } - acc, _ := js.srv.LookupAccount(result.Account) - if acc == nil { - // TODO(dlc) - log - return - } - - js.mu.Lock() - defer js.mu.Unlock() - - s, cc := js.srv, js.cluster - if cc == nil || cc.meta == nil { - return - } - - if sa := js.streamAssignment(result.Account, result.Stream); sa != nil && sa.consumers != nil { - if ca := sa.consumers[result.Consumer]; ca != nil && !ca.responded { - js.srv.sendAPIErrResponse(ca.Client, acc, ca.Subject, ca.Reply, _EMPTY_, s.jsonResponse(result.Response)) - ca.responded = true - - // Check if this failed. - // TODO(dlc) - Could have mixed results, should track per peer. - // Make sure this is recent response, do not delete existing consumers. - if result.Response.Error != nil && result.Response.Error != NewJSConsumerNameExistError() && time.Since(ca.Created) < 2*time.Second { - // So while we are deleting we will not respond to list/names requests. - ca.err = NewJSClusterNotAssignedError() - cc.meta.Propose(encodeDeleteConsumerAssignment(ca)) - s.Warnf("Proposing to delete consumer `%s > %s > %s' due to assignment response error: %v", - result.Account, result.Stream, result.Consumer, result.Response.Error) - } - } - } -} - -const ( - streamAssignmentSubj = "$SYS.JSC.STREAM.ASSIGNMENT.RESULT" - consumerAssignmentSubj = "$SYS.JSC.CONSUMER.ASSIGNMENT.RESULT" -) - -// Lock should be held. -func (js *jetStream) startUpdatesSub() { - cc, s, c := js.cluster, js.srv, js.cluster.c - if cc.streamResults == nil { - cc.streamResults, _ = s.systemSubscribe(streamAssignmentSubj, _EMPTY_, false, c, js.processStreamAssignmentResults) - } - if cc.consumerResults == nil { - cc.consumerResults, _ = s.systemSubscribe(consumerAssignmentSubj, _EMPTY_, false, c, js.processConsumerAssignmentResults) - } - if cc.stepdown == nil { - cc.stepdown, _ = s.systemSubscribe(JSApiLeaderStepDown, _EMPTY_, false, c, s.jsLeaderStepDownRequest) - } - if cc.peerRemove == nil { - cc.peerRemove, _ = s.systemSubscribe(JSApiRemoveServer, _EMPTY_, false, c, s.jsLeaderServerRemoveRequest) - } - if cc.peerStreamMove == nil { - cc.peerStreamMove, _ = s.systemSubscribe(JSApiServerStreamMove, _EMPTY_, false, c, s.jsLeaderServerStreamMoveRequest) - } - if cc.peerStreamCancelMove == nil { - cc.peerStreamCancelMove, _ = s.systemSubscribe(JSApiServerStreamCancelMove, _EMPTY_, false, c, s.jsLeaderServerStreamCancelMoveRequest) - } - if js.accountPurge == nil { - js.accountPurge, _ = s.systemSubscribe(JSApiAccountPurge, _EMPTY_, false, c, s.jsLeaderAccountPurgeRequest) - } -} - -// Lock should be held. -func (js *jetStream) stopUpdatesSub() { - cc := js.cluster - if cc.streamResults != nil { - cc.s.sysUnsubscribe(cc.streamResults) - cc.streamResults = nil - } - if cc.consumerResults != nil { - cc.s.sysUnsubscribe(cc.consumerResults) - cc.consumerResults = nil - } - if cc.stepdown != nil { - cc.s.sysUnsubscribe(cc.stepdown) - cc.stepdown = nil - } - if cc.peerRemove != nil { - cc.s.sysUnsubscribe(cc.peerRemove) - cc.peerRemove = nil - } - if cc.peerStreamMove != nil { - cc.s.sysUnsubscribe(cc.peerStreamMove) - cc.peerStreamMove = nil - } - if cc.peerStreamCancelMove != nil { - cc.s.sysUnsubscribe(cc.peerStreamCancelMove) - cc.peerStreamCancelMove = nil - } - if js.accountPurge != nil { - cc.s.sysUnsubscribe(js.accountPurge) - js.accountPurge = nil - } -} - -func (s *Server) sendDomainLeaderElectAdvisory() { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - - js.mu.RLock() - node := cc.meta - js.mu.RUnlock() - - adv := &JSDomainLeaderElectedAdvisory{ - TypedEvent: TypedEvent{ - Type: JSDomainLeaderElectedAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Leader: node.GroupLeader(), - Replicas: s.replicas(node), - Cluster: s.cachedClusterName(), - Domain: s.getOpts().JetStreamDomain, - } - - s.publishAdvisory(nil, JSAdvisoryDomainLeaderElected, adv) -} - -func (js *jetStream) processLeaderChange(isLeader bool) { - if js == nil { - return - } - s := js.srv - if s == nil { - return - } - // Update our server atomic. - s.isMetaLeader.Store(isLeader) - - if isLeader { - s.Noticef("Self is new JetStream cluster metadata leader") - s.sendDomainLeaderElectAdvisory() - } else { - var node string - if meta := js.getMetaGroup(); meta != nil { - node = meta.GroupLeader() - } - if node == _EMPTY_ { - s.Noticef("JetStream cluster no metadata leader") - } else if srv := js.srv.serverNameForNode(node); srv == _EMPTY_ { - s.Noticef("JetStream cluster new remote metadata leader") - } else if clst := js.srv.clusterNameForNode(node); clst == _EMPTY_ { - s.Noticef("JetStream cluster new metadata leader: %s", srv) - } else { - s.Noticef("JetStream cluster new metadata leader: %s/%s", srv, clst) - } - } - - js.mu.Lock() - defer js.mu.Unlock() - - if isLeader { - js.startUpdatesSub() - } else { - js.stopUpdatesSub() - // TODO(dlc) - stepdown. - } - - // If we have been signaled to check the streams, this is for a bug that left stream - // assignments with no sync subject after an update and no way to sync/catchup outside of the RAFT layer. - if isLeader && js.cluster.streamsCheck { - cc := js.cluster - for acc, asa := range cc.streams { - for _, sa := range asa { - if sa.Sync == _EMPTY_ { - s.Warnf("Stream assignment corrupt for stream '%s > %s'", acc, sa.Config.Name) - nsa := &streamAssignment{Group: sa.Group, Config: sa.Config, Subject: sa.Subject, Reply: sa.Reply, Client: sa.Client} - nsa.Sync = syncSubjForStream() - cc.meta.Propose(encodeUpdateStreamAssignment(nsa)) - } - } - } - // Clear check. - cc.streamsCheck = false - } -} - -// Lock should be held. -func (cc *jetStreamCluster) remapStreamAssignment(sa *streamAssignment, removePeer string) bool { - // Invoke placement algo passing RG peers that stay (existing) and the peer that is being removed (ignore) - var retain, ignore []string - for _, v := range sa.Group.Peers { - if v == removePeer { - ignore = append(ignore, v) - } else { - retain = append(retain, v) - } - } - - newPeers, placementError := cc.selectPeerGroup(len(sa.Group.Peers), sa.Group.Cluster, sa.Config, retain, 0, ignore) - - if placementError == nil { - sa.Group.Peers = newPeers - // Don't influence preferred leader. - sa.Group.Preferred = _EMPTY_ - return true - } - - // If R1 just return to avoid bricking the stream. - if sa.Group.node == nil || len(sa.Group.Peers) == 1 { - return false - } - - // If we are here let's remove the peer at least, as long as we are R>1 - for i, peer := range sa.Group.Peers { - if peer == removePeer { - sa.Group.Peers[i] = sa.Group.Peers[len(sa.Group.Peers)-1] - sa.Group.Peers = sa.Group.Peers[:len(sa.Group.Peers)-1] - break - } - } - return false -} - -type selectPeerError struct { - excludeTag bool - offline bool - noStorage bool - uniqueTag bool - misc bool - noJsClust bool - noMatchTags map[string]struct{} -} - -func (e *selectPeerError) Error() string { - b := strings.Builder{} - writeBoolErrReason := func(hasErr bool, errMsg string) { - if !hasErr { - return - } - b.WriteString(", ") - b.WriteString(errMsg) - } - b.WriteString("no suitable peers for placement") - writeBoolErrReason(e.offline, "peer offline") - writeBoolErrReason(e.excludeTag, "exclude tag set") - writeBoolErrReason(e.noStorage, "insufficient storage") - writeBoolErrReason(e.uniqueTag, "server tag not unique") - writeBoolErrReason(e.misc, "miscellaneous issue") - writeBoolErrReason(e.noJsClust, "jetstream not enabled in cluster") - if len(e.noMatchTags) != 0 { - b.WriteString(", tags not matched [") - var firstTagWritten bool - for tag := range e.noMatchTags { - if firstTagWritten { - b.WriteString(", ") - } - firstTagWritten = true - b.WriteRune('\'') - b.WriteString(tag) - b.WriteRune('\'') - } - b.WriteString("]") - } - return b.String() -} - -func (e *selectPeerError) addMissingTag(t string) { - if e.noMatchTags == nil { - e.noMatchTags = map[string]struct{}{} - } - e.noMatchTags[t] = struct{}{} -} - -func (e *selectPeerError) accumulate(eAdd *selectPeerError) { - if eAdd == nil { - return - } - acc := func(val *bool, valAdd bool) { - if valAdd { - *val = valAdd - } - } - acc(&e.offline, eAdd.offline) - acc(&e.excludeTag, eAdd.excludeTag) - acc(&e.noStorage, eAdd.noStorage) - acc(&e.uniqueTag, eAdd.uniqueTag) - acc(&e.misc, eAdd.misc) - acc(&e.noJsClust, eAdd.noJsClust) - for tag := range eAdd.noMatchTags { - e.addMissingTag(tag) - } -} - -// selectPeerGroup will select a group of peers to start a raft group. -// when peers exist already the unique tag prefix check for the replaceFirstExisting will be skipped -// js lock should be held. -func (cc *jetStreamCluster) selectPeerGroup(r int, cluster string, cfg *StreamConfig, existing []string, replaceFirstExisting int, ignore []string) ([]string, *selectPeerError) { - if cluster == _EMPTY_ || cfg == nil { - return nil, &selectPeerError{misc: true} - } - - var maxBytes uint64 - if cfg.MaxBytes > 0 { - maxBytes = uint64(cfg.MaxBytes) - } - - // Check for tags. - var tags []string - if cfg.Placement != nil && len(cfg.Placement.Tags) > 0 { - tags = cfg.Placement.Tags - } - - // Used for weighted sorting based on availability. - type wn struct { - id string - avail uint64 - ha int - ns int - } - - var nodes []wn - // peers is a randomized list - s, peers := cc.s, cc.meta.Peers() - - uniqueTagPrefix := s.getOpts().JetStreamUniqueTag - if uniqueTagPrefix != _EMPTY_ { - for _, tag := range tags { - if strings.HasPrefix(tag, uniqueTagPrefix) { - // disable uniqueness check if explicitly listed in tags - uniqueTagPrefix = _EMPTY_ - break - } - } - } - var uniqueTags = make(map[string]*nodeInfo) - - checkUniqueTag := func(ni *nodeInfo) (bool, *nodeInfo) { - for _, t := range ni.tags { - if strings.HasPrefix(t, uniqueTagPrefix) { - if n, ok := uniqueTags[t]; !ok { - uniqueTags[t] = ni - return true, ni - } else { - return false, n - } - } - } - // default requires the unique prefix to be present - return false, nil - } - - // Map existing. - var ep map[string]struct{} - if le := len(existing); le > 0 { - if le >= r { - return existing[:r], nil - } - ep = make(map[string]struct{}) - for i, p := range existing { - ep[p] = struct{}{} - if uniqueTagPrefix == _EMPTY_ { - continue - } - si, ok := s.nodeToInfo.Load(p) - if !ok || si == nil || i < replaceFirstExisting { - continue - } - ni := si.(nodeInfo) - // collect unique tags, but do not require them as this node is already part of the peerset - checkUniqueTag(&ni) - } - } - - // Map ignore - var ip map[string]struct{} - if li := len(ignore); li > 0 { - ip = make(map[string]struct{}) - for _, p := range ignore { - ip[p] = struct{}{} - } - } - - // Grab the number of streams and HA assets currently assigned to each peer. - // HAAssets under usage is async, so calculate here in realtime based on assignments. - peerStreams := make(map[string]int, len(peers)) - peerHA := make(map[string]int, len(peers)) - for _, asa := range cc.streams { - for _, sa := range asa { - isHA := len(sa.Group.Peers) > 1 - for _, peer := range sa.Group.Peers { - peerStreams[peer]++ - if isHA { - peerHA[peer]++ - } - } - } - } - - maxHaAssets := s.getOpts().JetStreamLimits.MaxHAAssets - - // An error is a result of multiple individual placement decisions. - // Which is why we keep taps on how often which one happened. - err := selectPeerError{} - - // Shuffle them up. - rand.Shuffle(len(peers), func(i, j int) { peers[i], peers[j] = peers[j], peers[i] }) - for _, p := range peers { - si, ok := s.nodeToInfo.Load(p.ID) - if !ok || si == nil { - err.misc = true - continue - } - ni := si.(nodeInfo) - // Only select from the designated named cluster. - if ni.cluster != cluster { - s.Debugf("Peer selection: discard %s@%s reason: not target cluster %s", ni.name, ni.cluster, cluster) - continue - } - - // If we know its offline or we do not have config or err don't consider. - if ni.offline || ni.cfg == nil || ni.stats == nil { - s.Debugf("Peer selection: discard %s@%s reason: offline", ni.name, ni.cluster) - err.offline = true - continue - } - - // If ignore skip - if _, ok := ip[p.ID]; ok { - continue - } - - // If existing also skip, we will add back in to front of the list when done. - if _, ok := ep[p.ID]; ok { - continue - } - - if ni.tags.Contains(jsExcludePlacement) { - s.Debugf("Peer selection: discard %s@%s tags: %v reason: %s present", - ni.name, ni.cluster, ni.tags, jsExcludePlacement) - err.excludeTag = true - continue - } - - if len(tags) > 0 { - matched := true - for _, t := range tags { - if !ni.tags.Contains(t) { - matched = false - s.Debugf("Peer selection: discard %s@%s tags: %v reason: mandatory tag %s not present", - ni.name, ni.cluster, ni.tags, t) - err.addMissingTag(t) - break - } - } - if !matched { - continue - } - } - - var available uint64 - if ni.stats != nil { - switch cfg.Storage { - case MemoryStorage: - used := ni.stats.ReservedMemory - if ni.stats.Memory > used { - used = ni.stats.Memory - } - if ni.cfg.MaxMemory > int64(used) { - available = uint64(ni.cfg.MaxMemory) - used - } - case FileStorage: - used := ni.stats.ReservedStore - if ni.stats.Store > used { - used = ni.stats.Store - } - if ni.cfg.MaxStore > int64(used) { - available = uint64(ni.cfg.MaxStore) - used - } - } - } - - // Otherwise check if we have enough room if maxBytes set. - if maxBytes > 0 && maxBytes > available { - s.Warnf("Peer selection: discard %s@%s (Max Bytes: %d) exceeds available %s storage of %d bytes", - ni.name, ni.cluster, maxBytes, cfg.Storage.String(), available) - err.noStorage = true - continue - } - // HAAssets contain _meta_ which we want to ignore, hence > and not >=. - if maxHaAssets > 0 && ni.stats != nil && ni.stats.HAAssets > maxHaAssets { - s.Warnf("Peer selection: discard %s@%s (HA Asset Count: %d) exceeds max ha asset limit of %d for stream placement", - ni.name, ni.cluster, ni.stats.HAAssets, maxHaAssets) - err.misc = true - continue - } - - if uniqueTagPrefix != _EMPTY_ { - if unique, owner := checkUniqueTag(&ni); !unique { - if owner != nil { - s.Debugf("Peer selection: discard %s@%s tags:%v reason: unique prefix %s owned by %s@%s", - ni.name, ni.cluster, ni.tags, owner.name, owner.cluster) - } else { - s.Debugf("Peer selection: discard %s@%s tags:%v reason: unique prefix %s not present", - ni.name, ni.cluster, ni.tags) - } - err.uniqueTag = true - continue - } - } - // Add to our list of potential nodes. - nodes = append(nodes, wn{p.ID, available, peerHA[p.ID], peerStreams[p.ID]}) - } - - // If we could not select enough peers, fail. - if len(nodes) < (r - len(existing)) { - s.Debugf("Peer selection: required %d nodes but found %d (cluster: %s replica: %d existing: %v/%d peers: %d result-peers: %d err: %+v)", - (r - len(existing)), len(nodes), cluster, r, existing, replaceFirstExisting, len(peers), len(nodes), err) - if len(peers) == 0 { - err.noJsClust = true - } - return nil, &err - } - // Sort based on available from most to least, breaking ties by number of total streams assigned to the peer. - slices.SortFunc(nodes, func(i, j wn) int { - if i.avail == j.avail { - return cmp.Compare(i.ns, j.ns) - } - return -cmp.Compare(i.avail, j.avail) // reverse - }) - // If we are placing a replicated stream, let's sort based on HAAssets, as that is more important to balance. - if cfg.Replicas > 1 { - slices.SortStableFunc(nodes, func(i, j wn) int { return cmp.Compare(i.ha, j.ha) }) - } - - var results []string - if len(existing) > 0 { - results = append(results, existing...) - r -= len(existing) - } - for _, r := range nodes[:r] { - results = append(results, r.id) - } - return results, nil -} - -func groupNameForStream(peers []string, storage StorageType) string { - return groupName("S", peers, storage) -} - -func groupNameForConsumer(peers []string, storage StorageType) string { - return groupName("C", peers, storage) -} - -func groupName(prefix string, peers []string, storage StorageType) string { - gns := getHash(nuid.Next()) - return fmt.Sprintf("%s-R%d%s-%s", prefix, len(peers), storage.String()[:1], gns) -} - -// returns stream count for this tier as well as applicable reservation size (not including cfg) -// jetStream read lock should be held -func tieredStreamAndReservationCount(asa map[string]*streamAssignment, tier string, cfg *StreamConfig) (int, int64) { - var numStreams int - var reservation int64 - for _, sa := range asa { - // Don't count the stream toward the limit if it already exists. - if (tier == _EMPTY_ || isSameTier(sa.Config, cfg)) && sa.Config.Name != cfg.Name { - numStreams++ - if sa.Config.MaxBytes > 0 && sa.Config.Storage == cfg.Storage { - // If tier is empty, all storage is flat and we should adjust for replicas. - // Otherwise if tiered, storage replication already taken into consideration. - if tier == _EMPTY_ && cfg.Replicas > 1 { - reservation += sa.Config.MaxBytes * int64(cfg.Replicas) - } else { - reservation += sa.Config.MaxBytes - } - } - } - } - return numStreams, reservation -} - -// createGroupForStream will create a group for assignment for the stream. -// Lock should be held. -func (js *jetStream) createGroupForStream(ci *ClientInfo, cfg *StreamConfig) (*raftGroup, *selectPeerError) { - replicas := cfg.Replicas - if replicas == 0 { - replicas = 1 - } - - // Default connected cluster from the request origin. - cc, cluster := js.cluster, ci.Cluster - // If specified, override the default. - clusterDefined := cfg.Placement != nil && cfg.Placement.Cluster != _EMPTY_ - if clusterDefined { - cluster = cfg.Placement.Cluster - } - clusters := []string{cluster} - if !clusterDefined { - clusters = append(clusters, ci.Alternates...) - } - - // Need to create a group here. - errs := &selectPeerError{} - for _, cn := range clusters { - peers, err := cc.selectPeerGroup(replicas, cn, cfg, nil, 0, nil) - if len(peers) < replicas { - errs.accumulate(err) - continue - } - return &raftGroup{Name: groupNameForStream(peers, cfg.Storage), Storage: cfg.Storage, Peers: peers, Cluster: cn}, nil - } - return nil, errs -} - -func (acc *Account) selectLimits(replicas int) (*JetStreamAccountLimits, string, *jsAccount, *ApiError) { - // Grab our jetstream account info. - acc.mu.RLock() - jsa := acc.js - acc.mu.RUnlock() - - if jsa == nil { - return nil, _EMPTY_, nil, NewJSNotEnabledForAccountError() - } - - jsa.usageMu.RLock() - selectedLimits, tierName, ok := jsa.selectLimits(replicas) - jsa.usageMu.RUnlock() - - if !ok { - return nil, _EMPTY_, nil, NewJSNoLimitsError() - } - return &selectedLimits, tierName, jsa, nil -} - -// Read lock needs to be held -func (js *jetStream) jsClusteredStreamLimitsCheck(acc *Account, cfg *StreamConfig) *ApiError { - var replicas int - if cfg != nil { - replicas = cfg.Replicas - } - selectedLimits, tier, _, apiErr := acc.selectLimits(replicas) - if apiErr != nil { - return apiErr - } - - asa := js.cluster.streams[acc.Name] - numStreams, reservations := tieredStreamAndReservationCount(asa, tier, cfg) - // Check for inflight proposals... - if cc := js.cluster; cc != nil && cc.inflight != nil { - streams := cc.inflight[acc.Name] - numStreams += len(streams) - // If inflight contains the same stream, don't count toward exceeding maximum. - if cfg != nil { - if _, ok := streams[cfg.Name]; ok { - numStreams-- - } - } - } - if selectedLimits.MaxStreams > 0 && numStreams >= selectedLimits.MaxStreams { - return NewJSMaximumStreamsLimitError() - } - // Check for account limits here before proposing. - if err := js.checkAccountLimits(selectedLimits, cfg, reservations); err != nil { - return NewJSStreamLimitsError(err, Unless(err)) - } - return nil -} - -func (s *Server) jsClusteredStreamRequest(ci *ClientInfo, acc *Account, subject, reply string, rmsg []byte, config *StreamConfigRequest) { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - - var resp = JSApiStreamCreateResponse{ApiResponse: ApiResponse{Type: JSApiStreamCreateResponseType}} - - ccfg, apiErr := s.checkStreamCfg(&config.StreamConfig, acc, config.Pedantic) - if apiErr != nil { - resp.Error = apiErr - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - cfg := &ccfg - - // Now process the request and proposal. - js.mu.Lock() - defer js.mu.Unlock() - - var self *streamAssignment - var rg *raftGroup - var syncSubject string - - // Capture if we have existing assignment first. - if osa := js.streamAssignment(acc.Name, cfg.Name); osa != nil { - copyStreamMetadata(cfg, osa.Config) - if !reflect.DeepEqual(osa.Config, cfg) { - resp.Error = NewJSStreamNameExistError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - // This is an equal assignment. - self, rg, syncSubject = osa, osa.Group, osa.Sync - } - - if cfg.Sealed { - resp.Error = NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration for create can not be sealed")) - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - - // Check for subject collisions here. - if cc.subjectsOverlap(acc.Name, cfg.Subjects, self) { - resp.Error = NewJSStreamSubjectOverlapError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - - apiErr = js.jsClusteredStreamLimitsCheck(acc, cfg) - // Check for stream limits here before proposing. These need to be tracked from meta layer, not jsa. - if apiErr != nil { - resp.Error = apiErr - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - - // Make sure inflight is setup properly. - if cc.inflight == nil { - cc.inflight = make(map[string]map[string]*inflightInfo) - } - streams, ok := cc.inflight[acc.Name] - if !ok { - streams = make(map[string]*inflightInfo) - cc.inflight[acc.Name] = streams - } - - // Raft group selection and placement. - if rg == nil { - // Check inflight before proposing in case we have an existing inflight proposal. - if existing, ok := streams[cfg.Name]; ok { - // We have existing for same stream. Re-use same group and syncSubject. - rg, syncSubject = existing.rg, existing.sync - } - } - // Create a new one here if needed. - if rg == nil { - nrg, err := js.createGroupForStream(ci, cfg) - if err != nil { - resp.Error = NewJSClusterNoPeersError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - rg = nrg - // Pick a preferred leader. - rg.setPreferred() - } - - if syncSubject == _EMPTY_ { - syncSubject = syncSubjForStream() - } - // Sync subject for post snapshot sync. - sa := &streamAssignment{Group: rg, Sync: syncSubject, Config: cfg, Subject: subject, Reply: reply, Client: ci, Created: time.Now().UTC()} - if err := cc.meta.Propose(encodeAddStreamAssignment(sa)); err == nil { - // On success, add this as an inflight proposal so we can apply limits - // on concurrent create requests while this stream assignment has - // possibly not been processed yet. - if streams, ok := cc.inflight[acc.Name]; ok && self == nil { - streams[cfg.Name] = &inflightInfo{rg, syncSubject} - } - } -} - -var ( - errReqTimeout = errors.New("timeout while waiting for response") - errReqSrvExit = errors.New("server shutdown while waiting for response") -) - -// blocking utility call to perform requests on the system account -// returns (synchronized) v or error -func sysRequest[T any](s *Server, subjFormat string, args ...any) (*T, error) { - isubj := fmt.Sprintf(subjFormat, args...) - - s.mu.Lock() - if s.sys == nil { - s.mu.Unlock() - return nil, ErrNoSysAccount - } - inbox := s.newRespInbox() - results := make(chan *T, 1) - s.sys.replies[inbox] = func(_ *subscription, _ *client, _ *Account, _, _ string, msg []byte) { - var v T - if err := json.Unmarshal(msg, &v); err != nil { - s.Warnf("Error unmarshalling response for request '%s':%v", isubj, err) - return - } - select { - case results <- &v: - default: - s.Warnf("Failed placing request response on internal channel") - } - } - s.mu.Unlock() - - s.sendInternalMsgLocked(isubj, inbox, nil, nil) - - defer func() { - s.mu.Lock() - defer s.mu.Unlock() - if s.sys != nil && s.sys.replies != nil { - delete(s.sys.replies, inbox) - } - }() - - ttl := time.NewTimer(2 * time.Second) - defer ttl.Stop() - - select { - case <-s.quitCh: - return nil, errReqSrvExit - case <-ttl.C: - return nil, errReqTimeout - case data := <-results: - return data, nil - } -} - -func (s *Server) jsClusteredStreamUpdateRequest(ci *ClientInfo, acc *Account, subject, reply string, rmsg []byte, cfg *StreamConfig, peerSet []string, pedantic bool) { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - - // Now process the request and proposal. - js.mu.Lock() - defer js.mu.Unlock() - meta := cc.meta - if meta == nil { - return - } - - var resp = JSApiStreamUpdateResponse{ApiResponse: ApiResponse{Type: JSApiStreamUpdateResponseType}} - - osa := js.streamAssignment(acc.Name, cfg.Name) - if osa == nil { - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - - // Update asset version metadata. - setStaticStreamMetadata(cfg) - - var newCfg *StreamConfig - if jsa := js.accounts[acc.Name]; jsa != nil { - js.mu.Unlock() - ncfg, err := jsa.configUpdateCheck(osa.Config, cfg, s, pedantic) - js.mu.Lock() - if err != nil { - resp.Error = NewJSStreamUpdateError(err, Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } else { - newCfg = ncfg - } - } else { - resp.Error = NewJSNotEnabledForAccountError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - // Check for mirror changes which are not allowed. - if !reflect.DeepEqual(newCfg.Mirror, osa.Config.Mirror) { - resp.Error = NewJSStreamMirrorNotUpdatableError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - - // Check for subject collisions here. - if cc.subjectsOverlap(acc.Name, cfg.Subjects, osa) { - resp.Error = NewJSStreamSubjectOverlapError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - - // Make copy so to not change original. - rg := osa.copyGroup().Group - - // Check for a move request. - var isMoveRequest, isMoveCancel bool - if lPeerSet := len(peerSet); lPeerSet > 0 { - isMoveRequest = true - // check if this is a cancellation - if lPeerSet == osa.Config.Replicas && lPeerSet <= len(rg.Peers) { - isMoveCancel = true - // can only be a cancellation if the peer sets overlap as expected - for i := 0; i < lPeerSet; i++ { - if peerSet[i] != rg.Peers[i] { - isMoveCancel = false - break - } - } - } - } else { - isMoveRequest = newCfg.Placement != nil && !reflect.DeepEqual(osa.Config.Placement, newCfg.Placement) - } - - // Check for replica changes. - isReplicaChange := newCfg.Replicas != osa.Config.Replicas - - // We stage consumer updates and do them after the stream update. - var consumers []*consumerAssignment - - // Check if this is a move request, but no cancellation, and we are already moving this stream. - if isMoveRequest && !isMoveCancel && osa.Config.Replicas != len(rg.Peers) { - // obtain stats to include in error message - msg := _EMPTY_ - if s.allPeersOffline(rg) { - msg = fmt.Sprintf("all %d peers offline", len(rg.Peers)) - } else { - // Need to release js lock. - js.mu.Unlock() - if si, err := sysRequest[StreamInfo](s, clusterStreamInfoT, ci.serviceAccount(), cfg.Name); err != nil { - msg = fmt.Sprintf("error retrieving info: %s", err.Error()) - } else if si != nil { - currentCount := 0 - if si.Cluster.Leader != _EMPTY_ { - currentCount++ - } - combinedLag := uint64(0) - for _, r := range si.Cluster.Replicas { - if r.Current { - currentCount++ - } - combinedLag += r.Lag - } - msg = fmt.Sprintf("total peers: %d, current peers: %d, combined lag: %d", - len(rg.Peers), currentCount, combinedLag) - } - // Re-acquire here. - js.mu.Lock() - } - resp.Error = NewJSStreamMoveInProgressError(msg) - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - - // Can not move and scale at same time. - if isMoveRequest && isReplicaChange { - resp.Error = NewJSStreamMoveAndScaleError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - - if isReplicaChange { - isScaleUp := newCfg.Replicas > len(rg.Peers) - // We are adding new peers here. - if isScaleUp { - // Check that we have the allocation available. - if err := js.jsClusteredStreamLimitsCheck(acc, newCfg); err != nil { - resp.Error = err - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - // Check if we do not have a cluster assigned, and if we do not make sure we - // try to pick one. This could happen with older streams that were assigned by - // previous servers. - if rg.Cluster == _EMPTY_ { - // Prefer placement directrives if we have them. - if newCfg.Placement != nil && newCfg.Placement.Cluster != _EMPTY_ { - rg.Cluster = newCfg.Placement.Cluster - } else { - // Fall back to the cluster assignment from the client. - rg.Cluster = ci.Cluster - } - } - peers, err := cc.selectPeerGroup(newCfg.Replicas, rg.Cluster, newCfg, rg.Peers, 0, nil) - if err != nil { - resp.Error = NewJSClusterNoPeersError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - // Single nodes are not recorded by the NRG layer so we can rename. - if len(peers) == 1 { - rg.Name = groupNameForStream(peers, rg.Storage) - } else if len(rg.Peers) == 1 { - // This is scale up from being a singelton, set preferred to that singelton. - rg.Preferred = rg.Peers[0] - } - rg.Peers = peers - } else { - // We are deleting nodes here. We want to do our best to preserve the current leader. - // We have support now from above that guarantees we are in our own Go routine, so can - // ask for stream info from the stream leader to make sure we keep the leader in the new list. - var curLeader string - if !s.allPeersOffline(rg) { - // Need to release js lock. - js.mu.Unlock() - if si, err := sysRequest[StreamInfo](s, clusterStreamInfoT, ci.serviceAccount(), cfg.Name); err != nil { - s.Warnf("Did not receive stream info results for '%s > %s' due to: %s", acc, cfg.Name, err) - } else if si != nil { - if cl := si.Cluster; cl != nil && cl.Leader != _EMPTY_ { - curLeader = getHash(cl.Leader) - } - } - // Re-acquire here. - js.mu.Lock() - } - // If we identified a leader make sure its part of the new group. - selected := make([]string, 0, newCfg.Replicas) - - if curLeader != _EMPTY_ { - selected = append(selected, curLeader) - } - for _, peer := range rg.Peers { - if len(selected) == newCfg.Replicas { - break - } - if peer == curLeader { - continue - } - if si, ok := s.nodeToInfo.Load(peer); ok && si != nil { - if si.(nodeInfo).offline { - continue - } - selected = append(selected, peer) - } - } - rg.Peers = selected - } - - // Need to remap any consumers. - for _, ca := range osa.consumers { - // Legacy ephemerals are R=1 but present as R=0, so only auto-remap named consumers, or if we are downsizing the consumer peers. - // If stream is interest or workqueue policy always remaps since they require peer parity with stream. - numPeers := len(ca.Group.Peers) - isAutoScale := ca.Config.Replicas == 0 && (ca.Config.Durable != _EMPTY_ || ca.Config.Name != _EMPTY_) - if isAutoScale || numPeers > len(rg.Peers) || cfg.Retention != LimitsPolicy { - cca := ca.copyGroup() - // Adjust preferred as needed. - if numPeers == 1 && isScaleUp { - cca.Group.Preferred = ca.Group.Peers[0] - } else { - cca.Group.Preferred = _EMPTY_ - } - // Assign new peers. - cca.Group.Peers = rg.Peers - // If the replicas was not 0 make sure it matches here. - if cca.Config.Replicas != 0 { - cca.Config.Replicas = len(rg.Peers) - } - // We can not propose here before the stream itself so we collect them. - consumers = append(consumers, cca) - - } else if !isScaleUp { - // We decided to leave this consumer's peer group alone but we are also scaling down. - // We need to make sure we do not have any peers that are no longer part of the stream. - // Note we handle down scaling of a consumer above if its number of peers were > new stream peers. - var needReplace []string - for _, rp := range ca.Group.Peers { - // Check if we have an orphaned peer now for this consumer. - if !rg.isMember(rp) { - needReplace = append(needReplace, rp) - } - } - if len(needReplace) > 0 { - newPeers := copyStrings(rg.Peers) - rand.Shuffle(len(newPeers), func(i, j int) { newPeers[i], newPeers[j] = newPeers[j], newPeers[i] }) - // If we had a small size then the peer set, restrict to the same number. - if lp := len(ca.Group.Peers); lp < len(newPeers) { - newPeers = newPeers[:lp] - } - cca := ca.copyGroup() - // Assign new peers. - cca.Group.Peers = newPeers - // If the replicas was not 0 make sure it matches here. - if cca.Config.Replicas != 0 { - cca.Config.Replicas = len(newPeers) - } - // Check if all peers are invalid. This can happen with R1 under replicated streams that are being scaled down. - if len(needReplace) == len(ca.Group.Peers) { - // We have to transfer state to new peers. - // we will grab our state and attach to the new assignment. - // TODO(dlc) - In practice we would want to make sure the consumer is paused. - // Need to release js lock. - js.mu.Unlock() - if ci, err := sysRequest[ConsumerInfo](s, clusterConsumerInfoT, acc, osa.Config.Name, ca.Name); err != nil { - s.Warnf("Did not receive consumer info results for '%s > %s > %s' due to: %s", acc, osa.Config.Name, ca.Name, err) - } else if ci != nil { - cca.State = &ConsumerState{ - Delivered: SequencePair{ - Consumer: ci.Delivered.Consumer, - Stream: ci.Delivered.Stream, - }, - AckFloor: SequencePair{ - Consumer: ci.AckFloor.Consumer, - Stream: ci.AckFloor.Stream, - }, - } - } - // Re-acquire here. - js.mu.Lock() - } - // We can not propose here before the stream itself so we collect them. - consumers = append(consumers, cca) - } - } - } - - } else if isMoveRequest { - if len(peerSet) == 0 { - nrg, err := js.createGroupForStream(ci, newCfg) - if err != nil { - resp.Error = NewJSClusterNoPeersError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - // filter peers present in both sets - for _, peer := range rg.Peers { - found := false - for _, newPeer := range nrg.Peers { - if peer == newPeer { - found = true - break - } - } - if !found { - peerSet = append(peerSet, peer) - } - } - peerSet = append(peerSet, nrg.Peers...) - } - if len(rg.Peers) == 1 { - rg.Preferred = peerSet[0] - } - rg.Peers = peerSet - - for _, ca := range osa.consumers { - cca := ca.copyGroup() - r := cca.Config.replicas(osa.Config) - // shuffle part of cluster peer set we will be keeping - randPeerSet := copyStrings(peerSet[len(peerSet)-newCfg.Replicas:]) - rand.Shuffle(newCfg.Replicas, func(i, j int) { randPeerSet[i], randPeerSet[j] = randPeerSet[j], randPeerSet[i] }) - // move overlapping peers at the end of randPeerSet and keep a tally of non overlapping peers - dropPeerSet := make([]string, 0, len(cca.Group.Peers)) - for _, p := range cca.Group.Peers { - found := false - for i, rp := range randPeerSet { - if p == rp { - randPeerSet[i] = randPeerSet[newCfg.Replicas-1] - randPeerSet[newCfg.Replicas-1] = p - found = true - break - } - } - if !found { - dropPeerSet = append(dropPeerSet, p) - } - } - cPeerSet := randPeerSet[newCfg.Replicas-r:] - // In case of a set or cancel simply assign - if len(peerSet) == newCfg.Replicas { - cca.Group.Peers = cPeerSet - } else { - cca.Group.Peers = append(dropPeerSet, cPeerSet...) - } - // make sure it overlaps with peers and remove if not - if cca.Group.Preferred != _EMPTY_ { - found := false - for _, p := range cca.Group.Peers { - if p == cca.Group.Preferred { - found = true - break - } - } - if !found { - cca.Group.Preferred = _EMPTY_ - } - } - // We can not propose here before the stream itself so we collect them. - consumers = append(consumers, cca) - } - } else { - // All other updates make sure no preferred is set. - rg.Preferred = _EMPTY_ - } - - sa := &streamAssignment{Group: rg, Sync: osa.Sync, Created: osa.Created, Config: newCfg, Subject: subject, Reply: reply, Client: ci} - meta.Propose(encodeUpdateStreamAssignment(sa)) - - // Process any staged consumers. - for _, ca := range consumers { - meta.Propose(encodeAddConsumerAssignment(ca)) - } -} - -func (s *Server) jsClusteredStreamDeleteRequest(ci *ClientInfo, acc *Account, stream, subject, reply string, rmsg []byte) { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - - js.mu.Lock() - defer js.mu.Unlock() - - if cc.meta == nil { - return - } - - osa := js.streamAssignment(acc.Name, stream) - if osa == nil { - var resp = JSApiStreamDeleteResponse{ApiResponse: ApiResponse{Type: JSApiStreamDeleteResponseType}} - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - - sa := &streamAssignment{Group: osa.Group, Config: osa.Config, Subject: subject, Reply: reply, Client: ci} - cc.meta.Propose(encodeDeleteStreamAssignment(sa)) -} - -// Process a clustered purge request. -func (s *Server) jsClusteredStreamPurgeRequest( - ci *ClientInfo, - acc *Account, - mset *stream, - stream, subject, reply string, - rmsg []byte, - preq *JSApiStreamPurgeRequest, -) { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - - js.mu.Lock() - sa := js.streamAssignment(acc.Name, stream) - if sa == nil { - resp := JSApiStreamPurgeResponse{ApiResponse: ApiResponse{Type: JSApiStreamPurgeResponseType}} - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - js.mu.Unlock() - return - } - - if n := sa.Group.node; n != nil { - sp := &streamPurge{Stream: stream, LastSeq: mset.state().LastSeq, Subject: subject, Reply: reply, Client: ci, Request: preq} - n.Propose(encodeStreamPurge(sp)) - js.mu.Unlock() - return - } - js.mu.Unlock() - - if mset == nil { - return - } - - var resp = JSApiStreamPurgeResponse{ApiResponse: ApiResponse{Type: JSApiStreamPurgeResponseType}} - purged, err := mset.purge(preq) - if err != nil { - resp.Error = NewJSStreamGeneralError(err, Unless(err)) - } else { - resp.Purged = purged - resp.Success = true - } - s.sendAPIResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(resp)) -} - -func (s *Server) jsClusteredStreamRestoreRequest( - ci *ClientInfo, - acc *Account, - req *JSApiStreamRestoreRequest, - subject, reply string, rmsg []byte) { - - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - - js.mu.Lock() - defer js.mu.Unlock() - - if cc.meta == nil { - return - } - - cfg := &req.Config - resp := JSApiStreamRestoreResponse{ApiResponse: ApiResponse{Type: JSApiStreamRestoreResponseType}} - - if err := js.jsClusteredStreamLimitsCheck(acc, cfg); err != nil { - resp.Error = err - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - - if sa := js.streamAssignment(ci.serviceAccount(), cfg.Name); sa != nil { - resp.Error = NewJSStreamNameExistRestoreFailedError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - - // Raft group selection and placement. - rg, err := js.createGroupForStream(ci, cfg) - if err != nil { - resp.Error = NewJSClusterNoPeersError(err) - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - // Pick a preferred leader. - rg.setPreferred() - sa := &streamAssignment{Group: rg, Sync: syncSubjForStream(), Config: cfg, Subject: subject, Reply: reply, Client: ci, Created: time.Now().UTC()} - // Now add in our restore state and pre-select a peer to handle the actual receipt of the snapshot. - sa.Restore = &req.State - cc.meta.Propose(encodeAddStreamAssignment(sa)) -} - -// Determine if all peers for this group are offline. -func (s *Server) allPeersOffline(rg *raftGroup) bool { - if rg == nil { - return false - } - // Check to see if this stream has any servers online to respond. - for _, peer := range rg.Peers { - if si, ok := s.nodeToInfo.Load(peer); ok && si != nil { - if !si.(nodeInfo).offline { - return false - } - } - } - return true -} - -// This will do a scatter and gather operation for all streams for this account. This is only called from metadata leader. -// This will be running in a separate Go routine. -func (s *Server) jsClusteredStreamListRequest(acc *Account, ci *ClientInfo, filter string, offset int, subject, reply string, rmsg []byte) { - defer s.grWG.Done() - - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - - js.mu.RLock() - - var streams []*streamAssignment - for _, sa := range cc.streams[acc.Name] { - if IsNatsErr(sa.err, JSClusterNotAssignedErr) { - continue - } - - if filter != _EMPTY_ { - // These could not have subjects auto-filled in since they are raw and unprocessed. - if len(sa.Config.Subjects) == 0 { - if SubjectsCollide(filter, sa.Config.Name) { - streams = append(streams, sa) - } - } else { - for _, subj := range sa.Config.Subjects { - if SubjectsCollide(filter, subj) { - streams = append(streams, sa) - break - } - } - } - } else { - streams = append(streams, sa) - } - } - - // Needs to be sorted for offsets etc. - if len(streams) > 1 { - slices.SortFunc(streams, func(i, j *streamAssignment) int { return cmp.Compare(i.Config.Name, j.Config.Name) }) - } - - scnt := len(streams) - if offset > scnt { - offset = scnt - } - if offset > 0 { - streams = streams[offset:] - } - if len(streams) > JSApiListLimit { - streams = streams[:JSApiListLimit] - } - - var resp = JSApiStreamListResponse{ - ApiResponse: ApiResponse{Type: JSApiStreamListResponseType}, - Streams: make([]*StreamInfo, 0, len(streams)), - } - - js.mu.RUnlock() - - if len(streams) == 0 { - resp.Limit = JSApiListLimit - resp.Offset = offset - s.sendAPIResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(resp)) - return - } - - // Create an inbox for our responses and send out our requests. - s.mu.Lock() - inbox := s.newRespInbox() - rc := make(chan *StreamInfo, len(streams)) - - // Store our handler. - s.sys.replies[inbox] = func(sub *subscription, _ *client, _ *Account, subject, _ string, msg []byte) { - var si StreamInfo - if err := json.Unmarshal(msg, &si); err != nil { - s.Warnf("Error unmarshalling clustered stream info response:%v", err) - return - } - select { - case rc <- &si: - default: - s.Warnf("Failed placing remote stream info result on internal channel") - } - } - s.mu.Unlock() - - // Cleanup after. - defer func() { - s.mu.Lock() - if s.sys != nil && s.sys.replies != nil { - delete(s.sys.replies, inbox) - } - s.mu.Unlock() - }() - - var missingNames []string - sent := map[string]int{} - - // Send out our requests here. - js.mu.RLock() - for _, sa := range streams { - if s.allPeersOffline(sa.Group) { - // Place offline onto our results by hand here. - si := &StreamInfo{ - Config: *sa.Config, - Created: sa.Created, - Cluster: js.offlineClusterInfo(sa.Group), - TimeStamp: time.Now().UTC(), - } - resp.Streams = append(resp.Streams, si) - missingNames = append(missingNames, sa.Config.Name) - } else { - isubj := fmt.Sprintf(clusterStreamInfoT, sa.Client.serviceAccount(), sa.Config.Name) - s.sendInternalMsgLocked(isubj, inbox, nil, nil) - sent[sa.Config.Name] = len(sa.consumers) - } - } - // Don't hold lock. - js.mu.RUnlock() - - const timeout = 4 * time.Second - notActive := time.NewTimer(timeout) - defer notActive.Stop() - -LOOP: - for len(sent) > 0 { - select { - case <-s.quitCh: - return - case <-notActive.C: - s.Warnf("Did not receive all stream info results for %q", acc) - for sName := range sent { - missingNames = append(missingNames, sName) - } - break LOOP - case si := <-rc: - consCount := sent[si.Config.Name] - if consCount > 0 { - si.State.Consumers = consCount - } - delete(sent, si.Config.Name) - resp.Streams = append(resp.Streams, si) - // Check to see if we are done. - if len(resp.Streams) == len(streams) { - break LOOP - } - } - } - - // Needs to be sorted as well. - if len(resp.Streams) > 1 { - slices.SortFunc(resp.Streams, func(i, j *StreamInfo) int { return cmp.Compare(i.Config.Name, j.Config.Name) }) - } - - resp.Total = scnt - resp.Limit = JSApiListLimit - resp.Offset = offset - resp.Missing = missingNames - s.sendAPIResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(resp)) -} - -// This will do a scatter and gather operation for all consumers for this stream and account. -// This will be running in a separate Go routine. -func (s *Server) jsClusteredConsumerListRequest(acc *Account, ci *ClientInfo, offset int, stream, subject, reply string, rmsg []byte) { - defer s.grWG.Done() - - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - - js.mu.RLock() - - var consumers []*consumerAssignment - if sas := cc.streams[acc.Name]; sas != nil { - if sa := sas[stream]; sa != nil { - // Copy over since we need to sort etc. - for _, ca := range sa.consumers { - consumers = append(consumers, ca) - } - } - } - // Needs to be sorted. - if len(consumers) > 1 { - slices.SortFunc(consumers, func(i, j *consumerAssignment) int { return cmp.Compare(i.Config.Name, j.Config.Name) }) - } - - ocnt := len(consumers) - if offset > ocnt { - offset = ocnt - } - if offset > 0 { - consumers = consumers[offset:] - } - if len(consumers) > JSApiListLimit { - consumers = consumers[:JSApiListLimit] - } - - // Send out our requests here. - var resp = JSApiConsumerListResponse{ - ApiResponse: ApiResponse{Type: JSApiConsumerListResponseType}, - Consumers: []*ConsumerInfo{}, - } - - js.mu.RUnlock() - - if len(consumers) == 0 { - resp.Limit = JSApiListLimit - resp.Offset = offset - s.sendAPIResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(resp)) - return - } - - // Create an inbox for our responses and send out requests. - s.mu.Lock() - inbox := s.newRespInbox() - rc := make(chan *ConsumerInfo, len(consumers)) - - // Store our handler. - s.sys.replies[inbox] = func(sub *subscription, _ *client, _ *Account, subject, _ string, msg []byte) { - var ci ConsumerInfo - if err := json.Unmarshal(msg, &ci); err != nil { - s.Warnf("Error unmarshaling clustered consumer info response:%v", err) - return - } - select { - case rc <- &ci: - default: - s.Warnf("Failed placing consumer info result on internal chan") - } - } - s.mu.Unlock() - - // Cleanup after. - defer func() { - s.mu.Lock() - if s.sys != nil && s.sys.replies != nil { - delete(s.sys.replies, inbox) - } - s.mu.Unlock() - }() - - var missingNames []string - sent := map[string]struct{}{} - - // Send out our requests here. - js.mu.RLock() - for _, ca := range consumers { - if s.allPeersOffline(ca.Group) { - // Place offline onto our results by hand here. - ci := &ConsumerInfo{ - Config: ca.Config, - Created: ca.Created, - Cluster: js.offlineClusterInfo(ca.Group), - TimeStamp: time.Now().UTC(), - } - resp.Consumers = append(resp.Consumers, ci) - missingNames = append(missingNames, ca.Name) - } else { - isubj := fmt.Sprintf(clusterConsumerInfoT, ca.Client.serviceAccount(), stream, ca.Name) - s.sendInternalMsgLocked(isubj, inbox, nil, nil) - sent[ca.Name] = struct{}{} - } - } - // Don't hold lock. - js.mu.RUnlock() - - const timeout = 4 * time.Second - notActive := time.NewTimer(timeout) - defer notActive.Stop() - -LOOP: - for len(sent) > 0 { - select { - case <-s.quitCh: - return - case <-notActive.C: - s.Warnf("Did not receive all consumer info results for '%s > %s'", acc, stream) - for cName := range sent { - missingNames = append(missingNames, cName) - } - break LOOP - case ci := <-rc: - delete(sent, ci.Name) - resp.Consumers = append(resp.Consumers, ci) - // Check to see if we are done. - if len(resp.Consumers) == len(consumers) { - break LOOP - } - } - } - - // Needs to be sorted as well. - if len(resp.Consumers) > 1 { - slices.SortFunc(resp.Consumers, func(i, j *ConsumerInfo) int { return cmp.Compare(i.Name, j.Name) }) - } - - resp.Total = ocnt - resp.Limit = JSApiListLimit - resp.Offset = offset - resp.Missing = missingNames - s.sendAPIResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(resp)) -} - -func encodeStreamPurge(sp *streamPurge) []byte { - var bb bytes.Buffer - bb.WriteByte(byte(purgeStreamOp)) - json.NewEncoder(&bb).Encode(sp) - return bb.Bytes() -} - -func decodeStreamPurge(buf []byte) (*streamPurge, error) { - var sp streamPurge - err := json.Unmarshal(buf, &sp) - return &sp, err -} - -func (s *Server) jsClusteredConsumerDeleteRequest(ci *ClientInfo, acc *Account, stream, consumer, subject, reply string, rmsg []byte) { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - - js.mu.Lock() - defer js.mu.Unlock() - - if cc.meta == nil { - return - } - - var resp = JSApiConsumerDeleteResponse{ApiResponse: ApiResponse{Type: JSApiConsumerDeleteResponseType}} - - sa := js.streamAssignment(acc.Name, stream) - if sa == nil { - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - - } - if sa.consumers == nil { - resp.Error = NewJSConsumerNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - oca := sa.consumers[consumer] - if oca == nil { - resp.Error = NewJSConsumerNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - oca.deleted = true - ca := &consumerAssignment{Group: oca.Group, Stream: stream, Name: consumer, Config: oca.Config, Subject: subject, Reply: reply, Client: ci} - cc.meta.Propose(encodeDeleteConsumerAssignment(ca)) -} - -func encodeMsgDelete(md *streamMsgDelete) []byte { - var bb bytes.Buffer - bb.WriteByte(byte(deleteMsgOp)) - json.NewEncoder(&bb).Encode(md) - return bb.Bytes() -} - -func decodeMsgDelete(buf []byte) (*streamMsgDelete, error) { - var md streamMsgDelete - err := json.Unmarshal(buf, &md) - return &md, err -} - -func (s *Server) jsClusteredMsgDeleteRequest(ci *ClientInfo, acc *Account, mset *stream, stream, subject, reply string, req *JSApiMsgDeleteRequest, rmsg []byte) { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - - js.mu.Lock() - sa := js.streamAssignment(acc.Name, stream) - if sa == nil { - s.Debugf("Message delete failed, could not locate stream '%s > %s'", acc.Name, stream) - js.mu.Unlock() - return - } - - // Check for single replica items. - if n := sa.Group.node; n != nil { - md := streamMsgDelete{Seq: req.Seq, NoErase: req.NoErase, Stream: stream, Subject: subject, Reply: reply, Client: ci} - n.Propose(encodeMsgDelete(&md)) - js.mu.Unlock() - return - } - js.mu.Unlock() - - if mset == nil { - return - } - - var err error - var removed bool - if req.NoErase { - removed, err = mset.removeMsg(req.Seq) - } else { - removed, err = mset.eraseMsg(req.Seq) - } - var resp = JSApiMsgDeleteResponse{ApiResponse: ApiResponse{Type: JSApiMsgDeleteResponseType}} - if err != nil { - resp.Error = NewJSStreamMsgDeleteFailedError(err, Unless(err)) - } else if !removed { - resp.Error = NewJSSequenceNotFoundError(req.Seq) - } else { - resp.Success = true - } - s.sendAPIResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(resp)) -} - -func encodeAddStreamAssignment(sa *streamAssignment) []byte { - csa := *sa - csa.Client = csa.Client.forProposal() - var bb bytes.Buffer - bb.WriteByte(byte(assignStreamOp)) - json.NewEncoder(&bb).Encode(csa) - return bb.Bytes() -} - -func encodeUpdateStreamAssignment(sa *streamAssignment) []byte { - csa := *sa - csa.Client = csa.Client.forProposal() - var bb bytes.Buffer - bb.WriteByte(byte(updateStreamOp)) - json.NewEncoder(&bb).Encode(csa) - return bb.Bytes() -} - -func encodeDeleteStreamAssignment(sa *streamAssignment) []byte { - csa := *sa - csa.Client = csa.Client.forProposal() - var bb bytes.Buffer - bb.WriteByte(byte(removeStreamOp)) - json.NewEncoder(&bb).Encode(csa) - return bb.Bytes() -} - -func decodeStreamAssignment(buf []byte) (*streamAssignment, error) { - var sa streamAssignment - err := json.Unmarshal(buf, &sa) - if err != nil { - return nil, err - } - fixCfgMirrorWithDedupWindow(sa.Config) - return &sa, err -} - -func encodeDeleteRange(dr *DeleteRange) []byte { - var bb bytes.Buffer - bb.WriteByte(byte(deleteRangeOp)) - json.NewEncoder(&bb).Encode(dr) - return bb.Bytes() -} - -func decodeDeleteRange(buf []byte) (*DeleteRange, error) { - var dr DeleteRange - err := json.Unmarshal(buf, &dr) - if err != nil { - return nil, err - } - return &dr, err -} - -// createGroupForConsumer will create a new group from same peer set as the stream. -func (cc *jetStreamCluster) createGroupForConsumer(cfg *ConsumerConfig, sa *streamAssignment) *raftGroup { - if len(sa.Group.Peers) == 0 || cfg.Replicas > len(sa.Group.Peers) { - return nil - } - - peers := copyStrings(sa.Group.Peers) - var _ss [5]string - active := _ss[:0] - - // Calculate all active peers. - for _, peer := range peers { - if sir, ok := cc.s.nodeToInfo.Load(peer); ok && sir != nil { - if !sir.(nodeInfo).offline { - active = append(active, peer) - } - } - } - if quorum := cfg.Replicas/2 + 1; quorum > len(active) { - // Not enough active to satisfy the request. - return nil - } - - // If we want less then our parent stream, select from active. - if cfg.Replicas > 0 && cfg.Replicas < len(peers) { - // Pedantic in case stream is say R5 and consumer is R3 and 3 or more offline, etc. - if len(active) < cfg.Replicas { - return nil - } - // First shuffle the active peers and then select to account for replica = 1. - rand.Shuffle(len(active), func(i, j int) { active[i], active[j] = active[j], active[i] }) - peers = active[:cfg.Replicas] - } - storage := sa.Config.Storage - if cfg.MemoryStorage { - storage = MemoryStorage - } - return &raftGroup{Name: groupNameForConsumer(peers, storage), Storage: storage, Peers: peers} -} - -// jsClusteredConsumerRequest is first point of entry to create a consumer in clustered mode. -func (s *Server) jsClusteredConsumerRequest(ci *ClientInfo, acc *Account, subject, reply string, rmsg []byte, stream string, cfg *ConsumerConfig, action ConsumerAction, pedantic bool) { - js, cc := s.getJetStreamCluster() - if js == nil || cc == nil { - return - } - - var resp = JSApiConsumerCreateResponse{ApiResponse: ApiResponse{Type: JSApiConsumerCreateResponseType}} - - streamCfg, ok := js.clusterStreamConfig(acc.Name, stream) - if !ok { - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - selectedLimits, _, _, apiErr := acc.selectLimits(cfg.replicas(&streamCfg)) - if apiErr != nil { - resp.Error = apiErr - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - srvLim := &s.getOpts().JetStreamLimits - // Make sure we have sane defaults - if err := setConsumerConfigDefaults(cfg, &streamCfg, srvLim, selectedLimits, pedantic); err != nil { - resp.Error = err - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - - if err := checkConsumerCfg(cfg, srvLim, &streamCfg, acc, selectedLimits, false); err != nil { - resp.Error = err - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - - js.mu.Lock() - defer js.mu.Unlock() - - if cc.meta == nil { - return - } - - // Lookup the stream assignment. - sa := js.streamAssignment(acc.Name, stream) - if sa == nil { - resp.Error = NewJSStreamNotFoundError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - - // Was a consumer name provided? - var oname string - if isDurableConsumer(cfg) || cfg.Name != _EMPTY_ { - if cfg.Name != _EMPTY_ { - oname = cfg.Name - } else { - oname = cfg.Durable - } - } - - // Check for max consumers here to short circuit if possible. - // Start with limit on a stream, but if one is defined at the level of the account - // and is lower, use that limit. - if action == ActionCreate || action == ActionCreateOrUpdate { - maxc := sa.Config.MaxConsumers - if maxc <= 0 || (selectedLimits.MaxConsumers > 0 && selectedLimits.MaxConsumers < maxc) { - maxc = selectedLimits.MaxConsumers - } - if maxc > 0 { - // Don't count DIRECTS. - total := 0 - for cn, ca := range sa.consumers { - // If the consumer name is specified and we think it already exists, then - // we're likely updating an existing consumer, so don't count it. Otherwise - // we will incorrectly return NewJSMaximumConsumersLimitError for an update. - if oname != _EMPTY_ && cn == oname && sa.consumers[oname] != nil { - continue - } - if ca.Config != nil && !ca.Config.Direct { - total++ - } - } - if total >= maxc { - resp.Error = NewJSMaximumConsumersLimitError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - } - } - - // Also short circuit if DeliverLastPerSubject is set with no FilterSubject. - if cfg.DeliverPolicy == DeliverLastPerSubject { - if cfg.FilterSubject == _EMPTY_ && len(cfg.FilterSubjects) == 0 { - resp.Error = NewJSConsumerInvalidPolicyError(fmt.Errorf("consumer delivery policy is deliver last per subject, but FilterSubject is not set")) - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - } - - // Setup proper default for ack wait if we are in explicit ack mode. - if cfg.AckWait == 0 && (cfg.AckPolicy == AckExplicit || cfg.AckPolicy == AckAll) { - cfg.AckWait = JsAckWaitDefault - } - // Setup default of -1, meaning no limit for MaxDeliver. - if cfg.MaxDeliver == 0 { - cfg.MaxDeliver = -1 - } - // Set proper default for max ack pending if we are ack explicit and none has been set. - if cfg.AckPolicy == AckExplicit && cfg.MaxAckPending == 0 { - cfg.MaxAckPending = JsDefaultMaxAckPending - } - - if cfg.PriorityPolicy == PriorityPinnedClient && cfg.PinnedTTL == 0 { - cfg.PinnedTTL = JsDefaultPinnedTTL - } - - var ca *consumerAssignment - - // See if we have an existing one already under same durable name or - // if name was set by the user. - if oname != _EMPTY_ { - if ca = sa.consumers[oname]; ca != nil && !ca.deleted { - // Provided config might miss metadata, copy from existing config. - copyConsumerMetadata(cfg, ca.Config) - - if action == ActionCreate && !reflect.DeepEqual(cfg, ca.Config) { - resp.Error = NewJSConsumerAlreadyExistsError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - // Do quick sanity check on new cfg to prevent here if possible. - if err := acc.checkNewConsumerConfig(ca.Config, cfg); err != nil { - resp.Error = NewJSConsumerCreateError(err, Unless(err)) - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - } else { - // Initialize/update asset version metadata. - // First time creating this consumer, or updating. - setStaticConsumerMetadata(cfg) - } - } - - // Initialize/update asset version metadata. - // But only if we're not creating, should only update it the first time - // to be idempotent with versions where there's no versioning metadata. - if action != ActionCreate { - setStaticConsumerMetadata(cfg) - } - - // If this is new consumer. - if ca == nil { - if action == ActionUpdate { - resp.Error = NewJSConsumerDoesNotExistError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - rg := cc.createGroupForConsumer(cfg, sa) - if rg == nil { - resp.Error = NewJSInsufficientResourcesError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - // Pick a preferred leader. - rg.setPreferred() - - // Inherit cluster from stream. - rg.Cluster = sa.Group.Cluster - - // We need to set the ephemeral here before replicating. - if !isDurableConsumer(cfg) { - // We chose to have ephemerals be R=1 unless stream is interest or workqueue. - // Consumer can override. - if sa.Config.Retention == LimitsPolicy && cfg.Replicas <= 1 { - rg.Peers = []string{rg.Preferred} - rg.Name = groupNameForConsumer(rg.Peers, rg.Storage) - } - if cfg.Name != _EMPTY_ { - oname = cfg.Name - } else { - // Make sure name is unique. - for { - oname = createConsumerName() - if sa.consumers != nil { - if sa.consumers[oname] != nil { - continue - } - } - break - } - } - } - if len(rg.Peers) > 1 { - if maxHaAssets := s.getOpts().JetStreamLimits.MaxHAAssets; maxHaAssets != 0 { - for _, peer := range rg.Peers { - if ni, ok := s.nodeToInfo.Load(peer); ok { - ni := ni.(nodeInfo) - if stats := ni.stats; stats != nil && stats.HAAssets > maxHaAssets { - resp.Error = NewJSInsufficientResourcesError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - s.Warnf("%s@%s (HA Asset Count: %d) exceeds max ha asset limit of %d"+ - " for (durable) consumer %s placement on stream %s", - ni.name, ni.cluster, ni.stats.HAAssets, maxHaAssets, oname, stream) - return - } - } - } - } - } - - // Check if we are work queue policy. - // We will do pre-checks here to avoid thrashing meta layer. - if sa.Config.Retention == WorkQueuePolicy && !cfg.Direct { - if cfg.AckPolicy != AckExplicit { - resp.Error = NewJSConsumerWQRequiresExplicitAckError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - subjects := gatherSubjectFilters(cfg.FilterSubject, cfg.FilterSubjects) - if len(subjects) == 0 && len(sa.consumers) > 0 { - resp.Error = NewJSConsumerWQMultipleUnfilteredError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - // Check here to make sure we have not collided with another. - if len(sa.consumers) > 0 { - for _, oca := range sa.consumers { - if oca.Name == oname { - continue - } - for _, psubj := range gatherSubjectFilters(oca.Config.FilterSubject, oca.Config.FilterSubjects) { - for _, subj := range subjects { - if SubjectsCollide(subj, psubj) { - resp.Error = NewJSConsumerWQConsumerNotUniqueError() - s.sendAPIErrResponse(ci, acc, subject, reply, string(rmsg), s.jsonResponse(&resp)) - return - } - } - } - } - } - } - - ca = &consumerAssignment{ - Group: rg, - Stream: stream, - Name: oname, - Config: cfg, - Subject: subject, - Reply: reply, - Client: ci, - Created: time.Now().UTC(), - } - } else { - // If the consumer already exists then don't allow updating the PauseUntil, just set - // it back to whatever the current configured value is. - cfg.PauseUntil = ca.Config.PauseUntil - - nca := ca.copyGroup() - - rBefore := nca.Config.replicas(sa.Config) - rAfter := cfg.replicas(sa.Config) - - var curLeader string - if rBefore != rAfter { - // We are modifying nodes here. We want to do our best to preserve the current leader. - // We have support now from above that guarantees we are in our own Go routine, so can - // ask for stream info from the stream leader to make sure we keep the leader in the new list. - if !s.allPeersOffline(ca.Group) { - // Need to release js lock. - js.mu.Unlock() - if ci, err := sysRequest[ConsumerInfo](s, clusterConsumerInfoT, ci.serviceAccount(), sa.Config.Name, cfg.Durable); err != nil { - s.Warnf("Did not receive consumer info results for '%s > %s > %s' due to: %s", acc, sa.Config.Name, cfg.Durable, err) - } else if ci != nil { - if cl := ci.Cluster; cl != nil { - curLeader = getHash(cl.Leader) - } - } - // Re-acquire here. - js.mu.Lock() - } - } - - if rBefore < rAfter { - newPeerSet := nca.Group.Peers - // scale up by adding new members from the stream peer set that are not yet in the consumer peer set - streamPeerSet := copyStrings(sa.Group.Peers) - rand.Shuffle(rAfter, func(i, j int) { streamPeerSet[i], streamPeerSet[j] = streamPeerSet[j], streamPeerSet[i] }) - for _, p := range streamPeerSet { - found := false - for _, sp := range newPeerSet { - if sp == p { - found = true - break - } - } - if !found { - newPeerSet = append(newPeerSet, p) - if len(newPeerSet) == rAfter { - break - } - } - } - nca.Group.Peers = newPeerSet - nca.Group.Preferred = curLeader - } else if rBefore > rAfter { - newPeerSet := nca.Group.Peers - // mark leader preferred and move it to end - nca.Group.Preferred = curLeader - if nca.Group.Preferred != _EMPTY_ { - for i, p := range newPeerSet { - if nca.Group.Preferred == p { - newPeerSet[i] = newPeerSet[len(newPeerSet)-1] - newPeerSet[len(newPeerSet)-1] = p - } - } - } - // scale down by removing peers from the end - newPeerSet = newPeerSet[len(newPeerSet)-rAfter:] - nca.Group.Peers = newPeerSet - } - - // Update config and client info on copy of existing. - nca.Config = cfg - nca.Client = ci - nca.Subject = subject - nca.Reply = reply - ca = nca - } - - // Do formal proposal. - if err := cc.meta.Propose(encodeAddConsumerAssignment(ca)); err == nil { - // Mark this as pending. - if sa.consumers == nil { - sa.consumers = make(map[string]*consumerAssignment) - } - ca.pending = true - sa.consumers[ca.Name] = ca - } -} - -func encodeAddConsumerAssignment(ca *consumerAssignment) []byte { - cca := *ca - cca.Client = cca.Client.forProposal() - var bb bytes.Buffer - bb.WriteByte(byte(assignConsumerOp)) - json.NewEncoder(&bb).Encode(cca) - return bb.Bytes() -} - -func encodeDeleteConsumerAssignment(ca *consumerAssignment) []byte { - cca := *ca - cca.Client = cca.Client.forProposal() - var bb bytes.Buffer - bb.WriteByte(byte(removeConsumerOp)) - json.NewEncoder(&bb).Encode(cca) - return bb.Bytes() -} - -func decodeConsumerAssignment(buf []byte) (*consumerAssignment, error) { - var ca consumerAssignment - err := json.Unmarshal(buf, &ca) - return &ca, err -} - -func encodeAddConsumerAssignmentCompressed(ca *consumerAssignment) []byte { - cca := *ca - cca.Client = cca.Client.forProposal() - var bb bytes.Buffer - bb.WriteByte(byte(assignCompressedConsumerOp)) - s2e := s2.NewWriter(&bb) - json.NewEncoder(s2e).Encode(cca) - s2e.Close() - return bb.Bytes() -} - -func decodeConsumerAssignmentCompressed(buf []byte) (*consumerAssignment, error) { - var ca consumerAssignment - bb := bytes.NewBuffer(buf) - s2d := s2.NewReader(bb) - return &ca, json.NewDecoder(s2d).Decode(&ca) -} - -var errBadStreamMsg = errors.New("jetstream cluster bad replicated stream msg") - -func decodeStreamMsg(buf []byte) (subject, reply string, hdr, msg []byte, lseq uint64, ts int64, sourced bool, err error) { - var le = binary.LittleEndian - if len(buf) < 26 { - return _EMPTY_, _EMPTY_, nil, nil, 0, 0, false, errBadStreamMsg - } - lseq = le.Uint64(buf) - buf = buf[8:] - ts = int64(le.Uint64(buf)) - buf = buf[8:] - sl := int(le.Uint16(buf)) - buf = buf[2:] - if len(buf) < sl { - return _EMPTY_, _EMPTY_, nil, nil, 0, 0, false, errBadStreamMsg - } - subject = string(buf[:sl]) - buf = buf[sl:] - if len(buf) < 2 { - return _EMPTY_, _EMPTY_, nil, nil, 0, 0, false, errBadStreamMsg - } - rl := int(le.Uint16(buf)) - buf = buf[2:] - if len(buf) < rl { - return _EMPTY_, _EMPTY_, nil, nil, 0, 0, false, errBadStreamMsg - } - reply = string(buf[:rl]) - buf = buf[rl:] - if len(buf) < 2 { - return _EMPTY_, _EMPTY_, nil, nil, 0, 0, false, errBadStreamMsg - } - hl := int(le.Uint16(buf)) - buf = buf[2:] - if len(buf) < hl { - return _EMPTY_, _EMPTY_, nil, nil, 0, 0, false, errBadStreamMsg - } - if hdr = buf[:hl]; len(hdr) == 0 { - hdr = nil - } - buf = buf[hl:] - if len(buf) < 4 { - return _EMPTY_, _EMPTY_, nil, nil, 0, 0, false, errBadStreamMsg - } - ml := int(le.Uint32(buf)) - buf = buf[4:] - if len(buf) < ml { - return _EMPTY_, _EMPTY_, nil, nil, 0, 0, false, errBadStreamMsg - } - if msg = buf[:ml]; len(msg) == 0 { - msg = nil - } - buf = buf[ml:] - if len(buf) > 0 { - flags, _ := binary.Uvarint(buf) - sourced = flags&msgFlagFromSourceOrMirror != 0 - } - return subject, reply, hdr, msg, lseq, ts, sourced, nil -} - -// Flags for encodeStreamMsg/decodeStreamMsg. -const ( - msgFlagFromSourceOrMirror uint64 = 1 << iota -) - -func encodeStreamMsg(subject, reply string, hdr, msg []byte, lseq uint64, ts int64, sourced bool) []byte { - return encodeStreamMsgAllowCompress(subject, reply, hdr, msg, lseq, ts, sourced) -} - -// Threshold for compression. -// TODO(dlc) - Eventually make configurable. -const compressThreshold = 8192 // 8k - -// If allowed and contents over the threshold we will compress. -func encodeStreamMsgAllowCompress(subject, reply string, hdr, msg []byte, lseq uint64, ts int64, sourced bool) []byte { - // Clip the subject, reply, header and msgs down. Operate on - // uint64 lengths to avoid overflowing. - slen := min(uint64(len(subject)), math.MaxUint16) - rlen := min(uint64(len(reply)), math.MaxUint16) - hlen := min(uint64(len(hdr)), math.MaxUint16) - mlen := min(uint64(len(msg)), math.MaxUint32) - total := slen + rlen + hlen + mlen - - shouldCompress := total > compressThreshold - elen := int(1 + 8 + 8 + total) - elen += (2 + 2 + 2 + 4 + 8) // Encoded lengths, 4bytes, flags are up to 8 bytes - - var flags uint64 - if sourced { - flags |= msgFlagFromSourceOrMirror - } - - buf := make([]byte, 1, elen) - buf[0] = byte(streamMsgOp) - - var le = binary.LittleEndian - buf = le.AppendUint64(buf, lseq) - buf = le.AppendUint64(buf, uint64(ts)) - buf = le.AppendUint16(buf, uint16(slen)) - buf = append(buf, subject[:slen]...) - buf = le.AppendUint16(buf, uint16(rlen)) - buf = append(buf, reply[:rlen]...) - buf = le.AppendUint16(buf, uint16(hlen)) - buf = append(buf, hdr[:hlen]...) - buf = le.AppendUint32(buf, uint32(mlen)) - buf = append(buf, msg[:mlen]...) - buf = binary.AppendUvarint(buf, flags) - - // Check if we should compress. - if shouldCompress { - nbuf := make([]byte, s2.MaxEncodedLen(elen)) - nbuf[0] = byte(compressedStreamMsgOp) - ebuf := s2.Encode(nbuf[1:], buf[1:]) - // Only pay the cost of decode on the other side if we compressed. - // S2 will allow us to try without major penalty for non-compressable data. - if len(ebuf) < len(buf) { - buf = nbuf[:len(ebuf)+1] - } - } - - return buf -} - -// Determine if all peers in our set support the binary snapshot. -func (mset *stream) supportsBinarySnapshot() bool { - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.supportsBinarySnapshotLocked() -} - -// Determine if all peers in our set support the binary snapshot. -// Lock should be held. -func (mset *stream) supportsBinarySnapshotLocked() bool { - s, n := mset.srv, mset.node - if s == nil || n == nil { - return false - } - // Grab our peers and walk them to make sure we can all support binary stream snapshots. - id, peers := n.ID(), n.Peers() - for _, p := range peers { - if p.ID == id { - // We know we support ourselves. - continue - } - // Since release 2.10.16 only deny if we know the other node does not support. - if sir, ok := s.nodeToInfo.Load(p.ID); ok && sir != nil && !sir.(nodeInfo).binarySnapshots { - return false - } - } - return true -} - -// StreamSnapshot is used for snapshotting and out of band catch up in clustered mode. -// Legacy, replace with binary stream snapshots. -type streamSnapshot struct { - Msgs uint64 `json:"messages"` - Bytes uint64 `json:"bytes"` - FirstSeq uint64 `json:"first_seq"` - LastSeq uint64 `json:"last_seq"` - Failed uint64 `json:"clfs"` - Deleted []uint64 `json:"deleted,omitempty"` -} - -// Grab a snapshot of a stream for clustered mode. -func (mset *stream) stateSnapshot() []byte { - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.stateSnapshotLocked() -} - -// Grab a snapshot of a stream for clustered mode. -// Lock should be held. -func (mset *stream) stateSnapshotLocked() []byte { - // Decide if we can support the new style of stream snapshots. - if mset.supportsBinarySnapshotLocked() { - snap, err := mset.store.EncodedStreamState(mset.getCLFS()) - if err != nil { - return nil - } - return snap - } - - // Older v1 version with deleted as a sorted []uint64. - state := mset.store.State() - snap := &streamSnapshot{ - Msgs: state.Msgs, - Bytes: state.Bytes, - FirstSeq: state.FirstSeq, - LastSeq: state.LastSeq, - Failed: mset.getCLFS(), - Deleted: state.Deleted, - } - b, _ := json.Marshal(snap) - return b -} - -// To warn when we are getting too far behind from what has been proposed vs what has been committed. -const streamLagWarnThreshold = 10_000 - -// processClusteredInboundMsg will propose the inbound message to the underlying raft group. -func (mset *stream) processClusteredInboundMsg(subject, reply string, hdr, msg []byte, mt *msgTrace, sourced bool) (retErr error) { - // For possible error response. - var response []byte - - mset.mu.RLock() - canRespond := !mset.cfg.NoAck && len(reply) > 0 - name, stype, store := mset.cfg.Name, mset.cfg.Storage, mset.store - s, js, jsa, st, r, tierName, outq, node := mset.srv, mset.js, mset.jsa, mset.cfg.Storage, mset.cfg.Replicas, mset.tier, mset.outq, mset.node - maxMsgSize, lseq := int(mset.cfg.MaxMsgSize), mset.lseq - interestPolicy, discard, maxMsgs, maxBytes := mset.cfg.Retention != LimitsPolicy, mset.cfg.Discard, mset.cfg.MaxMsgs, mset.cfg.MaxBytes - isLeader, isSealed, allowTTL := mset.isLeader(), mset.cfg.Sealed, mset.cfg.AllowMsgTTL - mset.mu.RUnlock() - - // This should not happen but possible now that we allow scale up, and scale down where this could trigger. - // - // We also invoke this in clustering mode for message tracing when not - // performing message delivery. - if node == nil || mt.traceOnly() { - return mset.processJetStreamMsg(subject, reply, hdr, msg, 0, 0, mt, sourced) - } - - // If message tracing (with message delivery), we will need to send the - // event on exit in case there was an error (if message was not proposed). - // Otherwise, the event will be sent from processJetStreamMsg when - // invoked by the leader (from applyStreamEntries). - if mt != nil { - defer func() { - if retErr != nil { - mt.sendEventFromJetStream(retErr) - } - }() - } - - // Check that we are the leader. This can be false if we have scaled up from an R1 that had inbound queued messages. - if !isLeader { - return NewJSClusterNotLeaderError() - } - - // Bail here if sealed. - if isSealed { - var resp = JSPubAckResponse{PubAck: &PubAck{Stream: mset.name()}, Error: NewJSStreamSealedError()} - b, _ := json.Marshal(resp) - mset.outq.sendMsg(reply, b) - return NewJSStreamSealedError() - } - - // Check here pre-emptively if we have exceeded this server limits. - if js.limitsExceeded(stype) { - s.resourcesExceededError() - if canRespond { - b, _ := json.Marshal(&JSPubAckResponse{PubAck: &PubAck{Stream: name}, Error: NewJSInsufficientResourcesError()}) - outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, b, nil, 0)) - } - // Stepdown regardless. - if node := mset.raftNode(); node != nil { - node.StepDown() - } - return NewJSInsufficientResourcesError() - } - - // Check here pre-emptively if we have exceeded our account limits. - if exceeded, err := jsa.wouldExceedLimits(st, tierName, r, subject, hdr, msg); exceeded { - if err == nil { - err = NewJSAccountResourcesExceededError() - } - s.RateLimitWarnf("JetStream account limits exceeded for '%s': %s", jsa.acc().GetName(), err.Error()) - if canRespond { - var resp = &JSPubAckResponse{PubAck: &PubAck{Stream: name}} - resp.Error = err - response, _ = json.Marshal(resp) - outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, response, nil, 0)) - } - return err - } - - // Check msgSize if we have a limit set there. Again this works if it goes through but better to be pre-emptive. - if maxMsgSize >= 0 && (len(hdr)+len(msg)) > maxMsgSize { - err := fmt.Errorf("JetStream message size exceeds limits for '%s > %s'", jsa.acc().Name, mset.cfg.Name) - s.RateLimitWarnf("%s", err.Error()) - if canRespond { - var resp = &JSPubAckResponse{PubAck: &PubAck{Stream: name}} - resp.Error = NewJSStreamMessageExceedsMaximumError() - response, _ = json.Marshal(resp) - outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, response, nil, 0)) - } - return err - } - - // Some header checks can be checked pre proposal. Most can not. - var msgId string - if len(hdr) > 0 { - // Since we encode header len as u16 make sure we do not exceed. - // Again this works if it goes through but better to be pre-emptive. - if len(hdr) > math.MaxUint16 { - err := fmt.Errorf("JetStream header size exceeds limits for '%s > %s'", jsa.acc().Name, mset.cfg.Name) - s.RateLimitWarnf("%s", err.Error()) - if canRespond { - var resp = &JSPubAckResponse{PubAck: &PubAck{Stream: name}} - resp.Error = NewJSStreamHeaderExceedsMaximumError() - response, _ = json.Marshal(resp) - outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, response, nil, 0)) - } - return err - } - // Expected stream name can also be pre-checked. - if sname := getExpectedStream(hdr); sname != _EMPTY_ && sname != name { - if canRespond { - var resp = &JSPubAckResponse{PubAck: &PubAck{Stream: name}} - resp.PubAck = &PubAck{Stream: name} - resp.Error = NewJSStreamNotMatchError() - b, _ := json.Marshal(resp) - outq.sendMsg(reply, b) - } - return errStreamMismatch - } - // Check for MsgIds here at the cluster level to avoid excessive CLFS accounting. - // Will help during restarts. - if msgId = getMsgId(hdr); msgId != _EMPTY_ { - mset.mu.Lock() - if dde := mset.checkMsgId(msgId); dde != nil { - var buf [256]byte - pubAck := append(buf[:0], mset.pubAck...) - seq := dde.seq - mset.mu.Unlock() - // Should not return an invalid sequence, in that case error. - if canRespond { - if seq > 0 { - response := append(pubAck, strconv.FormatUint(seq, 10)...) - response = append(response, ",\"duplicate\": true}"...) - outq.sendMsg(reply, response) - } else { - var resp = &JSPubAckResponse{PubAck: &PubAck{Stream: name}} - resp.Error = ApiErrors[JSStreamDuplicateMessageConflict] - b, _ := json.Marshal(resp) - outq.sendMsg(reply, b) - } - } - return errMsgIdDuplicate - } - // FIXME(dlc) - locking conflict with accessing mset.clseq - // For now we stage with zero, and will update in processStreamMsg. - mset.storeMsgIdLocked(&ddentry{msgId, 0, time.Now().UnixNano()}) - mset.mu.Unlock() - } - - // TTL'd messages are rejected entirely if TTLs are not enabled on the stream. - if ttl, _ := getMessageTTL(hdr); !sourced && ttl != 0 && !allowTTL { - if canRespond { - var resp = &JSPubAckResponse{PubAck: &PubAck{Stream: name}} - resp.Error = NewJSMessageTTLDisabledError() - b, _ := json.Marshal(resp) - outq.sendMsg(reply, b) - } - return errMsgTTLDisabled - } - } - - // Proceed with proposing this message. - - // We only use mset.clseq for clustering and in case we run ahead of actual commits. - // Check if we need to set initial value here - mset.clMu.Lock() - if mset.clseq == 0 || mset.clseq < lseq+mset.clfs { - // Re-capture - lseq = mset.lastSeq() - mset.clseq = lseq + mset.clfs - } - - // Check if we have an interest policy and discard new with max msgs or bytes. - // We need to deny here otherwise it could succeed on some peers and not others - // depending on consumer ack state. So we deny here, if we allow that means we know - // it would succeed on every peer. - if interestPolicy && discard == DiscardNew && (maxMsgs > 0 || maxBytes > 0) { - // Track inflight. - if mset.inflight == nil { - mset.inflight = make(map[uint64]uint64) - } - if stype == FileStorage { - mset.inflight[mset.clseq] = fileStoreMsgSize(subject, hdr, msg) - } else { - mset.inflight[mset.clseq] = memStoreMsgSize(subject, hdr, msg) - } - - var state StreamState - mset.store.FastState(&state) - - var err error - if maxMsgs > 0 && state.Msgs+uint64(len(mset.inflight)) > uint64(maxMsgs) { - err = ErrMaxMsgs - } else if maxBytes > 0 { - // TODO(dlc) - Could track this rollup independently. - var bytesPending uint64 - for _, nb := range mset.inflight { - bytesPending += nb - } - if state.Bytes+bytesPending > uint64(maxBytes) { - err = ErrMaxBytes - } - } - if err != nil { - delete(mset.inflight, mset.clseq) - mset.clMu.Unlock() - if canRespond { - var resp = &JSPubAckResponse{PubAck: &PubAck{Stream: name}} - resp.Error = NewJSStreamStoreFailedError(err, Unless(err)) - response, _ = json.Marshal(resp) - outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, response, nil, 0)) - } - return err - } - } - - if len(hdr) > 0 { - // Expected last sequence per subject. - if seq, exists := getExpectedLastSeqPerSubject(hdr); exists && store != nil { - // Allow override of the subject used for the check. - seqSubj := subject - if optSubj := getExpectedLastSeqPerSubjectForSubject(hdr); optSubj != _EMPTY_ { - seqSubj = optSubj - } - - // If subject is already in process, block as otherwise we could have multiple messages inflight with same subject. - if _, found := mset.expectedPerSubjectInProcess[seqSubj]; found { - // Could have set inflight above, cleanup here. - delete(mset.inflight, mset.clseq) - mset.clMu.Unlock() - if canRespond { - var resp = &JSPubAckResponse{PubAck: &PubAck{Stream: name}} - resp.PubAck = &PubAck{Stream: name} - resp.Error = NewJSStreamWrongLastSequenceConstantError() - b, _ := json.Marshal(resp) - outq.sendMsg(reply, b) - } - return fmt.Errorf("last sequence by subject mismatch") - } - - var smv StoreMsg - var fseq uint64 - sm, err := store.LoadLastMsg(seqSubj, &smv) - if sm != nil { - fseq = sm.seq - } - if err == ErrStoreMsgNotFound && seq == 0 { - fseq, err = 0, nil - } - if err != nil || fseq != seq { - // Could have set inflight above, cleanup here. - delete(mset.inflight, mset.clseq) - mset.clMu.Unlock() - if canRespond { - var resp = &JSPubAckResponse{PubAck: &PubAck{Stream: name}} - resp.PubAck = &PubAck{Stream: name} - resp.Error = NewJSStreamWrongLastSequenceError(fseq) - b, _ := json.Marshal(resp) - outq.sendMsg(reply, b) - } - return fmt.Errorf("last sequence by subject mismatch: %d vs %d", seq, fseq) - } - - // Track sequence and subject. - if mset.expectedPerSubjectSequence == nil { - mset.expectedPerSubjectSequence = make(map[uint64]string) - } - if mset.expectedPerSubjectInProcess == nil { - mset.expectedPerSubjectInProcess = make(map[string]struct{}) - } - mset.expectedPerSubjectSequence[mset.clseq] = seqSubj - mset.expectedPerSubjectInProcess[seqSubj] = struct{}{} - } - } - - esm := encodeStreamMsgAllowCompress(subject, reply, hdr, msg, mset.clseq, time.Now().UnixNano(), sourced) - var mtKey uint64 - if mt != nil { - mtKey = mset.clseq - if mset.mt == nil { - mset.mt = make(map[uint64]*msgTrace) - } - mset.mt[mtKey] = mt - } - - // Do proposal. - err := node.Propose(esm) - if err == nil { - mset.clseq++ - // If we are using the system account for NRG, add in the extra sent msgs and bytes to our account - // so that the end user / account owner has visibility. - if node.IsSystemAccount() && mset.acc != nil && r > 1 { - atomic.AddInt64(&mset.acc.outMsgs, int64(r-1)) - atomic.AddInt64(&mset.acc.outBytes, int64(len(esm)*(r-1))) - } - } - - // Check to see if we are being overrun. - // TODO(dlc) - Make this a limit where we drop messages to protect ourselves, but allow to be configured. - if mset.clseq-(lseq+mset.clfs) > streamLagWarnThreshold { - lerr := fmt.Errorf("JetStream stream '%s > %s' has high message lag", jsa.acc().Name, name) - s.RateLimitWarnf("%s", lerr.Error()) - } - mset.clMu.Unlock() - - if err != nil { - if mt != nil { - mset.getAndDeleteMsgTrace(mtKey) - } - if canRespond { - var resp = &JSPubAckResponse{PubAck: &PubAck{Stream: mset.cfg.Name}} - resp.Error = &ApiError{Code: 503, Description: err.Error()} - response, _ = json.Marshal(resp) - // If we errored out respond here. - outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, response, nil, 0)) - } - if isOutOfSpaceErr(err) { - s.handleOutOfSpace(mset) - } - } - - return err -} - -func (mset *stream) getAndDeleteMsgTrace(lseq uint64) *msgTrace { - if mset == nil { - return nil - } - mset.clMu.Lock() - mt, ok := mset.mt[lseq] - if ok { - delete(mset.mt, lseq) - } - mset.clMu.Unlock() - return mt -} - -// For requesting messages post raft snapshot to catch up streams post server restart. -// Any deleted msgs etc will be handled inline on catchup. -type streamSyncRequest struct { - Peer string `json:"peer,omitempty"` - FirstSeq uint64 `json:"first_seq"` - LastSeq uint64 `json:"last_seq"` - DeleteRangesOk bool `json:"delete_ranges"` - MinApplied uint64 `json:"min_applied"` -} - -// Given a stream state that represents a snapshot, calculate the sync request based on our current state. -// Stream lock must be held. -func (mset *stream) calculateSyncRequest(state *StreamState, snap *StreamReplicatedState, index uint64) *streamSyncRequest { - // Shouldn't happen, but consequences are pretty bad if we have the lock held and - // our caller tries to take the lock again on panic defer, as in processSnapshot. - if state == nil || snap == nil || mset.node == nil { - return nil - } - // Quick check if we are already caught up. - if state.LastSeq >= snap.LastSeq { - return nil - } - return &streamSyncRequest{FirstSeq: state.LastSeq + 1, LastSeq: snap.LastSeq, Peer: mset.node.ID(), DeleteRangesOk: true, MinApplied: index} -} - -// processSnapshotDeletes will update our current store based on the snapshot -// but only processing deletes and new FirstSeq / purges. -func (mset *stream) processSnapshotDeletes(snap *StreamReplicatedState) { - mset.mu.Lock() - var state StreamState - mset.store.FastState(&state) - // Always adjust if FirstSeq has moved beyond our state. - var didReset bool - if snap.FirstSeq > state.FirstSeq { - mset.store.Compact(snap.FirstSeq) - mset.store.FastState(&state) - mset.lseq = state.LastSeq - mset.clearAllPreAcksBelowFloor(state.FirstSeq) - didReset = true - } - s := mset.srv - mset.mu.Unlock() - - if didReset { - s.Warnf("Catchup for stream '%s > %s' resetting first sequence: %d on catchup request", - mset.account(), mset.name(), snap.FirstSeq) - } - - if len(snap.Deleted) > 0 { - mset.store.SyncDeleted(snap.Deleted) - } -} - -func (mset *stream) setCatchupPeer(peer string, lag uint64) { - if peer == _EMPTY_ { - return - } - mset.mu.Lock() - if mset.catchups == nil { - mset.catchups = make(map[string]uint64) - } - mset.catchups[peer] = lag - mset.mu.Unlock() -} - -// Will decrement by one. -func (mset *stream) updateCatchupPeer(peer string) { - if peer == _EMPTY_ { - return - } - mset.mu.Lock() - if lag := mset.catchups[peer]; lag > 0 { - mset.catchups[peer] = lag - 1 - } - mset.mu.Unlock() -} - -func (mset *stream) decrementCatchupPeer(peer string, num uint64) { - if peer == _EMPTY_ { - return - } - mset.mu.Lock() - if lag := mset.catchups[peer]; lag > 0 { - if lag >= num { - lag -= num - } else { - lag = 0 - } - mset.catchups[peer] = lag - } - mset.mu.Unlock() -} - -func (mset *stream) clearCatchupPeer(peer string) { - mset.mu.Lock() - if mset.catchups != nil { - delete(mset.catchups, peer) - } - mset.mu.Unlock() -} - -// Lock should be held. -func (mset *stream) clearAllCatchupPeers() { - if mset.catchups != nil { - mset.catchups = nil - } -} - -func (mset *stream) lagForCatchupPeer(peer string) uint64 { - mset.mu.RLock() - defer mset.mu.RUnlock() - if mset.catchups == nil { - return 0 - } - return mset.catchups[peer] -} - -func (mset *stream) hasCatchupPeers() bool { - mset.mu.RLock() - defer mset.mu.RUnlock() - return len(mset.catchups) > 0 -} - -func (mset *stream) setCatchingUp() { - mset.catchup.Store(true) -} - -func (mset *stream) clearCatchingUp() { - mset.catchup.Store(false) -} - -func (mset *stream) isCatchingUp() bool { - return mset.catchup.Load() -} - -// Determine if a non-leader is current. -// Lock should be held. -func (mset *stream) isCurrent() bool { - if mset.node == nil { - return true - } - return mset.node.Current() && !mset.catchup.Load() -} - -// Maximum requests for the whole server that can be in flight at the same time. -const maxConcurrentSyncRequests = 32 - -var ( - errCatchupCorruptSnapshot = errors.New("corrupt stream snapshot detected") - errCatchupStalled = errors.New("catchup stalled") - errCatchupStreamStopped = errors.New("stream has been stopped") // when a catchup is terminated due to the stream going away. - errCatchupBadMsg = errors.New("bad catchup msg") - errCatchupWrongSeqForSkip = errors.New("wrong sequence for skipped msg") - errCatchupAbortedNoLeader = errors.New("catchup aborted, no leader") - errCatchupTooManyRetries = errors.New("catchup failed, too many retries") -) - -// Process a stream snapshot. -func (mset *stream) processSnapshot(snap *StreamReplicatedState, index uint64) (e error) { - // Update any deletes, etc. - mset.processSnapshotDeletes(snap) - mset.setCLFS(snap.Failed) - - mset.mu.Lock() - var state StreamState - mset.store.FastState(&state) - sreq := mset.calculateSyncRequest(&state, snap, index) - - s, js, subject, n, st := mset.srv, mset.js, mset.sa.Sync, mset.node, mset.cfg.Storage - qname := fmt.Sprintf("[ACC:%s] stream '%s' snapshot", mset.acc.Name, mset.cfg.Name) - mset.mu.Unlock() - - // Bug that would cause this to be empty on stream update. - if subject == _EMPTY_ { - return errCatchupCorruptSnapshot - } - - // Just return if up to date or already exceeded limits. - if sreq == nil || js.limitsExceeded(st) { - return nil - } - - // Pause the apply channel for our raft group while we catch up. - if err := n.PauseApply(); err != nil { - return err - } - - defer func() { - // Don't bother resuming if server or stream is gone. - if e != errCatchupStreamStopped && e != ErrServerNotRunning { - n.ResumeApply() - } - }() - - // Set our catchup state. - mset.setCatchingUp() - defer mset.clearCatchingUp() - - var sub *subscription - var err error - - const ( - startInterval = 5 * time.Second - activityInterval = 30 * time.Second - ) - notActive := time.NewTimer(startInterval) - defer notActive.Stop() - - defer func() { - if sub != nil { - s.sysUnsubscribe(sub) - } - // Make sure any consumers are updated for the pending amounts. - mset.mu.Lock() - for _, o := range mset.consumers { - o.mu.Lock() - if o.isLeader() { - o.streamNumPending() - } - o.mu.Unlock() - } - mset.mu.Unlock() - - // If we are interest based make sure to check our ack floor state. - // We will delay a bit to allow consumer states to also catchup. - if mset.isInterestRetention() { - fire := time.Duration(rand.Intn(10)+5) * time.Second - time.AfterFunc(fire, mset.checkInterestState) - } - }() - - var releaseSem bool - releaseSyncOutSem := func() { - if !releaseSem { - return - } - // Need to use select for the server shutdown case. - select { - case s.syncOutSem <- struct{}{}: - default: - } - releaseSem = false - } - // On exit, we will release our semaphore if we acquired it. - defer releaseSyncOutSem() - - // Do not let this go on forever. - const maxRetries = 3 - var numRetries int - -RETRY: - // On retry, we need to release the semaphore we got. Call will be no-op - // if releaseSem boolean has not been set to true on successfully getting - // the semaphore. - releaseSyncOutSem() - - if n.Leaderless() { - // Prevent us from spinning if we've installed a snapshot from a leader but there's no leader online. - // We wait a bit to check if a leader has come online in the meantime, if so we can continue. - var canContinue bool - if numRetries == 0 { - time.Sleep(startInterval) - canContinue = !n.Leaderless() - } - if !canContinue { - return fmt.Errorf("%w for stream '%s > %s'", errCatchupAbortedNoLeader, mset.account(), mset.name()) - } - } - - // If we have a sub clear that here. - if sub != nil { - s.sysUnsubscribe(sub) - sub = nil - } - - if !s.isRunning() { - return ErrServerNotRunning - } - - numRetries++ - if numRetries > maxRetries { - // Force a hard reset here. - return errCatchupTooManyRetries - } - - // Block here if we have too many requests in flight. - <-s.syncOutSem - releaseSem = true - - // We may have been blocked for a bit, so the reset needs to ensure that we - // consume the already fired timer. - if !notActive.Stop() { - select { - case <-notActive.C: - default: - } - } - notActive.Reset(startInterval) - - // Grab sync request again on failures. - if sreq == nil { - mset.mu.RLock() - var state StreamState - mset.store.FastState(&state) - sreq = mset.calculateSyncRequest(&state, snap, index) - mset.mu.RUnlock() - if sreq == nil { - return nil - } - } - - // Used to transfer message from the wire to another Go routine internally. - type im struct { - msg []byte - reply string - } - // This is used to notify the leader that it should stop the runCatchup - // because we are either bailing out or going to retry due to an error. - notifyLeaderStopCatchup := func(mrec *im, err error) { - if mrec.reply == _EMPTY_ { - return - } - s.sendInternalMsgLocked(mrec.reply, _EMPTY_, nil, err.Error()) - } - - msgsQ := newIPQueue[*im](s, qname) - defer msgsQ.unregister() - - // Send our catchup request here. - reply := syncReplySubject() - sub, err = s.sysSubscribe(reply, func(_ *subscription, _ *client, _ *Account, _, reply string, msg []byte) { - // Make copy since we are using a buffer from the inbound client/route. - msgsQ.push(&im{copyBytes(msg), reply}) - }) - if err != nil { - s.Errorf("Could not subscribe to stream catchup: %v", err) - goto RETRY - } - - // Send our sync request. - b, _ := json.Marshal(sreq) - s.sendInternalMsgLocked(subject, reply, nil, b) - - // Remember when we sent this out to avoid loop spins on errors below. - reqSendTime := time.Now() - - // Clear our sync request. - sreq = nil - - // Run our own select loop here. - for qch, lch := n.QuitC(), n.LeadChangeC(); ; { - select { - case <-msgsQ.ch: - notActive.Reset(activityInterval) - - mrecs := msgsQ.pop() - for _, mrec := range mrecs { - msg := mrec.msg - // Check for eof signaling. - if len(msg) == 0 { - msgsQ.recycle(&mrecs) - return nil - } - if _, err := mset.processCatchupMsg(msg); err == nil { - if mrec.reply != _EMPTY_ { - s.sendInternalMsgLocked(mrec.reply, _EMPTY_, nil, nil) - } - } else if isOutOfSpaceErr(err) { - notifyLeaderStopCatchup(mrec, err) - msgsQ.recycle(&mrecs) - return err - } else if err == NewJSInsufficientResourcesError() { - notifyLeaderStopCatchup(mrec, err) - if mset.js.limitsExceeded(mset.cfg.Storage) { - s.resourcesExceededError() - } else { - s.Warnf("Catchup for stream '%s > %s' errored, account resources exceeded: %v", mset.account(), mset.name(), err) - } - msgsQ.recycle(&mrecs) - return err - } else { - notifyLeaderStopCatchup(mrec, err) - s.Warnf("Catchup for stream '%s > %s' errored, will retry: %v", mset.account(), mset.name(), err) - msgsQ.recycle(&mrecs) - - // Make sure we do not spin and make things worse. - const minRetryWait = 2 * time.Second - elapsed := time.Since(reqSendTime) - if elapsed < minRetryWait { - select { - case <-s.quitCh: - return ErrServerNotRunning - case <-qch: - return errCatchupStreamStopped - case <-time.After(minRetryWait - elapsed): - } - } - goto RETRY - } - } - notActive.Reset(activityInterval) - msgsQ.recycle(&mrecs) - case <-notActive.C: - if mrecs := msgsQ.pop(); len(mrecs) > 0 { - mrec := mrecs[0] - notifyLeaderStopCatchup(mrec, errCatchupStalled) - msgsQ.recycle(&mrecs) - } - s.Warnf("Catchup for stream '%s > %s' stalled", mset.account(), mset.name()) - goto RETRY - case <-s.quitCh: - return ErrServerNotRunning - case <-qch: - return errCatchupStreamStopped - case isLeader := <-lch: - if isLeader { - n.StepDown() - goto RETRY - } - } - } -} - -// processCatchupMsg will be called to process out of band catchup msgs from a sync request. -func (mset *stream) processCatchupMsg(msg []byte) (uint64, error) { - if len(msg) == 0 { - return 0, errCatchupBadMsg - } - op := entryOp(msg[0]) - if op != streamMsgOp && op != compressedStreamMsgOp && op != deleteRangeOp { - return 0, errCatchupBadMsg - } - - mbuf := msg[1:] - if op == deleteRangeOp { - dr, err := decodeDeleteRange(mbuf) - if err != nil { - return 0, errCatchupBadMsg - } - // Handle the delete range. - // Make sure the sequences match up properly. - mset.mu.Lock() - if len(mset.preAcks) > 0 { - for seq := dr.First; seq < dr.First+dr.Num; seq++ { - mset.clearAllPreAcks(seq) - } - } - if err = mset.store.SkipMsgs(dr.First, dr.Num); err != nil { - mset.mu.Unlock() - return 0, errCatchupWrongSeqForSkip - } - mset.lseq = dr.First + dr.Num - 1 - lseq := mset.lseq - mset.mu.Unlock() - return lseq, nil - } - - if op == compressedStreamMsgOp { - var err error - mbuf, err = s2.Decode(nil, mbuf) - if err != nil { - panic(err.Error()) - } - } - - subj, _, hdr, msg, seq, ts, _, err := decodeStreamMsg(mbuf) - if err != nil { - return 0, errCatchupBadMsg - } - - mset.mu.Lock() - st := mset.cfg.Storage - ddloaded := mset.ddloaded - if mset.hasAllPreAcks(seq, subj) { - mset.clearAllPreAcks(seq) - // Mark this to be skipped - subj, ts = _EMPTY_, 0 - } - mset.mu.Unlock() - - // Since we're clustered we do not want to check limits based on tier here and possibly introduce skew. - if mset.js.limitsExceeded(st) { - return 0, NewJSInsufficientResourcesError() - } - - // Find the message TTL if any. - // TODO(nat): If the TTL isn't valid by this stage then there isn't really a - // lot we can do about it, as we'd break the catchup if we reject the message. - ttl, _ := getMessageTTL(hdr) - - // Put into our store - // Messages to be skipped have no subject or timestamp. - // TODO(dlc) - formalize with skipMsgOp - if subj == _EMPTY_ && ts == 0 { - if lseq := mset.store.SkipMsg(); lseq != seq { - return 0, errCatchupWrongSeqForSkip - } - } else if err := mset.store.StoreRawMsg(subj, hdr, msg, seq, ts, ttl); err != nil { - return 0, err - } - - mset.mu.Lock() - defer mset.mu.Unlock() - // Update our lseq. - mset.setLastSeq(seq) - - // Check for MsgId and if we have one here make sure to update our internal map. - if len(hdr) > 0 { - if msgId := getMsgId(hdr); msgId != _EMPTY_ { - if !ddloaded { - mset.rebuildDedupe() - } - mset.storeMsgIdLocked(&ddentry{msgId, seq, ts}) - } - } - - return seq, nil -} - -func (mset *stream) handleClusterSyncRequest(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) { - var sreq streamSyncRequest - if err := json.Unmarshal(msg, &sreq); err != nil { - // Log error. - return - } - mset.srv.startGoRoutine(func() { mset.runCatchup(reply, &sreq) }) -} - -// Lock should be held. -func (js *jetStream) offlineClusterInfo(rg *raftGroup) *ClusterInfo { - s := js.srv - - ci := &ClusterInfo{Name: s.ClusterName(), RaftGroup: rg.Name} - for _, peer := range rg.Peers { - if sir, ok := s.nodeToInfo.Load(peer); ok && sir != nil { - si := sir.(nodeInfo) - pi := &PeerInfo{Peer: peer, Name: si.name, Current: false, Offline: true} - ci.Replicas = append(ci.Replicas, pi) - } - } - return ci -} - -// clusterInfo will report on the status of the raft group. -func (js *jetStream) clusterInfo(rg *raftGroup) *ClusterInfo { - if js == nil { - return nil - } - js.mu.RLock() - defer js.mu.RUnlock() - - s := js.srv - if rg == nil || rg.node == nil { - return &ClusterInfo{ - Name: s.cachedClusterName(), - Leader: s.Name(), - } - } - - n := rg.node - ci := &ClusterInfo{ - Name: s.cachedClusterName(), - Leader: s.serverNameForNode(n.GroupLeader()), - RaftGroup: rg.Name, - } - - now := time.Now() - id, peers := n.ID(), n.Peers() - - // If we are leaderless, do not suppress putting us in the peer list. - if ci.Leader == _EMPTY_ { - id = _EMPTY_ - } - - for _, rp := range peers { - if rp.ID != id && rg.isMember(rp.ID) { - var lastSeen time.Duration - if now.After(rp.Last) && rp.Last.Unix() != 0 { - lastSeen = now.Sub(rp.Last) - } - current := rp.Current - if current && lastSeen > lostQuorumInterval { - current = false - } - // Create a peer info with common settings if the peer has not been seen - // yet (which can happen after the whole cluster is stopped and only some - // of the nodes are restarted). - pi := &PeerInfo{ - Current: current, - Offline: true, - Active: lastSeen, - Lag: rp.Lag, - Peer: rp.ID, - } - // If node is found, complete/update the settings. - if sir, ok := s.nodeToInfo.Load(rp.ID); ok && sir != nil { - si := sir.(nodeInfo) - pi.Name, pi.Offline, pi.cluster = si.name, si.offline, si.cluster - } else { - // If not, then add a name that indicates that the server name - // is unknown at this time, and clear the lag since it is misleading - // (the node may not have that much lag). - // Note: We return now the Peer ID in PeerInfo, so the "(peerID: %s)" - // would technically not be required, but keeping it for now. - pi.Name, pi.Lag = fmt.Sprintf("Server name unknown at this time (peerID: %s)", rp.ID), 0 - } - ci.Replicas = append(ci.Replicas, pi) - } - } - // Order the result based on the name so that we get something consistent - // when doing repeated stream info in the CLI, etc... - slices.SortFunc(ci.Replicas, func(i, j *PeerInfo) int { return cmp.Compare(i.Name, j.Name) }) - return ci -} - -func (mset *stream) checkClusterInfo(ci *ClusterInfo) { - for _, r := range ci.Replicas { - peer := getHash(r.Name) - if lag := mset.lagForCatchupPeer(peer); lag > 0 { - r.Current = false - r.Lag = lag - } - } -} - -// Return a list of alternates, ranked by preference order to the request, of stream mirrors. -// This allows clients to select or get more information about read replicas that could be a -// better option to connect to versus the original source. -func (js *jetStream) streamAlternates(ci *ClientInfo, stream string) []StreamAlternate { - if js == nil { - return nil - } - - js.mu.RLock() - defer js.mu.RUnlock() - - s, cc := js.srv, js.cluster - // Track our domain. - domain := s.getOpts().JetStreamDomain - - // No clustering just return nil. - if cc == nil { - return nil - } - acc, _ := s.LookupAccount(ci.serviceAccount()) - if acc == nil { - return nil - } - - // Collect our ordering first for clusters. - weights := make(map[string]int) - all := []string{ci.Cluster} - all = append(all, ci.Alternates...) - - for i := 0; i < len(all); i++ { - weights[all[i]] = len(all) - i - } - - var alts []StreamAlternate - for _, sa := range cc.streams[acc.Name] { - // Add in ourselves and any mirrors. - if sa.Config.Name == stream || (sa.Config.Mirror != nil && sa.Config.Mirror.Name == stream) { - alts = append(alts, StreamAlternate{Name: sa.Config.Name, Domain: domain, Cluster: sa.Group.Cluster}) - } - } - // If just us don't fill in. - if len(alts) == 1 { - return nil - } - - // Sort based on our weights that originate from the request itself. - // reverse sort - slices.SortFunc(alts, func(i, j StreamAlternate) int { return -cmp.Compare(weights[i.Cluster], weights[j.Cluster]) }) - - return alts -} - -// Internal request for stream info, this is coming on the wire so do not block here. -func (mset *stream) handleClusterStreamInfoRequest(_ *subscription, c *client, _ *Account, subject, reply string, _ []byte) { - go mset.processClusterStreamInfoRequest(reply) -} - -func (mset *stream) processClusterStreamInfoRequest(reply string) { - mset.mu.RLock() - sysc, js, sa, config := mset.sysc, mset.srv.js.Load(), mset.sa, mset.cfg - isLeader := mset.isLeader() - mset.mu.RUnlock() - - // By design all members will receive this. Normally we only want the leader answering. - // But if we have stalled and lost quorom all can respond. - if sa != nil && !js.isGroupLeaderless(sa.Group) && !isLeader { - return - } - - // If we are not the leader let someone else possibly respond first. - if !isLeader { - time.Sleep(500 * time.Millisecond) - } - - si := &StreamInfo{ - Created: mset.createdTime(), - State: mset.state(), - Config: config, - Cluster: js.clusterInfo(mset.raftGroup()), - Sources: mset.sourcesInfo(), - Mirror: mset.mirrorInfo(), - TimeStamp: time.Now().UTC(), - } - - // Check for out of band catchups. - if mset.hasCatchupPeers() { - mset.checkClusterInfo(si.Cluster) - } - - sysc.sendInternalMsg(reply, _EMPTY_, nil, si) -} - -// 64MB for now, for the total server. This is max we will blast out if asked to -// do so to another server for purposes of catchups. -// This number should be ok on 1Gbit interface. -const defaultMaxTotalCatchupOutBytes = int64(64 * 1024 * 1024) - -// Current total outstanding catchup bytes. -func (s *Server) gcbTotal() int64 { - s.gcbMu.RLock() - defer s.gcbMu.RUnlock() - return s.gcbOut -} - -// Returns true if Current total outstanding catchup bytes is below -// the maximum configured. -func (s *Server) gcbBelowMax() bool { - s.gcbMu.RLock() - defer s.gcbMu.RUnlock() - return s.gcbOut <= s.gcbOutMax -} - -// Adds `sz` to the server's total outstanding catchup bytes and to `localsz` -// under the gcbMu lock. The `localsz` points to the local outstanding catchup -// bytes of the runCatchup go routine of a given stream. -func (s *Server) gcbAdd(localsz *int64, sz int64) { - s.gcbMu.Lock() - atomic.AddInt64(localsz, sz) - s.gcbOut += sz - if s.gcbOut >= s.gcbOutMax && s.gcbKick == nil { - s.gcbKick = make(chan struct{}) - } - s.gcbMu.Unlock() -} - -// Removes `sz` from the server's total outstanding catchup bytes and from -// `localsz`, but only if `localsz` is non 0, which would signal that gcSubLast -// has already been invoked. See that function for details. -// Must be invoked under the gcbMu lock. -func (s *Server) gcbSubLocked(localsz *int64, sz int64) { - if atomic.LoadInt64(localsz) == 0 { - return - } - atomic.AddInt64(localsz, -sz) - s.gcbOut -= sz - if s.gcbKick != nil && s.gcbOut < s.gcbOutMax { - close(s.gcbKick) - s.gcbKick = nil - } -} - -// Locked version of gcbSubLocked() -func (s *Server) gcbSub(localsz *int64, sz int64) { - s.gcbMu.Lock() - s.gcbSubLocked(localsz, sz) - s.gcbMu.Unlock() -} - -// Similar to gcbSub() but reset `localsz` to 0 at the end under the gcbMu lock. -// This will signal further calls to gcbSub() for this `localsz` pointer that -// nothing should be done because runCatchup() has exited and any remaining -// outstanding bytes value has already been decremented. -func (s *Server) gcbSubLast(localsz *int64) { - s.gcbMu.Lock() - s.gcbSubLocked(localsz, *localsz) - *localsz = 0 - s.gcbMu.Unlock() -} - -// Returns our kick chan, or nil if it does not exist. -func (s *Server) cbKickChan() <-chan struct{} { - s.gcbMu.RLock() - defer s.gcbMu.RUnlock() - return s.gcbKick -} - -func (mset *stream) runCatchup(sendSubject string, sreq *streamSyncRequest) { - s := mset.srv - defer s.grWG.Done() - - const maxOutBytes = int64(64 * 1024 * 1024) // 64MB for now, these are all internal, from server to server - const maxOutMsgs = int32(256 * 1024) // 256k in case we have lots of small messages or skip msgs. - outb := int64(0) - outm := int32(0) - - // On abnormal exit make sure to update global total. - defer s.gcbSubLast(&outb) - - // Flow control processing. - ackReplySize := func(subj string) int64 { - if li := strings.LastIndexByte(subj, btsep); li > 0 && li < len(subj) { - return parseAckReplyNum(subj[li+1:]) - } - return 0 - } - - nextBatchC := make(chan struct{}, 4) - nextBatchC <- struct{}{} - remoteQuitCh := make(chan struct{}) - - const activityInterval = 30 * time.Second - notActive := time.NewTimer(activityInterval) - defer notActive.Stop() - - // Setup ackReply for flow control. - ackReply := syncAckSubject() - ackSub, _ := s.sysSubscribe(ackReply, func(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) { - if len(msg) > 0 { - s.Warnf("Catchup for stream '%s > %s' was aborted on the remote due to: %q", - mset.account(), mset.name(), msg) - s.sysUnsubscribe(sub) - close(remoteQuitCh) - return - } - sz := ackReplySize(subject) - s.gcbSub(&outb, sz) - atomic.AddInt32(&outm, -1) - mset.updateCatchupPeer(sreq.Peer) - // Kick ourselves and anyone else who might have stalled on global state. - select { - case nextBatchC <- struct{}{}: - default: - } - // Reset our activity - notActive.Reset(activityInterval) - }) - defer s.sysUnsubscribe(ackSub) - ackReplyT := strings.ReplaceAll(ackReply, ".*", ".%d") - - // Grab our state. - var state StreamState - // mset.store never changes after being set, don't need lock. - mset.store.FastState(&state) - - // Setup sequences to walk through. - seq, last := sreq.FirstSeq, sreq.LastSeq - - // The follower received a snapshot from another leader, and we've become leader since. - // We have an up-to-date log but could be behind on applies. We must wait until we've reached the minimum required. - // The follower will automatically retry after a timeout, so we can safely return here. - if node := mset.raftNode(); node != nil { - index, _, applied := node.Progress() - // Only skip if our log has enough entries, and they could be applied in the future. - if index >= sreq.MinApplied && applied < sreq.MinApplied { - return - } - // We know here we've either applied enough entries, or our log doesn't have enough entries. - // In the latter case the request expects us to have more. Just continue and value availability here. - // This should only be possible if the logs have already desynced, and we shouldn't have become leader - // in the first place. Not much we can do here in this (hypothetical) scenario. - } - - mset.setCatchupPeer(sreq.Peer, last-seq) - - var spb int - const minWait = 5 * time.Second - - sendNextBatchAndContinue := func(qch chan struct{}) bool { - // Check if we know we will not enter the loop because we are done. - if seq > last { - s.Noticef("Catchup for stream '%s > %s' complete", mset.account(), mset.name()) - // EOF - s.sendInternalMsgLocked(sendSubject, _EMPTY_, nil, nil) - return false - } - - // If we already sent a batch, we will try to make sure we can at least send a minimum - // batch before sending the next batch. - if spb > 0 { - // Wait til we can send at least 4k - const minBatchWait = int32(4 * 1024) - mw := time.NewTimer(minWait) - for done := maxOutMsgs-atomic.LoadInt32(&outm) > minBatchWait; !done; { - select { - case <-nextBatchC: - done = maxOutMsgs-atomic.LoadInt32(&outm) > minBatchWait - if !done { - // Wait for a small bit. - time.Sleep(100 * time.Millisecond) - } else { - // GC friendly. - mw.Stop() - } - case <-mw.C: - done = true - case <-s.quitCh: - return false - case <-qch: - return false - case <-remoteQuitCh: - return false - } - } - spb = 0 - } - - // Send an encoded msg. - sendEM := func(em []byte) { - // Place size in reply subject for flow control. - l := int64(len(em)) - reply := fmt.Sprintf(ackReplyT, l) - s.gcbAdd(&outb, l) - atomic.AddInt32(&outm, 1) - s.sendInternalMsgLocked(sendSubject, reply, nil, em) - spb++ - } - - // If we support gap markers. - var dr DeleteRange - drOk := sreq.DeleteRangesOk - - // Will send our delete range. - // Should already be checked for being valid. - sendDR := func() { - if dr.Num == 1 { - // Send like a normal skip msg. - sendEM(encodeStreamMsg(_EMPTY_, _EMPTY_, nil, nil, dr.First, 0, false)) - } else { - // We have a run, send a gap record. We send these without reply or tracking. - s.sendInternalMsgLocked(sendSubject, _EMPTY_, nil, encodeDeleteRange(&dr)) - // Clear out the pending for catchup. - mset.decrementCatchupPeer(sreq.Peer, dr.Num) - } - // Reset always. - dr.First, dr.Num = 0, 0 - } - - // See if we should use LoadNextMsg instead of walking sequence by sequence if we have an order magnitude more interior deletes. - // Only makes sense with delete range capabilities. - useLoadNext := drOk && (uint64(state.NumDeleted) > 10*state.Msgs) - - var smv StoreMsg - for ; seq <= last && atomic.LoadInt64(&outb) <= maxOutBytes && atomic.LoadInt32(&outm) <= maxOutMsgs && s.gcbBelowMax(); seq++ { - var sm *StoreMsg - var err error - // If we should use load next do so here. - if useLoadNext { - var nseq uint64 - sm, nseq, err = mset.store.LoadNextMsg(fwcs, true, seq, &smv) - if err == nil && nseq > seq { - // If we jumped over the requested last sequence, clamp it down. - // Otherwise, we would send too much to the follower. - if nseq > last { - nseq = last - sm = nil - } - dr.First, dr.Num = seq, nseq-seq - // Jump ahead - seq = nseq - } else if err == ErrStoreEOF { - dr.First, dr.Num = seq, last-seq - // Clear EOF here for normal processing. - err = nil - // Jump ahead - seq = last - } - } else { - sm, err = mset.store.LoadMsg(seq, &smv) - } - - // if this is not a deleted msg, bail out. - if err != nil && err != ErrStoreMsgNotFound && err != errDeletedMsg { - if err == ErrStoreEOF { - var state StreamState - mset.store.FastState(&state) - if seq > state.LastSeq { - // The snapshot has a larger last sequence then we have. This could be due to a truncation - // when trying to recover after corruption, still not 100% sure. Could be off by 1 too somehow, - // but tested a ton of those with no success. - s.Warnf("Catchup for stream '%s > %s' completed, but requested sequence %d was larger then current state: %+v", - mset.account(), mset.name(), seq, state) - // Try our best to redo our invalidated snapshot as well. - if n := mset.raftNode(); n != nil { - if snap := mset.stateSnapshot(); snap != nil { - n.InstallSnapshot(snap) - } - } - // If we allow gap markers check if we have one pending. - if drOk && dr.First > 0 { - sendDR() - } - // Signal EOF - s.sendInternalMsgLocked(sendSubject, _EMPTY_, nil, nil) - return false - } - } - s.Warnf("Error loading message for catchup '%s > %s': %v", mset.account(), mset.name(), err) - return false - } - - if sm != nil { - // If we allow gap markers check if we have one pending. - if drOk && dr.First > 0 { - sendDR() - } - // Send the normal message now. - sendEM(encodeStreamMsgAllowCompress(sm.subj, _EMPTY_, sm.hdr, sm.msg, sm.seq, sm.ts, false)) - } else { - if drOk { - if dr.First == 0 { - dr.First, dr.Num = seq, 1 - } else { - dr.Num++ - } - } else { - // Skip record for deleted msg. - sendEM(encodeStreamMsg(_EMPTY_, _EMPTY_, nil, nil, seq, 0, false)) - } - } - - // Check if we are done. - if seq == last { - // Need to see if we have a pending delete range. - if drOk && dr.First > 0 { - sendDR() - } - s.Noticef("Catchup for stream '%s > %s' complete", mset.account(), mset.name()) - // EOF - s.sendInternalMsgLocked(sendSubject, _EMPTY_, nil, nil) - return false - } - select { - case <-remoteQuitCh: - return false - default: - } - } - if drOk && dr.First > 0 { - sendDR() - } - return true - } - - // Check is this stream got closed. - mset.mu.RLock() - qch := mset.qch - mset.mu.RUnlock() - if qch == nil { - return - } - - // Run as long as we are still active and need catchup. - // FIXME(dlc) - Purge event? Stream delete? - for { - // Get this each time, will be non-nil if globally blocked and we will close to wake everyone up. - cbKick := s.cbKickChan() - - select { - case <-s.quitCh: - return - case <-qch: - return - case <-remoteQuitCh: - mset.clearCatchupPeer(sreq.Peer) - return - case <-notActive.C: - s.Warnf("Catchup for stream '%s > %s' stalled", mset.account(), mset.name()) - mset.clearCatchupPeer(sreq.Peer) - return - case <-nextBatchC: - if !sendNextBatchAndContinue(qch) { - mset.clearCatchupPeer(sreq.Peer) - return - } - case <-cbKick: - if !sendNextBatchAndContinue(qch) { - mset.clearCatchupPeer(sreq.Peer) - return - } - case <-time.After(500 * time.Millisecond): - if !sendNextBatchAndContinue(qch) { - mset.clearCatchupPeer(sreq.Peer) - return - } - } - } -} - -const jscAllSubj = "$JSC.>" - -func syncSubjForStream() string { - return syncSubject("$JSC.SYNC") -} - -func syncReplySubject() string { - return syncSubject("$JSC.R") -} - -func infoReplySubject() string { - return syncSubject("$JSC.R") -} - -func syncAckSubject() string { - return syncSubject("$JSC.ACK") + ".*" -} - -func syncSubject(pre string) string { - var sb strings.Builder - sb.WriteString(pre) - sb.WriteByte(btsep) - - var b [replySuffixLen]byte - rn := rand.Int63() - for i, l := 0, rn; i < len(b); i++ { - b[i] = digits[l%base] - l /= base - } - - sb.Write(b[:]) - return sb.String() -} - -const ( - clusterStreamInfoT = "$JSC.SI.%s.%s" - clusterConsumerInfoT = "$JSC.CI.%s.%s.%s" - jsaUpdatesSubT = "$JSC.ARU.%s.*" - jsaUpdatesPubT = "$JSC.ARU.%s.%s" -) diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_errors.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_errors.go deleted file mode 100644 index 5a81aa67..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_errors.go +++ /dev/null @@ -1,102 +0,0 @@ -package server - -import ( - "fmt" -) - -type errOpts struct { - err error -} - -// ErrorOption configures a NATS Error helper -type ErrorOption func(*errOpts) - -// Unless ensures that if err is a ApiErr that err will be returned rather than the one being created via the helper -func Unless(err error) ErrorOption { - return func(opts *errOpts) { - opts.err = err - } -} - -func parseOpts(opts []ErrorOption) *errOpts { - eopts := &errOpts{} - for _, opt := range opts { - opt(eopts) - } - return eopts -} - -type ErrorIdentifier uint16 - -// IsNatsErr determines if an error matches ID, if multiple IDs are given if the error matches any of these the function will be true -func IsNatsErr(err error, ids ...ErrorIdentifier) bool { - if err == nil { - return false - } - - ce, ok := err.(*ApiError) - if !ok || ce == nil { - return false - } - - for _, id := range ids { - ae, ok := ApiErrors[id] - if !ok || ae == nil { - continue - } - - if ce.ErrCode == ae.ErrCode { - return true - } - } - - return false -} - -// ApiError is included in all responses if there was an error. -type ApiError struct { - Code int `json:"code"` - ErrCode uint16 `json:"err_code,omitempty"` - Description string `json:"description,omitempty"` -} - -// ErrorsData is the source data for generated errors as found in errors.json -type ErrorsData struct { - Constant string `json:"constant"` - Code int `json:"code"` - ErrCode uint16 `json:"error_code"` - Description string `json:"description"` - Comment string `json:"comment"` - Help string `json:"help"` - URL string `json:"url"` - Deprecates string `json:"deprecates"` -} - -func (e *ApiError) Error() string { - return fmt.Sprintf("%s (%d)", e.Description, e.ErrCode) -} - -func (e *ApiError) toReplacerArgs(replacements []any) []string { - var ( - ra []string - key string - ) - - for i, replacement := range replacements { - if i%2 == 0 { - key = replacement.(string) - continue - } - - switch v := replacement.(type) { - case string: - ra = append(ra, key, v) - case error: - ra = append(ra, key, v.Error()) - default: - ra = append(ra, key, fmt.Sprintf("%v", v)) - } - } - - return ra -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_errors_generated.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_errors_generated.go deleted file mode 100644 index 30ed884e..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_errors_generated.go +++ /dev/null @@ -1,2628 +0,0 @@ -// Generated code, do not edit. See errors.json and run go generate to update - -package server - -import "strings" - -const ( - // JSAccountResourcesExceededErr resource limits exceeded for account - JSAccountResourcesExceededErr ErrorIdentifier = 10002 - - // JSBadRequestErr bad request - JSBadRequestErr ErrorIdentifier = 10003 - - // JSClusterIncompleteErr incomplete results - JSClusterIncompleteErr ErrorIdentifier = 10004 - - // JSClusterNoPeersErrF Error causing no peers to be available ({err}) - JSClusterNoPeersErrF ErrorIdentifier = 10005 - - // JSClusterNotActiveErr JetStream not in clustered mode - JSClusterNotActiveErr ErrorIdentifier = 10006 - - // JSClusterNotAssignedErr JetStream cluster not assigned to this server - JSClusterNotAssignedErr ErrorIdentifier = 10007 - - // JSClusterNotAvailErr JetStream system temporarily unavailable - JSClusterNotAvailErr ErrorIdentifier = 10008 - - // JSClusterNotLeaderErr JetStream cluster can not handle request - JSClusterNotLeaderErr ErrorIdentifier = 10009 - - // JSClusterPeerNotMemberErr peer not a member - JSClusterPeerNotMemberErr ErrorIdentifier = 10040 - - // JSClusterRequiredErr JetStream clustering support required - JSClusterRequiredErr ErrorIdentifier = 10010 - - // JSClusterServerNotMemberErr server is not a member of the cluster - JSClusterServerNotMemberErr ErrorIdentifier = 10044 - - // JSClusterTagsErr tags placement not supported for operation - JSClusterTagsErr ErrorIdentifier = 10011 - - // JSClusterUnSupportFeatureErr not currently supported in clustered mode - JSClusterUnSupportFeatureErr ErrorIdentifier = 10036 - - // JSConsumerAlreadyExists action CREATE is used for a existing consumer with a different config (consumer already exists) - JSConsumerAlreadyExists ErrorIdentifier = 10148 - - // JSConsumerBadDurableNameErr durable name can not contain '.', '*', '>' - JSConsumerBadDurableNameErr ErrorIdentifier = 10103 - - // JSConsumerConfigRequiredErr consumer config required - JSConsumerConfigRequiredErr ErrorIdentifier = 10078 - - // JSConsumerCreateDurableAndNameMismatch Consumer Durable and Name have to be equal if both are provided - JSConsumerCreateDurableAndNameMismatch ErrorIdentifier = 10132 - - // JSConsumerCreateErrF General consumer creation failure string ({err}) - JSConsumerCreateErrF ErrorIdentifier = 10012 - - // JSConsumerCreateFilterSubjectMismatchErr Consumer create request did not match filtered subject from create subject - JSConsumerCreateFilterSubjectMismatchErr ErrorIdentifier = 10131 - - // JSConsumerDeliverCycleErr consumer deliver subject forms a cycle - JSConsumerDeliverCycleErr ErrorIdentifier = 10081 - - // JSConsumerDeliverToWildcardsErr consumer deliver subject has wildcards - JSConsumerDeliverToWildcardsErr ErrorIdentifier = 10079 - - // JSConsumerDescriptionTooLongErrF consumer description is too long, maximum allowed is {max} - JSConsumerDescriptionTooLongErrF ErrorIdentifier = 10107 - - // JSConsumerDirectRequiresEphemeralErr consumer direct requires an ephemeral consumer - JSConsumerDirectRequiresEphemeralErr ErrorIdentifier = 10091 - - // JSConsumerDirectRequiresPushErr consumer direct requires a push based consumer - JSConsumerDirectRequiresPushErr ErrorIdentifier = 10090 - - // JSConsumerDoesNotExist action UPDATE is used for a nonexisting consumer (consumer does not exist) - JSConsumerDoesNotExist ErrorIdentifier = 10149 - - // JSConsumerDuplicateFilterSubjects consumer cannot have both FilterSubject and FilterSubjects specified - JSConsumerDuplicateFilterSubjects ErrorIdentifier = 10136 - - // JSConsumerDurableNameNotInSubjectErr consumer expected to be durable but no durable name set in subject - JSConsumerDurableNameNotInSubjectErr ErrorIdentifier = 10016 - - // JSConsumerDurableNameNotMatchSubjectErr consumer name in subject does not match durable name in request - JSConsumerDurableNameNotMatchSubjectErr ErrorIdentifier = 10017 - - // JSConsumerDurableNameNotSetErr consumer expected to be durable but a durable name was not set - JSConsumerDurableNameNotSetErr ErrorIdentifier = 10018 - - // JSConsumerEmptyFilter consumer filter in FilterSubjects cannot be empty - JSConsumerEmptyFilter ErrorIdentifier = 10139 - - // JSConsumerEmptyGroupName Group name cannot be an empty string - JSConsumerEmptyGroupName ErrorIdentifier = 10161 - - // JSConsumerEphemeralWithDurableInSubjectErr consumer expected to be ephemeral but detected a durable name set in subject - JSConsumerEphemeralWithDurableInSubjectErr ErrorIdentifier = 10019 - - // JSConsumerEphemeralWithDurableNameErr consumer expected to be ephemeral but a durable name was set in request - JSConsumerEphemeralWithDurableNameErr ErrorIdentifier = 10020 - - // JSConsumerExistingActiveErr consumer already exists and is still active - JSConsumerExistingActiveErr ErrorIdentifier = 10105 - - // JSConsumerFCRequiresPushErr consumer flow control requires a push based consumer - JSConsumerFCRequiresPushErr ErrorIdentifier = 10089 - - // JSConsumerFilterNotSubsetErr consumer filter subject is not a valid subset of the interest subjects - JSConsumerFilterNotSubsetErr ErrorIdentifier = 10093 - - // JSConsumerHBRequiresPushErr consumer idle heartbeat requires a push based consumer - JSConsumerHBRequiresPushErr ErrorIdentifier = 10088 - - // JSConsumerInactiveThresholdExcess consumer inactive threshold exceeds system limit of {limit} - JSConsumerInactiveThresholdExcess ErrorIdentifier = 10153 - - // JSConsumerInvalidDeliverSubject invalid push consumer deliver subject - JSConsumerInvalidDeliverSubject ErrorIdentifier = 10112 - - // JSConsumerInvalidGroupNameErr Valid priority group name must match A-Z, a-z, 0-9, -_/=)+ and may not exceed 16 characters - JSConsumerInvalidGroupNameErr ErrorIdentifier = 10162 - - // JSConsumerInvalidPolicyErrF Generic delivery policy error ({err}) - JSConsumerInvalidPolicyErrF ErrorIdentifier = 10094 - - // JSConsumerInvalidPriorityGroupErr Provided priority group does not exist for this consumer - JSConsumerInvalidPriorityGroupErr ErrorIdentifier = 10160 - - // JSConsumerInvalidSamplingErrF failed to parse consumer sampling configuration: {err} - JSConsumerInvalidSamplingErrF ErrorIdentifier = 10095 - - // JSConsumerMaxDeliverBackoffErr max deliver is required to be > length of backoff values - JSConsumerMaxDeliverBackoffErr ErrorIdentifier = 10116 - - // JSConsumerMaxPendingAckExcessErrF consumer max ack pending exceeds system limit of {limit} - JSConsumerMaxPendingAckExcessErrF ErrorIdentifier = 10121 - - // JSConsumerMaxPendingAckPolicyRequiredErr consumer requires ack policy for max ack pending - JSConsumerMaxPendingAckPolicyRequiredErr ErrorIdentifier = 10082 - - // JSConsumerMaxRequestBatchExceededF consumer max request batch exceeds server limit of {limit} - JSConsumerMaxRequestBatchExceededF ErrorIdentifier = 10125 - - // JSConsumerMaxRequestBatchNegativeErr consumer max request batch needs to be > 0 - JSConsumerMaxRequestBatchNegativeErr ErrorIdentifier = 10114 - - // JSConsumerMaxRequestExpiresToSmall consumer max request expires needs to be >= 1ms - JSConsumerMaxRequestExpiresToSmall ErrorIdentifier = 10115 - - // JSConsumerMaxWaitingNegativeErr consumer max waiting needs to be positive - JSConsumerMaxWaitingNegativeErr ErrorIdentifier = 10087 - - // JSConsumerMetadataLengthErrF consumer metadata exceeds maximum size of {limit} - JSConsumerMetadataLengthErrF ErrorIdentifier = 10135 - - // JSConsumerMultipleFiltersNotAllowed consumer with multiple subject filters cannot use subject based API - JSConsumerMultipleFiltersNotAllowed ErrorIdentifier = 10137 - - // JSConsumerNameContainsPathSeparatorsErr Consumer name can not contain path separators - JSConsumerNameContainsPathSeparatorsErr ErrorIdentifier = 10127 - - // JSConsumerNameExistErr consumer name already in use - JSConsumerNameExistErr ErrorIdentifier = 10013 - - // JSConsumerNameTooLongErrF consumer name is too long, maximum allowed is {max} - JSConsumerNameTooLongErrF ErrorIdentifier = 10102 - - // JSConsumerNotFoundErr consumer not found - JSConsumerNotFoundErr ErrorIdentifier = 10014 - - // JSConsumerOfflineErr consumer is offline - JSConsumerOfflineErr ErrorIdentifier = 10119 - - // JSConsumerOnMappedErr consumer direct on a mapped consumer - JSConsumerOnMappedErr ErrorIdentifier = 10092 - - // JSConsumerOverlappingSubjectFilters consumer subject filters cannot overlap - JSConsumerOverlappingSubjectFilters ErrorIdentifier = 10138 - - // JSConsumerPriorityPolicyWithoutGroup Setting PriorityPolicy requires at least one PriorityGroup to be set - JSConsumerPriorityPolicyWithoutGroup ErrorIdentifier = 10159 - - // JSConsumerPullNotDurableErr consumer in pull mode requires a durable name - JSConsumerPullNotDurableErr ErrorIdentifier = 10085 - - // JSConsumerPullRequiresAckErr consumer in pull mode requires explicit ack policy on workqueue stream - JSConsumerPullRequiresAckErr ErrorIdentifier = 10084 - - // JSConsumerPullWithRateLimitErr consumer in pull mode can not have rate limit set - JSConsumerPullWithRateLimitErr ErrorIdentifier = 10086 - - // JSConsumerPushMaxWaitingErr consumer in push mode can not set max waiting - JSConsumerPushMaxWaitingErr ErrorIdentifier = 10080 - - // JSConsumerReplacementWithDifferentNameErr consumer replacement durable config not the same - JSConsumerReplacementWithDifferentNameErr ErrorIdentifier = 10106 - - // JSConsumerReplicasExceedsStream consumer config replica count exceeds parent stream - JSConsumerReplicasExceedsStream ErrorIdentifier = 10126 - - // JSConsumerReplicasShouldMatchStream consumer config replicas must match interest retention stream's replicas - JSConsumerReplicasShouldMatchStream ErrorIdentifier = 10134 - - // JSConsumerSmallHeartbeatErr consumer idle heartbeat needs to be >= 100ms - JSConsumerSmallHeartbeatErr ErrorIdentifier = 10083 - - // JSConsumerStoreFailedErrF error creating store for consumer: {err} - JSConsumerStoreFailedErrF ErrorIdentifier = 10104 - - // JSConsumerWQConsumerNotDeliverAllErr consumer must be deliver all on workqueue stream - JSConsumerWQConsumerNotDeliverAllErr ErrorIdentifier = 10101 - - // JSConsumerWQConsumerNotUniqueErr filtered consumer not unique on workqueue stream - JSConsumerWQConsumerNotUniqueErr ErrorIdentifier = 10100 - - // JSConsumerWQMultipleUnfilteredErr multiple non-filtered consumers not allowed on workqueue stream - JSConsumerWQMultipleUnfilteredErr ErrorIdentifier = 10099 - - // JSConsumerWQRequiresExplicitAckErr workqueue stream requires explicit ack - JSConsumerWQRequiresExplicitAckErr ErrorIdentifier = 10098 - - // JSConsumerWithFlowControlNeedsHeartbeats consumer with flow control also needs heartbeats - JSConsumerWithFlowControlNeedsHeartbeats ErrorIdentifier = 10108 - - // JSInsufficientResourcesErr insufficient resources - JSInsufficientResourcesErr ErrorIdentifier = 10023 - - // JSInvalidJSONErr invalid JSON: {err} - JSInvalidJSONErr ErrorIdentifier = 10025 - - // JSMaximumConsumersLimitErr maximum consumers limit reached - JSMaximumConsumersLimitErr ErrorIdentifier = 10026 - - // JSMaximumStreamsLimitErr maximum number of streams reached - JSMaximumStreamsLimitErr ErrorIdentifier = 10027 - - // JSMemoryResourcesExceededErr insufficient memory resources available - JSMemoryResourcesExceededErr ErrorIdentifier = 10028 - - // JSMessageTTLDisabledErr per-message TTL is disabled - JSMessageTTLDisabledErr ErrorIdentifier = 10166 - - // JSMessageTTLInvalidErr invalid per-message TTL - JSMessageTTLInvalidErr ErrorIdentifier = 10165 - - // JSMirrorConsumerSetupFailedErrF generic mirror consumer setup failure string ({err}) - JSMirrorConsumerSetupFailedErrF ErrorIdentifier = 10029 - - // JSMirrorInvalidStreamName mirrored stream name is invalid - JSMirrorInvalidStreamName ErrorIdentifier = 10142 - - // JSMirrorInvalidSubjectFilter mirror transform source: {err} - JSMirrorInvalidSubjectFilter ErrorIdentifier = 10151 - - // JSMirrorInvalidTransformDestination mirror transform: {err} - JSMirrorInvalidTransformDestination ErrorIdentifier = 10154 - - // JSMirrorMaxMessageSizeTooBigErr stream mirror must have max message size >= source - JSMirrorMaxMessageSizeTooBigErr ErrorIdentifier = 10030 - - // JSMirrorMultipleFiltersNotAllowed mirror with multiple subject transforms cannot also have a single subject filter - JSMirrorMultipleFiltersNotAllowed ErrorIdentifier = 10150 - - // JSMirrorOverlappingSubjectFilters mirror subject filters can not overlap - JSMirrorOverlappingSubjectFilters ErrorIdentifier = 10152 - - // JSMirrorWithFirstSeqErr stream mirrors can not have first sequence configured - JSMirrorWithFirstSeqErr ErrorIdentifier = 10143 - - // JSMirrorWithSourcesErr stream mirrors can not also contain other sources - JSMirrorWithSourcesErr ErrorIdentifier = 10031 - - // JSMirrorWithStartSeqAndTimeErr stream mirrors can not have both start seq and start time configured - JSMirrorWithStartSeqAndTimeErr ErrorIdentifier = 10032 - - // JSMirrorWithSubjectFiltersErr stream mirrors can not contain filtered subjects - JSMirrorWithSubjectFiltersErr ErrorIdentifier = 10033 - - // JSMirrorWithSubjectsErr stream mirrors can not contain subjects - JSMirrorWithSubjectsErr ErrorIdentifier = 10034 - - // JSNoAccountErr account not found - JSNoAccountErr ErrorIdentifier = 10035 - - // JSNoLimitsErr no JetStream default or applicable tiered limit present - JSNoLimitsErr ErrorIdentifier = 10120 - - // JSNoMessageFoundErr no message found - JSNoMessageFoundErr ErrorIdentifier = 10037 - - // JSNotEmptyRequestErr expected an empty request payload - JSNotEmptyRequestErr ErrorIdentifier = 10038 - - // JSNotEnabledErr JetStream not enabled - JSNotEnabledErr ErrorIdentifier = 10076 - - // JSNotEnabledForAccountErr JetStream not enabled for account - JSNotEnabledForAccountErr ErrorIdentifier = 10039 - - // JSPedanticErrF pedantic mode: {err} - JSPedanticErrF ErrorIdentifier = 10157 - - // JSPeerRemapErr peer remap failed - JSPeerRemapErr ErrorIdentifier = 10075 - - // JSRaftGeneralErrF General RAFT error string ({err}) - JSRaftGeneralErrF ErrorIdentifier = 10041 - - // JSReplicasCountCannotBeNegative replicas count cannot be negative - JSReplicasCountCannotBeNegative ErrorIdentifier = 10133 - - // JSRestoreSubscribeFailedErrF JetStream unable to subscribe to restore snapshot {subject}: {err} - JSRestoreSubscribeFailedErrF ErrorIdentifier = 10042 - - // JSSequenceNotFoundErrF sequence {seq} not found - JSSequenceNotFoundErrF ErrorIdentifier = 10043 - - // JSSnapshotDeliverSubjectInvalidErr deliver subject not valid - JSSnapshotDeliverSubjectInvalidErr ErrorIdentifier = 10015 - - // JSSourceConsumerSetupFailedErrF General source consumer setup failure string ({err}) - JSSourceConsumerSetupFailedErrF ErrorIdentifier = 10045 - - // JSSourceDuplicateDetected source stream, filter and transform (plus external if present) must form a unique combination (duplicate source configuration detected) - JSSourceDuplicateDetected ErrorIdentifier = 10140 - - // JSSourceInvalidStreamName sourced stream name is invalid - JSSourceInvalidStreamName ErrorIdentifier = 10141 - - // JSSourceInvalidSubjectFilter source transform source: {err} - JSSourceInvalidSubjectFilter ErrorIdentifier = 10145 - - // JSSourceInvalidTransformDestination source transform: {err} - JSSourceInvalidTransformDestination ErrorIdentifier = 10146 - - // JSSourceMaxMessageSizeTooBigErr stream source must have max message size >= target - JSSourceMaxMessageSizeTooBigErr ErrorIdentifier = 10046 - - // JSSourceMultipleFiltersNotAllowed source with multiple subject transforms cannot also have a single subject filter - JSSourceMultipleFiltersNotAllowed ErrorIdentifier = 10144 - - // JSSourceOverlappingSubjectFilters source filters can not overlap - JSSourceOverlappingSubjectFilters ErrorIdentifier = 10147 - - // JSStorageResourcesExceededErr insufficient storage resources available - JSStorageResourcesExceededErr ErrorIdentifier = 10047 - - // JSStreamAssignmentErrF Generic stream assignment error string ({err}) - JSStreamAssignmentErrF ErrorIdentifier = 10048 - - // JSStreamCreateErrF Generic stream creation error string ({err}) - JSStreamCreateErrF ErrorIdentifier = 10049 - - // JSStreamDeleteErrF General stream deletion error string ({err}) - JSStreamDeleteErrF ErrorIdentifier = 10050 - - // JSStreamDuplicateMessageConflict duplicate message id is in process - JSStreamDuplicateMessageConflict ErrorIdentifier = 10158 - - // JSStreamExpectedLastSeqPerSubjectNotReady expected last sequence per subject temporarily unavailable - JSStreamExpectedLastSeqPerSubjectNotReady ErrorIdentifier = 10163 - - // JSStreamExternalApiOverlapErrF stream external api prefix {prefix} must not overlap with {subject} - JSStreamExternalApiOverlapErrF ErrorIdentifier = 10021 - - // JSStreamExternalDelPrefixOverlapsErrF stream external delivery prefix {prefix} overlaps with stream subject {subject} - JSStreamExternalDelPrefixOverlapsErrF ErrorIdentifier = 10022 - - // JSStreamGeneralErrorF General stream failure string ({err}) - JSStreamGeneralErrorF ErrorIdentifier = 10051 - - // JSStreamHeaderExceedsMaximumErr header size exceeds maximum allowed of 64k - JSStreamHeaderExceedsMaximumErr ErrorIdentifier = 10097 - - // JSStreamInfoMaxSubjectsErr subject details would exceed maximum allowed - JSStreamInfoMaxSubjectsErr ErrorIdentifier = 10117 - - // JSStreamInvalidConfigF Stream configuration validation error string ({err}) - JSStreamInvalidConfigF ErrorIdentifier = 10052 - - // JSStreamInvalidErr stream not valid - JSStreamInvalidErr ErrorIdentifier = 10096 - - // JSStreamInvalidExternalDeliverySubjErrF stream external delivery prefix {prefix} must not contain wildcards - JSStreamInvalidExternalDeliverySubjErrF ErrorIdentifier = 10024 - - // JSStreamLimitsErrF General stream limits exceeded error string ({err}) - JSStreamLimitsErrF ErrorIdentifier = 10053 - - // JSStreamMaxBytesRequired account requires a stream config to have max bytes set - JSStreamMaxBytesRequired ErrorIdentifier = 10113 - - // JSStreamMaxStreamBytesExceeded stream max bytes exceeds account limit max stream bytes - JSStreamMaxStreamBytesExceeded ErrorIdentifier = 10122 - - // JSStreamMessageExceedsMaximumErr message size exceeds maximum allowed - JSStreamMessageExceedsMaximumErr ErrorIdentifier = 10054 - - // JSStreamMirrorNotUpdatableErr stream mirror configuration can not be updated - JSStreamMirrorNotUpdatableErr ErrorIdentifier = 10055 - - // JSStreamMismatchErr stream name in subject does not match request - JSStreamMismatchErr ErrorIdentifier = 10056 - - // JSStreamMoveAndScaleErr can not move and scale a stream in a single update - JSStreamMoveAndScaleErr ErrorIdentifier = 10123 - - // JSStreamMoveInProgressF stream move already in progress: {msg} - JSStreamMoveInProgressF ErrorIdentifier = 10124 - - // JSStreamMoveNotInProgress stream move not in progress - JSStreamMoveNotInProgress ErrorIdentifier = 10129 - - // JSStreamMsgDeleteFailedF Generic message deletion failure error string ({err}) - JSStreamMsgDeleteFailedF ErrorIdentifier = 10057 - - // JSStreamNameContainsPathSeparatorsErr Stream name can not contain path separators - JSStreamNameContainsPathSeparatorsErr ErrorIdentifier = 10128 - - // JSStreamNameExistErr stream name already in use with a different configuration - JSStreamNameExistErr ErrorIdentifier = 10058 - - // JSStreamNameExistRestoreFailedErr stream name already in use, cannot restore - JSStreamNameExistRestoreFailedErr ErrorIdentifier = 10130 - - // JSStreamNotFoundErr stream not found - JSStreamNotFoundErr ErrorIdentifier = 10059 - - // JSStreamNotMatchErr expected stream does not match - JSStreamNotMatchErr ErrorIdentifier = 10060 - - // JSStreamOfflineErr stream is offline - JSStreamOfflineErr ErrorIdentifier = 10118 - - // JSStreamPurgeFailedF Generic stream purge failure error string ({err}) - JSStreamPurgeFailedF ErrorIdentifier = 10110 - - // JSStreamReplicasNotSupportedErr replicas > 1 not supported in non-clustered mode - JSStreamReplicasNotSupportedErr ErrorIdentifier = 10074 - - // JSStreamReplicasNotUpdatableErr Replicas configuration can not be updated - JSStreamReplicasNotUpdatableErr ErrorIdentifier = 10061 - - // JSStreamRestoreErrF restore failed: {err} - JSStreamRestoreErrF ErrorIdentifier = 10062 - - // JSStreamRollupFailedF Generic stream rollup failure error string ({err}) - JSStreamRollupFailedF ErrorIdentifier = 10111 - - // JSStreamSealedErr invalid operation on sealed stream - JSStreamSealedErr ErrorIdentifier = 10109 - - // JSStreamSequenceNotMatchErr expected stream sequence does not match - JSStreamSequenceNotMatchErr ErrorIdentifier = 10063 - - // JSStreamSnapshotErrF snapshot failed: {err} - JSStreamSnapshotErrF ErrorIdentifier = 10064 - - // JSStreamStoreFailedF Generic error when storing a message failed ({err}) - JSStreamStoreFailedF ErrorIdentifier = 10077 - - // JSStreamSubjectOverlapErr subjects overlap with an existing stream - JSStreamSubjectOverlapErr ErrorIdentifier = 10065 - - // JSStreamTemplateCreateErrF Generic template creation failed string ({err}) - JSStreamTemplateCreateErrF ErrorIdentifier = 10066 - - // JSStreamTemplateDeleteErrF Generic stream template deletion failed error string ({err}) - JSStreamTemplateDeleteErrF ErrorIdentifier = 10067 - - // JSStreamTemplateNotFoundErr template not found - JSStreamTemplateNotFoundErr ErrorIdentifier = 10068 - - // JSStreamTooManyRequests too many requests - JSStreamTooManyRequests ErrorIdentifier = 10167 - - // JSStreamTransformInvalidDestination stream transform: {err} - JSStreamTransformInvalidDestination ErrorIdentifier = 10156 - - // JSStreamTransformInvalidSource stream transform source: {err} - JSStreamTransformInvalidSource ErrorIdentifier = 10155 - - // JSStreamUpdateErrF Generic stream update error string ({err}) - JSStreamUpdateErrF ErrorIdentifier = 10069 - - // JSStreamWrongLastMsgIDErrF wrong last msg ID: {id} - JSStreamWrongLastMsgIDErrF ErrorIdentifier = 10070 - - // JSStreamWrongLastSequenceConstantErr wrong last sequence - JSStreamWrongLastSequenceConstantErr ErrorIdentifier = 10164 - - // JSStreamWrongLastSequenceErrF wrong last sequence: {seq} - JSStreamWrongLastSequenceErrF ErrorIdentifier = 10071 - - // JSTempStorageFailedErr JetStream unable to open temp storage for restore - JSTempStorageFailedErr ErrorIdentifier = 10072 - - // JSTemplateNameNotMatchSubjectErr template name in subject does not match request - JSTemplateNameNotMatchSubjectErr ErrorIdentifier = 10073 -) - -var ( - ApiErrors = map[ErrorIdentifier]*ApiError{ - JSAccountResourcesExceededErr: {Code: 400, ErrCode: 10002, Description: "resource limits exceeded for account"}, - JSBadRequestErr: {Code: 400, ErrCode: 10003, Description: "bad request"}, - JSClusterIncompleteErr: {Code: 503, ErrCode: 10004, Description: "incomplete results"}, - JSClusterNoPeersErrF: {Code: 400, ErrCode: 10005, Description: "{err}"}, - JSClusterNotActiveErr: {Code: 500, ErrCode: 10006, Description: "JetStream not in clustered mode"}, - JSClusterNotAssignedErr: {Code: 500, ErrCode: 10007, Description: "JetStream cluster not assigned to this server"}, - JSClusterNotAvailErr: {Code: 503, ErrCode: 10008, Description: "JetStream system temporarily unavailable"}, - JSClusterNotLeaderErr: {Code: 500, ErrCode: 10009, Description: "JetStream cluster can not handle request"}, - JSClusterPeerNotMemberErr: {Code: 400, ErrCode: 10040, Description: "peer not a member"}, - JSClusterRequiredErr: {Code: 503, ErrCode: 10010, Description: "JetStream clustering support required"}, - JSClusterServerNotMemberErr: {Code: 400, ErrCode: 10044, Description: "server is not a member of the cluster"}, - JSClusterTagsErr: {Code: 400, ErrCode: 10011, Description: "tags placement not supported for operation"}, - JSClusterUnSupportFeatureErr: {Code: 503, ErrCode: 10036, Description: "not currently supported in clustered mode"}, - JSConsumerAlreadyExists: {Code: 400, ErrCode: 10148, Description: "consumer already exists"}, - JSConsumerBadDurableNameErr: {Code: 400, ErrCode: 10103, Description: "durable name can not contain '.', '*', '>'"}, - JSConsumerConfigRequiredErr: {Code: 400, ErrCode: 10078, Description: "consumer config required"}, - JSConsumerCreateDurableAndNameMismatch: {Code: 400, ErrCode: 10132, Description: "Consumer Durable and Name have to be equal if both are provided"}, - JSConsumerCreateErrF: {Code: 500, ErrCode: 10012, Description: "{err}"}, - JSConsumerCreateFilterSubjectMismatchErr: {Code: 400, ErrCode: 10131, Description: "Consumer create request did not match filtered subject from create subject"}, - JSConsumerDeliverCycleErr: {Code: 400, ErrCode: 10081, Description: "consumer deliver subject forms a cycle"}, - JSConsumerDeliverToWildcardsErr: {Code: 400, ErrCode: 10079, Description: "consumer deliver subject has wildcards"}, - JSConsumerDescriptionTooLongErrF: {Code: 400, ErrCode: 10107, Description: "consumer description is too long, maximum allowed is {max}"}, - JSConsumerDirectRequiresEphemeralErr: {Code: 400, ErrCode: 10091, Description: "consumer direct requires an ephemeral consumer"}, - JSConsumerDirectRequiresPushErr: {Code: 400, ErrCode: 10090, Description: "consumer direct requires a push based consumer"}, - JSConsumerDoesNotExist: {Code: 400, ErrCode: 10149, Description: "consumer does not exist"}, - JSConsumerDuplicateFilterSubjects: {Code: 400, ErrCode: 10136, Description: "consumer cannot have both FilterSubject and FilterSubjects specified"}, - JSConsumerDurableNameNotInSubjectErr: {Code: 400, ErrCode: 10016, Description: "consumer expected to be durable but no durable name set in subject"}, - JSConsumerDurableNameNotMatchSubjectErr: {Code: 400, ErrCode: 10017, Description: "consumer name in subject does not match durable name in request"}, - JSConsumerDurableNameNotSetErr: {Code: 400, ErrCode: 10018, Description: "consumer expected to be durable but a durable name was not set"}, - JSConsumerEmptyFilter: {Code: 400, ErrCode: 10139, Description: "consumer filter in FilterSubjects cannot be empty"}, - JSConsumerEmptyGroupName: {Code: 400, ErrCode: 10161, Description: "Group name cannot be an empty string"}, - JSConsumerEphemeralWithDurableInSubjectErr: {Code: 400, ErrCode: 10019, Description: "consumer expected to be ephemeral but detected a durable name set in subject"}, - JSConsumerEphemeralWithDurableNameErr: {Code: 400, ErrCode: 10020, Description: "consumer expected to be ephemeral but a durable name was set in request"}, - JSConsumerExistingActiveErr: {Code: 400, ErrCode: 10105, Description: "consumer already exists and is still active"}, - JSConsumerFCRequiresPushErr: {Code: 400, ErrCode: 10089, Description: "consumer flow control requires a push based consumer"}, - JSConsumerFilterNotSubsetErr: {Code: 400, ErrCode: 10093, Description: "consumer filter subject is not a valid subset of the interest subjects"}, - JSConsumerHBRequiresPushErr: {Code: 400, ErrCode: 10088, Description: "consumer idle heartbeat requires a push based consumer"}, - JSConsumerInactiveThresholdExcess: {Code: 400, ErrCode: 10153, Description: "consumer inactive threshold exceeds system limit of {limit}"}, - JSConsumerInvalidDeliverSubject: {Code: 400, ErrCode: 10112, Description: "invalid push consumer deliver subject"}, - JSConsumerInvalidGroupNameErr: {Code: 400, ErrCode: 10162, Description: "Valid priority group name must match A-Z, a-z, 0-9, -_/=)+ and may not exceed 16 characters"}, - JSConsumerInvalidPolicyErrF: {Code: 400, ErrCode: 10094, Description: "{err}"}, - JSConsumerInvalidPriorityGroupErr: {Code: 400, ErrCode: 10160, Description: "Provided priority group does not exist for this consumer"}, - JSConsumerInvalidSamplingErrF: {Code: 400, ErrCode: 10095, Description: "failed to parse consumer sampling configuration: {err}"}, - JSConsumerMaxDeliverBackoffErr: {Code: 400, ErrCode: 10116, Description: "max deliver is required to be > length of backoff values"}, - JSConsumerMaxPendingAckExcessErrF: {Code: 400, ErrCode: 10121, Description: "consumer max ack pending exceeds system limit of {limit}"}, - JSConsumerMaxPendingAckPolicyRequiredErr: {Code: 400, ErrCode: 10082, Description: "consumer requires ack policy for max ack pending"}, - JSConsumerMaxRequestBatchExceededF: {Code: 400, ErrCode: 10125, Description: "consumer max request batch exceeds server limit of {limit}"}, - JSConsumerMaxRequestBatchNegativeErr: {Code: 400, ErrCode: 10114, Description: "consumer max request batch needs to be > 0"}, - JSConsumerMaxRequestExpiresToSmall: {Code: 400, ErrCode: 10115, Description: "consumer max request expires needs to be >= 1ms"}, - JSConsumerMaxWaitingNegativeErr: {Code: 400, ErrCode: 10087, Description: "consumer max waiting needs to be positive"}, - JSConsumerMetadataLengthErrF: {Code: 400, ErrCode: 10135, Description: "consumer metadata exceeds maximum size of {limit}"}, - JSConsumerMultipleFiltersNotAllowed: {Code: 400, ErrCode: 10137, Description: "consumer with multiple subject filters cannot use subject based API"}, - JSConsumerNameContainsPathSeparatorsErr: {Code: 400, ErrCode: 10127, Description: "Consumer name can not contain path separators"}, - JSConsumerNameExistErr: {Code: 400, ErrCode: 10013, Description: "consumer name already in use"}, - JSConsumerNameTooLongErrF: {Code: 400, ErrCode: 10102, Description: "consumer name is too long, maximum allowed is {max}"}, - JSConsumerNotFoundErr: {Code: 404, ErrCode: 10014, Description: "consumer not found"}, - JSConsumerOfflineErr: {Code: 500, ErrCode: 10119, Description: "consumer is offline"}, - JSConsumerOnMappedErr: {Code: 400, ErrCode: 10092, Description: "consumer direct on a mapped consumer"}, - JSConsumerOverlappingSubjectFilters: {Code: 400, ErrCode: 10138, Description: "consumer subject filters cannot overlap"}, - JSConsumerPriorityPolicyWithoutGroup: {Code: 400, ErrCode: 10159, Description: "Setting PriorityPolicy requires at least one PriorityGroup to be set"}, - JSConsumerPullNotDurableErr: {Code: 400, ErrCode: 10085, Description: "consumer in pull mode requires a durable name"}, - JSConsumerPullRequiresAckErr: {Code: 400, ErrCode: 10084, Description: "consumer in pull mode requires explicit ack policy on workqueue stream"}, - JSConsumerPullWithRateLimitErr: {Code: 400, ErrCode: 10086, Description: "consumer in pull mode can not have rate limit set"}, - JSConsumerPushMaxWaitingErr: {Code: 400, ErrCode: 10080, Description: "consumer in push mode can not set max waiting"}, - JSConsumerReplacementWithDifferentNameErr: {Code: 400, ErrCode: 10106, Description: "consumer replacement durable config not the same"}, - JSConsumerReplicasExceedsStream: {Code: 400, ErrCode: 10126, Description: "consumer config replica count exceeds parent stream"}, - JSConsumerReplicasShouldMatchStream: {Code: 400, ErrCode: 10134, Description: "consumer config replicas must match interest retention stream's replicas"}, - JSConsumerSmallHeartbeatErr: {Code: 400, ErrCode: 10083, Description: "consumer idle heartbeat needs to be >= 100ms"}, - JSConsumerStoreFailedErrF: {Code: 500, ErrCode: 10104, Description: "error creating store for consumer: {err}"}, - JSConsumerWQConsumerNotDeliverAllErr: {Code: 400, ErrCode: 10101, Description: "consumer must be deliver all on workqueue stream"}, - JSConsumerWQConsumerNotUniqueErr: {Code: 400, ErrCode: 10100, Description: "filtered consumer not unique on workqueue stream"}, - JSConsumerWQMultipleUnfilteredErr: {Code: 400, ErrCode: 10099, Description: "multiple non-filtered consumers not allowed on workqueue stream"}, - JSConsumerWQRequiresExplicitAckErr: {Code: 400, ErrCode: 10098, Description: "workqueue stream requires explicit ack"}, - JSConsumerWithFlowControlNeedsHeartbeats: {Code: 400, ErrCode: 10108, Description: "consumer with flow control also needs heartbeats"}, - JSInsufficientResourcesErr: {Code: 503, ErrCode: 10023, Description: "insufficient resources"}, - JSInvalidJSONErr: {Code: 400, ErrCode: 10025, Description: "invalid JSON: {err}"}, - JSMaximumConsumersLimitErr: {Code: 400, ErrCode: 10026, Description: "maximum consumers limit reached"}, - JSMaximumStreamsLimitErr: {Code: 400, ErrCode: 10027, Description: "maximum number of streams reached"}, - JSMemoryResourcesExceededErr: {Code: 500, ErrCode: 10028, Description: "insufficient memory resources available"}, - JSMessageTTLDisabledErr: {Code: 400, ErrCode: 10166, Description: "per-message TTL is disabled"}, - JSMessageTTLInvalidErr: {Code: 400, ErrCode: 10165, Description: "invalid per-message TTL"}, - JSMirrorConsumerSetupFailedErrF: {Code: 500, ErrCode: 10029, Description: "{err}"}, - JSMirrorInvalidStreamName: {Code: 400, ErrCode: 10142, Description: "mirrored stream name is invalid"}, - JSMirrorInvalidSubjectFilter: {Code: 400, ErrCode: 10151, Description: "mirror transform source: {err}"}, - JSMirrorInvalidTransformDestination: {Code: 400, ErrCode: 10154, Description: "mirror transform: {err}"}, - JSMirrorMaxMessageSizeTooBigErr: {Code: 400, ErrCode: 10030, Description: "stream mirror must have max message size >= source"}, - JSMirrorMultipleFiltersNotAllowed: {Code: 400, ErrCode: 10150, Description: "mirror with multiple subject transforms cannot also have a single subject filter"}, - JSMirrorOverlappingSubjectFilters: {Code: 400, ErrCode: 10152, Description: "mirror subject filters can not overlap"}, - JSMirrorWithFirstSeqErr: {Code: 400, ErrCode: 10143, Description: "stream mirrors can not have first sequence configured"}, - JSMirrorWithSourcesErr: {Code: 400, ErrCode: 10031, Description: "stream mirrors can not also contain other sources"}, - JSMirrorWithStartSeqAndTimeErr: {Code: 400, ErrCode: 10032, Description: "stream mirrors can not have both start seq and start time configured"}, - JSMirrorWithSubjectFiltersErr: {Code: 400, ErrCode: 10033, Description: "stream mirrors can not contain filtered subjects"}, - JSMirrorWithSubjectsErr: {Code: 400, ErrCode: 10034, Description: "stream mirrors can not contain subjects"}, - JSNoAccountErr: {Code: 503, ErrCode: 10035, Description: "account not found"}, - JSNoLimitsErr: {Code: 400, ErrCode: 10120, Description: "no JetStream default or applicable tiered limit present"}, - JSNoMessageFoundErr: {Code: 404, ErrCode: 10037, Description: "no message found"}, - JSNotEmptyRequestErr: {Code: 400, ErrCode: 10038, Description: "expected an empty request payload"}, - JSNotEnabledErr: {Code: 503, ErrCode: 10076, Description: "JetStream not enabled"}, - JSNotEnabledForAccountErr: {Code: 503, ErrCode: 10039, Description: "JetStream not enabled for account"}, - JSPedanticErrF: {Code: 400, ErrCode: 10157, Description: "pedantic mode: {err}"}, - JSPeerRemapErr: {Code: 503, ErrCode: 10075, Description: "peer remap failed"}, - JSRaftGeneralErrF: {Code: 500, ErrCode: 10041, Description: "{err}"}, - JSReplicasCountCannotBeNegative: {Code: 400, ErrCode: 10133, Description: "replicas count cannot be negative"}, - JSRestoreSubscribeFailedErrF: {Code: 500, ErrCode: 10042, Description: "JetStream unable to subscribe to restore snapshot {subject}: {err}"}, - JSSequenceNotFoundErrF: {Code: 400, ErrCode: 10043, Description: "sequence {seq} not found"}, - JSSnapshotDeliverSubjectInvalidErr: {Code: 400, ErrCode: 10015, Description: "deliver subject not valid"}, - JSSourceConsumerSetupFailedErrF: {Code: 500, ErrCode: 10045, Description: "{err}"}, - JSSourceDuplicateDetected: {Code: 400, ErrCode: 10140, Description: "duplicate source configuration detected"}, - JSSourceInvalidStreamName: {Code: 400, ErrCode: 10141, Description: "sourced stream name is invalid"}, - JSSourceInvalidSubjectFilter: {Code: 400, ErrCode: 10145, Description: "source transform source: {err}"}, - JSSourceInvalidTransformDestination: {Code: 400, ErrCode: 10146, Description: "source transform: {err}"}, - JSSourceMaxMessageSizeTooBigErr: {Code: 400, ErrCode: 10046, Description: "stream source must have max message size >= target"}, - JSSourceMultipleFiltersNotAllowed: {Code: 400, ErrCode: 10144, Description: "source with multiple subject transforms cannot also have a single subject filter"}, - JSSourceOverlappingSubjectFilters: {Code: 400, ErrCode: 10147, Description: "source filters can not overlap"}, - JSStorageResourcesExceededErr: {Code: 500, ErrCode: 10047, Description: "insufficient storage resources available"}, - JSStreamAssignmentErrF: {Code: 500, ErrCode: 10048, Description: "{err}"}, - JSStreamCreateErrF: {Code: 500, ErrCode: 10049, Description: "{err}"}, - JSStreamDeleteErrF: {Code: 500, ErrCode: 10050, Description: "{err}"}, - JSStreamDuplicateMessageConflict: {Code: 409, ErrCode: 10158, Description: "duplicate message id is in process"}, - JSStreamExpectedLastSeqPerSubjectNotReady: {Code: 503, ErrCode: 10163, Description: "expected last sequence per subject temporarily unavailable"}, - JSStreamExternalApiOverlapErrF: {Code: 400, ErrCode: 10021, Description: "stream external api prefix {prefix} must not overlap with {subject}"}, - JSStreamExternalDelPrefixOverlapsErrF: {Code: 400, ErrCode: 10022, Description: "stream external delivery prefix {prefix} overlaps with stream subject {subject}"}, - JSStreamGeneralErrorF: {Code: 500, ErrCode: 10051, Description: "{err}"}, - JSStreamHeaderExceedsMaximumErr: {Code: 400, ErrCode: 10097, Description: "header size exceeds maximum allowed of 64k"}, - JSStreamInfoMaxSubjectsErr: {Code: 500, ErrCode: 10117, Description: "subject details would exceed maximum allowed"}, - JSStreamInvalidConfigF: {Code: 500, ErrCode: 10052, Description: "{err}"}, - JSStreamInvalidErr: {Code: 500, ErrCode: 10096, Description: "stream not valid"}, - JSStreamInvalidExternalDeliverySubjErrF: {Code: 400, ErrCode: 10024, Description: "stream external delivery prefix {prefix} must not contain wildcards"}, - JSStreamLimitsErrF: {Code: 500, ErrCode: 10053, Description: "{err}"}, - JSStreamMaxBytesRequired: {Code: 400, ErrCode: 10113, Description: "account requires a stream config to have max bytes set"}, - JSStreamMaxStreamBytesExceeded: {Code: 400, ErrCode: 10122, Description: "stream max bytes exceeds account limit max stream bytes"}, - JSStreamMessageExceedsMaximumErr: {Code: 400, ErrCode: 10054, Description: "message size exceeds maximum allowed"}, - JSStreamMirrorNotUpdatableErr: {Code: 400, ErrCode: 10055, Description: "stream mirror configuration can not be updated"}, - JSStreamMismatchErr: {Code: 400, ErrCode: 10056, Description: "stream name in subject does not match request"}, - JSStreamMoveAndScaleErr: {Code: 400, ErrCode: 10123, Description: "can not move and scale a stream in a single update"}, - JSStreamMoveInProgressF: {Code: 400, ErrCode: 10124, Description: "stream move already in progress: {msg}"}, - JSStreamMoveNotInProgress: {Code: 400, ErrCode: 10129, Description: "stream move not in progress"}, - JSStreamMsgDeleteFailedF: {Code: 500, ErrCode: 10057, Description: "{err}"}, - JSStreamNameContainsPathSeparatorsErr: {Code: 400, ErrCode: 10128, Description: "Stream name can not contain path separators"}, - JSStreamNameExistErr: {Code: 400, ErrCode: 10058, Description: "stream name already in use with a different configuration"}, - JSStreamNameExistRestoreFailedErr: {Code: 400, ErrCode: 10130, Description: "stream name already in use, cannot restore"}, - JSStreamNotFoundErr: {Code: 404, ErrCode: 10059, Description: "stream not found"}, - JSStreamNotMatchErr: {Code: 400, ErrCode: 10060, Description: "expected stream does not match"}, - JSStreamOfflineErr: {Code: 500, ErrCode: 10118, Description: "stream is offline"}, - JSStreamPurgeFailedF: {Code: 500, ErrCode: 10110, Description: "{err}"}, - JSStreamReplicasNotSupportedErr: {Code: 500, ErrCode: 10074, Description: "replicas > 1 not supported in non-clustered mode"}, - JSStreamReplicasNotUpdatableErr: {Code: 400, ErrCode: 10061, Description: "Replicas configuration can not be updated"}, - JSStreamRestoreErrF: {Code: 500, ErrCode: 10062, Description: "restore failed: {err}"}, - JSStreamRollupFailedF: {Code: 500, ErrCode: 10111, Description: "{err}"}, - JSStreamSealedErr: {Code: 400, ErrCode: 10109, Description: "invalid operation on sealed stream"}, - JSStreamSequenceNotMatchErr: {Code: 503, ErrCode: 10063, Description: "expected stream sequence does not match"}, - JSStreamSnapshotErrF: {Code: 500, ErrCode: 10064, Description: "snapshot failed: {err}"}, - JSStreamStoreFailedF: {Code: 503, ErrCode: 10077, Description: "{err}"}, - JSStreamSubjectOverlapErr: {Code: 400, ErrCode: 10065, Description: "subjects overlap with an existing stream"}, - JSStreamTemplateCreateErrF: {Code: 500, ErrCode: 10066, Description: "{err}"}, - JSStreamTemplateDeleteErrF: {Code: 500, ErrCode: 10067, Description: "{err}"}, - JSStreamTemplateNotFoundErr: {Code: 404, ErrCode: 10068, Description: "template not found"}, - JSStreamTooManyRequests: {Code: 429, ErrCode: 10167, Description: "too many requests"}, - JSStreamTransformInvalidDestination: {Code: 400, ErrCode: 10156, Description: "stream transform: {err}"}, - JSStreamTransformInvalidSource: {Code: 400, ErrCode: 10155, Description: "stream transform source: {err}"}, - JSStreamUpdateErrF: {Code: 500, ErrCode: 10069, Description: "{err}"}, - JSStreamWrongLastMsgIDErrF: {Code: 400, ErrCode: 10070, Description: "wrong last msg ID: {id}"}, - JSStreamWrongLastSequenceConstantErr: {Code: 400, ErrCode: 10164, Description: "wrong last sequence"}, - JSStreamWrongLastSequenceErrF: {Code: 400, ErrCode: 10071, Description: "wrong last sequence: {seq}"}, - JSTempStorageFailedErr: {Code: 500, ErrCode: 10072, Description: "JetStream unable to open temp storage for restore"}, - JSTemplateNameNotMatchSubjectErr: {Code: 400, ErrCode: 10073, Description: "template name in subject does not match request"}, - } - // ErrJetStreamNotClustered Deprecated by JSClusterNotActiveErr ApiError, use IsNatsError() for comparisons - ErrJetStreamNotClustered = ApiErrors[JSClusterNotActiveErr] - // ErrJetStreamNotAssigned Deprecated by JSClusterNotAssignedErr ApiError, use IsNatsError() for comparisons - ErrJetStreamNotAssigned = ApiErrors[JSClusterNotAssignedErr] - // ErrJetStreamNotLeader Deprecated by JSClusterNotLeaderErr ApiError, use IsNatsError() for comparisons - ErrJetStreamNotLeader = ApiErrors[JSClusterNotLeaderErr] - // ErrJetStreamConsumerAlreadyUsed Deprecated by JSConsumerNameExistErr ApiError, use IsNatsError() for comparisons - ErrJetStreamConsumerAlreadyUsed = ApiErrors[JSConsumerNameExistErr] - // ErrJetStreamResourcesExceeded Deprecated by JSInsufficientResourcesErr ApiError, use IsNatsError() for comparisons - ErrJetStreamResourcesExceeded = ApiErrors[JSInsufficientResourcesErr] - // ErrMemoryResourcesExceeded Deprecated by JSMemoryResourcesExceededErr ApiError, use IsNatsError() for comparisons - ErrMemoryResourcesExceeded = ApiErrors[JSMemoryResourcesExceededErr] - // ErrJetStreamNotEnabled Deprecated by JSNotEnabledErr ApiError, use IsNatsError() for comparisons - ErrJetStreamNotEnabled = ApiErrors[JSNotEnabledErr] - // ErrStorageResourcesExceeded Deprecated by JSStorageResourcesExceededErr ApiError, use IsNatsError() for comparisons - ErrStorageResourcesExceeded = ApiErrors[JSStorageResourcesExceededErr] - // ErrJetStreamStreamAlreadyUsed Deprecated by JSStreamNameExistErr ApiError, use IsNatsError() for comparisons - ErrJetStreamStreamAlreadyUsed = ApiErrors[JSStreamNameExistErr] - // ErrJetStreamStreamNotFound Deprecated by JSStreamNotFoundErr ApiError, use IsNatsError() for comparisons - ErrJetStreamStreamNotFound = ApiErrors[JSStreamNotFoundErr] - // ErrReplicasNotSupported Deprecated by JSStreamReplicasNotSupportedErr ApiError, use IsNatsError() for comparisons - ErrReplicasNotSupported = ApiErrors[JSStreamReplicasNotSupportedErr] -) - -// NewJSAccountResourcesExceededError creates a new JSAccountResourcesExceededErr error: "resource limits exceeded for account" -func NewJSAccountResourcesExceededError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSAccountResourcesExceededErr] -} - -// NewJSBadRequestError creates a new JSBadRequestErr error: "bad request" -func NewJSBadRequestError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSBadRequestErr] -} - -// NewJSClusterIncompleteError creates a new JSClusterIncompleteErr error: "incomplete results" -func NewJSClusterIncompleteError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSClusterIncompleteErr] -} - -// NewJSClusterNoPeersError creates a new JSClusterNoPeersErrF error: "{err}" -func NewJSClusterNoPeersError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSClusterNoPeersErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSClusterNotActiveError creates a new JSClusterNotActiveErr error: "JetStream not in clustered mode" -func NewJSClusterNotActiveError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSClusterNotActiveErr] -} - -// NewJSClusterNotAssignedError creates a new JSClusterNotAssignedErr error: "JetStream cluster not assigned to this server" -func NewJSClusterNotAssignedError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSClusterNotAssignedErr] -} - -// NewJSClusterNotAvailError creates a new JSClusterNotAvailErr error: "JetStream system temporarily unavailable" -func NewJSClusterNotAvailError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSClusterNotAvailErr] -} - -// NewJSClusterNotLeaderError creates a new JSClusterNotLeaderErr error: "JetStream cluster can not handle request" -func NewJSClusterNotLeaderError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSClusterNotLeaderErr] -} - -// NewJSClusterPeerNotMemberError creates a new JSClusterPeerNotMemberErr error: "peer not a member" -func NewJSClusterPeerNotMemberError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSClusterPeerNotMemberErr] -} - -// NewJSClusterRequiredError creates a new JSClusterRequiredErr error: "JetStream clustering support required" -func NewJSClusterRequiredError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSClusterRequiredErr] -} - -// NewJSClusterServerNotMemberError creates a new JSClusterServerNotMemberErr error: "server is not a member of the cluster" -func NewJSClusterServerNotMemberError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSClusterServerNotMemberErr] -} - -// NewJSClusterTagsError creates a new JSClusterTagsErr error: "tags placement not supported for operation" -func NewJSClusterTagsError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSClusterTagsErr] -} - -// NewJSClusterUnSupportFeatureError creates a new JSClusterUnSupportFeatureErr error: "not currently supported in clustered mode" -func NewJSClusterUnSupportFeatureError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSClusterUnSupportFeatureErr] -} - -// NewJSConsumerAlreadyExistsError creates a new JSConsumerAlreadyExists error: "consumer already exists" -func NewJSConsumerAlreadyExistsError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerAlreadyExists] -} - -// NewJSConsumerBadDurableNameError creates a new JSConsumerBadDurableNameErr error: "durable name can not contain '.', '*', '>'" -func NewJSConsumerBadDurableNameError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerBadDurableNameErr] -} - -// NewJSConsumerConfigRequiredError creates a new JSConsumerConfigRequiredErr error: "consumer config required" -func NewJSConsumerConfigRequiredError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerConfigRequiredErr] -} - -// NewJSConsumerCreateDurableAndNameMismatchError creates a new JSConsumerCreateDurableAndNameMismatch error: "Consumer Durable and Name have to be equal if both are provided" -func NewJSConsumerCreateDurableAndNameMismatchError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerCreateDurableAndNameMismatch] -} - -// NewJSConsumerCreateError creates a new JSConsumerCreateErrF error: "{err}" -func NewJSConsumerCreateError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSConsumerCreateErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSConsumerCreateFilterSubjectMismatchError creates a new JSConsumerCreateFilterSubjectMismatchErr error: "Consumer create request did not match filtered subject from create subject" -func NewJSConsumerCreateFilterSubjectMismatchError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerCreateFilterSubjectMismatchErr] -} - -// NewJSConsumerDeliverCycleError creates a new JSConsumerDeliverCycleErr error: "consumer deliver subject forms a cycle" -func NewJSConsumerDeliverCycleError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerDeliverCycleErr] -} - -// NewJSConsumerDeliverToWildcardsError creates a new JSConsumerDeliverToWildcardsErr error: "consumer deliver subject has wildcards" -func NewJSConsumerDeliverToWildcardsError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerDeliverToWildcardsErr] -} - -// NewJSConsumerDescriptionTooLongError creates a new JSConsumerDescriptionTooLongErrF error: "consumer description is too long, maximum allowed is {max}" -func NewJSConsumerDescriptionTooLongError(max interface{}, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSConsumerDescriptionTooLongErrF] - args := e.toReplacerArgs([]interface{}{"{max}", max}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSConsumerDirectRequiresEphemeralError creates a new JSConsumerDirectRequiresEphemeralErr error: "consumer direct requires an ephemeral consumer" -func NewJSConsumerDirectRequiresEphemeralError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerDirectRequiresEphemeralErr] -} - -// NewJSConsumerDirectRequiresPushError creates a new JSConsumerDirectRequiresPushErr error: "consumer direct requires a push based consumer" -func NewJSConsumerDirectRequiresPushError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerDirectRequiresPushErr] -} - -// NewJSConsumerDoesNotExistError creates a new JSConsumerDoesNotExist error: "consumer does not exist" -func NewJSConsumerDoesNotExistError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerDoesNotExist] -} - -// NewJSConsumerDuplicateFilterSubjectsError creates a new JSConsumerDuplicateFilterSubjects error: "consumer cannot have both FilterSubject and FilterSubjects specified" -func NewJSConsumerDuplicateFilterSubjectsError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerDuplicateFilterSubjects] -} - -// NewJSConsumerDurableNameNotInSubjectError creates a new JSConsumerDurableNameNotInSubjectErr error: "consumer expected to be durable but no durable name set in subject" -func NewJSConsumerDurableNameNotInSubjectError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerDurableNameNotInSubjectErr] -} - -// NewJSConsumerDurableNameNotMatchSubjectError creates a new JSConsumerDurableNameNotMatchSubjectErr error: "consumer name in subject does not match durable name in request" -func NewJSConsumerDurableNameNotMatchSubjectError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerDurableNameNotMatchSubjectErr] -} - -// NewJSConsumerDurableNameNotSetError creates a new JSConsumerDurableNameNotSetErr error: "consumer expected to be durable but a durable name was not set" -func NewJSConsumerDurableNameNotSetError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerDurableNameNotSetErr] -} - -// NewJSConsumerEmptyFilterError creates a new JSConsumerEmptyFilter error: "consumer filter in FilterSubjects cannot be empty" -func NewJSConsumerEmptyFilterError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerEmptyFilter] -} - -// NewJSConsumerEmptyGroupNameError creates a new JSConsumerEmptyGroupName error: "Group name cannot be an empty string" -func NewJSConsumerEmptyGroupNameError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerEmptyGroupName] -} - -// NewJSConsumerEphemeralWithDurableInSubjectError creates a new JSConsumerEphemeralWithDurableInSubjectErr error: "consumer expected to be ephemeral but detected a durable name set in subject" -func NewJSConsumerEphemeralWithDurableInSubjectError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerEphemeralWithDurableInSubjectErr] -} - -// NewJSConsumerEphemeralWithDurableNameError creates a new JSConsumerEphemeralWithDurableNameErr error: "consumer expected to be ephemeral but a durable name was set in request" -func NewJSConsumerEphemeralWithDurableNameError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerEphemeralWithDurableNameErr] -} - -// NewJSConsumerExistingActiveError creates a new JSConsumerExistingActiveErr error: "consumer already exists and is still active" -func NewJSConsumerExistingActiveError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerExistingActiveErr] -} - -// NewJSConsumerFCRequiresPushError creates a new JSConsumerFCRequiresPushErr error: "consumer flow control requires a push based consumer" -func NewJSConsumerFCRequiresPushError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerFCRequiresPushErr] -} - -// NewJSConsumerFilterNotSubsetError creates a new JSConsumerFilterNotSubsetErr error: "consumer filter subject is not a valid subset of the interest subjects" -func NewJSConsumerFilterNotSubsetError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerFilterNotSubsetErr] -} - -// NewJSConsumerHBRequiresPushError creates a new JSConsumerHBRequiresPushErr error: "consumer idle heartbeat requires a push based consumer" -func NewJSConsumerHBRequiresPushError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerHBRequiresPushErr] -} - -// NewJSConsumerInactiveThresholdExcessError creates a new JSConsumerInactiveThresholdExcess error: "consumer inactive threshold exceeds system limit of {limit}" -func NewJSConsumerInactiveThresholdExcessError(limit interface{}, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSConsumerInactiveThresholdExcess] - args := e.toReplacerArgs([]interface{}{"{limit}", limit}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSConsumerInvalidDeliverSubjectError creates a new JSConsumerInvalidDeliverSubject error: "invalid push consumer deliver subject" -func NewJSConsumerInvalidDeliverSubjectError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerInvalidDeliverSubject] -} - -// NewJSConsumerInvalidGroupNameError creates a new JSConsumerInvalidGroupNameErr error: "Valid priority group name must match A-Z, a-z, 0-9, -_/=)+ and may not exceed 16 characters" -func NewJSConsumerInvalidGroupNameError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerInvalidGroupNameErr] -} - -// NewJSConsumerInvalidPolicyError creates a new JSConsumerInvalidPolicyErrF error: "{err}" -func NewJSConsumerInvalidPolicyError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSConsumerInvalidPolicyErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSConsumerInvalidPriorityGroupError creates a new JSConsumerInvalidPriorityGroupErr error: "Provided priority group does not exist for this consumer" -func NewJSConsumerInvalidPriorityGroupError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerInvalidPriorityGroupErr] -} - -// NewJSConsumerInvalidSamplingError creates a new JSConsumerInvalidSamplingErrF error: "failed to parse consumer sampling configuration: {err}" -func NewJSConsumerInvalidSamplingError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSConsumerInvalidSamplingErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSConsumerMaxDeliverBackoffError creates a new JSConsumerMaxDeliverBackoffErr error: "max deliver is required to be > length of backoff values" -func NewJSConsumerMaxDeliverBackoffError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerMaxDeliverBackoffErr] -} - -// NewJSConsumerMaxPendingAckExcessError creates a new JSConsumerMaxPendingAckExcessErrF error: "consumer max ack pending exceeds system limit of {limit}" -func NewJSConsumerMaxPendingAckExcessError(limit interface{}, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSConsumerMaxPendingAckExcessErrF] - args := e.toReplacerArgs([]interface{}{"{limit}", limit}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSConsumerMaxPendingAckPolicyRequiredError creates a new JSConsumerMaxPendingAckPolicyRequiredErr error: "consumer requires ack policy for max ack pending" -func NewJSConsumerMaxPendingAckPolicyRequiredError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerMaxPendingAckPolicyRequiredErr] -} - -// NewJSConsumerMaxRequestBatchExceededError creates a new JSConsumerMaxRequestBatchExceededF error: "consumer max request batch exceeds server limit of {limit}" -func NewJSConsumerMaxRequestBatchExceededError(limit interface{}, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSConsumerMaxRequestBatchExceededF] - args := e.toReplacerArgs([]interface{}{"{limit}", limit}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSConsumerMaxRequestBatchNegativeError creates a new JSConsumerMaxRequestBatchNegativeErr error: "consumer max request batch needs to be > 0" -func NewJSConsumerMaxRequestBatchNegativeError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerMaxRequestBatchNegativeErr] -} - -// NewJSConsumerMaxRequestExpiresToSmallError creates a new JSConsumerMaxRequestExpiresToSmall error: "consumer max request expires needs to be >= 1ms" -func NewJSConsumerMaxRequestExpiresToSmallError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerMaxRequestExpiresToSmall] -} - -// NewJSConsumerMaxWaitingNegativeError creates a new JSConsumerMaxWaitingNegativeErr error: "consumer max waiting needs to be positive" -func NewJSConsumerMaxWaitingNegativeError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerMaxWaitingNegativeErr] -} - -// NewJSConsumerMetadataLengthError creates a new JSConsumerMetadataLengthErrF error: "consumer metadata exceeds maximum size of {limit}" -func NewJSConsumerMetadataLengthError(limit interface{}, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSConsumerMetadataLengthErrF] - args := e.toReplacerArgs([]interface{}{"{limit}", limit}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSConsumerMultipleFiltersNotAllowedError creates a new JSConsumerMultipleFiltersNotAllowed error: "consumer with multiple subject filters cannot use subject based API" -func NewJSConsumerMultipleFiltersNotAllowedError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerMultipleFiltersNotAllowed] -} - -// NewJSConsumerNameContainsPathSeparatorsError creates a new JSConsumerNameContainsPathSeparatorsErr error: "Consumer name can not contain path separators" -func NewJSConsumerNameContainsPathSeparatorsError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerNameContainsPathSeparatorsErr] -} - -// NewJSConsumerNameExistError creates a new JSConsumerNameExistErr error: "consumer name already in use" -func NewJSConsumerNameExistError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerNameExistErr] -} - -// NewJSConsumerNameTooLongError creates a new JSConsumerNameTooLongErrF error: "consumer name is too long, maximum allowed is {max}" -func NewJSConsumerNameTooLongError(max interface{}, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSConsumerNameTooLongErrF] - args := e.toReplacerArgs([]interface{}{"{max}", max}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSConsumerNotFoundError creates a new JSConsumerNotFoundErr error: "consumer not found" -func NewJSConsumerNotFoundError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerNotFoundErr] -} - -// NewJSConsumerOfflineError creates a new JSConsumerOfflineErr error: "consumer is offline" -func NewJSConsumerOfflineError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerOfflineErr] -} - -// NewJSConsumerOnMappedError creates a new JSConsumerOnMappedErr error: "consumer direct on a mapped consumer" -func NewJSConsumerOnMappedError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerOnMappedErr] -} - -// NewJSConsumerOverlappingSubjectFiltersError creates a new JSConsumerOverlappingSubjectFilters error: "consumer subject filters cannot overlap" -func NewJSConsumerOverlappingSubjectFiltersError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerOverlappingSubjectFilters] -} - -// NewJSConsumerPriorityPolicyWithoutGroupError creates a new JSConsumerPriorityPolicyWithoutGroup error: "Setting PriorityPolicy requires at least one PriorityGroup to be set" -func NewJSConsumerPriorityPolicyWithoutGroupError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerPriorityPolicyWithoutGroup] -} - -// NewJSConsumerPullNotDurableError creates a new JSConsumerPullNotDurableErr error: "consumer in pull mode requires a durable name" -func NewJSConsumerPullNotDurableError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerPullNotDurableErr] -} - -// NewJSConsumerPullRequiresAckError creates a new JSConsumerPullRequiresAckErr error: "consumer in pull mode requires explicit ack policy on workqueue stream" -func NewJSConsumerPullRequiresAckError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerPullRequiresAckErr] -} - -// NewJSConsumerPullWithRateLimitError creates a new JSConsumerPullWithRateLimitErr error: "consumer in pull mode can not have rate limit set" -func NewJSConsumerPullWithRateLimitError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerPullWithRateLimitErr] -} - -// NewJSConsumerPushMaxWaitingError creates a new JSConsumerPushMaxWaitingErr error: "consumer in push mode can not set max waiting" -func NewJSConsumerPushMaxWaitingError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerPushMaxWaitingErr] -} - -// NewJSConsumerReplacementWithDifferentNameError creates a new JSConsumerReplacementWithDifferentNameErr error: "consumer replacement durable config not the same" -func NewJSConsumerReplacementWithDifferentNameError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerReplacementWithDifferentNameErr] -} - -// NewJSConsumerReplicasExceedsStreamError creates a new JSConsumerReplicasExceedsStream error: "consumer config replica count exceeds parent stream" -func NewJSConsumerReplicasExceedsStreamError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerReplicasExceedsStream] -} - -// NewJSConsumerReplicasShouldMatchStreamError creates a new JSConsumerReplicasShouldMatchStream error: "consumer config replicas must match interest retention stream's replicas" -func NewJSConsumerReplicasShouldMatchStreamError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerReplicasShouldMatchStream] -} - -// NewJSConsumerSmallHeartbeatError creates a new JSConsumerSmallHeartbeatErr error: "consumer idle heartbeat needs to be >= 100ms" -func NewJSConsumerSmallHeartbeatError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerSmallHeartbeatErr] -} - -// NewJSConsumerStoreFailedError creates a new JSConsumerStoreFailedErrF error: "error creating store for consumer: {err}" -func NewJSConsumerStoreFailedError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSConsumerStoreFailedErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSConsumerWQConsumerNotDeliverAllError creates a new JSConsumerWQConsumerNotDeliverAllErr error: "consumer must be deliver all on workqueue stream" -func NewJSConsumerWQConsumerNotDeliverAllError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerWQConsumerNotDeliverAllErr] -} - -// NewJSConsumerWQConsumerNotUniqueError creates a new JSConsumerWQConsumerNotUniqueErr error: "filtered consumer not unique on workqueue stream" -func NewJSConsumerWQConsumerNotUniqueError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerWQConsumerNotUniqueErr] -} - -// NewJSConsumerWQMultipleUnfilteredError creates a new JSConsumerWQMultipleUnfilteredErr error: "multiple non-filtered consumers not allowed on workqueue stream" -func NewJSConsumerWQMultipleUnfilteredError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerWQMultipleUnfilteredErr] -} - -// NewJSConsumerWQRequiresExplicitAckError creates a new JSConsumerWQRequiresExplicitAckErr error: "workqueue stream requires explicit ack" -func NewJSConsumerWQRequiresExplicitAckError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerWQRequiresExplicitAckErr] -} - -// NewJSConsumerWithFlowControlNeedsHeartbeatsError creates a new JSConsumerWithFlowControlNeedsHeartbeats error: "consumer with flow control also needs heartbeats" -func NewJSConsumerWithFlowControlNeedsHeartbeatsError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSConsumerWithFlowControlNeedsHeartbeats] -} - -// NewJSInsufficientResourcesError creates a new JSInsufficientResourcesErr error: "insufficient resources" -func NewJSInsufficientResourcesError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSInsufficientResourcesErr] -} - -// NewJSInvalidJSONError creates a new JSInvalidJSONErr error: "invalid JSON: {err}" -func NewJSInvalidJSONError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSInvalidJSONErr] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSMaximumConsumersLimitError creates a new JSMaximumConsumersLimitErr error: "maximum consumers limit reached" -func NewJSMaximumConsumersLimitError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSMaximumConsumersLimitErr] -} - -// NewJSMaximumStreamsLimitError creates a new JSMaximumStreamsLimitErr error: "maximum number of streams reached" -func NewJSMaximumStreamsLimitError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSMaximumStreamsLimitErr] -} - -// NewJSMemoryResourcesExceededError creates a new JSMemoryResourcesExceededErr error: "insufficient memory resources available" -func NewJSMemoryResourcesExceededError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSMemoryResourcesExceededErr] -} - -// NewJSMessageTTLDisabledError creates a new JSMessageTTLDisabledErr error: "per-message TTL is disabled" -func NewJSMessageTTLDisabledError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSMessageTTLDisabledErr] -} - -// NewJSMessageTTLInvalidError creates a new JSMessageTTLInvalidErr error: "invalid per-message TTL" -func NewJSMessageTTLInvalidError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSMessageTTLInvalidErr] -} - -// NewJSMirrorConsumerSetupFailedError creates a new JSMirrorConsumerSetupFailedErrF error: "{err}" -func NewJSMirrorConsumerSetupFailedError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSMirrorConsumerSetupFailedErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSMirrorInvalidStreamNameError creates a new JSMirrorInvalidStreamName error: "mirrored stream name is invalid" -func NewJSMirrorInvalidStreamNameError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSMirrorInvalidStreamName] -} - -// NewJSMirrorInvalidSubjectFilterError creates a new JSMirrorInvalidSubjectFilter error: "mirror transform source: {err}" -func NewJSMirrorInvalidSubjectFilterError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSMirrorInvalidSubjectFilter] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSMirrorInvalidTransformDestinationError creates a new JSMirrorInvalidTransformDestination error: "mirror transform: {err}" -func NewJSMirrorInvalidTransformDestinationError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSMirrorInvalidTransformDestination] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSMirrorMaxMessageSizeTooBigError creates a new JSMirrorMaxMessageSizeTooBigErr error: "stream mirror must have max message size >= source" -func NewJSMirrorMaxMessageSizeTooBigError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSMirrorMaxMessageSizeTooBigErr] -} - -// NewJSMirrorMultipleFiltersNotAllowedError creates a new JSMirrorMultipleFiltersNotAllowed error: "mirror with multiple subject transforms cannot also have a single subject filter" -func NewJSMirrorMultipleFiltersNotAllowedError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSMirrorMultipleFiltersNotAllowed] -} - -// NewJSMirrorOverlappingSubjectFiltersError creates a new JSMirrorOverlappingSubjectFilters error: "mirror subject filters can not overlap" -func NewJSMirrorOverlappingSubjectFiltersError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSMirrorOverlappingSubjectFilters] -} - -// NewJSMirrorWithFirstSeqError creates a new JSMirrorWithFirstSeqErr error: "stream mirrors can not have first sequence configured" -func NewJSMirrorWithFirstSeqError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSMirrorWithFirstSeqErr] -} - -// NewJSMirrorWithSourcesError creates a new JSMirrorWithSourcesErr error: "stream mirrors can not also contain other sources" -func NewJSMirrorWithSourcesError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSMirrorWithSourcesErr] -} - -// NewJSMirrorWithStartSeqAndTimeError creates a new JSMirrorWithStartSeqAndTimeErr error: "stream mirrors can not have both start seq and start time configured" -func NewJSMirrorWithStartSeqAndTimeError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSMirrorWithStartSeqAndTimeErr] -} - -// NewJSMirrorWithSubjectFiltersError creates a new JSMirrorWithSubjectFiltersErr error: "stream mirrors can not contain filtered subjects" -func NewJSMirrorWithSubjectFiltersError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSMirrorWithSubjectFiltersErr] -} - -// NewJSMirrorWithSubjectsError creates a new JSMirrorWithSubjectsErr error: "stream mirrors can not contain subjects" -func NewJSMirrorWithSubjectsError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSMirrorWithSubjectsErr] -} - -// NewJSNoAccountError creates a new JSNoAccountErr error: "account not found" -func NewJSNoAccountError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSNoAccountErr] -} - -// NewJSNoLimitsError creates a new JSNoLimitsErr error: "no JetStream default or applicable tiered limit present" -func NewJSNoLimitsError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSNoLimitsErr] -} - -// NewJSNoMessageFoundError creates a new JSNoMessageFoundErr error: "no message found" -func NewJSNoMessageFoundError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSNoMessageFoundErr] -} - -// NewJSNotEmptyRequestError creates a new JSNotEmptyRequestErr error: "expected an empty request payload" -func NewJSNotEmptyRequestError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSNotEmptyRequestErr] -} - -// NewJSNotEnabledError creates a new JSNotEnabledErr error: "JetStream not enabled" -func NewJSNotEnabledError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSNotEnabledErr] -} - -// NewJSNotEnabledForAccountError creates a new JSNotEnabledForAccountErr error: "JetStream not enabled for account" -func NewJSNotEnabledForAccountError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSNotEnabledForAccountErr] -} - -// NewJSPedanticError creates a new JSPedanticErrF error: "pedantic mode: {err}" -func NewJSPedanticError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSPedanticErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSPeerRemapError creates a new JSPeerRemapErr error: "peer remap failed" -func NewJSPeerRemapError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSPeerRemapErr] -} - -// NewJSRaftGeneralError creates a new JSRaftGeneralErrF error: "{err}" -func NewJSRaftGeneralError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSRaftGeneralErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSReplicasCountCannotBeNegativeError creates a new JSReplicasCountCannotBeNegative error: "replicas count cannot be negative" -func NewJSReplicasCountCannotBeNegativeError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSReplicasCountCannotBeNegative] -} - -// NewJSRestoreSubscribeFailedError creates a new JSRestoreSubscribeFailedErrF error: "JetStream unable to subscribe to restore snapshot {subject}: {err}" -func NewJSRestoreSubscribeFailedError(err error, subject interface{}, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSRestoreSubscribeFailedErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err, "{subject}", subject}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSSequenceNotFoundError creates a new JSSequenceNotFoundErrF error: "sequence {seq} not found" -func NewJSSequenceNotFoundError(seq uint64, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSSequenceNotFoundErrF] - args := e.toReplacerArgs([]interface{}{"{seq}", seq}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSSnapshotDeliverSubjectInvalidError creates a new JSSnapshotDeliverSubjectInvalidErr error: "deliver subject not valid" -func NewJSSnapshotDeliverSubjectInvalidError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSSnapshotDeliverSubjectInvalidErr] -} - -// NewJSSourceConsumerSetupFailedError creates a new JSSourceConsumerSetupFailedErrF error: "{err}" -func NewJSSourceConsumerSetupFailedError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSSourceConsumerSetupFailedErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSSourceDuplicateDetectedError creates a new JSSourceDuplicateDetected error: "duplicate source configuration detected" -func NewJSSourceDuplicateDetectedError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSSourceDuplicateDetected] -} - -// NewJSSourceInvalidStreamNameError creates a new JSSourceInvalidStreamName error: "sourced stream name is invalid" -func NewJSSourceInvalidStreamNameError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSSourceInvalidStreamName] -} - -// NewJSSourceInvalidSubjectFilterError creates a new JSSourceInvalidSubjectFilter error: "source transform source: {err}" -func NewJSSourceInvalidSubjectFilterError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSSourceInvalidSubjectFilter] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSSourceInvalidTransformDestinationError creates a new JSSourceInvalidTransformDestination error: "source transform: {err}" -func NewJSSourceInvalidTransformDestinationError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSSourceInvalidTransformDestination] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSSourceMaxMessageSizeTooBigError creates a new JSSourceMaxMessageSizeTooBigErr error: "stream source must have max message size >= target" -func NewJSSourceMaxMessageSizeTooBigError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSSourceMaxMessageSizeTooBigErr] -} - -// NewJSSourceMultipleFiltersNotAllowedError creates a new JSSourceMultipleFiltersNotAllowed error: "source with multiple subject transforms cannot also have a single subject filter" -func NewJSSourceMultipleFiltersNotAllowedError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSSourceMultipleFiltersNotAllowed] -} - -// NewJSSourceOverlappingSubjectFiltersError creates a new JSSourceOverlappingSubjectFilters error: "source filters can not overlap" -func NewJSSourceOverlappingSubjectFiltersError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSSourceOverlappingSubjectFilters] -} - -// NewJSStorageResourcesExceededError creates a new JSStorageResourcesExceededErr error: "insufficient storage resources available" -func NewJSStorageResourcesExceededError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStorageResourcesExceededErr] -} - -// NewJSStreamAssignmentError creates a new JSStreamAssignmentErrF error: "{err}" -func NewJSStreamAssignmentError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamAssignmentErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamCreateError creates a new JSStreamCreateErrF error: "{err}" -func NewJSStreamCreateError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamCreateErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamDeleteError creates a new JSStreamDeleteErrF error: "{err}" -func NewJSStreamDeleteError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamDeleteErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamDuplicateMessageConflictError creates a new JSStreamDuplicateMessageConflict error: "duplicate message id is in process" -func NewJSStreamDuplicateMessageConflictError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamDuplicateMessageConflict] -} - -// NewJSStreamExpectedLastSeqPerSubjectNotReadyError creates a new JSStreamExpectedLastSeqPerSubjectNotReady error: "expected last sequence per subject temporarily unavailable" -func NewJSStreamExpectedLastSeqPerSubjectNotReadyError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamExpectedLastSeqPerSubjectNotReady] -} - -// NewJSStreamExternalApiOverlapError creates a new JSStreamExternalApiOverlapErrF error: "stream external api prefix {prefix} must not overlap with {subject}" -func NewJSStreamExternalApiOverlapError(prefix interface{}, subject interface{}, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamExternalApiOverlapErrF] - args := e.toReplacerArgs([]interface{}{"{prefix}", prefix, "{subject}", subject}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamExternalDelPrefixOverlapsError creates a new JSStreamExternalDelPrefixOverlapsErrF error: "stream external delivery prefix {prefix} overlaps with stream subject {subject}" -func NewJSStreamExternalDelPrefixOverlapsError(prefix interface{}, subject interface{}, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamExternalDelPrefixOverlapsErrF] - args := e.toReplacerArgs([]interface{}{"{prefix}", prefix, "{subject}", subject}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamGeneralError creates a new JSStreamGeneralErrorF error: "{err}" -func NewJSStreamGeneralError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamGeneralErrorF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamHeaderExceedsMaximumError creates a new JSStreamHeaderExceedsMaximumErr error: "header size exceeds maximum allowed of 64k" -func NewJSStreamHeaderExceedsMaximumError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamHeaderExceedsMaximumErr] -} - -// NewJSStreamInfoMaxSubjectsError creates a new JSStreamInfoMaxSubjectsErr error: "subject details would exceed maximum allowed" -func NewJSStreamInfoMaxSubjectsError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamInfoMaxSubjectsErr] -} - -// NewJSStreamInvalidConfigError creates a new JSStreamInvalidConfigF error: "{err}" -func NewJSStreamInvalidConfigError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamInvalidConfigF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamInvalidError creates a new JSStreamInvalidErr error: "stream not valid" -func NewJSStreamInvalidError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamInvalidErr] -} - -// NewJSStreamInvalidExternalDeliverySubjError creates a new JSStreamInvalidExternalDeliverySubjErrF error: "stream external delivery prefix {prefix} must not contain wildcards" -func NewJSStreamInvalidExternalDeliverySubjError(prefix interface{}, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamInvalidExternalDeliverySubjErrF] - args := e.toReplacerArgs([]interface{}{"{prefix}", prefix}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamLimitsError creates a new JSStreamLimitsErrF error: "{err}" -func NewJSStreamLimitsError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamLimitsErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamMaxBytesRequiredError creates a new JSStreamMaxBytesRequired error: "account requires a stream config to have max bytes set" -func NewJSStreamMaxBytesRequiredError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamMaxBytesRequired] -} - -// NewJSStreamMaxStreamBytesExceededError creates a new JSStreamMaxStreamBytesExceeded error: "stream max bytes exceeds account limit max stream bytes" -func NewJSStreamMaxStreamBytesExceededError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamMaxStreamBytesExceeded] -} - -// NewJSStreamMessageExceedsMaximumError creates a new JSStreamMessageExceedsMaximumErr error: "message size exceeds maximum allowed" -func NewJSStreamMessageExceedsMaximumError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamMessageExceedsMaximumErr] -} - -// NewJSStreamMirrorNotUpdatableError creates a new JSStreamMirrorNotUpdatableErr error: "stream mirror configuration can not be updated" -func NewJSStreamMirrorNotUpdatableError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamMirrorNotUpdatableErr] -} - -// NewJSStreamMismatchError creates a new JSStreamMismatchErr error: "stream name in subject does not match request" -func NewJSStreamMismatchError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamMismatchErr] -} - -// NewJSStreamMoveAndScaleError creates a new JSStreamMoveAndScaleErr error: "can not move and scale a stream in a single update" -func NewJSStreamMoveAndScaleError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamMoveAndScaleErr] -} - -// NewJSStreamMoveInProgressError creates a new JSStreamMoveInProgressF error: "stream move already in progress: {msg}" -func NewJSStreamMoveInProgressError(msg interface{}, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamMoveInProgressF] - args := e.toReplacerArgs([]interface{}{"{msg}", msg}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamMoveNotInProgressError creates a new JSStreamMoveNotInProgress error: "stream move not in progress" -func NewJSStreamMoveNotInProgressError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamMoveNotInProgress] -} - -// NewJSStreamMsgDeleteFailedError creates a new JSStreamMsgDeleteFailedF error: "{err}" -func NewJSStreamMsgDeleteFailedError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamMsgDeleteFailedF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamNameContainsPathSeparatorsError creates a new JSStreamNameContainsPathSeparatorsErr error: "Stream name can not contain path separators" -func NewJSStreamNameContainsPathSeparatorsError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamNameContainsPathSeparatorsErr] -} - -// NewJSStreamNameExistError creates a new JSStreamNameExistErr error: "stream name already in use with a different configuration" -func NewJSStreamNameExistError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamNameExistErr] -} - -// NewJSStreamNameExistRestoreFailedError creates a new JSStreamNameExistRestoreFailedErr error: "stream name already in use, cannot restore" -func NewJSStreamNameExistRestoreFailedError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamNameExistRestoreFailedErr] -} - -// NewJSStreamNotFoundError creates a new JSStreamNotFoundErr error: "stream not found" -func NewJSStreamNotFoundError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamNotFoundErr] -} - -// NewJSStreamNotMatchError creates a new JSStreamNotMatchErr error: "expected stream does not match" -func NewJSStreamNotMatchError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamNotMatchErr] -} - -// NewJSStreamOfflineError creates a new JSStreamOfflineErr error: "stream is offline" -func NewJSStreamOfflineError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamOfflineErr] -} - -// NewJSStreamPurgeFailedError creates a new JSStreamPurgeFailedF error: "{err}" -func NewJSStreamPurgeFailedError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamPurgeFailedF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamReplicasNotSupportedError creates a new JSStreamReplicasNotSupportedErr error: "replicas > 1 not supported in non-clustered mode" -func NewJSStreamReplicasNotSupportedError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamReplicasNotSupportedErr] -} - -// NewJSStreamReplicasNotUpdatableError creates a new JSStreamReplicasNotUpdatableErr error: "Replicas configuration can not be updated" -func NewJSStreamReplicasNotUpdatableError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamReplicasNotUpdatableErr] -} - -// NewJSStreamRestoreError creates a new JSStreamRestoreErrF error: "restore failed: {err}" -func NewJSStreamRestoreError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamRestoreErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamRollupFailedError creates a new JSStreamRollupFailedF error: "{err}" -func NewJSStreamRollupFailedError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamRollupFailedF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamSealedError creates a new JSStreamSealedErr error: "invalid operation on sealed stream" -func NewJSStreamSealedError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamSealedErr] -} - -// NewJSStreamSequenceNotMatchError creates a new JSStreamSequenceNotMatchErr error: "expected stream sequence does not match" -func NewJSStreamSequenceNotMatchError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamSequenceNotMatchErr] -} - -// NewJSStreamSnapshotError creates a new JSStreamSnapshotErrF error: "snapshot failed: {err}" -func NewJSStreamSnapshotError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamSnapshotErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamStoreFailedError creates a new JSStreamStoreFailedF error: "{err}" -func NewJSStreamStoreFailedError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamStoreFailedF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamSubjectOverlapError creates a new JSStreamSubjectOverlapErr error: "subjects overlap with an existing stream" -func NewJSStreamSubjectOverlapError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamSubjectOverlapErr] -} - -// NewJSStreamTemplateCreateError creates a new JSStreamTemplateCreateErrF error: "{err}" -func NewJSStreamTemplateCreateError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamTemplateCreateErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamTemplateDeleteError creates a new JSStreamTemplateDeleteErrF error: "{err}" -func NewJSStreamTemplateDeleteError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamTemplateDeleteErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamTemplateNotFoundError creates a new JSStreamTemplateNotFoundErr error: "template not found" -func NewJSStreamTemplateNotFoundError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamTemplateNotFoundErr] -} - -// NewJSStreamTooManyRequestsError creates a new JSStreamTooManyRequests error: "too many requests" -func NewJSStreamTooManyRequestsError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamTooManyRequests] -} - -// NewJSStreamTransformInvalidDestinationError creates a new JSStreamTransformInvalidDestination error: "stream transform: {err}" -func NewJSStreamTransformInvalidDestinationError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamTransformInvalidDestination] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamTransformInvalidSourceError creates a new JSStreamTransformInvalidSource error: "stream transform source: {err}" -func NewJSStreamTransformInvalidSourceError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamTransformInvalidSource] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamUpdateError creates a new JSStreamUpdateErrF error: "{err}" -func NewJSStreamUpdateError(err error, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamUpdateErrF] - args := e.toReplacerArgs([]interface{}{"{err}", err}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamWrongLastMsgIDError creates a new JSStreamWrongLastMsgIDErrF error: "wrong last msg ID: {id}" -func NewJSStreamWrongLastMsgIDError(id interface{}, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamWrongLastMsgIDErrF] - args := e.toReplacerArgs([]interface{}{"{id}", id}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSStreamWrongLastSequenceConstantError creates a new JSStreamWrongLastSequenceConstantErr error: "wrong last sequence" -func NewJSStreamWrongLastSequenceConstantError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSStreamWrongLastSequenceConstantErr] -} - -// NewJSStreamWrongLastSequenceError creates a new JSStreamWrongLastSequenceErrF error: "wrong last sequence: {seq}" -func NewJSStreamWrongLastSequenceError(seq uint64, opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - e := ApiErrors[JSStreamWrongLastSequenceErrF] - args := e.toReplacerArgs([]interface{}{"{seq}", seq}) - return &ApiError{ - Code: e.Code, - ErrCode: e.ErrCode, - Description: strings.NewReplacer(args...).Replace(e.Description), - } -} - -// NewJSTempStorageFailedError creates a new JSTempStorageFailedErr error: "JetStream unable to open temp storage for restore" -func NewJSTempStorageFailedError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSTempStorageFailedErr] -} - -// NewJSTemplateNameNotMatchSubjectError creates a new JSTemplateNameNotMatchSubjectErr error: "template name in subject does not match request" -func NewJSTemplateNameNotMatchSubjectError(opts ...ErrorOption) *ApiError { - eopts := parseOpts(opts) - if ae, ok := eopts.err.(*ApiError); ok { - return ae - } - - return ApiErrors[JSTemplateNameNotMatchSubjectErr] -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_events.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_events.go deleted file mode 100644 index 6b4ac9de..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_events.go +++ /dev/null @@ -1,345 +0,0 @@ -// Copyright 2020-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "encoding/json" - "time" -) - -// publishAdvisory sends the given advisory into the account. Returns true if -// it was sent, false if not (i.e. due to lack of interest or a marshal error). -func (s *Server) publishAdvisory(acc *Account, subject string, adv any) bool { - if acc == nil { - acc = s.SystemAccount() - if acc == nil { - return false - } - } - - // If there is no one listening for this advisory then save ourselves the effort - // and don't bother encoding the JSON or sending it. - if sl := acc.sl; (sl != nil && !sl.HasInterest(subject)) && !s.hasGatewayInterest(acc.Name, subject) { - return false - } - - ej, err := json.Marshal(adv) - if err == nil { - err = s.sendInternalAccountMsg(acc, subject, ej) - if err != nil { - s.Warnf("Advisory could not be sent for account %q: %v", acc.Name, err) - } - } else { - s.Warnf("Advisory could not be serialized for account %q: %v", acc.Name, err) - } - return err == nil -} - -// JSAPIAudit is an advisory about administrative actions taken on JetStream -type JSAPIAudit struct { - TypedEvent - Server string `json:"server"` - Client *ClientInfo `json:"client"` - Subject string `json:"subject"` - Request string `json:"request,omitempty"` - Response string `json:"response"` - Domain string `json:"domain,omitempty"` -} - -const JSAPIAuditType = "io.nats.jetstream.advisory.v1.api_audit" - -// ActionAdvisoryType indicates which action against a stream, consumer or template triggered an advisory -type ActionAdvisoryType string - -const ( - CreateEvent ActionAdvisoryType = "create" - DeleteEvent ActionAdvisoryType = "delete" - ModifyEvent ActionAdvisoryType = "modify" -) - -// JSStreamActionAdvisory indicates that a stream was created, edited or deleted -type JSStreamActionAdvisory struct { - TypedEvent - Stream string `json:"stream"` - Action ActionAdvisoryType `json:"action"` - Template string `json:"template,omitempty"` - Domain string `json:"domain,omitempty"` -} - -const JSStreamActionAdvisoryType = "io.nats.jetstream.advisory.v1.stream_action" - -// JSConsumerActionAdvisory indicates that a consumer was created or deleted -type JSConsumerActionAdvisory struct { - TypedEvent - Stream string `json:"stream"` - Consumer string `json:"consumer"` - Action ActionAdvisoryType `json:"action"` - Domain string `json:"domain,omitempty"` -} - -const JSConsumerActionAdvisoryType = "io.nats.jetstream.advisory.v1.consumer_action" - -// JSConsumerPauseAdvisory indicates that a consumer was paused or unpaused -type JSConsumerPauseAdvisory struct { - TypedEvent - Stream string `json:"stream"` - Consumer string `json:"consumer"` - Paused bool `json:"paused"` - PauseUntil time.Time `json:"pause_until,omitempty"` - Domain string `json:"domain,omitempty"` -} - -const JSConsumerPauseAdvisoryType = "io.nats.jetstream.advisory.v1.consumer_pause" - -// JSConsumerAckMetric is a metric published when a user acknowledges a message, the -// number of these that will be published is dependent on SampleFrequency -type JSConsumerAckMetric struct { - TypedEvent - Stream string `json:"stream"` - Consumer string `json:"consumer"` - ConsumerSeq uint64 `json:"consumer_seq"` - StreamSeq uint64 `json:"stream_seq"` - Delay int64 `json:"ack_time"` - Deliveries uint64 `json:"deliveries"` - Domain string `json:"domain,omitempty"` -} - -// JSConsumerAckMetricType is the schema type for JSConsumerAckMetricType -const JSConsumerAckMetricType = "io.nats.jetstream.metric.v1.consumer_ack" - -// JSConsumerDeliveryExceededAdvisory is an advisory informing that a message hit -// its MaxDeliver threshold and so might be a candidate for DLQ handling -type JSConsumerDeliveryExceededAdvisory struct { - TypedEvent - Stream string `json:"stream"` - Consumer string `json:"consumer"` - StreamSeq uint64 `json:"stream_seq"` - Deliveries uint64 `json:"deliveries"` - Domain string `json:"domain,omitempty"` -} - -// JSConsumerDeliveryExceededAdvisoryType is the schema type for JSConsumerDeliveryExceededAdvisory -const JSConsumerDeliveryExceededAdvisoryType = "io.nats.jetstream.advisory.v1.max_deliver" - -// JSConsumerDeliveryNakAdvisory is an advisory informing that a message was -// naked by the consumer -type JSConsumerDeliveryNakAdvisory struct { - TypedEvent - Stream string `json:"stream"` - Consumer string `json:"consumer"` - ConsumerSeq uint64 `json:"consumer_seq"` - StreamSeq uint64 `json:"stream_seq"` - Deliveries uint64 `json:"deliveries"` - Domain string `json:"domain,omitempty"` -} - -// JSConsumerDeliveryNakAdvisoryType is the schema type for JSConsumerDeliveryNakAdvisory -const JSConsumerDeliveryNakAdvisoryType = "io.nats.jetstream.advisory.v1.nak" - -// JSConsumerDeliveryTerminatedAdvisory is an advisory informing that a message was -// terminated by the consumer, so might be a candidate for DLQ handling -type JSConsumerDeliveryTerminatedAdvisory struct { - TypedEvent - Stream string `json:"stream"` - Consumer string `json:"consumer"` - ConsumerSeq uint64 `json:"consumer_seq"` - StreamSeq uint64 `json:"stream_seq"` - Deliveries uint64 `json:"deliveries"` - Reason string `json:"reason,omitempty"` - Domain string `json:"domain,omitempty"` -} - -// JSConsumerDeliveryTerminatedAdvisoryType is the schema type for JSConsumerDeliveryTerminatedAdvisory -const JSConsumerDeliveryTerminatedAdvisoryType = "io.nats.jetstream.advisory.v1.terminated" - -// JSSnapshotCreateAdvisory is an advisory sent after a snapshot is successfully started -type JSSnapshotCreateAdvisory struct { - TypedEvent - Stream string `json:"stream"` - State StreamState `json:"state"` - Client *ClientInfo `json:"client"` - Domain string `json:"domain,omitempty"` -} - -// JSSnapshotCreatedAdvisoryType is the schema type for JSSnapshotCreateAdvisory -const JSSnapshotCreatedAdvisoryType = "io.nats.jetstream.advisory.v1.snapshot_create" - -// JSSnapshotCompleteAdvisory is an advisory sent after a snapshot is successfully started -type JSSnapshotCompleteAdvisory struct { - TypedEvent - Stream string `json:"stream"` - Start time.Time `json:"start"` - End time.Time `json:"end"` - Client *ClientInfo `json:"client"` - Domain string `json:"domain,omitempty"` -} - -// JSSnapshotCompleteAdvisoryType is the schema type for JSSnapshotCreateAdvisory -const JSSnapshotCompleteAdvisoryType = "io.nats.jetstream.advisory.v1.snapshot_complete" - -// JSRestoreCreateAdvisory is an advisory sent after a snapshot is successfully started -type JSRestoreCreateAdvisory struct { - TypedEvent - Stream string `json:"stream"` - Client *ClientInfo `json:"client"` - Domain string `json:"domain,omitempty"` -} - -// JSRestoreCreateAdvisoryType is the schema type for JSSnapshotCreateAdvisory -const JSRestoreCreateAdvisoryType = "io.nats.jetstream.advisory.v1.restore_create" - -// JSRestoreCompleteAdvisory is an advisory sent after a snapshot is successfully started -type JSRestoreCompleteAdvisory struct { - TypedEvent - Stream string `json:"stream"` - Start time.Time `json:"start"` - End time.Time `json:"end"` - Bytes int64 `json:"bytes"` - Client *ClientInfo `json:"client"` - Domain string `json:"domain,omitempty"` -} - -// JSRestoreCompleteAdvisoryType is the schema type for JSSnapshotCreateAdvisory -const JSRestoreCompleteAdvisoryType = "io.nats.jetstream.advisory.v1.restore_complete" - -// Clustering specific. - -// JSClusterLeaderElectedAdvisoryType is sent when the system elects a new meta leader. -const JSDomainLeaderElectedAdvisoryType = "io.nats.jetstream.advisory.v1.domain_leader_elected" - -// JSClusterLeaderElectedAdvisory indicates that a domain has elected a new leader. -type JSDomainLeaderElectedAdvisory struct { - TypedEvent - Leader string `json:"leader"` - Replicas []*PeerInfo `json:"replicas"` - Cluster string `json:"cluster"` - Domain string `json:"domain,omitempty"` -} - -// JSStreamLeaderElectedAdvisoryType is sent when the system elects a new leader for a stream. -const JSStreamLeaderElectedAdvisoryType = "io.nats.jetstream.advisory.v1.stream_leader_elected" - -// JSStreamLeaderElectedAdvisory indicates that a stream has elected a new leader. -type JSStreamLeaderElectedAdvisory struct { - TypedEvent - Account string `json:"account,omitempty"` - Stream string `json:"stream"` - Leader string `json:"leader"` - Replicas []*PeerInfo `json:"replicas"` - Domain string `json:"domain,omitempty"` -} - -// JSStreamQuorumLostAdvisoryType is sent when the system detects a clustered stream and -// its consumers are stalled and unable to make progress. -const JSStreamQuorumLostAdvisoryType = "io.nats.jetstream.advisory.v1.stream_quorum_lost" - -// JSStreamQuorumLostAdvisory indicates that a stream has lost quorum and is stalled. -type JSStreamQuorumLostAdvisory struct { - TypedEvent - Account string `json:"account,omitempty"` - Stream string `json:"stream"` - Replicas []*PeerInfo `json:"replicas"` - Domain string `json:"domain,omitempty"` -} - -// JSConsumerLeaderElectedAdvisoryType is sent when the system elects a leader for a consumer. -const JSConsumerLeaderElectedAdvisoryType = "io.nats.jetstream.advisory.v1.consumer_leader_elected" - -// JSConsumerLeaderElectedAdvisory indicates that a consumer has elected a new leader. -type JSConsumerLeaderElectedAdvisory struct { - TypedEvent - Account string `json:"account,omitempty"` - Stream string `json:"stream"` - Consumer string `json:"consumer"` - Leader string `json:"leader"` - Replicas []*PeerInfo `json:"replicas"` - Domain string `json:"domain,omitempty"` -} - -// JSConsumerQuorumLostAdvisoryType is sent when the system detects a clustered consumer and -// is stalled and unable to make progress. -const JSConsumerQuorumLostAdvisoryType = "io.nats.jetstream.advisory.v1.consumer_quorum_lost" - -// JSConsumerQuorumLostAdvisory indicates that a consumer has lost quorum and is stalled. -type JSConsumerQuorumLostAdvisory struct { - TypedEvent - Account string `json:"account,omitempty"` - Stream string `json:"stream"` - Consumer string `json:"consumer"` - Replicas []*PeerInfo `json:"replicas"` - Domain string `json:"domain,omitempty"` -} - -const JSConsumerGroupPinnedAdvisoryType = "io.nats.jetstream.advisory.v1.consumer_group_pinned" - -// JSConsumerGroupPinnedAdvisory that a group switched to a new pinned client -type JSConsumerGroupPinnedAdvisory struct { - TypedEvent - Account string `json:"account,omitempty"` - Stream string `json:"stream"` - Consumer string `json:"consumer"` - Domain string `json:"domain,omitempty"` - Group string `json:"group"` - PinnedClientId string `json:"pinned_id"` -} - -const JSConsumerGroupUnpinnedAdvisoryType = "io.nats.jetstream.advisory.v1.consumer_group_unpinned" - -// JSConsumerGroupUnpinnedAdvisory indicates that a pin was lost -type JSConsumerGroupUnpinnedAdvisory struct { - TypedEvent - Account string `json:"account,omitempty"` - Stream string `json:"stream"` - Consumer string `json:"consumer"` - Domain string `json:"domain,omitempty"` - Group string `json:"group"` - // one of "admin" or "timeout", could be an enum up to the implementor to decide - Reason string `json:"reason"` -} - -// JSServerOutOfStorageAdvisoryType is sent when the server is out of storage space. -const JSServerOutOfStorageAdvisoryType = "io.nats.jetstream.advisory.v1.server_out_of_space" - -// JSServerOutOfSpaceAdvisory indicates that a stream has lost quorum and is stalled. -type JSServerOutOfSpaceAdvisory struct { - TypedEvent - Server string `json:"server"` - ServerID string `json:"server_id"` - Stream string `json:"stream,omitempty"` - Cluster string `json:"cluster"` - Domain string `json:"domain,omitempty"` -} - -// JSServerRemovedAdvisoryType is sent when the server has been removed and JS disabled. -const JSServerRemovedAdvisoryType = "io.nats.jetstream.advisory.v1.server_removed" - -// JSServerRemovedAdvisory indicates that a stream has lost quorum and is stalled. -type JSServerRemovedAdvisory struct { - TypedEvent - Server string `json:"server"` - ServerID string `json:"server_id"` - Cluster string `json:"cluster"` - Domain string `json:"domain,omitempty"` -} - -// JSAPILimitReachedAdvisoryType is sent when the JS API request queue limit is reached. -const JSAPILimitReachedAdvisoryType = "io.nats.jetstream.advisory.v1.api_limit_reached" - -// JSAPILimitReachedAdvisory is a advisory published when JetStream hits the queue length limit. -type JSAPILimitReachedAdvisory struct { - TypedEvent - Server string `json:"server"` // Server that created the event, name or ID - Domain string `json:"domain,omitempty"` // Domain the server belongs to - Dropped int64 `json:"dropped"` // How many messages did we drop from the queue -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_versioning.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_versioning.go deleted file mode 100644 index 1192968b..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jetstream_versioning.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2024-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import "strconv" - -const ( - // JSApiLevel is the maximum supported JetStream API level for this server. - JSApiLevel int = 1 - - JSRequiredLevelMetadataKey = "_nats.req.level" - JSServerVersionMetadataKey = "_nats.ver" - JSServerLevelMetadataKey = "_nats.level" -) - -// setStaticStreamMetadata sets JetStream stream metadata, like the server version and API level. -// Any dynamic metadata is removed, it must not be stored and only be added for responses. -func setStaticStreamMetadata(cfg *StreamConfig) { - if cfg.Metadata == nil { - cfg.Metadata = make(map[string]string) - } else { - deleteDynamicMetadata(cfg.Metadata) - } - - var requiredApiLevel int - requires := func(level int) { - if level > requiredApiLevel { - requiredApiLevel = level - } - } - - // TTLs were added in v2.11 and require API level 1. - if cfg.AllowMsgTTL || cfg.SubjectDeleteMarkerTTL > 0 { - requires(1) - } - - cfg.Metadata[JSRequiredLevelMetadataKey] = strconv.Itoa(requiredApiLevel) -} - -// setDynamicStreamMetadata adds dynamic fields into the (copied) metadata. -func setDynamicStreamMetadata(cfg *StreamConfig) *StreamConfig { - newCfg := *cfg - newCfg.Metadata = make(map[string]string) - for key, value := range cfg.Metadata { - newCfg.Metadata[key] = value - } - newCfg.Metadata[JSServerVersionMetadataKey] = VERSION - newCfg.Metadata[JSServerLevelMetadataKey] = strconv.Itoa(JSApiLevel) - return &newCfg -} - -// copyConsumerMetadata copies versioning fields from metadata of prevCfg into cfg. -// Removes versioning fields if no previous metadata, updates if set, and removes fields if it doesn't exist in prevCfg. -// Any dynamic metadata is removed, it must not be stored and only be added for responses. -// -// Note: useful when doing equality checks on cfg and prevCfg, but ignoring any versioning metadata differences. -func copyStreamMetadata(cfg *StreamConfig, prevCfg *StreamConfig) { - if cfg.Metadata != nil { - deleteDynamicMetadata(cfg.Metadata) - } - setOrDeleteInStreamMetadata(cfg, prevCfg, JSRequiredLevelMetadataKey) -} - -// setOrDeleteInConsumerMetadata sets field with key/value in metadata of cfg if set, deletes otherwise. -func setOrDeleteInStreamMetadata(cfg *StreamConfig, prevCfg *StreamConfig, key string) { - if prevCfg != nil && prevCfg.Metadata != nil { - if value, ok := prevCfg.Metadata[key]; ok { - if cfg.Metadata == nil { - cfg.Metadata = make(map[string]string) - } - cfg.Metadata[key] = value - return - } - } - delete(cfg.Metadata, key) - if len(cfg.Metadata) == 0 { - cfg.Metadata = nil - } -} - -// setStaticConsumerMetadata sets JetStream consumer metadata, like the server version and API level. -// Any dynamic metadata is removed, it must not be stored and only be added for responses. -func setStaticConsumerMetadata(cfg *ConsumerConfig) { - if cfg.Metadata == nil { - cfg.Metadata = make(map[string]string) - } else { - deleteDynamicMetadata(cfg.Metadata) - } - - var requiredApiLevel int - requires := func(level int) { - if level > requiredApiLevel { - requiredApiLevel = level - } - } - - // Added in 2.11, absent | zero is the feature is not used. - // one could be stricter and say even if its set but the time - // has already passed it is also not needed to restore the consumer - if cfg.PauseUntil != nil && !cfg.PauseUntil.IsZero() { - requires(1) - } - - if cfg.PriorityPolicy != PriorityNone || cfg.PinnedTTL != 0 || len(cfg.PriorityGroups) > 0 { - requires(1) - } - - cfg.Metadata[JSRequiredLevelMetadataKey] = strconv.Itoa(requiredApiLevel) -} - -// setDynamicConsumerMetadata adds dynamic fields into the (copied) metadata. -func setDynamicConsumerMetadata(cfg *ConsumerConfig) *ConsumerConfig { - newCfg := *cfg - newCfg.Metadata = make(map[string]string) - for key, value := range cfg.Metadata { - newCfg.Metadata[key] = value - } - newCfg.Metadata[JSServerVersionMetadataKey] = VERSION - newCfg.Metadata[JSServerLevelMetadataKey] = strconv.Itoa(JSApiLevel) - return &newCfg -} - -// setDynamicConsumerInfoMetadata adds dynamic fields into the (copied) metadata. -func setDynamicConsumerInfoMetadata(info *ConsumerInfo) *ConsumerInfo { - if info == nil { - return nil - } - - newInfo := *info - cfg := setDynamicConsumerMetadata(info.Config) - newInfo.Config = cfg - return &newInfo -} - -// copyConsumerMetadata copies versioning fields from metadata of prevCfg into cfg. -// Removes versioning fields if no previous metadata, updates if set, and removes fields if it doesn't exist in prevCfg. -// Any dynamic metadata is removed, it must not be stored and only be added for responses. -// -// Note: useful when doing equality checks on cfg and prevCfg, but ignoring any versioning metadata differences. -func copyConsumerMetadata(cfg *ConsumerConfig, prevCfg *ConsumerConfig) { - if cfg.Metadata != nil { - deleteDynamicMetadata(cfg.Metadata) - } - setOrDeleteInConsumerMetadata(cfg, prevCfg, JSRequiredLevelMetadataKey) -} - -// setOrDeleteInConsumerMetadata sets field with key/value in metadata of cfg if set, deletes otherwise. -func setOrDeleteInConsumerMetadata(cfg *ConsumerConfig, prevCfg *ConsumerConfig, key string) { - if prevCfg != nil && prevCfg.Metadata != nil { - if value, ok := prevCfg.Metadata[key]; ok { - if cfg.Metadata == nil { - cfg.Metadata = make(map[string]string) - } - cfg.Metadata[key] = value - return - } - } - delete(cfg.Metadata, key) - if len(cfg.Metadata) == 0 { - cfg.Metadata = nil - } -} - -// deleteDynamicMetadata deletes dynamic fields from the metadata. -func deleteDynamicMetadata(metadata map[string]string) { - delete(metadata, JSServerVersionMetadataKey) - delete(metadata, JSServerLevelMetadataKey) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jwt.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jwt.go deleted file mode 100644 index ada2d91f..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/jwt.go +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2018-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "errors" - "fmt" - "net" - "os" - "strings" - "time" - - "github.com/nats-io/jwt/v2" - "github.com/nats-io/nkeys" -) - -// All JWTs once encoded start with this -const jwtPrefix = "eyJ" - -// ReadOperatorJWT will read a jwt file for an operator claim. This can be a decorated file. -func ReadOperatorJWT(jwtfile string) (*jwt.OperatorClaims, error) { - _, claim, err := readOperatorJWT(jwtfile) - return claim, err -} - -func readOperatorJWT(jwtfile string) (string, *jwt.OperatorClaims, error) { - contents, err := os.ReadFile(jwtfile) - if err != nil { - // Check to see if the JWT has been inlined. - if !strings.HasPrefix(jwtfile, jwtPrefix) { - return "", nil, err - } - // We may have an inline jwt here. - contents = []byte(jwtfile) - } - defer wipeSlice(contents) - - theJWT, err := jwt.ParseDecoratedJWT(contents) - if err != nil { - return "", nil, err - } - opc, err := jwt.DecodeOperatorClaims(theJWT) - if err != nil { - return "", nil, err - } - return theJWT, opc, nil -} - -// Just wipe slice with 'x', for clearing contents of nkey seed file. -func wipeSlice(buf []byte) { - for i := range buf { - buf[i] = 'x' - } -} - -// validateTrustedOperators will check that we do not have conflicts with -// assigned trusted keys and trusted operators. If operators are defined we -// will expand the trusted keys in options. -func validateTrustedOperators(o *Options) error { - if len(o.TrustedOperators) == 0 { - return nil - } - if o.AccountResolver == nil { - return fmt.Errorf("operators require an account resolver to be configured") - } - if len(o.Accounts) > 0 { - return fmt.Errorf("operators do not allow Accounts to be configured directly") - } - if len(o.Users) > 0 || len(o.Nkeys) > 0 { - return fmt.Errorf("operators do not allow users to be configured directly") - } - if len(o.TrustedOperators) > 0 && len(o.TrustedKeys) > 0 { - return fmt.Errorf("conflicting options for 'TrustedKeys' and 'TrustedOperators'") - } - if o.SystemAccount != _EMPTY_ { - foundSys := false - foundNonEmpty := false - for _, op := range o.TrustedOperators { - if op.SystemAccount != _EMPTY_ { - foundNonEmpty = true - } - if op.SystemAccount == o.SystemAccount { - foundSys = true - break - } - } - if foundNonEmpty && !foundSys { - return fmt.Errorf("system_account in config and operator JWT must be identical") - } - } else if o.TrustedOperators[0].SystemAccount == _EMPTY_ { - // In case the system account is neither defined in config nor in the first operator. - // If it would be needed due to the nats account resolver, raise an error. - switch o.AccountResolver.(type) { - case *DirAccResolver, *CacheDirAccResolver: - return fmt.Errorf("using nats based account resolver - the system account needs to be specified in configuration or the operator jwt") - } - } - - srvMajor, srvMinor, srvUpdate, _ := versionComponents(VERSION) - for _, opc := range o.TrustedOperators { - if major, minor, update, err := jwt.ParseServerVersion(opc.AssertServerVersion); err != nil { - return fmt.Errorf("operator %s expects version %s got error instead: %s", - opc.Subject, opc.AssertServerVersion, err) - } else if major > srvMajor { - return fmt.Errorf("operator %s expected major version %d > server major version %d", - opc.Subject, major, srvMajor) - } else if srvMajor > major { - } else if minor > srvMinor { - return fmt.Errorf("operator %s expected minor version %d > server minor version %d", - opc.Subject, minor, srvMinor) - } else if srvMinor > minor { - } else if update > srvUpdate { - return fmt.Errorf("operator %s expected update version %d > server update version %d", - opc.Subject, update, srvUpdate) - } - } - // If we have operators, fill in the trusted keys. - // FIXME(dlc) - We had TrustedKeys before TrustedOperators. The jwt.OperatorClaims - // has a DidSign(). Use that longer term. For now we can expand in place. - for _, opc := range o.TrustedOperators { - if o.TrustedKeys == nil { - o.TrustedKeys = make([]string, 0, 4) - } - if !opc.StrictSigningKeyUsage { - o.TrustedKeys = append(o.TrustedKeys, opc.Subject) - } - o.TrustedKeys = append(o.TrustedKeys, opc.SigningKeys...) - } - for _, key := range o.TrustedKeys { - if !nkeys.IsValidPublicOperatorKey(key) { - return fmt.Errorf("trusted Keys %q are required to be a valid public operator nkey", key) - } - } - if len(o.resolverPinnedAccounts) > 0 { - for key := range o.resolverPinnedAccounts { - if !nkeys.IsValidPublicAccountKey(key) { - return fmt.Errorf("pinned account key %q is not a valid public account nkey", key) - } - } - // ensure the system account (belonging to the operator can always connect) - if o.SystemAccount != _EMPTY_ { - o.resolverPinnedAccounts[o.SystemAccount] = struct{}{} - } - } - - // If we have an auth callout defined make sure we are not in operator mode. - if o.AuthCallout != nil { - return errors.New("operators do not allow authorization callouts to be configured directly") - } - - return nil -} - -func validateSrc(claims *jwt.UserClaims, host string) bool { - if claims == nil { - return false - } else if len(claims.Src) == 0 { - return true - } else if host == "" { - return false - } - ip := net.ParseIP(host) - if ip == nil { - return false - } - for _, cidr := range claims.Src { - if _, net, err := net.ParseCIDR(cidr); err != nil { - return false // should not happen as this jwt is invalid - } else if net.Contains(ip) { - return true - } - } - return false -} - -func validateTimes(claims *jwt.UserClaims) (bool, time.Duration) { - if claims == nil { - return false, time.Duration(0) - } else if len(claims.Times) == 0 { - return true, time.Duration(0) - } - now := time.Now() - loc := time.Local - if claims.Locale != "" { - var err error - if loc, err = time.LoadLocation(claims.Locale); err != nil { - return false, time.Duration(0) // parsing not expected to fail at this point - } - now = now.In(loc) - } - for _, timeRange := range claims.Times { - y, m, d := now.Date() - m = m - 1 - d = d - 1 - start, err := time.ParseInLocation("15:04:05", timeRange.Start, loc) - if err != nil { - return false, time.Duration(0) // parsing not expected to fail at this point - } - end, err := time.ParseInLocation("15:04:05", timeRange.End, loc) - if err != nil { - return false, time.Duration(0) // parsing not expected to fail at this point - } - if start.After(end) { - start = start.AddDate(y, int(m), d) - d++ // the intent is to be the next day - } else { - start = start.AddDate(y, int(m), d) - } - if start.Before(now) { - end = end.AddDate(y, int(m), d) - if end.After(now) { - return true, end.Sub(now) - } - } - } - return false, time.Duration(0) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/leafnode.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/leafnode.go deleted file mode 100644 index 4904aee2..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/leafnode.go +++ /dev/null @@ -1,3190 +0,0 @@ -// Copyright 2019-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bufio" - "bytes" - "crypto/tls" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "math/rand" - "net" - "net/http" - "net/url" - "os" - "path" - "reflect" - "regexp" - "runtime" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/klauspost/compress/s2" - "github.com/nats-io/jwt/v2" - "github.com/nats-io/nkeys" - "github.com/nats-io/nuid" -) - -const ( - // Warning when user configures leafnode TLS insecure - leafnodeTLSInsecureWarning = "TLS certificate chain and hostname of solicited leafnodes will not be verified. DO NOT USE IN PRODUCTION!" - - // When a loop is detected, delay the reconnect of solicited connection. - leafNodeReconnectDelayAfterLoopDetected = 30 * time.Second - - // When a server receives a message causing a permission violation, the - // connection is closed and it won't attempt to reconnect for that long. - leafNodeReconnectAfterPermViolation = 30 * time.Second - - // When we have the same cluster name as the hub. - leafNodeReconnectDelayAfterClusterNameSame = 30 * time.Second - - // Prefix for loop detection subject - leafNodeLoopDetectionSubjectPrefix = "$LDS." - - // Path added to URL to indicate to WS server that the connection is a - // LEAF connection as opposed to a CLIENT. - leafNodeWSPath = "/leafnode" - - // This is the time the server will wait, when receiving a CONNECT, - // before closing the connection if the required minimum version is not met. - leafNodeWaitBeforeClose = 5 * time.Second -) - -type leaf struct { - // We have any auth stuff here for solicited connections. - remote *leafNodeCfg - // isSpoke tells us what role we are playing. - // Used when we receive a connection but otherside tells us they are a hub. - isSpoke bool - // remoteCluster is when we are a hub but the spoke leafnode is part of a cluster. - remoteCluster string - // remoteServer holds onto the remove server's name or ID. - remoteServer string - // domain name of remote server - remoteDomain string - // account name of remote server - remoteAccName string - // Used to suppress sub and unsub interest. Same as routes but our audience - // here is tied to this leaf node. This will hold all subscriptions except this - // leaf nodes. This represents all the interest we want to send to the other side. - smap map[string]int32 - // This map will contain all the subscriptions that have been added to the smap - // during initLeafNodeSmapAndSendSubs. It is short lived and is there to avoid - // race between processing of a sub where sub is added to account sublist but - // updateSmap has not be called on that "thread", while in the LN readloop, - // when processing CONNECT, initLeafNodeSmapAndSendSubs is invoked and add - // this subscription to smap. When processing of the sub then calls updateSmap, - // we would add it a second time in the smap causing later unsub to suppress the LS-. - tsub map[*subscription]struct{} - tsubt *time.Timer - // Selected compression mode, which may be different from the server configured mode. - compression string - // This is for GW map replies. - gwSub *subscription -} - -// Used for remote (solicited) leafnodes. -type leafNodeCfg struct { - sync.RWMutex - *RemoteLeafOpts - urls []*url.URL - curURL *url.URL - tlsName string - username string - password string - perms *Permissions - connDelay time.Duration // Delay before a connect, could be used while detecting loop condition, etc.. - jsMigrateTimer *time.Timer -} - -// Check to see if this is a solicited leafnode. We do special processing for solicited. -func (c *client) isSolicitedLeafNode() bool { - return c.kind == LEAF && c.leaf.remote != nil -} - -// Returns true if this is a solicited leafnode and is not configured to be treated as a hub or a receiving -// connection leafnode where the otherside has declared itself to be the hub. -func (c *client) isSpokeLeafNode() bool { - return c.kind == LEAF && c.leaf.isSpoke -} - -func (c *client) isHubLeafNode() bool { - return c.kind == LEAF && !c.leaf.isSpoke -} - -// This will spin up go routines to solicit the remote leaf node connections. -func (s *Server) solicitLeafNodeRemotes(remotes []*RemoteLeafOpts) { - sysAccName := _EMPTY_ - sAcc := s.SystemAccount() - if sAcc != nil { - sysAccName = sAcc.Name - } - addRemote := func(r *RemoteLeafOpts, isSysAccRemote bool) *leafNodeCfg { - s.mu.Lock() - remote := newLeafNodeCfg(r) - creds := remote.Credentials - accName := remote.LocalAccount - s.leafRemoteCfgs = append(s.leafRemoteCfgs, remote) - // Print notice if - if isSysAccRemote { - if len(remote.DenyExports) > 0 { - s.Noticef("Remote for System Account uses restricted export permissions") - } - if len(remote.DenyImports) > 0 { - s.Noticef("Remote for System Account uses restricted import permissions") - } - } - s.mu.Unlock() - if creds != _EMPTY_ { - contents, err := os.ReadFile(creds) - defer wipeSlice(contents) - if err != nil { - s.Errorf("Error reading LeafNode Remote Credentials file %q: %v", creds, err) - } else if items := credsRe.FindAllSubmatch(contents, -1); len(items) < 2 { - s.Errorf("LeafNode Remote Credentials file %q malformed", creds) - } else if _, err := nkeys.FromSeed(items[1][1]); err != nil { - s.Errorf("LeafNode Remote Credentials file %q has malformed seed", creds) - } else if uc, err := jwt.DecodeUserClaims(string(items[0][1])); err != nil { - s.Errorf("LeafNode Remote Credentials file %q has malformed user jwt", creds) - } else if isSysAccRemote { - if !uc.Permissions.Pub.Empty() || !uc.Permissions.Sub.Empty() || uc.Permissions.Resp != nil { - s.Noticef("LeafNode Remote for System Account uses credentials file %q with restricted permissions", creds) - } - } else { - if !uc.Permissions.Pub.Empty() || !uc.Permissions.Sub.Empty() || uc.Permissions.Resp != nil { - s.Noticef("LeafNode Remote for Account %s uses credentials file %q with restricted permissions", accName, creds) - } - } - } - return remote - } - for _, r := range remotes { - remote := addRemote(r, r.LocalAccount == sysAccName) - s.startGoRoutine(func() { s.connectToRemoteLeafNode(remote, true) }) - } -} - -func (s *Server) remoteLeafNodeStillValid(remote *leafNodeCfg) bool { - for _, ri := range s.getOpts().LeafNode.Remotes { - // FIXME(dlc) - What about auth changes? - if reflect.DeepEqual(ri.URLs, remote.URLs) { - return true - } - } - return false -} - -// Ensure that leafnode is properly configured. -func validateLeafNode(o *Options) error { - if err := validateLeafNodeAuthOptions(o); err != nil { - return err - } - - // Users can bind to any local account, if its empty we will assume the $G account. - for _, r := range o.LeafNode.Remotes { - if r.LocalAccount == _EMPTY_ { - r.LocalAccount = globalAccountName - } - } - - // In local config mode, check that leafnode configuration refers to accounts that exist. - if len(o.TrustedOperators) == 0 { - accNames := map[string]struct{}{} - for _, a := range o.Accounts { - accNames[a.Name] = struct{}{} - } - // global account is always created - accNames[DEFAULT_GLOBAL_ACCOUNT] = struct{}{} - // in the context of leaf nodes, empty account means global account - accNames[_EMPTY_] = struct{}{} - // system account either exists or, if not disabled, will be created - if o.SystemAccount == _EMPTY_ && !o.NoSystemAccount { - accNames[DEFAULT_SYSTEM_ACCOUNT] = struct{}{} - } - checkAccountExists := func(accName string, cfgType string) error { - if _, ok := accNames[accName]; !ok { - return fmt.Errorf("cannot find local account %q specified in leafnode %s", accName, cfgType) - } - return nil - } - if err := checkAccountExists(o.LeafNode.Account, "authorization"); err != nil { - return err - } - for _, lu := range o.LeafNode.Users { - if lu.Account == nil { // means global account - continue - } - if err := checkAccountExists(lu.Account.Name, "authorization"); err != nil { - return err - } - } - for _, r := range o.LeafNode.Remotes { - if err := checkAccountExists(r.LocalAccount, "remote"); err != nil { - return err - } - } - } else { - if len(o.LeafNode.Users) != 0 { - return fmt.Errorf("operator mode does not allow specifying users in leafnode config") - } - for _, r := range o.LeafNode.Remotes { - if !nkeys.IsValidPublicAccountKey(r.LocalAccount) { - return fmt.Errorf( - "operator mode requires account nkeys in remotes. " + - "Please add an `account` key to each remote in your `leafnodes` section, to assign it to an account. " + - "Each account value should be a 56 character public key, starting with the letter 'A'") - } - } - if o.LeafNode.Port != 0 && o.LeafNode.Account != "" && !nkeys.IsValidPublicAccountKey(o.LeafNode.Account) { - return fmt.Errorf("operator mode and non account nkeys are incompatible") - } - } - - // Validate compression settings - if o.LeafNode.Compression.Mode != _EMPTY_ { - if err := validateAndNormalizeCompressionOption(&o.LeafNode.Compression, CompressionS2Auto); err != nil { - return err - } - } - - // If a remote has a websocket scheme, all need to have it. - for _, rcfg := range o.LeafNode.Remotes { - if len(rcfg.URLs) >= 2 { - firstIsWS, ok := isWSURL(rcfg.URLs[0]), true - for i := 1; i < len(rcfg.URLs); i++ { - u := rcfg.URLs[i] - if isWS := isWSURL(u); isWS && !firstIsWS || !isWS && firstIsWS { - ok = false - break - } - } - if !ok { - return fmt.Errorf("remote leaf node configuration cannot have a mix of websocket and non-websocket urls: %q", redactURLList(rcfg.URLs)) - } - } - // Validate compression settings - if rcfg.Compression.Mode != _EMPTY_ { - if err := validateAndNormalizeCompressionOption(&rcfg.Compression, CompressionS2Auto); err != nil { - return err - } - } - } - - if o.LeafNode.Port == 0 { - return nil - } - - // If MinVersion is defined, check that it is valid. - if mv := o.LeafNode.MinVersion; mv != _EMPTY_ { - if err := checkLeafMinVersionConfig(mv); err != nil { - return err - } - } - - // The checks below will be done only when detecting that we are configured - // with gateways. So if an option validation needs to be done regardless, - // it MUST be done before this point! - - if o.Gateway.Name == _EMPTY_ && o.Gateway.Port == 0 { - return nil - } - // If we are here we have both leaf nodes and gateways defined, make sure there - // is a system account defined. - if o.SystemAccount == _EMPTY_ { - return fmt.Errorf("leaf nodes and gateways (both being defined) require a system account to also be configured") - } - if err := validatePinnedCerts(o.LeafNode.TLSPinnedCerts); err != nil { - return fmt.Errorf("leafnode: %v", err) - } - return nil -} - -func checkLeafMinVersionConfig(mv string) error { - if ok, err := versionAtLeastCheckError(mv, 2, 8, 0); !ok || err != nil { - if err != nil { - return fmt.Errorf("invalid leafnode's minimum version: %v", err) - } else { - return fmt.Errorf("the minimum version should be at least 2.8.0") - } - } - return nil -} - -// Used to validate user names in LeafNode configuration. -// - rejects mix of single and multiple users. -// - rejects duplicate user names. -func validateLeafNodeAuthOptions(o *Options) error { - if len(o.LeafNode.Users) == 0 { - return nil - } - if o.LeafNode.Username != _EMPTY_ { - return fmt.Errorf("can not have a single user/pass and a users array") - } - if o.LeafNode.Nkey != _EMPTY_ { - return fmt.Errorf("can not have a single nkey and a users array") - } - users := map[string]struct{}{} - for _, u := range o.LeafNode.Users { - if _, exists := users[u.Username]; exists { - return fmt.Errorf("duplicate user %q detected in leafnode authorization", u.Username) - } - users[u.Username] = struct{}{} - } - return nil -} - -// Update remote LeafNode TLS configurations after a config reload. -func (s *Server) updateRemoteLeafNodesTLSConfig(opts *Options) { - max := len(opts.LeafNode.Remotes) - if max == 0 { - return - } - - s.mu.RLock() - defer s.mu.RUnlock() - - // Changes in the list of remote leaf nodes is not supported. - // However, make sure that we don't go over the arrays. - if len(s.leafRemoteCfgs) < max { - max = len(s.leafRemoteCfgs) - } - for i := 0; i < max; i++ { - ro := opts.LeafNode.Remotes[i] - cfg := s.leafRemoteCfgs[i] - if ro.TLSConfig != nil { - cfg.Lock() - cfg.TLSConfig = ro.TLSConfig.Clone() - cfg.TLSHandshakeFirst = ro.TLSHandshakeFirst - cfg.Unlock() - } - } -} - -func (s *Server) reConnectToRemoteLeafNode(remote *leafNodeCfg) { - delay := s.getOpts().LeafNode.ReconnectInterval - select { - case <-time.After(delay): - case <-s.quitCh: - s.grWG.Done() - return - } - s.connectToRemoteLeafNode(remote, false) -} - -// Creates a leafNodeCfg object that wraps the RemoteLeafOpts. -func newLeafNodeCfg(remote *RemoteLeafOpts) *leafNodeCfg { - cfg := &leafNodeCfg{ - RemoteLeafOpts: remote, - urls: make([]*url.URL, 0, len(remote.URLs)), - } - if len(remote.DenyExports) > 0 || len(remote.DenyImports) > 0 { - perms := &Permissions{} - if len(remote.DenyExports) > 0 { - perms.Publish = &SubjectPermission{Deny: remote.DenyExports} - } - if len(remote.DenyImports) > 0 { - perms.Subscribe = &SubjectPermission{Deny: remote.DenyImports} - } - cfg.perms = perms - } - // Start with the one that is configured. We will add to this - // array when receiving async leafnode INFOs. - cfg.urls = append(cfg.urls, cfg.URLs...) - // If allowed to randomize, do it on our copy of URLs - if !remote.NoRandomize { - rand.Shuffle(len(cfg.urls), func(i, j int) { - cfg.urls[i], cfg.urls[j] = cfg.urls[j], cfg.urls[i] - }) - } - // If we are TLS make sure we save off a proper servername if possible. - // Do same for user/password since we may need them to connect to - // a bare URL that we get from INFO protocol. - for _, u := range cfg.urls { - cfg.saveTLSHostname(u) - cfg.saveUserPassword(u) - // If the url(s) have the "wss://" scheme, and we don't have a TLS - // config, mark that we should be using TLS anyway. - if !cfg.TLS && isWSSURL(u) { - cfg.TLS = true - } - } - return cfg -} - -// Will pick an URL from the list of available URLs. -func (cfg *leafNodeCfg) pickNextURL() *url.URL { - cfg.Lock() - defer cfg.Unlock() - // If the current URL is the first in the list and we have more than - // one URL, then move that one to end of the list. - if cfg.curURL != nil && len(cfg.urls) > 1 && urlsAreEqual(cfg.curURL, cfg.urls[0]) { - first := cfg.urls[0] - copy(cfg.urls, cfg.urls[1:]) - cfg.urls[len(cfg.urls)-1] = first - } - cfg.curURL = cfg.urls[0] - return cfg.curURL -} - -// Returns the current URL -func (cfg *leafNodeCfg) getCurrentURL() *url.URL { - cfg.RLock() - defer cfg.RUnlock() - return cfg.curURL -} - -// Returns how long the server should wait before attempting -// to solicit a remote leafnode connection. -func (cfg *leafNodeCfg) getConnectDelay() time.Duration { - cfg.RLock() - delay := cfg.connDelay - cfg.RUnlock() - return delay -} - -// Sets the connect delay. -func (cfg *leafNodeCfg) setConnectDelay(delay time.Duration) { - cfg.Lock() - cfg.connDelay = delay - cfg.Unlock() -} - -// Ensure that non-exported options (used in tests) have -// been properly set. -func (s *Server) setLeafNodeNonExportedOptions() { - opts := s.getOpts() - s.leafNodeOpts.dialTimeout = opts.LeafNode.dialTimeout - if s.leafNodeOpts.dialTimeout == 0 { - // Use same timeouts as routes for now. - s.leafNodeOpts.dialTimeout = DEFAULT_ROUTE_DIAL - } - s.leafNodeOpts.resolver = opts.LeafNode.resolver - if s.leafNodeOpts.resolver == nil { - s.leafNodeOpts.resolver = net.DefaultResolver - } -} - -const sharedSysAccDelay = 250 * time.Millisecond - -func (s *Server) connectToRemoteLeafNode(remote *leafNodeCfg, firstConnect bool) { - defer s.grWG.Done() - - if remote == nil || len(remote.URLs) == 0 { - s.Debugf("Empty remote leafnode definition, nothing to connect") - return - } - - opts := s.getOpts() - reconnectDelay := opts.LeafNode.ReconnectInterval - s.mu.RLock() - dialTimeout := s.leafNodeOpts.dialTimeout - resolver := s.leafNodeOpts.resolver - var isSysAcc bool - if s.eventsEnabled() { - isSysAcc = remote.LocalAccount == s.sys.account.Name - } - jetstreamMigrateDelay := remote.JetStreamClusterMigrateDelay - s.mu.RUnlock() - - // If we are sharing a system account and we are not standalone delay to gather some info prior. - if firstConnect && isSysAcc && !s.standAloneMode() { - s.Debugf("Will delay first leafnode connect to shared system account due to clustering") - remote.setConnectDelay(sharedSysAccDelay) - } - - if connDelay := remote.getConnectDelay(); connDelay > 0 { - select { - case <-time.After(connDelay): - case <-s.quitCh: - return - } - remote.setConnectDelay(0) - } - - var conn net.Conn - - const connErrFmt = "Error trying to connect as leafnode to remote server %q (attempt %v): %v" - - attempts := 0 - - for s.isRunning() && s.remoteLeafNodeStillValid(remote) { - rURL := remote.pickNextURL() - url, err := s.getRandomIP(resolver, rURL.Host, nil) - if err == nil { - var ipStr string - if url != rURL.Host { - ipStr = fmt.Sprintf(" (%s)", url) - } - // Some test may want to disable remotes from connecting - if s.isLeafConnectDisabled() { - s.Debugf("Will not attempt to connect to remote server on %q%s, leafnodes currently disabled", rURL.Host, ipStr) - err = ErrLeafNodeDisabled - } else { - s.Debugf("Trying to connect as leafnode to remote server on %q%s", rURL.Host, ipStr) - conn, err = natsDialTimeout("tcp", url, dialTimeout) - } - } - if err != nil { - jitter := time.Duration(rand.Int63n(int64(reconnectDelay))) - delay := reconnectDelay + jitter - attempts++ - if s.shouldReportConnectErr(firstConnect, attempts) { - s.Errorf(connErrFmt, rURL.Host, attempts, err) - } else { - s.Debugf(connErrFmt, rURL.Host, attempts, err) - } - remote.Lock() - // if we are using a delay to start migrating assets, kick off a migrate timer. - if remote.jsMigrateTimer == nil && jetstreamMigrateDelay > 0 { - remote.jsMigrateTimer = time.AfterFunc(jetstreamMigrateDelay, func() { - s.checkJetStreamMigrate(remote) - }) - } - remote.Unlock() - select { - case <-s.quitCh: - remote.cancelMigrateTimer() - return - case <-time.After(delay): - // Check if we should migrate any JetStream assets immediately while this remote is down. - // This will be used if JetStreamClusterMigrateDelay was not set - if jetstreamMigrateDelay == 0 { - s.checkJetStreamMigrate(remote) - } - continue - } - } - remote.cancelMigrateTimer() - if !s.remoteLeafNodeStillValid(remote) { - conn.Close() - return - } - - // We have a connection here to a remote server. - // Go ahead and create our leaf node and return. - s.createLeafNode(conn, rURL, remote, nil) - - // Clear any observer states if we had them. - s.clearObserverState(remote) - - return - } -} - -func (cfg *leafNodeCfg) cancelMigrateTimer() { - cfg.Lock() - stopAndClearTimer(&cfg.jsMigrateTimer) - cfg.Unlock() -} - -// This will clear any observer state such that stream or consumer assets on this server can become leaders again. -func (s *Server) clearObserverState(remote *leafNodeCfg) { - s.mu.RLock() - accName := remote.LocalAccount - s.mu.RUnlock() - - acc, err := s.LookupAccount(accName) - if err != nil { - s.Warnf("Error looking up account [%s] checking for JetStream clear observer state on a leafnode", accName) - return - } - - acc.jscmMu.Lock() - defer acc.jscmMu.Unlock() - - // Walk all streams looking for any clustered stream, skip otherwise. - for _, mset := range acc.streams() { - node := mset.raftNode() - if node == nil { - // Not R>1 - continue - } - // Check consumers - for _, o := range mset.getConsumers() { - if n := o.raftNode(); n != nil { - // Ensure we can become a leader again. - n.SetObserver(false) - } - } - // Ensure we can not become a leader again. - node.SetObserver(false) - } -} - -// Check to see if we should migrate any assets from this account. -func (s *Server) checkJetStreamMigrate(remote *leafNodeCfg) { - s.mu.RLock() - accName, shouldMigrate := remote.LocalAccount, remote.JetStreamClusterMigrate - s.mu.RUnlock() - - if !shouldMigrate { - return - } - - acc, err := s.LookupAccount(accName) - if err != nil { - s.Warnf("Error looking up account [%s] checking for JetStream migration on a leafnode", accName) - return - } - - acc.jscmMu.Lock() - defer acc.jscmMu.Unlock() - - // Walk all streams looking for any clustered stream, skip otherwise. - // If we are the leader force stepdown. - for _, mset := range acc.streams() { - node := mset.raftNode() - if node == nil { - // Not R>1 - continue - } - // Collect any consumers - for _, o := range mset.getConsumers() { - if n := o.raftNode(); n != nil { - n.StepDown() - // Ensure we can not become a leader while in this state. - n.SetObserver(true) - } - } - // Stepdown if this stream was leader. - node.StepDown() - // Ensure we can not become a leader while in this state. - node.SetObserver(true) - } -} - -// Helper for checking. -func (s *Server) isLeafConnectDisabled() bool { - s.mu.RLock() - defer s.mu.RUnlock() - return s.leafDisableConnect -} - -// Save off the tlsName for when we use TLS and mix hostnames and IPs. IPs usually -// come from the server we connect to. -// -// We used to save the name only if there was a TLSConfig or scheme equal to "tls". -// However, this was causing failures for users that did not set the scheme (and -// their remote connections did not have a tls{} block). -// We now save the host name regardless in case the remote returns an INFO indicating -// that TLS is required. -func (cfg *leafNodeCfg) saveTLSHostname(u *url.URL) { - if cfg.tlsName == _EMPTY_ && net.ParseIP(u.Hostname()) == nil { - cfg.tlsName = u.Hostname() - } -} - -// Save off the username/password for when we connect using a bare URL -// that we get from the INFO protocol. -func (cfg *leafNodeCfg) saveUserPassword(u *url.URL) { - if cfg.username == _EMPTY_ && u.User != nil { - cfg.username = u.User.Username() - cfg.password, _ = u.User.Password() - } -} - -// This starts the leafnode accept loop in a go routine, unless it -// is detected that the server has already been shutdown. -func (s *Server) startLeafNodeAcceptLoop() { - // Snapshot server options. - opts := s.getOpts() - - port := opts.LeafNode.Port - if port == -1 { - port = 0 - } - - if s.isShuttingDown() { - return - } - - s.mu.Lock() - hp := net.JoinHostPort(opts.LeafNode.Host, strconv.Itoa(port)) - l, e := natsListen("tcp", hp) - s.leafNodeListenerErr = e - if e != nil { - s.mu.Unlock() - s.Fatalf("Error listening on leafnode port: %d - %v", opts.LeafNode.Port, e) - return - } - - s.Noticef("Listening for leafnode connections on %s", - net.JoinHostPort(opts.LeafNode.Host, strconv.Itoa(l.Addr().(*net.TCPAddr).Port))) - - tlsRequired := opts.LeafNode.TLSConfig != nil - tlsVerify := tlsRequired && opts.LeafNode.TLSConfig.ClientAuth == tls.RequireAndVerifyClientCert - // Do not set compression in this Info object, it would possibly cause - // issues when sending asynchronous INFO to the remote. - info := Info{ - ID: s.info.ID, - Name: s.info.Name, - Version: s.info.Version, - GitCommit: gitCommit, - GoVersion: runtime.Version(), - AuthRequired: true, - TLSRequired: tlsRequired, - TLSVerify: tlsVerify, - MaxPayload: s.info.MaxPayload, // TODO(dlc) - Allow override? - Headers: s.supportsHeaders(), - JetStream: opts.JetStream, - Domain: opts.JetStreamDomain, - Proto: s.getServerProto(), - InfoOnConnect: true, - } - // If we have selected a random port... - if port == 0 { - // Write resolved port back to options. - opts.LeafNode.Port = l.Addr().(*net.TCPAddr).Port - } - - s.leafNodeInfo = info - // Possibly override Host/Port and set IP based on Cluster.Advertise - if err := s.setLeafNodeInfoHostPortAndIP(); err != nil { - s.Fatalf("Error setting leafnode INFO with LeafNode.Advertise value of %s, err=%v", opts.LeafNode.Advertise, err) - l.Close() - s.mu.Unlock() - return - } - s.leafURLsMap[s.leafNodeInfo.IP]++ - s.generateLeafNodeInfoJSON() - - // Setup state that can enable shutdown - s.leafNodeListener = l - - // As of now, a server that does not have remotes configured would - // never solicit a connection, so we should not have to warn if - // InsecureSkipVerify is set in main LeafNodes config (since - // this TLS setting matters only when soliciting a connection). - // Still, warn if insecure is set in any of LeafNode block. - // We need to check remotes, even if tls is not required on accept. - warn := tlsRequired && opts.LeafNode.TLSConfig.InsecureSkipVerify - if !warn { - for _, r := range opts.LeafNode.Remotes { - if r.TLSConfig != nil && r.TLSConfig.InsecureSkipVerify { - warn = true - break - } - } - } - if warn { - s.Warnf(leafnodeTLSInsecureWarning) - } - go s.acceptConnections(l, "Leafnode", func(conn net.Conn) { s.createLeafNode(conn, nil, nil, nil) }, nil) - s.mu.Unlock() -} - -// RegEx to match a creds file with user JWT and Seed. -var credsRe = regexp.MustCompile(`\s*(?:(?:[-]{3,}.*[-]{3,}\r?\n)([\w\-.=]+)(?:\r?\n[-]{3,}.*[-]{3,}(\r?\n|\z)))`) - -// clusterName is provided as argument to avoid lock ordering issues with the locked client c -// Lock should be held entering here. -func (c *client) sendLeafConnect(clusterName string, headers bool) error { - // We support basic user/pass and operator based user JWT with signatures. - cinfo := leafConnectInfo{ - Version: VERSION, - ID: c.srv.info.ID, - Domain: c.srv.info.Domain, - Name: c.srv.info.Name, - Hub: c.leaf.remote.Hub, - Cluster: clusterName, - Headers: headers, - JetStream: c.acc.jetStreamConfigured(), - DenyPub: c.leaf.remote.DenyImports, - Compression: c.leaf.compression, - RemoteAccount: c.acc.GetName(), - Proto: c.srv.getServerProto(), - } - - // If a signature callback is specified, this takes precedence over anything else. - if cb := c.leaf.remote.SignatureCB; cb != nil { - nonce := c.nonce - c.mu.Unlock() - jwt, sigraw, err := cb(nonce) - c.mu.Lock() - if err == nil && c.isClosed() { - err = ErrConnectionClosed - } - if err != nil { - c.Errorf("Error signing the nonce: %v", err) - return err - } - sig := base64.RawURLEncoding.EncodeToString(sigraw) - cinfo.JWT, cinfo.Sig = jwt, sig - - } else if creds := c.leaf.remote.Credentials; creds != _EMPTY_ { - // Check for credentials first, that will take precedence.. - c.Debugf("Authenticating with credentials file %q", c.leaf.remote.Credentials) - contents, err := os.ReadFile(creds) - if err != nil { - c.Errorf("%v", err) - return err - } - defer wipeSlice(contents) - items := credsRe.FindAllSubmatch(contents, -1) - if len(items) < 2 { - c.Errorf("Credentials file malformed") - return err - } - // First result should be the user JWT. - // We copy here so that the file containing the seed will be wiped appropriately. - raw := items[0][1] - tmp := make([]byte, len(raw)) - copy(tmp, raw) - // Seed is second item. - kp, err := nkeys.FromSeed(items[1][1]) - if err != nil { - c.Errorf("Credentials file has malformed seed") - return err - } - // Wipe our key on exit. - defer kp.Wipe() - - sigraw, _ := kp.Sign(c.nonce) - sig := base64.RawURLEncoding.EncodeToString(sigraw) - cinfo.JWT = bytesToString(tmp) - cinfo.Sig = sig - } else if nkey := c.leaf.remote.Nkey; nkey != _EMPTY_ { - kp, err := nkeys.FromSeed([]byte(nkey)) - if err != nil { - c.Errorf("Remote nkey has malformed seed") - return err - } - // Wipe our key on exit. - defer kp.Wipe() - sigraw, _ := kp.Sign(c.nonce) - sig := base64.RawURLEncoding.EncodeToString(sigraw) - pkey, _ := kp.PublicKey() - cinfo.Nkey = pkey - cinfo.Sig = sig - } - // In addition, and this is to allow auth callout, set user/password or - // token if applicable. - if userInfo := c.leaf.remote.curURL.User; userInfo != nil { - // For backward compatibility, if only username is provided, set both - // Token and User, not just Token. - cinfo.User = userInfo.Username() - var ok bool - cinfo.Pass, ok = userInfo.Password() - if !ok { - cinfo.Token = cinfo.User - } - } else if c.leaf.remote.username != _EMPTY_ { - cinfo.User = c.leaf.remote.username - cinfo.Pass = c.leaf.remote.password - } - b, err := json.Marshal(cinfo) - if err != nil { - c.Errorf("Error marshaling CONNECT to remote leafnode: %v\n", err) - return err - } - // Although this call is made before the writeLoop is created, - // we don't really need to send in place. The protocol will be - // sent out by the writeLoop. - c.enqueueProto([]byte(fmt.Sprintf(ConProto, b))) - return nil -} - -// Makes a deep copy of the LeafNode Info structure. -// The server lock is held on entry. -func (s *Server) copyLeafNodeInfo() *Info { - clone := s.leafNodeInfo - // Copy the array of urls. - if len(s.leafNodeInfo.LeafNodeURLs) > 0 { - clone.LeafNodeURLs = append([]string(nil), s.leafNodeInfo.LeafNodeURLs...) - } - return &clone -} - -// Adds a LeafNode URL that we get when a route connects to the Info structure. -// Regenerates the JSON byte array so that it can be sent to LeafNode connections. -// Returns a boolean indicating if the URL was added or not. -// Server lock is held on entry -func (s *Server) addLeafNodeURL(urlStr string) bool { - if s.leafURLsMap.addUrl(urlStr) { - s.generateLeafNodeInfoJSON() - return true - } - return false -} - -// Removes a LeafNode URL of the route that is disconnecting from the Info structure. -// Regenerates the JSON byte array so that it can be sent to LeafNode connections. -// Returns a boolean indicating if the URL was removed or not. -// Server lock is held on entry. -func (s *Server) removeLeafNodeURL(urlStr string) bool { - // Don't need to do this if we are removing the route connection because - // we are shuting down... - if s.isShuttingDown() { - return false - } - if s.leafURLsMap.removeUrl(urlStr) { - s.generateLeafNodeInfoJSON() - return true - } - return false -} - -// Server lock is held on entry -func (s *Server) generateLeafNodeInfoJSON() { - s.leafNodeInfo.Cluster = s.cachedClusterName() - s.leafNodeInfo.LeafNodeURLs = s.leafURLsMap.getAsStringSlice() - s.leafNodeInfo.WSConnectURLs = s.websocket.connectURLsMap.getAsStringSlice() - s.leafNodeInfoJSON = generateInfoJSON(&s.leafNodeInfo) -} - -// Sends an async INFO protocol so that the connected servers can update -// their list of LeafNode urls. -func (s *Server) sendAsyncLeafNodeInfo() { - for _, c := range s.leafs { - c.mu.Lock() - c.enqueueProto(s.leafNodeInfoJSON) - c.mu.Unlock() - } -} - -// Called when an inbound leafnode connection is accepted or we create one for a solicited leafnode. -func (s *Server) createLeafNode(conn net.Conn, rURL *url.URL, remote *leafNodeCfg, ws *websocket) *client { - // Snapshot server options. - opts := s.getOpts() - - maxPay := int32(opts.MaxPayload) - maxSubs := int32(opts.MaxSubs) - // For system, maxSubs of 0 means unlimited, so re-adjust here. - if maxSubs == 0 { - maxSubs = -1 - } - now := time.Now().UTC() - - c := &client{srv: s, nc: conn, kind: LEAF, opts: defaultOpts, mpay: maxPay, msubs: maxSubs, start: now, last: now} - // Do not update the smap here, we need to do it in initLeafNodeSmapAndSendSubs - c.leaf = &leaf{} - - // For accepted LN connections, ws will be != nil if it was accepted - // through the Websocket port. - c.ws = ws - - // For remote, check if the scheme starts with "ws", if so, we will initiate - // a remote Leaf Node connection as a websocket connection. - if remote != nil && rURL != nil && isWSURL(rURL) { - remote.RLock() - c.ws = &websocket{compress: remote.Websocket.Compression, maskwrite: !remote.Websocket.NoMasking} - remote.RUnlock() - } - - // Determines if we are soliciting the connection or not. - var solicited bool - var acc *Account - var remoteSuffix string - if remote != nil { - // For now, if lookup fails, we will constantly try - // to recreate this LN connection. - lacc := remote.LocalAccount - var err error - acc, err = s.LookupAccount(lacc) - if err != nil { - // An account not existing is something that can happen with nats/http account resolver and the account - // has not yet been pushed, or the request failed for other reasons. - // remote needs to be set or retry won't happen - c.leaf.remote = remote - c.closeConnection(MissingAccount) - s.Errorf("Unable to lookup account %s for solicited leafnode connection: %v", lacc, err) - return nil - } - remoteSuffix = fmt.Sprintf(" for account: %s", acc.traceLabel()) - } - - c.mu.Lock() - c.initClient() - c.Noticef("Leafnode connection created%s %s", remoteSuffix, c.opts.Name) - - var ( - tlsFirst bool - tlsFirstFallback time.Duration - infoTimeout time.Duration - ) - if remote != nil { - solicited = true - remote.Lock() - c.leaf.remote = remote - c.setPermissions(remote.perms) - if !c.leaf.remote.Hub { - c.leaf.isSpoke = true - } - tlsFirst = remote.TLSHandshakeFirst - infoTimeout = remote.FirstInfoTimeout - remote.Unlock() - c.acc = acc - } else { - c.flags.set(expectConnect) - if ws != nil { - c.Debugf("Leafnode compression=%v", c.ws.compress) - } - tlsFirst = opts.LeafNode.TLSHandshakeFirst - if f := opts.LeafNode.TLSHandshakeFirstFallback; f > 0 { - tlsFirstFallback = f - } - } - c.mu.Unlock() - - var nonce [nonceLen]byte - var info *Info - - // Grab this before the client lock below. - if !solicited { - // Grab server variables - s.mu.Lock() - info = s.copyLeafNodeInfo() - // For tests that want to simulate old servers, do not set the compression - // on the INFO protocol if configured with CompressionNotSupported. - if cm := opts.LeafNode.Compression.Mode; cm != CompressionNotSupported { - info.Compression = cm - } - s.generateNonce(nonce[:]) - s.mu.Unlock() - } - - // Grab lock - c.mu.Lock() - - var preBuf []byte - if solicited { - // For websocket connection, we need to send an HTTP request, - // and get the response before starting the readLoop to get - // the INFO, etc.. - if c.isWebsocket() { - var err error - var closeReason ClosedState - - preBuf, closeReason, err = c.leafNodeSolicitWSConnection(opts, rURL, remote) - if err != nil { - c.Errorf("Error soliciting websocket connection: %v", err) - c.mu.Unlock() - if closeReason != 0 { - c.closeConnection(closeReason) - } - return nil - } - } else { - // If configured to do TLS handshake first - if tlsFirst { - if _, err := c.leafClientHandshakeIfNeeded(remote, opts); err != nil { - c.mu.Unlock() - return nil - } - } - // We need to wait for the info, but not for too long. - c.nc.SetReadDeadline(time.Now().Add(infoTimeout)) - } - - // We will process the INFO from the readloop and finish by - // sending the CONNECT and finish registration later. - } else { - // Send our info to the other side. - // Remember the nonce we sent here for signatures, etc. - c.nonce = make([]byte, nonceLen) - copy(c.nonce, nonce[:]) - info.Nonce = bytesToString(c.nonce) - info.CID = c.cid - proto := generateInfoJSON(info) - - var pre []byte - // We need first to check for "TLS First" fallback delay. - if tlsFirstFallback > 0 { - // We wait and see if we are getting any data. Since we did not send - // the INFO protocol yet, only clients that use TLS first should be - // sending data (the TLS handshake). We don't really check the content: - // if it is a rogue agent and not an actual client performing the - // TLS handshake, the error will be detected when performing the - // handshake on our side. - pre = make([]byte, 4) - c.nc.SetReadDeadline(time.Now().Add(tlsFirstFallback)) - n, _ := io.ReadFull(c.nc, pre[:]) - c.nc.SetReadDeadline(time.Time{}) - // If we get any data (regardless of possible timeout), we will proceed - // with the TLS handshake. - if n > 0 { - pre = pre[:n] - } else { - // We did not get anything so we will send the INFO protocol. - pre = nil - // Set the boolean to false for the rest of the function. - tlsFirst = false - } - } - - if !tlsFirst { - // We have to send from this go routine because we may - // have to block for TLS handshake before we start our - // writeLoop go routine. The other side needs to receive - // this before it can initiate the TLS handshake.. - c.sendProtoNow(proto) - - // The above call could have marked the connection as closed (due to TCP error). - if c.isClosed() { - c.mu.Unlock() - c.closeConnection(WriteError) - return nil - } - } - - // Check to see if we need to spin up TLS. - if !c.isWebsocket() && info.TLSRequired { - // If we have a prebuffer create a multi-reader. - if len(pre) > 0 { - c.nc = &tlsMixConn{c.nc, bytes.NewBuffer(pre)} - } - // Perform server-side TLS handshake. - if err := c.doTLSServerHandshake(tlsHandshakeLeaf, opts.LeafNode.TLSConfig, opts.LeafNode.TLSTimeout, opts.LeafNode.TLSPinnedCerts); err != nil { - c.mu.Unlock() - return nil - } - } - - // If the user wants the TLS handshake to occur first, now that it is - // done, send the INFO protocol. - if tlsFirst { - c.flags.set(didTLSFirst) - c.sendProtoNow(proto) - if c.isClosed() { - c.mu.Unlock() - c.closeConnection(WriteError) - return nil - } - } - - // Leaf nodes will always require a CONNECT to let us know - // when we are properly bound to an account. - // - // If compression is configured, we can't set the authTimer here because - // it would cause the parser to fail any incoming protocol that is not a - // CONNECT (and we need to exchange INFO protocols for compression - // negotiation). So instead, use the ping timer until we are done with - // negotiation and can set the auth timer. - timeout := secondsToDuration(opts.LeafNode.AuthTimeout) - if needsCompression(opts.LeafNode.Compression.Mode) { - c.ping.tmr = time.AfterFunc(timeout, func() { - c.authTimeout() - }) - } else { - c.setAuthTimer(timeout) - } - } - - // Keep track in case server is shutdown before we can successfully register. - if !s.addToTempClients(c.cid, c) { - c.mu.Unlock() - c.setNoReconnect() - c.closeConnection(ServerShutdown) - return nil - } - - // Spin up the read loop. - s.startGoRoutine(func() { c.readLoop(preBuf) }) - - // We will spin the write loop for solicited connections only - // when processing the INFO and after switching to TLS if needed. - if !solicited { - s.startGoRoutine(func() { c.writeLoop() }) - } - - c.mu.Unlock() - - return c -} - -// Will perform the client-side TLS handshake if needed. Assumes that this -// is called by the solicit side (remote will be non nil). Returns `true` -// if TLS is required, `false` otherwise. -// Lock held on entry. -func (c *client) leafClientHandshakeIfNeeded(remote *leafNodeCfg, opts *Options) (bool, error) { - // Check if TLS is required and gather TLS config variables. - tlsRequired, tlsConfig, tlsName, tlsTimeout := c.leafNodeGetTLSConfigForSolicit(remote) - if !tlsRequired { - return false, nil - } - - // If TLS required, peform handshake. - // Get the URL that was used to connect to the remote server. - rURL := remote.getCurrentURL() - - // Perform the client-side TLS handshake. - if resetTLSName, err := c.doTLSClientHandshake(tlsHandshakeLeaf, rURL, tlsConfig, tlsName, tlsTimeout, opts.LeafNode.TLSPinnedCerts); err != nil { - // Check if we need to reset the remote's TLS name. - if resetTLSName { - remote.Lock() - remote.tlsName = _EMPTY_ - remote.Unlock() - } - return false, err - } - return true, nil -} - -func (c *client) processLeafnodeInfo(info *Info) { - c.mu.Lock() - if c.leaf == nil || c.isClosed() { - c.mu.Unlock() - return - } - s := c.srv - opts := s.getOpts() - remote := c.leaf.remote - didSolicit := remote != nil - firstINFO := !c.flags.isSet(infoReceived) - - // In case of websocket, the TLS handshake has been already done. - // So check only for non websocket connections and for configurations - // where the TLS Handshake was not done first. - if didSolicit && !c.flags.isSet(handshakeComplete) && !c.isWebsocket() && !remote.TLSHandshakeFirst { - // If the server requires TLS, we need to set this in the remote - // otherwise if there is no TLS configuration block for the remote, - // the solicit side will not attempt to perform the TLS handshake. - if firstINFO && info.TLSRequired { - remote.TLS = true - } - if _, err := c.leafClientHandshakeIfNeeded(remote, opts); err != nil { - c.mu.Unlock() - return - } - } - - // Check for compression, unless already done. - if firstINFO && !c.flags.isSet(compressionNegotiated) { - // Prevent from getting back here. - c.flags.set(compressionNegotiated) - - var co *CompressionOpts - if !didSolicit { - co = &opts.LeafNode.Compression - } else { - co = &remote.Compression - } - if needsCompression(co.Mode) { - // Release client lock since following function will need server lock. - c.mu.Unlock() - compress, err := s.negotiateLeafCompression(c, didSolicit, info.Compression, co) - if err != nil { - c.sendErrAndErr(err.Error()) - c.closeConnection(ProtocolViolation) - return - } - if compress { - // Done for now, will get back another INFO protocol... - return - } - // No compression because one side does not want/can't, so proceed. - c.mu.Lock() - // Check that the connection did not close if the lock was released. - if c.isClosed() { - c.mu.Unlock() - return - } - } else { - // Coming from an old server, the Compression field would be the empty - // string. For servers that are configured with CompressionNotSupported, - // this makes them behave as old servers. - if info.Compression == _EMPTY_ || co.Mode == CompressionNotSupported { - c.leaf.compression = CompressionNotSupported - } else { - c.leaf.compression = CompressionOff - } - } - // Accepting side does not normally process an INFO protocol during - // initial connection handshake. So we keep it consistent by returning - // if we are not soliciting. - if !didSolicit { - // If we had created the ping timer instead of the auth timer, we will - // clear the ping timer and set the auth timer now that the compression - // negotiation is done. - if info.Compression != _EMPTY_ && c.ping.tmr != nil { - clearTimer(&c.ping.tmr) - c.setAuthTimer(secondsToDuration(opts.LeafNode.AuthTimeout)) - } - c.mu.Unlock() - return - } - // Fall through and process the INFO protocol as usual. - } - - // Note: For now, only the initial INFO has a nonce. We - // will probably do auto key rotation at some point. - if firstINFO { - // Mark that the INFO protocol has been received. - c.flags.set(infoReceived) - // Prevent connecting to non leafnode port. Need to do this only for - // the first INFO, not for async INFO updates... - // - // Content of INFO sent by the server when accepting a tcp connection. - // ------------------------------------------------------------------- - // Listen Port Of | CID | ClientConnectURLs | LeafNodeURLs | Gateway | - // ------------------------------------------------------------------- - // CLIENT | X* | X** | | | - // ROUTE | | X** | X*** | | - // GATEWAY | | | | X | - // LEAFNODE | X | | X | | - // ------------------------------------------------------------------- - // * Not on older servers. - // ** Not if "no advertise" is enabled. - // *** Not if leafnode's "no advertise" is enabled. - // - // As seen from above, a solicited LeafNode connection should receive - // from the remote server an INFO with CID and LeafNodeURLs. Anything - // else should be considered an attempt to connect to a wrong port. - if didSolicit && (info.CID == 0 || info.LeafNodeURLs == nil) { - c.mu.Unlock() - c.Errorf(ErrConnectedToWrongPort.Error()) - c.closeConnection(WrongPort) - return - } - // Reject a cluster that contains spaces. - if info.Cluster != _EMPTY_ && strings.Contains(info.Cluster, " ") { - c.mu.Unlock() - c.sendErrAndErr(ErrClusterNameHasSpaces.Error()) - c.closeConnection(ProtocolViolation) - return - } - // Capture a nonce here. - c.nonce = []byte(info.Nonce) - if info.TLSRequired && didSolicit { - remote.TLS = true - } - supportsHeaders := c.srv.supportsHeaders() - c.headers = supportsHeaders && info.Headers - - // Remember the remote server. - // Pre 2.2.0 servers are not sending their server name. - // In that case, use info.ID, which, for those servers, matches - // the content of the field `Name` in the leafnode CONNECT protocol. - if info.Name == _EMPTY_ { - c.leaf.remoteServer = info.ID - } else { - c.leaf.remoteServer = info.Name - } - c.leaf.remoteDomain = info.Domain - c.leaf.remoteCluster = info.Cluster - // We send the protocol version in the INFO protocol. - // Keep track of it, so we know if this connection supports message - // tracing for instance. - c.opts.Protocol = info.Proto - } - - // For both initial INFO and async INFO protocols, Possibly - // update our list of remote leafnode URLs we can connect to. - if didSolicit && (len(info.LeafNodeURLs) > 0 || len(info.WSConnectURLs) > 0) { - // Consider the incoming array as the most up-to-date - // representation of the remote cluster's list of URLs. - c.updateLeafNodeURLs(info) - } - - // Check to see if we have permissions updates here. - if info.Import != nil || info.Export != nil { - perms := &Permissions{ - Publish: info.Export, - Subscribe: info.Import, - } - // Check if we have local deny clauses that we need to merge. - if remote := c.leaf.remote; remote != nil { - if len(remote.DenyExports) > 0 { - if perms.Publish == nil { - perms.Publish = &SubjectPermission{} - } - perms.Publish.Deny = append(perms.Publish.Deny, remote.DenyExports...) - } - if len(remote.DenyImports) > 0 { - if perms.Subscribe == nil { - perms.Subscribe = &SubjectPermission{} - } - perms.Subscribe.Deny = append(perms.Subscribe.Deny, remote.DenyImports...) - } - } - c.setPermissions(perms) - } - - var resumeConnect bool - - // If this is a remote connection and this is the first INFO protocol, - // then we need to finish the connect process by sending CONNECT, etc.. - if firstINFO && didSolicit { - // Clear deadline that was set in createLeafNode while waiting for the INFO. - c.nc.SetDeadline(time.Time{}) - resumeConnect = true - } else if !firstINFO && didSolicit { - c.leaf.remoteAccName = info.RemoteAccount - } - - // Check if we have the remote account information and if so make sure it's stored. - if info.RemoteAccount != _EMPTY_ { - s.leafRemoteAccounts.Store(c.acc.Name, info.RemoteAccount) - } - c.mu.Unlock() - - finishConnect := info.ConnectInfo - if resumeConnect && s != nil { - s.leafNodeResumeConnectProcess(c) - if !info.InfoOnConnect { - finishConnect = true - } - } - if finishConnect { - s.leafNodeFinishConnectProcess(c) - } -} - -func (s *Server) negotiateLeafCompression(c *client, didSolicit bool, infoCompression string, co *CompressionOpts) (bool, error) { - // Negotiate the appropriate compression mode (or no compression) - cm, err := selectCompressionMode(co.Mode, infoCompression) - if err != nil { - return false, err - } - c.mu.Lock() - // For "auto" mode, set the initial compression mode based on RTT - if cm == CompressionS2Auto { - if c.rttStart.IsZero() { - c.rtt = computeRTT(c.start) - } - cm = selectS2AutoModeBasedOnRTT(c.rtt, co.RTTThresholds) - } - // Keep track of the negotiated compression mode. - c.leaf.compression = cm - cid := c.cid - var nonce string - if !didSolicit { - nonce = bytesToString(c.nonce) - } - c.mu.Unlock() - - if !needsCompression(cm) { - return false, nil - } - - // If we end-up doing compression... - - // Generate an INFO with the chosen compression mode. - s.mu.Lock() - info := s.copyLeafNodeInfo() - info.Compression, info.CID, info.Nonce = compressionModeForInfoProtocol(co, cm), cid, nonce - infoProto := generateInfoJSON(info) - s.mu.Unlock() - - // If we solicited, then send this INFO protocol BEFORE switching - // to compression writer. However, if we did not, we send it after. - c.mu.Lock() - if didSolicit { - c.enqueueProto(infoProto) - // Make sure it is completely flushed (the pending bytes goes to - // 0) before proceeding. - for c.out.pb > 0 && !c.isClosed() { - c.flushOutbound() - } - } - // This is to notify the readLoop that it should switch to a - // (de)compression reader. - c.in.flags.set(switchToCompression) - // Create the compress writer before queueing the INFO protocol for - // a route that did not solicit. It will make sure that that proto - // is sent with compression on. - c.out.cw = s2.NewWriter(nil, s2WriterOptions(cm)...) - if !didSolicit { - c.enqueueProto(infoProto) - } - c.mu.Unlock() - return true, nil -} - -// When getting a leaf node INFO protocol, use the provided -// array of urls to update the list of possible endpoints. -func (c *client) updateLeafNodeURLs(info *Info) { - cfg := c.leaf.remote - cfg.Lock() - defer cfg.Unlock() - - // We have ensured that if a remote has a WS scheme, then all are. - // So check if first is WS, then add WS URLs, otherwise, add non WS ones. - if len(cfg.URLs) > 0 && isWSURL(cfg.URLs[0]) { - // It does not really matter if we use "ws://" or "wss://" here since - // we will have already marked that the remote should use TLS anyway. - // But use proper scheme for log statements, etc... - proto := wsSchemePrefix - if cfg.TLS { - proto = wsSchemePrefixTLS - } - c.doUpdateLNURLs(cfg, proto, info.WSConnectURLs) - return - } - c.doUpdateLNURLs(cfg, "nats-leaf", info.LeafNodeURLs) -} - -func (c *client) doUpdateLNURLs(cfg *leafNodeCfg, scheme string, URLs []string) { - cfg.urls = make([]*url.URL, 0, 1+len(URLs)) - // Add the ones we receive in the protocol - for _, surl := range URLs { - url, err := url.Parse(fmt.Sprintf("%s://%s", scheme, surl)) - if err != nil { - // As per below, the URLs we receive should not have contained URL info, so this should be safe to log. - c.Errorf("Error parsing url %q: %v", surl, err) - continue - } - // Do not add if it's the same as what we already have configured. - var dup bool - for _, u := range cfg.URLs { - // URLs that we receive never have user info, but the - // ones that were configured may have. Simply compare - // host and port to decide if they are equal or not. - if url.Host == u.Host && url.Port() == u.Port() { - dup = true - break - } - } - if !dup { - cfg.urls = append(cfg.urls, url) - cfg.saveTLSHostname(url) - } - } - // Add the configured one - cfg.urls = append(cfg.urls, cfg.URLs...) -} - -// Similar to setInfoHostPortAndGenerateJSON, but for leafNodeInfo. -func (s *Server) setLeafNodeInfoHostPortAndIP() error { - opts := s.getOpts() - if opts.LeafNode.Advertise != _EMPTY_ { - advHost, advPort, err := parseHostPort(opts.LeafNode.Advertise, opts.LeafNode.Port) - if err != nil { - return err - } - s.leafNodeInfo.Host = advHost - s.leafNodeInfo.Port = advPort - } else { - s.leafNodeInfo.Host = opts.LeafNode.Host - s.leafNodeInfo.Port = opts.LeafNode.Port - // If the host is "0.0.0.0" or "::" we need to resolve to a public IP. - // This will return at most 1 IP. - hostIsIPAny, ips, err := s.getNonLocalIPsIfHostIsIPAny(s.leafNodeInfo.Host, false) - if err != nil { - return err - } - if hostIsIPAny { - if len(ips) == 0 { - s.Errorf("Could not find any non-local IP for leafnode's listen specification %q", - s.leafNodeInfo.Host) - } else { - // Take the first from the list... - s.leafNodeInfo.Host = ips[0] - } - } - } - // Use just host:port for the IP - s.leafNodeInfo.IP = net.JoinHostPort(s.leafNodeInfo.Host, strconv.Itoa(s.leafNodeInfo.Port)) - if opts.LeafNode.Advertise != _EMPTY_ { - s.Noticef("Advertise address for leafnode is set to %s", s.leafNodeInfo.IP) - } - return nil -} - -// Add the connection to the map of leaf nodes. -// If `checkForDup` is true (invoked when a leafnode is accepted), then we check -// if a connection already exists for the same server name and account. -// That can happen when the remote is attempting to reconnect while the accepting -// side did not detect the connection as broken yet. -// But it can also happen when there is a misconfiguration and the remote is -// creating two (or more) connections that bind to the same account on the accept -// side. -// When a duplicate is found, the new connection is accepted and the old is closed -// (this solves the stale connection situation). An error is returned to help the -// remote detect the misconfiguration when the duplicate is the result of that -// misconfiguration. -func (s *Server) addLeafNodeConnection(c *client, srvName, clusterName string, checkForDup bool) { - var accName string - c.mu.Lock() - cid := c.cid - acc := c.acc - if acc != nil { - accName = acc.Name - } - myRemoteDomain := c.leaf.remoteDomain - mySrvName := c.leaf.remoteServer - remoteAccName := c.leaf.remoteAccName - myClustName := c.leaf.remoteCluster - solicited := c.leaf.remote != nil - c.mu.Unlock() - - var old *client - s.mu.Lock() - // We check for empty because in some test we may send empty CONNECT{} - if checkForDup && srvName != _EMPTY_ { - for _, ol := range s.leafs { - ol.mu.Lock() - // We care here only about non solicited Leafnode. This function - // is more about replacing stale connections than detecting loops. - // We have code for the loop detection elsewhere, which also delays - // attempt to reconnect. - if !ol.isSolicitedLeafNode() && ol.leaf.remoteServer == srvName && - ol.leaf.remoteCluster == clusterName && ol.acc.Name == accName && - remoteAccName != _EMPTY_ && ol.leaf.remoteAccName == remoteAccName { - old = ol - } - ol.mu.Unlock() - if old != nil { - break - } - } - } - // Store new connection in the map - s.leafs[cid] = c - s.mu.Unlock() - s.removeFromTempClients(cid) - - // If applicable, evict the old one. - if old != nil { - old.sendErrAndErr(DuplicateRemoteLeafnodeConnection.String()) - old.closeConnection(DuplicateRemoteLeafnodeConnection) - c.Warnf("Replacing connection from same server") - } - - srvDecorated := func() string { - if myClustName == _EMPTY_ { - return mySrvName - } - return fmt.Sprintf("%s/%s", mySrvName, myClustName) - } - - opts := s.getOpts() - sysAcc := s.SystemAccount() - js := s.getJetStream() - var meta *raft - if js != nil { - if mg := js.getMetaGroup(); mg != nil { - meta = mg.(*raft) - } - } - blockMappingOutgoing := false - // Deny (non domain) JetStream API traffic unless system account is shared - // and domain names are identical and extending is not disabled - - // Check if backwards compatibility has been enabled and needs to be acted on - forceSysAccDeny := false - if len(opts.JsAccDefaultDomain) > 0 { - if acc == sysAcc { - for _, d := range opts.JsAccDefaultDomain { - if d == _EMPTY_ { - // Extending JetStream via leaf node is mutually exclusive with a domain mapping to the empty/default domain. - // As soon as one mapping to "" is found, disable the ability to extend JS via a leaf node. - c.Noticef("Not extending remote JetStream domain %q due to presence of empty default domain", myRemoteDomain) - forceSysAccDeny = true - break - } - } - } else if domain, ok := opts.JsAccDefaultDomain[accName]; ok && domain == _EMPTY_ { - // for backwards compatibility with old setups that do not have a domain name set - c.Debugf("Skipping deny %q for account %q due to default domain", jsAllAPI, accName) - return - } - } - - // If the server has JS disabled, it may still be part of a JetStream that could be extended. - // This is either signaled by js being disabled and a domain set, - // or in cases where no domain name exists, an extension hint is set. - // However, this is only relevant in mixed setups. - // - // If the system account connects but default domains are present, JetStream can't be extended. - if opts.JetStreamDomain != myRemoteDomain || (!opts.JetStream && (opts.JetStreamDomain == _EMPTY_ && opts.JetStreamExtHint != jsWillExtend)) || - sysAcc == nil || acc == nil || forceSysAccDeny { - // If domain names mismatch always deny. This applies to system accounts as well as non system accounts. - // Not having a system account, account or JetStream disabled is considered a mismatch as well. - if acc != nil && acc == sysAcc { - c.Noticef("System account connected from %s", srvDecorated()) - c.Noticef("JetStream not extended, domains differ") - c.mergeDenyPermissionsLocked(both, denyAllJs) - // When a remote with a system account is present in a server, unless otherwise disabled, the server will be - // started in observer mode. Now that it is clear that this not used, turn the observer mode off. - if solicited && meta != nil && meta.IsObserver() { - meta.setObserver(false, extNotExtended) - c.Debugf("Turning JetStream metadata controller Observer Mode off") - // Take note that the domain was not extended to avoid this state from startup. - writePeerState(js.config.StoreDir, meta.currentPeerState()) - // Meta controller can't be leader yet. - // Yet it is possible that due to observer mode every server already stopped campaigning. - // Therefore this server needs to be kicked into campaigning gear explicitly. - meta.Campaign() - } - } else { - c.Noticef("JetStream using domains: local %q, remote %q", opts.JetStreamDomain, myRemoteDomain) - c.mergeDenyPermissionsLocked(both, denyAllClientJs) - } - blockMappingOutgoing = true - } else if acc == sysAcc { - // system account and same domain - s.sys.client.Noticef("Extending JetStream domain %q as System Account connected from server %s", - myRemoteDomain, srvDecorated()) - // In an extension use case, pin leadership to server remotes connect to. - // Therefore, server with a remote that are not already in observer mode, need to be put into it. - if solicited && meta != nil && !meta.IsObserver() { - meta.setObserver(true, extExtended) - c.Debugf("Turning JetStream metadata controller Observer Mode on - System Account Connected") - // Take note that the domain was not extended to avoid this state next startup. - writePeerState(js.config.StoreDir, meta.currentPeerState()) - // If this server is the leader already, step down so a new leader can be elected (that is not an observer) - meta.StepDown() - } - } else { - // This deny is needed in all cases (system account shared or not) - // If the system account is shared, jsAllAPI traffic will go through the system account. - // So in order to prevent duplicate delivery (from system and actual account) suppress it on the account. - // If the system account is NOT shared, jsAllAPI traffic has no business - c.Debugf("Adding deny %+v for account %q", denyAllClientJs, accName) - c.mergeDenyPermissionsLocked(both, denyAllClientJs) - } - // If we have a specified JetStream domain we will want to add a mapping to - // allow access cross domain for each non-system account. - if opts.JetStreamDomain != _EMPTY_ && opts.JetStream && acc != nil && acc != sysAcc { - for src, dest := range generateJSMappingTable(opts.JetStreamDomain) { - if err := acc.AddMapping(src, dest); err != nil { - c.Debugf("Error adding JetStream domain mapping: %s", err.Error()) - } else { - c.Debugf("Adding JetStream Domain Mapping %q -> %s to account %q", src, dest, accName) - } - } - if blockMappingOutgoing { - src := fmt.Sprintf(jsDomainAPI, opts.JetStreamDomain) - // make sure that messages intended for this domain, do not leave the cluster via this leaf node connection - // This is a guard against a miss-config with two identical domain names and will only cover some forms - // of this issue, not all of them. - // This guards against a hub and a spoke having the same domain name. - // But not two spokes having the same one and the request coming from the hub. - c.mergeDenyPermissionsLocked(pub, []string{src}) - c.Debugf("Adding deny %q for outgoing messages to account %q", src, accName) - } - } -} - -func (s *Server) removeLeafNodeConnection(c *client) { - c.mu.Lock() - cid := c.cid - if c.leaf != nil { - if c.leaf.tsubt != nil { - c.leaf.tsubt.Stop() - c.leaf.tsubt = nil - } - if c.leaf.gwSub != nil { - s.gwLeafSubs.Remove(c.leaf.gwSub) - // We need to set this to nil for GC to release the connection - c.leaf.gwSub = nil - } - } - c.mu.Unlock() - s.mu.Lock() - delete(s.leafs, cid) - s.mu.Unlock() - s.removeFromTempClients(cid) -} - -// Connect information for solicited leafnodes. -type leafConnectInfo struct { - Version string `json:"version,omitempty"` - Nkey string `json:"nkey,omitempty"` - JWT string `json:"jwt,omitempty"` - Sig string `json:"sig,omitempty"` - User string `json:"user,omitempty"` - Pass string `json:"pass,omitempty"` - Token string `json:"auth_token,omitempty"` - ID string `json:"server_id,omitempty"` - Domain string `json:"domain,omitempty"` - Name string `json:"name,omitempty"` - Hub bool `json:"is_hub,omitempty"` - Cluster string `json:"cluster,omitempty"` - Headers bool `json:"headers,omitempty"` - JetStream bool `json:"jetstream,omitempty"` - DenyPub []string `json:"deny_pub,omitempty"` - - // There was an existing field called: - // >> Comp bool `json:"compression,omitempty"` - // that has never been used. With support for compression, we now need - // a field that is a string. So we use a different json tag: - Compression string `json:"compress_mode,omitempty"` - - // Just used to detect wrong connection attempts. - Gateway string `json:"gateway,omitempty"` - - // Tells the accept side which account the remote is binding to. - RemoteAccount string `json:"remote_account,omitempty"` - - // The accept side of a LEAF connection, unlike ROUTER and GATEWAY, receives - // only the CONNECT protocol, and no INFO. So we need to send the protocol - // version as part of the CONNECT. It will indicate if a connection supports - // some features, such as message tracing. - // We use `protocol` as the JSON tag, so this is automatically unmarshal'ed - // in the low level process CONNECT. - Proto int `json:"protocol,omitempty"` -} - -// processLeafNodeConnect will process the inbound connect args. -// Once we are here we are bound to an account, so can send any interest that -// we would have to the other side. -func (c *client) processLeafNodeConnect(s *Server, arg []byte, lang string) error { - // Way to detect clients that incorrectly connect to the route listen - // port. Client provided "lang" in the CONNECT protocol while LEAFNODEs don't. - if lang != _EMPTY_ { - c.sendErrAndErr(ErrClientConnectedToLeafNodePort.Error()) - c.closeConnection(WrongPort) - return ErrClientConnectedToLeafNodePort - } - - // Unmarshal as a leaf node connect protocol - proto := &leafConnectInfo{} - if err := json.Unmarshal(arg, proto); err != nil { - return err - } - - // Reject a cluster that contains spaces. - if proto.Cluster != _EMPTY_ && strings.Contains(proto.Cluster, " ") { - c.sendErrAndErr(ErrClusterNameHasSpaces.Error()) - c.closeConnection(ProtocolViolation) - return ErrClusterNameHasSpaces - } - - // Check for cluster name collisions. - if cn := s.cachedClusterName(); cn != _EMPTY_ && proto.Cluster != _EMPTY_ && proto.Cluster == cn { - c.sendErrAndErr(ErrLeafNodeHasSameClusterName.Error()) - c.closeConnection(ClusterNamesIdentical) - return ErrLeafNodeHasSameClusterName - } - - // Reject if this has Gateway which means that it would be from a gateway - // connection that incorrectly connects to the leafnode port. - if proto.Gateway != _EMPTY_ { - errTxt := fmt.Sprintf("Rejecting connection from gateway %q on the leafnode port", proto.Gateway) - c.Errorf(errTxt) - c.sendErr(errTxt) - c.closeConnection(WrongGateway) - return ErrWrongGateway - } - - if mv := s.getOpts().LeafNode.MinVersion; mv != _EMPTY_ { - major, minor, update, _ := versionComponents(mv) - if !versionAtLeast(proto.Version, major, minor, update) { - // We are going to send back an INFO because otherwise recent - // versions of the remote server would simply break the connection - // after 2 seconds if not receiving it. Instead, we want the - // other side to just "stall" until we finish waiting for the holding - // period and close the connection below. - s.sendPermsAndAccountInfo(c) - c.sendErrAndErr(fmt.Sprintf("connection rejected since minimum version required is %q", mv)) - select { - case <-c.srv.quitCh: - case <-time.After(leafNodeWaitBeforeClose): - } - c.closeConnection(MinimumVersionRequired) - return ErrMinimumVersionRequired - } - } - - // Check if this server supports headers. - supportHeaders := c.srv.supportsHeaders() - - c.mu.Lock() - // Leaf Nodes do not do echo or verbose or pedantic. - c.opts.Verbose = false - c.opts.Echo = false - c.opts.Pedantic = false - // This inbound connection will be marked as supporting headers if this server - // support headers and the remote has sent in the CONNECT protocol that it does - // support headers too. - c.headers = supportHeaders && proto.Headers - // If the compression level is still not set, set it based on what has been - // given to us in the CONNECT protocol. - if c.leaf.compression == _EMPTY_ { - // But if proto.Compression is _EMPTY_, set it to CompressionNotSupported - if proto.Compression == _EMPTY_ { - c.leaf.compression = CompressionNotSupported - } else { - c.leaf.compression = proto.Compression - } - } - - // Remember the remote server. - c.leaf.remoteServer = proto.Name - // Remember the remote account name - c.leaf.remoteAccName = proto.RemoteAccount - - // If the other side has declared itself a hub, so we will take on the spoke role. - if proto.Hub { - c.leaf.isSpoke = true - } - - // The soliciting side is part of a cluster. - if proto.Cluster != _EMPTY_ { - c.leaf.remoteCluster = proto.Cluster - } - - c.leaf.remoteDomain = proto.Domain - - // When a leaf solicits a connection to a hub, the perms that it will use on the soliciting leafnode's - // behalf are correct for them, but inside the hub need to be reversed since data is flowing in the opposite direction. - if !c.isSolicitedLeafNode() && c.perms != nil { - sp, pp := c.perms.sub, c.perms.pub - c.perms.sub, c.perms.pub = pp, sp - if c.opts.Import != nil { - c.darray = c.opts.Import.Deny - } else { - c.darray = nil - } - } - - // Set the Ping timer - c.setFirstPingTimer() - - // If we received pub deny permissions from the other end, merge with existing ones. - c.mergeDenyPermissions(pub, proto.DenyPub) - - c.mu.Unlock() - - // Register the cluster, even if empty, as long as we are acting as a hub. - if !proto.Hub { - c.acc.registerLeafNodeCluster(proto.Cluster) - } - - // Add in the leafnode here since we passed through auth at this point. - s.addLeafNodeConnection(c, proto.Name, proto.Cluster, true) - - // If we have permissions bound to this leafnode we need to send then back to the - // origin server for local enforcement. - s.sendPermsAndAccountInfo(c) - - // Create and initialize the smap since we know our bound account now. - // This will send all registered subs too. - s.initLeafNodeSmapAndSendSubs(c) - - // Announce the account connect event for a leaf node. - // This will no-op as needed. - s.sendLeafNodeConnect(c.acc) - - return nil -} - -// Returns the remote cluster name. This is set only once so does not require a lock. -func (c *client) remoteCluster() string { - if c.leaf == nil { - return _EMPTY_ - } - return c.leaf.remoteCluster -} - -// Sends back an info block to the soliciting leafnode to let it know about -// its permission settings for local enforcement. -func (s *Server) sendPermsAndAccountInfo(c *client) { - // Copy - info := s.copyLeafNodeInfo() - c.mu.Lock() - info.CID = c.cid - info.Import = c.opts.Import - info.Export = c.opts.Export - info.RemoteAccount = c.acc.Name - info.ConnectInfo = true - c.enqueueProto(generateInfoJSON(info)) - c.mu.Unlock() -} - -// Snapshot the current subscriptions from the sublist into our smap which -// we will keep updated from now on. -// Also send the registered subscriptions. -func (s *Server) initLeafNodeSmapAndSendSubs(c *client) { - acc := c.acc - if acc == nil { - c.Debugf("Leafnode does not have an account bound") - return - } - // Collect all account subs here. - _subs := [1024]*subscription{} - subs := _subs[:0] - ims := []string{} - - // Hold the client lock otherwise there can be a race and miss some subs. - c.mu.Lock() - defer c.mu.Unlock() - - acc.mu.RLock() - accName := acc.Name - accNTag := acc.nameTag - - // To make printing look better when no friendly name present. - if accNTag != _EMPTY_ { - accNTag = "/" + accNTag - } - - // If we are solicited we only send interest for local clients. - if c.isSpokeLeafNode() { - acc.sl.localSubs(&subs, true) - } else { - acc.sl.All(&subs) - } - - // Check if we have an existing service import reply. - siReply := copyBytes(acc.siReply) - - // Since leaf nodes only send on interest, if the bound - // account has import services we need to send those over. - for isubj := range acc.imports.services { - if c.isSpokeLeafNode() && !c.canSubscribe(isubj) { - c.Debugf("Not permitted to import service %q on behalf of %s%s", isubj, accName, accNTag) - continue - } - ims = append(ims, isubj) - } - // Likewise for mappings. - for _, m := range acc.mappings { - if c.isSpokeLeafNode() && !c.canSubscribe(m.src) { - c.Debugf("Not permitted to import mapping %q on behalf of %s%s", m.src, accName, accNTag) - continue - } - ims = append(ims, m.src) - } - - // Create a unique subject that will be used for loop detection. - lds := acc.lds - acc.mu.RUnlock() - - // Check if we have to create the LDS. - if lds == _EMPTY_ { - lds = leafNodeLoopDetectionSubjectPrefix + nuid.Next() - acc.mu.Lock() - acc.lds = lds - acc.mu.Unlock() - } - - // Now check for gateway interest. Leafnodes will put this into - // the proper mode to propagate, but they are not held in the account. - gwsa := [16]*client{} - gws := gwsa[:0] - s.getOutboundGatewayConnections(&gws) - for _, cgw := range gws { - cgw.mu.Lock() - gw := cgw.gw - cgw.mu.Unlock() - if gw != nil { - if ei, _ := gw.outsim.Load(accName); ei != nil { - if e := ei.(*outsie); e != nil && e.sl != nil { - e.sl.All(&subs) - } - } - } - } - - applyGlobalRouting := s.gateway.enabled - if c.isSpokeLeafNode() { - // Add a fake subscription for this solicited leafnode connection - // so that we can send back directly for mapped GW replies. - // We need to keep track of this subscription so it can be removed - // when the connection is closed so that the GC can release it. - c.leaf.gwSub = &subscription{client: c, subject: []byte(gwReplyPrefix + ">")} - c.srv.gwLeafSubs.Insert(c.leaf.gwSub) - } - - // Now walk the results and add them to our smap - rc := c.leaf.remoteCluster - c.leaf.smap = make(map[string]int32) - for _, sub := range subs { - // Check perms regardless of role. - if c.perms != nil && !c.canSubscribe(string(sub.subject)) { - c.Debugf("Not permitted to subscribe to %q on behalf of %s%s", sub.subject, accName, accNTag) - continue - } - // We ignore ourselves here. - // Also don't add the subscription if it has a origin cluster and the - // cluster name matches the one of the client we are sending to. - if c != sub.client && (sub.origin == nil || (bytesToString(sub.origin) != rc)) { - count := int32(1) - if len(sub.queue) > 0 && sub.qw > 0 { - count = sub.qw - } - c.leaf.smap[keyFromSub(sub)] += count - if c.leaf.tsub == nil { - c.leaf.tsub = make(map[*subscription]struct{}) - } - c.leaf.tsub[sub] = struct{}{} - } - } - // FIXME(dlc) - We need to update appropriately on an account claims update. - for _, isubj := range ims { - c.leaf.smap[isubj]++ - } - // If we have gateways enabled we need to make sure the other side sends us responses - // that have been augmented from the original subscription. - // TODO(dlc) - Should we lock this down more? - if applyGlobalRouting { - c.leaf.smap[oldGWReplyPrefix+"*.>"]++ - c.leaf.smap[gwReplyPrefix+">"]++ - } - // Detect loops by subscribing to a specific subject and checking - // if this sub is coming back to us. - c.leaf.smap[lds]++ - - // Check if we need to add an existing siReply to our map. - // This will be a prefix so add on the wildcard. - if siReply != nil { - wcsub := append(siReply, '>') - c.leaf.smap[string(wcsub)]++ - } - // Queue all protocols. There is no max pending limit for LN connection, - // so we don't need chunking. The writes will happen from the writeLoop. - var b bytes.Buffer - for key, n := range c.leaf.smap { - c.writeLeafSub(&b, key, n) - } - if b.Len() > 0 { - c.enqueueProto(b.Bytes()) - } - if c.leaf.tsub != nil { - // Clear the tsub map after 5 seconds. - c.leaf.tsubt = time.AfterFunc(5*time.Second, func() { - c.mu.Lock() - if c.leaf != nil { - c.leaf.tsub = nil - c.leaf.tsubt = nil - } - c.mu.Unlock() - }) - } -} - -// updateInterestForAccountOnGateway called from gateway code when processing RS+ and RS-. -func (s *Server) updateInterestForAccountOnGateway(accName string, sub *subscription, delta int32) { - acc, err := s.LookupAccount(accName) - if acc == nil || err != nil { - s.Debugf("No or bad account for %q, failed to update interest from gateway", accName) - return - } - acc.updateLeafNodes(sub, delta) -} - -// updateLeafNodes will make sure to update the account smap for the subscription. -// Will also forward to all leaf nodes as needed. -func (acc *Account) updateLeafNodes(sub *subscription, delta int32) { - if acc == nil || sub == nil { - return - } - - // We will do checks for no leafnodes and same cluster here inline and under the - // general account read lock. - // If we feel we need to update the leafnodes we will do that out of line to avoid - // blocking routes or GWs. - - acc.mu.RLock() - // First check if we even have leafnodes here. - if acc.nleafs == 0 { - acc.mu.RUnlock() - return - } - - // Is this a loop detection subject. - isLDS := bytes.HasPrefix(sub.subject, []byte(leafNodeLoopDetectionSubjectPrefix)) - - // Capture the cluster even if its empty. - var cluster string - if sub.origin != nil { - cluster = bytesToString(sub.origin) - } - - // If we have an isolated cluster we can return early, as long as it is not a loop detection subject. - // Empty clusters will return false for the check. - if !isLDS && acc.isLeafNodeClusterIsolated(cluster) { - acc.mu.RUnlock() - return - } - - // We can release the general account lock. - acc.mu.RUnlock() - - // We can hold the list lock here to avoid having to copy a large slice. - acc.lmu.RLock() - defer acc.lmu.RUnlock() - - // Do this once. - subject := string(sub.subject) - - // Walk the connected leafnodes. - for _, ln := range acc.lleafs { - if ln == sub.client { - continue - } - // Check to make sure this sub does not have an origin cluster that matches the leafnode. - ln.mu.Lock() - // If skipped, make sure that we still let go the "$LDS." subscription that allows - // the detection of loops as long as different cluster. - clusterDifferent := cluster != ln.remoteCluster() - if (isLDS && clusterDifferent) || ((cluster == _EMPTY_ || clusterDifferent) && (delta <= 0 || ln.canSubscribe(subject))) { - ln.updateSmap(sub, delta, isLDS) - } - ln.mu.Unlock() - } -} - -// This will make an update to our internal smap and determine if we should send out -// an interest update to the remote side. -// Lock should be held. -func (c *client) updateSmap(sub *subscription, delta int32, isLDS bool) { - if c.leaf.smap == nil { - return - } - - // If we are solicited make sure this is a local client or a non-solicited leaf node - skind := sub.client.kind - updateClient := skind == CLIENT || skind == SYSTEM || skind == JETSTREAM || skind == ACCOUNT - if !isLDS && c.isSpokeLeafNode() && !(updateClient || (skind == LEAF && !sub.client.isSpokeLeafNode())) { - return - } - - // For additions, check if that sub has just been processed during initLeafNodeSmapAndSendSubs - if delta > 0 && c.leaf.tsub != nil { - if _, present := c.leaf.tsub[sub]; present { - delete(c.leaf.tsub, sub) - if len(c.leaf.tsub) == 0 { - c.leaf.tsub = nil - c.leaf.tsubt.Stop() - c.leaf.tsubt = nil - } - return - } - } - - key := keyFromSub(sub) - n, ok := c.leaf.smap[key] - if delta < 0 && !ok { - return - } - - // We will update if its a queue, if count is zero (or negative), or we were 0 and are N > 0. - update := sub.queue != nil || (n <= 0 && n+delta > 0) || (n > 0 && n+delta <= 0) - n += delta - if n > 0 { - c.leaf.smap[key] = n - } else { - delete(c.leaf.smap, key) - } - if update { - c.sendLeafNodeSubUpdate(key, n) - } -} - -// Used to force add subjects to the subject map. -func (c *client) forceAddToSmap(subj string) { - c.mu.Lock() - defer c.mu.Unlock() - - if c.leaf.smap == nil { - return - } - n := c.leaf.smap[subj] - if n != 0 { - return - } - // Place into the map since it was not there. - c.leaf.smap[subj] = 1 - c.sendLeafNodeSubUpdate(subj, 1) -} - -// Used to force remove a subject from the subject map. -func (c *client) forceRemoveFromSmap(subj string) { - c.mu.Lock() - defer c.mu.Unlock() - - if c.leaf.smap == nil { - return - } - n := c.leaf.smap[subj] - if n == 0 { - return - } - n-- - if n == 0 { - // Remove is now zero - delete(c.leaf.smap, subj) - c.sendLeafNodeSubUpdate(subj, 0) - } else { - c.leaf.smap[subj] = n - } -} - -// Send the subscription interest change to the other side. -// Lock should be held. -func (c *client) sendLeafNodeSubUpdate(key string, n int32) { - // If we are a spoke, we need to check if we are allowed to send this subscription over to the hub. - if c.isSpokeLeafNode() { - checkPerms := true - if len(key) > 0 && (key[0] == '$' || key[0] == '_') { - if strings.HasPrefix(key, leafNodeLoopDetectionSubjectPrefix) || - strings.HasPrefix(key, oldGWReplyPrefix) || - strings.HasPrefix(key, gwReplyPrefix) { - checkPerms = false - } - } - if checkPerms { - var subject string - if sep := strings.IndexByte(key, ' '); sep != -1 { - subject = key[:sep] - } else { - subject = key - } - if !c.canSubscribe(subject) { - return - } - } - } - // If we are here we can send over to the other side. - _b := [64]byte{} - b := bytes.NewBuffer(_b[:0]) - c.writeLeafSub(b, key, n) - c.enqueueProto(b.Bytes()) -} - -// Helper function to build the key. -func keyFromSub(sub *subscription) string { - var sb strings.Builder - sb.Grow(len(sub.subject) + len(sub.queue) + 1) - sb.Write(sub.subject) - if sub.queue != nil { - // Just make the key subject spc group, e.g. 'foo bar' - sb.WriteByte(' ') - sb.Write(sub.queue) - } - return sb.String() -} - -const ( - keyRoutedSub = "R" - keyRoutedSubByte = 'R' - keyRoutedLeafSub = "L" - keyRoutedLeafSubByte = 'L' -) - -// Helper function to build the key that prevents collisions between normal -// routed subscriptions and routed subscriptions on behalf of a leafnode. -// Keys will look like this: -// "R foo" -> plain routed sub on "foo" -// "R foo bar" -> queue routed sub on "foo", queue "bar" -// "L foo bar" -> plain routed leaf sub on "foo", leaf "bar" -// "L foo bar baz" -> queue routed sub on "foo", queue "bar", leaf "baz" -func keyFromSubWithOrigin(sub *subscription) string { - var sb strings.Builder - sb.Grow(2 + len(sub.origin) + 1 + len(sub.subject) + 1 + len(sub.queue)) - leaf := len(sub.origin) > 0 - if leaf { - sb.WriteByte(keyRoutedLeafSubByte) - } else { - sb.WriteByte(keyRoutedSubByte) - } - sb.WriteByte(' ') - sb.Write(sub.subject) - if sub.queue != nil { - sb.WriteByte(' ') - sb.Write(sub.queue) - } - if leaf { - sb.WriteByte(' ') - sb.Write(sub.origin) - } - return sb.String() -} - -// Lock should be held. -func (c *client) writeLeafSub(w *bytes.Buffer, key string, n int32) { - if key == _EMPTY_ { - return - } - if n > 0 { - w.WriteString("LS+ " + key) - // Check for queue semantics, if found write n. - if strings.Contains(key, " ") { - w.WriteString(" ") - var b [12]byte - var i = len(b) - for l := n; l > 0; l /= 10 { - i-- - b[i] = digits[l%10] - } - w.Write(b[i:]) - if c.trace { - arg := fmt.Sprintf("%s %d", key, n) - c.traceOutOp("LS+", []byte(arg)) - } - } else if c.trace { - c.traceOutOp("LS+", []byte(key)) - } - } else { - w.WriteString("LS- " + key) - if c.trace { - c.traceOutOp("LS-", []byte(key)) - } - } - w.WriteString(CR_LF) -} - -// processLeafSub will process an inbound sub request for the remote leaf node. -func (c *client) processLeafSub(argo []byte) (err error) { - // Indicate activity. - c.in.subs++ - - srv := c.srv - if srv == nil { - return nil - } - - // Copy so we do not reference a potentially large buffer - arg := make([]byte, len(argo)) - copy(arg, argo) - - args := splitArg(arg) - sub := &subscription{client: c} - - delta := int32(1) - switch len(args) { - case 1: - sub.queue = nil - case 3: - sub.queue = args[1] - sub.qw = int32(parseSize(args[2])) - // TODO: (ik) We should have a non empty queue name and a queue - // weight >= 1. For 2.11, we may want to return an error if that - // is not the case, but for now just overwrite `delta` if queue - // weight is greater than 1 (it is possible after a reconnect/ - // server restart to receive a queue weight > 1 for a new sub). - if sub.qw > 1 { - delta = sub.qw - } - default: - return fmt.Errorf("processLeafSub Parse Error: '%s'", arg) - } - sub.subject = args[0] - - c.mu.Lock() - if c.isClosed() { - c.mu.Unlock() - return nil - } - - acc := c.acc - // Check if we have a loop. - ldsPrefix := bytes.HasPrefix(sub.subject, []byte(leafNodeLoopDetectionSubjectPrefix)) - - if ldsPrefix && bytesToString(sub.subject) == acc.getLDSubject() { - c.mu.Unlock() - c.handleLeafNodeLoop(true) - return nil - } - - // Check permissions if applicable. (but exclude the $LDS, $GR and _GR_) - checkPerms := true - if sub.subject[0] == '$' || sub.subject[0] == '_' { - if ldsPrefix || - bytes.HasPrefix(sub.subject, []byte(oldGWReplyPrefix)) || - bytes.HasPrefix(sub.subject, []byte(gwReplyPrefix)) { - checkPerms = false - } - } - - // If we are a hub check that we can publish to this subject. - if checkPerms { - subj := string(sub.subject) - if subjectIsLiteral(subj) && !c.pubAllowedFullCheck(subj, true, true) { - c.mu.Unlock() - c.leafSubPermViolation(sub.subject) - c.Debugf(fmt.Sprintf("Permissions Violation for Subscription to %q", sub.subject)) - return nil - } - } - - // Check if we have a maximum on the number of subscriptions. - if c.subsAtLimit() { - c.mu.Unlock() - c.maxSubsExceeded() - return nil - } - - // If we have an origin cluster associated mark that in the sub. - if rc := c.remoteCluster(); rc != _EMPTY_ { - sub.origin = []byte(rc) - } - - // Like Routes, we store local subs by account and subject and optionally queue name. - // If we have a queue it will have a trailing weight which we do not want. - if sub.queue != nil { - sub.sid = arg[:len(arg)-len(args[2])-1] - } else { - sub.sid = arg - } - key := bytesToString(sub.sid) - osub := c.subs[key] - if osub == nil { - c.subs[key] = sub - // Now place into the account sl. - if err := acc.sl.Insert(sub); err != nil { - delete(c.subs, key) - c.mu.Unlock() - c.Errorf("Could not insert subscription: %v", err) - c.sendErr("Invalid Subscription") - return nil - } - } else if sub.queue != nil { - // For a queue we need to update the weight. - delta = sub.qw - atomic.LoadInt32(&osub.qw) - atomic.StoreInt32(&osub.qw, sub.qw) - acc.sl.UpdateRemoteQSub(osub) - } - spoke := c.isSpokeLeafNode() - c.mu.Unlock() - - // Only add in shadow subs if a new sub or qsub. - if osub == nil { - if err := c.addShadowSubscriptions(acc, sub, true); err != nil { - c.Errorf(err.Error()) - } - } - - // If we are not solicited, treat leaf node subscriptions similar to a - // client subscription, meaning we forward them to routes, gateways and - // other leaf nodes as needed. - if !spoke { - // If we are routing add to the route map for the associated account. - srv.updateRouteSubscriptionMap(acc, sub, delta) - if srv.gateway.enabled { - srv.gatewayUpdateSubInterest(acc.Name, sub, delta) - } - } - // Now check on leafnode updates for other leaf nodes. We understand solicited - // and non-solicited state in this call so we will do the right thing. - acc.updateLeafNodes(sub, delta) - - return nil -} - -// If the leafnode is a solicited, set the connect delay based on default -// or private option (for tests). Sends the error to the other side, log and -// close the connection. -func (c *client) handleLeafNodeLoop(sendErr bool) { - accName, delay := c.setLeafConnectDelayIfSoliciting(leafNodeReconnectDelayAfterLoopDetected) - errTxt := fmt.Sprintf("Loop detected for leafnode account=%q. Delaying attempt to reconnect for %v", accName, delay) - if sendErr { - c.sendErr(errTxt) - } - - c.Errorf(errTxt) - // If we are here with "sendErr" false, it means that this is the server - // that received the error. The other side will have closed the connection, - // but does not hurt to close here too. - c.closeConnection(ProtocolViolation) -} - -// processLeafUnsub will process an inbound unsub request for the remote leaf node. -func (c *client) processLeafUnsub(arg []byte) error { - // Indicate any activity, so pub and sub or unsubs. - c.in.subs++ - - acc := c.acc - srv := c.srv - - c.mu.Lock() - if c.isClosed() { - c.mu.Unlock() - return nil - } - - spoke := c.isSpokeLeafNode() - // We store local subs by account and subject and optionally queue name. - // LS- will have the arg exactly as the key. - sub, ok := c.subs[string(arg)] - if !ok { - // If not found, don't try to update routes/gws/leaf nodes. - c.mu.Unlock() - return nil - } - delta := int32(1) - if len(sub.queue) > 0 { - delta = sub.qw - } - c.mu.Unlock() - - c.unsubscribe(acc, sub, true, true) - if !spoke { - // If we are routing subtract from the route map for the associated account. - srv.updateRouteSubscriptionMap(acc, sub, -delta) - // Gateways - if srv.gateway.enabled { - srv.gatewayUpdateSubInterest(acc.Name, sub, -delta) - } - } - // Now check on leafnode updates for other leaf nodes. - acc.updateLeafNodes(sub, -delta) - return nil -} - -func (c *client) processLeafHeaderMsgArgs(arg []byte) error { - // Unroll splitArgs to avoid runtime/heap issues - a := [MAX_MSG_ARGS][]byte{} - args := a[:0] - start := -1 - for i, b := range arg { - switch b { - case ' ', '\t', '\r', '\n': - if start >= 0 { - args = append(args, arg[start:i]) - start = -1 - } - default: - if start < 0 { - start = i - } - } - } - if start >= 0 { - args = append(args, arg[start:]) - } - - c.pa.arg = arg - switch len(args) { - case 0, 1, 2: - return fmt.Errorf("processLeafHeaderMsgArgs Parse Error: '%s'", args) - case 3: - c.pa.reply = nil - c.pa.queues = nil - c.pa.hdb = args[1] - c.pa.hdr = parseSize(args[1]) - c.pa.szb = args[2] - c.pa.size = parseSize(args[2]) - case 4: - c.pa.reply = args[1] - c.pa.queues = nil - c.pa.hdb = args[2] - c.pa.hdr = parseSize(args[2]) - c.pa.szb = args[3] - c.pa.size = parseSize(args[3]) - default: - // args[1] is our reply indicator. Should be + or | normally. - if len(args[1]) != 1 { - return fmt.Errorf("processLeafHeaderMsgArgs Bad or Missing Reply Indicator: '%s'", args[1]) - } - switch args[1][0] { - case '+': - c.pa.reply = args[2] - case '|': - c.pa.reply = nil - default: - return fmt.Errorf("processLeafHeaderMsgArgs Bad or Missing Reply Indicator: '%s'", args[1]) - } - // Grab header size. - c.pa.hdb = args[len(args)-2] - c.pa.hdr = parseSize(c.pa.hdb) - - // Grab size. - c.pa.szb = args[len(args)-1] - c.pa.size = parseSize(c.pa.szb) - - // Grab queue names. - if c.pa.reply != nil { - c.pa.queues = args[3 : len(args)-2] - } else { - c.pa.queues = args[2 : len(args)-2] - } - } - if c.pa.hdr < 0 { - return fmt.Errorf("processLeafHeaderMsgArgs Bad or Missing Header Size: '%s'", arg) - } - if c.pa.size < 0 { - return fmt.Errorf("processLeafHeaderMsgArgs Bad or Missing Size: '%s'", args) - } - if c.pa.hdr > c.pa.size { - return fmt.Errorf("processLeafHeaderMsgArgs Header Size larger then TotalSize: '%s'", arg) - } - - // Common ones processed after check for arg length - c.pa.subject = args[0] - - return nil -} - -func (c *client) processLeafMsgArgs(arg []byte) error { - // Unroll splitArgs to avoid runtime/heap issues - a := [MAX_MSG_ARGS][]byte{} - args := a[:0] - start := -1 - for i, b := range arg { - switch b { - case ' ', '\t', '\r', '\n': - if start >= 0 { - args = append(args, arg[start:i]) - start = -1 - } - default: - if start < 0 { - start = i - } - } - } - if start >= 0 { - args = append(args, arg[start:]) - } - - c.pa.arg = arg - switch len(args) { - case 0, 1: - return fmt.Errorf("processLeafMsgArgs Parse Error: '%s'", args) - case 2: - c.pa.reply = nil - c.pa.queues = nil - c.pa.szb = args[1] - c.pa.size = parseSize(args[1]) - case 3: - c.pa.reply = args[1] - c.pa.queues = nil - c.pa.szb = args[2] - c.pa.size = parseSize(args[2]) - default: - // args[1] is our reply indicator. Should be + or | normally. - if len(args[1]) != 1 { - return fmt.Errorf("processLeafMsgArgs Bad or Missing Reply Indicator: '%s'", args[1]) - } - switch args[1][0] { - case '+': - c.pa.reply = args[2] - case '|': - c.pa.reply = nil - default: - return fmt.Errorf("processLeafMsgArgs Bad or Missing Reply Indicator: '%s'", args[1]) - } - // Grab size. - c.pa.szb = args[len(args)-1] - c.pa.size = parseSize(c.pa.szb) - - // Grab queue names. - if c.pa.reply != nil { - c.pa.queues = args[3 : len(args)-1] - } else { - c.pa.queues = args[2 : len(args)-1] - } - } - if c.pa.size < 0 { - return fmt.Errorf("processLeafMsgArgs Bad or Missing Size: '%s'", args) - } - - // Common ones processed after check for arg length - c.pa.subject = args[0] - - return nil -} - -// processInboundLeafMsg is called to process an inbound msg from a leaf node. -func (c *client) processInboundLeafMsg(msg []byte) { - // Update statistics - // The msg includes the CR_LF, so pull back out for accounting. - c.in.msgs++ - c.in.bytes += int32(len(msg) - LEN_CR_LF) - - srv, acc, subject := c.srv, c.acc, string(c.pa.subject) - - // Mostly under testing scenarios. - if srv == nil || acc == nil { - return - } - - // Match the subscriptions. We will use our own L1 map if - // it's still valid, avoiding contention on the shared sublist. - var r *SublistResult - var ok bool - - genid := atomic.LoadUint64(&c.acc.sl.genid) - if genid == c.in.genid && c.in.results != nil { - r, ok = c.in.results[subject] - } else { - // Reset our L1 completely. - c.in.results = make(map[string]*SublistResult) - c.in.genid = genid - } - - // Go back to the sublist data structure. - if !ok { - r = c.acc.sl.Match(subject) - // Prune the results cache. Keeps us from unbounded growth. Random delete. - if len(c.in.results) >= maxResultCacheSize { - n := 0 - for subj := range c.in.results { - delete(c.in.results, subj) - if n++; n > pruneSize { - break - } - } - } - // Then add the new cache entry. - c.in.results[subject] = r - } - - // Collect queue names if needed. - var qnames [][]byte - - // Check for no interest, short circuit if so. - // This is the fanout scale. - if len(r.psubs)+len(r.qsubs) > 0 { - flag := pmrNoFlag - // If we have queue subs in this cluster, then if we run in gateway - // mode and the remote gateways have queue subs, then we need to - // collect the queue groups this message was sent to so that we - // exclude them when sending to gateways. - if len(r.qsubs) > 0 && c.srv.gateway.enabled && - atomic.LoadInt64(&c.srv.gateway.totalQSubs) > 0 { - flag |= pmrCollectQueueNames - } - // If this is a mapped subject that means the mapped interest - // is what got us here, but this might not have a queue designation - // If that is the case, make sure we ignore to process local queue subscribers. - if len(c.pa.mapped) > 0 && len(c.pa.queues) == 0 { - flag |= pmrIgnoreEmptyQueueFilter - } - _, qnames = c.processMsgResults(acc, r, msg, nil, c.pa.subject, c.pa.reply, flag) - } - - // Now deal with gateways - if c.srv.gateway.enabled { - c.sendMsgToGateways(acc, msg, c.pa.subject, c.pa.reply, qnames, true) - } -} - -// Handles a subscription permission violation. -// See leafPermViolation() for details. -func (c *client) leafSubPermViolation(subj []byte) { - c.leafPermViolation(false, subj) -} - -// Common function to process publish or subscribe leafnode permission violation. -// Sends the permission violation error to the remote, logs it and closes the connection. -// If this is from a server soliciting, the reconnection will be delayed. -func (c *client) leafPermViolation(pub bool, subj []byte) { - if c.isSpokeLeafNode() { - // For spokes these are no-ops since the hub server told us our permissions. - // We just need to not send these over to the other side since we will get cutoff. - return - } - // FIXME(dlc) ? - c.setLeafConnectDelayIfSoliciting(leafNodeReconnectAfterPermViolation) - var action string - if pub { - c.sendErr(fmt.Sprintf("Permissions Violation for Publish to %q", subj)) - action = "Publish" - } else { - c.sendErr(fmt.Sprintf("Permissions Violation for Subscription to %q", subj)) - action = "Subscription" - } - c.Errorf("%s Violation on %q - Check other side configuration", action, subj) - // TODO: add a new close reason that is more appropriate? - c.closeConnection(ProtocolViolation) -} - -// Invoked from generic processErr() for LEAF connections. -func (c *client) leafProcessErr(errStr string) { - // Check if we got a cluster name collision. - if strings.Contains(errStr, ErrLeafNodeHasSameClusterName.Error()) { - _, delay := c.setLeafConnectDelayIfSoliciting(leafNodeReconnectDelayAfterClusterNameSame) - c.Errorf("Leafnode connection dropped with same cluster name error. Delaying attempt to reconnect for %v", delay) - return - } - - // We will look for Loop detected error coming from the other side. - // If we solicit, set the connect delay. - if !strings.Contains(errStr, "Loop detected") { - return - } - c.handleLeafNodeLoop(false) -} - -// If this leaf connection solicits, sets the connect delay to the given value, -// or the one from the server option's LeafNode.connDelay if one is set (for tests). -// Returns the connection's account name and delay. -func (c *client) setLeafConnectDelayIfSoliciting(delay time.Duration) (string, time.Duration) { - c.mu.Lock() - if c.isSolicitedLeafNode() { - if s := c.srv; s != nil { - if srvdelay := s.getOpts().LeafNode.connDelay; srvdelay != 0 { - delay = srvdelay - } - } - c.leaf.remote.setConnectDelay(delay) - } - accName := c.acc.Name - c.mu.Unlock() - return accName, delay -} - -// For the given remote Leafnode configuration, this function returns -// if TLS is required, and if so, will return a clone of the TLS Config -// (since some fields will be changed during handshake), the TLS server -// name that is remembered, and the TLS timeout. -func (c *client) leafNodeGetTLSConfigForSolicit(remote *leafNodeCfg) (bool, *tls.Config, string, float64) { - var ( - tlsConfig *tls.Config - tlsName string - tlsTimeout float64 - ) - - remote.RLock() - defer remote.RUnlock() - - tlsRequired := remote.TLS || remote.TLSConfig != nil - if tlsRequired { - if remote.TLSConfig != nil { - tlsConfig = remote.TLSConfig.Clone() - } else { - tlsConfig = &tls.Config{MinVersion: tls.VersionTLS12} - } - tlsName = remote.tlsName - tlsTimeout = remote.TLSTimeout - if tlsTimeout == 0 { - tlsTimeout = float64(TLS_TIMEOUT / time.Second) - } - } - - return tlsRequired, tlsConfig, tlsName, tlsTimeout -} - -// Initiates the LeafNode Websocket connection by: -// - doing the TLS handshake if needed -// - sending the HTTP request -// - waiting for the HTTP response -// -// Since some bufio reader is used to consume the HTTP response, this function -// returns the slice of buffered bytes (if any) so that the readLoop that will -// be started after that consume those first before reading from the socket. -// The boolean -// -// Lock held on entry. -func (c *client) leafNodeSolicitWSConnection(opts *Options, rURL *url.URL, remote *leafNodeCfg) ([]byte, ClosedState, error) { - remote.RLock() - compress := remote.Websocket.Compression - // By default the server will mask outbound frames, but it can be disabled with this option. - noMasking := remote.Websocket.NoMasking - infoTimeout := remote.FirstInfoTimeout - remote.RUnlock() - // Will do the client-side TLS handshake if needed. - tlsRequired, err := c.leafClientHandshakeIfNeeded(remote, opts) - if err != nil { - // 0 will indicate that the connection was already closed - return nil, 0, err - } - - // For http request, we need the passed URL to contain either http or https scheme. - scheme := "http" - if tlsRequired { - scheme = "https" - } - // We will use the `/leafnode` path to tell the accepting WS server that it should - // create a LEAF connection, not a CLIENT. - // In case we use the user's URL path in the future, make sure we append the user's - // path to our `/leafnode` path. - lpath := leafNodeWSPath - if curPath := rURL.EscapedPath(); curPath != _EMPTY_ { - if curPath[0] == '/' { - curPath = curPath[1:] - } - lpath = path.Join(curPath, lpath) - } else { - lpath = lpath[1:] - } - ustr := fmt.Sprintf("%s://%s/%s", scheme, rURL.Host, lpath) - u, _ := url.Parse(ustr) - req := &http.Request{ - Method: "GET", - URL: u, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Header: make(http.Header), - Host: u.Host, - } - wsKey, err := wsMakeChallengeKey() - if err != nil { - return nil, WriteError, err - } - - req.Header["Upgrade"] = []string{"websocket"} - req.Header["Connection"] = []string{"Upgrade"} - req.Header["Sec-WebSocket-Key"] = []string{wsKey} - req.Header["Sec-WebSocket-Version"] = []string{"13"} - if compress { - req.Header.Add("Sec-WebSocket-Extensions", wsPMCReqHeaderValue) - } - if noMasking { - req.Header.Add(wsNoMaskingHeader, wsNoMaskingValue) - } - c.nc.SetDeadline(time.Now().Add(infoTimeout)) - if err := req.Write(c.nc); err != nil { - return nil, WriteError, err - } - - var resp *http.Response - - br := bufio.NewReaderSize(c.nc, MAX_CONTROL_LINE_SIZE) - resp, err = http.ReadResponse(br, req) - if err == nil && - (resp.StatusCode != 101 || - !strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") || - !strings.EqualFold(resp.Header.Get("Connection"), "upgrade") || - resp.Header.Get("Sec-Websocket-Accept") != wsAcceptKey(wsKey)) { - - err = fmt.Errorf("invalid websocket connection") - } - // Check compression extension... - if err == nil && c.ws.compress { - // Check that not only permessage-deflate extension is present, but that - // we also have server and client no context take over. - srvCompress, noCtxTakeover := wsPMCExtensionSupport(resp.Header, false) - - // If server does not support compression, then simply disable it in our side. - if !srvCompress { - c.ws.compress = false - } else if !noCtxTakeover { - err = fmt.Errorf("compression negotiation error") - } - } - // Same for no masking... - if err == nil && noMasking { - // Check if server accepts no masking - if resp.Header.Get(wsNoMaskingHeader) != wsNoMaskingValue { - // Nope, need to mask our writes as any client would do. - c.ws.maskwrite = true - } - } - if resp != nil { - resp.Body.Close() - } - if err != nil { - return nil, ReadError, err - } - c.Debugf("Leafnode compression=%v masking=%v", c.ws.compress, c.ws.maskwrite) - - var preBuf []byte - // We have to slurp whatever is in the bufio reader and pass that to the readloop. - if n := br.Buffered(); n != 0 { - preBuf, _ = br.Peek(n) - } - return preBuf, 0, nil -} - -const connectProcessTimeout = 2 * time.Second - -// This is invoked for remote LEAF remote connections after processing the INFO -// protocol. -func (s *Server) leafNodeResumeConnectProcess(c *client) { - clusterName := s.ClusterName() - - c.mu.Lock() - if c.isClosed() { - c.mu.Unlock() - return - } - if err := c.sendLeafConnect(clusterName, c.headers); err != nil { - c.mu.Unlock() - c.closeConnection(WriteError) - return - } - - // Spin up the write loop. - s.startGoRoutine(func() { c.writeLoop() }) - - // timeout leafNodeFinishConnectProcess - c.ping.tmr = time.AfterFunc(connectProcessTimeout, func() { - c.mu.Lock() - // check if leafNodeFinishConnectProcess was called and prevent later leafNodeFinishConnectProcess - if !c.flags.setIfNotSet(connectProcessFinished) { - c.mu.Unlock() - return - } - clearTimer(&c.ping.tmr) - closed := c.isClosed() - c.mu.Unlock() - if !closed { - c.sendErrAndDebug("Stale Leaf Node Connection - Closing") - c.closeConnection(StaleConnection) - } - }) - c.mu.Unlock() - c.Debugf("Remote leafnode connect msg sent") -} - -// This is invoked for remote LEAF connections after processing the INFO -// protocol and leafNodeResumeConnectProcess. -// This will send LS+ the CONNECT protocol and register the leaf node. -func (s *Server) leafNodeFinishConnectProcess(c *client) { - c.mu.Lock() - if !c.flags.setIfNotSet(connectProcessFinished) { - c.mu.Unlock() - return - } - if c.isClosed() { - c.mu.Unlock() - s.removeLeafNodeConnection(c) - return - } - remote := c.leaf.remote - // Check if we will need to send the system connect event. - remote.RLock() - sendSysConnectEvent := remote.Hub - remote.RUnlock() - - // Capture account before releasing lock - acc := c.acc - // cancel connectProcessTimeout - clearTimer(&c.ping.tmr) - c.mu.Unlock() - - // Make sure we register with the account here. - if err := c.registerWithAccount(acc); err != nil { - if err == ErrTooManyAccountConnections { - c.maxAccountConnExceeded() - return - } else if err == ErrLeafNodeLoop { - c.handleLeafNodeLoop(true) - return - } - c.Errorf("Registering leaf with account %s resulted in error: %v", acc.Name, err) - c.closeConnection(ProtocolViolation) - return - } - s.addLeafNodeConnection(c, _EMPTY_, _EMPTY_, false) - s.initLeafNodeSmapAndSendSubs(c) - if sendSysConnectEvent { - s.sendLeafNodeConnect(acc) - } - - // The above functions are not atomically under the client - // lock doing those operations. It is possible - since we - // have started the read/write loops - that the connection - // is closed before or in between. This would leave the - // closed LN connection possible registered with the account - // and/or the server's leafs map. So check if connection - // is closed, and if so, manually cleanup. - c.mu.Lock() - closed := c.isClosed() - if !closed { - c.setFirstPingTimer() - } - c.mu.Unlock() - if closed { - s.removeLeafNodeConnection(c) - if prev := acc.removeClient(c); prev == 1 { - s.decActiveAccounts() - } - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/log.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/log.go deleted file mode 100644 index 9a4b7ed4..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/log.go +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright 2012-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "fmt" - "io" - "os" - "sync/atomic" - "time" - - srvlog "github.com/nats-io/nats-server/v2/logger" -) - -// Logger interface of the NATS Server -type Logger interface { - - // Log a notice statement - Noticef(format string, v ...any) - - // Log a warning statement - Warnf(format string, v ...any) - - // Log a fatal error - Fatalf(format string, v ...any) - - // Log an error - Errorf(format string, v ...any) - - // Log a debug statement - Debugf(format string, v ...any) - - // Log a trace statement - Tracef(format string, v ...any) -} - -// ConfigureLogger configures and sets the logger for the server. -func (s *Server) ConfigureLogger() { - var ( - log Logger - - // Snapshot server options. - opts = s.getOpts() - ) - - if opts.NoLog { - return - } - - syslog := opts.Syslog - if isWindowsService() && opts.LogFile == "" { - // Enable syslog if no log file is specified and we're running as a - // Windows service so that logs are written to the Windows event log. - syslog = true - } - - if opts.LogFile != "" { - log = srvlog.NewFileLogger(opts.LogFile, opts.Logtime, opts.Debug, opts.Trace, true, srvlog.LogUTC(opts.LogtimeUTC)) - if opts.LogSizeLimit > 0 { - if l, ok := log.(*srvlog.Logger); ok { - l.SetSizeLimit(opts.LogSizeLimit) - } - } - if opts.LogMaxFiles > 0 { - if l, ok := log.(*srvlog.Logger); ok { - al := int(opts.LogMaxFiles) - if int64(al) != opts.LogMaxFiles { - // set to default (no max) on overflow - al = 0 - } - l.SetMaxNumFiles(al) - } - } - } else if opts.RemoteSyslog != "" { - log = srvlog.NewRemoteSysLogger(opts.RemoteSyslog, opts.Debug, opts.Trace) - } else if syslog { - log = srvlog.NewSysLogger(opts.Debug, opts.Trace) - } else { - colors := true - // Check to see if stderr is being redirected and if so turn off color - // Also turn off colors if we're running on Windows where os.Stderr.Stat() returns an invalid handle-error - stat, err := os.Stderr.Stat() - if err != nil || (stat.Mode()&os.ModeCharDevice) == 0 { - colors = false - } - log = srvlog.NewStdLogger(opts.Logtime, opts.Debug, opts.Trace, colors, true, srvlog.LogUTC(opts.LogtimeUTC)) - } - - s.SetLoggerV2(log, opts.Debug, opts.Trace, opts.TraceVerbose) -} - -// Returns our current logger. -func (s *Server) Logger() Logger { - s.logging.Lock() - defer s.logging.Unlock() - return s.logging.logger -} - -// SetLogger sets the logger of the server -func (s *Server) SetLogger(logger Logger, debugFlag, traceFlag bool) { - s.SetLoggerV2(logger, debugFlag, traceFlag, false) -} - -// SetLogger sets the logger of the server -func (s *Server) SetLoggerV2(logger Logger, debugFlag, traceFlag, sysTrace bool) { - if debugFlag { - atomic.StoreInt32(&s.logging.debug, 1) - } else { - atomic.StoreInt32(&s.logging.debug, 0) - } - if traceFlag { - atomic.StoreInt32(&s.logging.trace, 1) - } else { - atomic.StoreInt32(&s.logging.trace, 0) - } - if sysTrace { - atomic.StoreInt32(&s.logging.traceSysAcc, 1) - } else { - atomic.StoreInt32(&s.logging.traceSysAcc, 0) - } - s.logging.Lock() - if s.logging.logger != nil { - // Check to see if the logger implements io.Closer. This could be a - // logger from another process embedding the NATS server or a dummy - // test logger that may not implement that interface. - if l, ok := s.logging.logger.(io.Closer); ok { - if err := l.Close(); err != nil { - s.Errorf("Error closing logger: %v", err) - } - } - } - s.logging.logger = logger - s.logging.Unlock() -} - -// ReOpenLogFile if the logger is a file based logger, close and re-open the file. -// This allows for file rotation by 'mv'ing the file then signaling -// the process to trigger this function. -func (s *Server) ReOpenLogFile() { - // Check to make sure this is a file logger. - s.logging.RLock() - ll := s.logging.logger - s.logging.RUnlock() - - if ll == nil { - s.Noticef("File log re-open ignored, no logger") - return - } - - // Snapshot server options. - opts := s.getOpts() - - if opts.LogFile == "" { - s.Noticef("File log re-open ignored, not a file logger") - } else { - fileLog := srvlog.NewFileLogger( - opts.LogFile, opts.Logtime, - opts.Debug, opts.Trace, true, - srvlog.LogUTC(opts.LogtimeUTC), - ) - s.SetLogger(fileLog, opts.Debug, opts.Trace) - if opts.LogSizeLimit > 0 { - fileLog.SetSizeLimit(opts.LogSizeLimit) - } - s.Noticef("File log re-opened") - } -} - -// Noticef logs a notice statement -func (s *Server) Noticef(format string, v ...any) { - s.executeLogCall(func(logger Logger, format string, v ...any) { - logger.Noticef(format, v...) - }, format, v...) -} - -// Errorf logs an error -func (s *Server) Errorf(format string, v ...any) { - s.executeLogCall(func(logger Logger, format string, v ...any) { - logger.Errorf(format, v...) - }, format, v...) -} - -// Error logs an error with a scope -func (s *Server) Errors(scope any, e error) { - s.executeLogCall(func(logger Logger, format string, v ...any) { - logger.Errorf(format, v...) - }, "%s - %s", scope, UnpackIfErrorCtx(e)) -} - -// Error logs an error with a context -func (s *Server) Errorc(ctx string, e error) { - s.executeLogCall(func(logger Logger, format string, v ...any) { - logger.Errorf(format, v...) - }, "%s: %s", ctx, UnpackIfErrorCtx(e)) -} - -// Error logs an error with a scope and context -func (s *Server) Errorsc(scope any, ctx string, e error) { - s.executeLogCall(func(logger Logger, format string, v ...any) { - logger.Errorf(format, v...) - }, "%s - %s: %s", scope, ctx, UnpackIfErrorCtx(e)) -} - -// Warnf logs a warning error -func (s *Server) Warnf(format string, v ...any) { - s.executeLogCall(func(logger Logger, format string, v ...any) { - logger.Warnf(format, v...) - }, format, v...) -} - -func (s *Server) rateLimitFormatWarnf(format string, v ...any) { - if _, loaded := s.rateLimitLogging.LoadOrStore(format, time.Now()); loaded { - return - } - statement := fmt.Sprintf(format, v...) - s.Warnf("%s", statement) -} - -func (s *Server) RateLimitWarnf(format string, v ...any) { - statement := fmt.Sprintf(format, v...) - if _, loaded := s.rateLimitLogging.LoadOrStore(statement, time.Now()); loaded { - return - } - s.Warnf("%s", statement) -} - -func (s *Server) RateLimitDebugf(format string, v ...any) { - statement := fmt.Sprintf(format, v...) - if _, loaded := s.rateLimitLogging.LoadOrStore(statement, time.Now()); loaded { - return - } - s.Debugf("%s", statement) -} - -// Fatalf logs a fatal error -func (s *Server) Fatalf(format string, v ...any) { - s.executeLogCall(func(logger Logger, format string, v ...any) { - logger.Fatalf(format, v...) - }, format, v...) -} - -// Debugf logs a debug statement -func (s *Server) Debugf(format string, v ...any) { - if atomic.LoadInt32(&s.logging.debug) == 0 { - return - } - - s.executeLogCall(func(logger Logger, format string, v ...any) { - logger.Debugf(format, v...) - }, format, v...) -} - -// Tracef logs a trace statement -func (s *Server) Tracef(format string, v ...any) { - if atomic.LoadInt32(&s.logging.trace) == 0 { - return - } - - s.executeLogCall(func(logger Logger, format string, v ...any) { - logger.Tracef(format, v...) - }, format, v...) -} - -func (s *Server) executeLogCall(f func(logger Logger, format string, v ...any), format string, args ...any) { - s.logging.RLock() - defer s.logging.RUnlock() - if s.logging.logger == nil { - return - } - - f(s.logging.logger, format, args...) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/memstore.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/memstore.go deleted file mode 100644 index 297787f9..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/memstore.go +++ /dev/null @@ -1,2274 +0,0 @@ -// Copyright 2019-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - crand "crypto/rand" - "encoding/binary" - "fmt" - "math" - "slices" - "sort" - "sync" - "time" - - "github.com/nats-io/nats-server/v2/server/avl" - "github.com/nats-io/nats-server/v2/server/stree" - "github.com/nats-io/nats-server/v2/server/thw" -) - -// TODO(dlc) - This is a fairly simplistic approach but should do for now. -type memStore struct { - mu sync.RWMutex - cfg StreamConfig - state StreamState - msgs map[uint64]*StoreMsg - fss *stree.SubjectTree[SimpleState] - dmap avl.SequenceSet - maxp int64 - scb StorageUpdateHandler - sdmcb SubjectDeleteMarkerUpdateHandler - ageChk *time.Timer - consumers int - receivedAny bool - ttls *thw.HashWheel - markers []string -} - -func newMemStore(cfg *StreamConfig) (*memStore, error) { - if cfg == nil { - return nil, fmt.Errorf("config required") - } - if cfg.Storage != MemoryStorage { - return nil, fmt.Errorf("memStore requires memory storage type in config") - } - ms := &memStore{ - msgs: make(map[uint64]*StoreMsg), - fss: stree.NewSubjectTree[SimpleState](), - maxp: cfg.MaxMsgsPer, - cfg: *cfg, - } - // Only create a THW if we're going to allow TTLs. - if cfg.AllowMsgTTL { - ms.ttls = thw.NewHashWheel() - } - if cfg.FirstSeq > 0 { - if _, err := ms.purge(cfg.FirstSeq, true); err != nil { - return nil, err - } - } - - return ms, nil -} - -func (ms *memStore) UpdateConfig(cfg *StreamConfig) error { - if cfg == nil { - return fmt.Errorf("config required") - } - if cfg.Storage != MemoryStorage { - return fmt.Errorf("memStore requires memory storage type in config") - } - - ms.mu.Lock() - ms.cfg = *cfg - // Limits checks and enforcement. - ms.enforceMsgLimit() - ms.enforceBytesLimit() - // Do age timers. - if ms.ageChk == nil && ms.cfg.MaxAge != 0 { - ms.startAgeChk() - } - if ms.ageChk != nil && ms.cfg.MaxAge == 0 { - ms.ageChk.Stop() - ms.ageChk = nil - } - // Make sure to update MaxMsgsPer - if cfg.MaxMsgsPer < -1 { - cfg.MaxMsgsPer = -1 - } - maxp := ms.maxp - ms.maxp = cfg.MaxMsgsPer - // If the value is smaller, or was unset before, we need to enforce that. - if ms.maxp > 0 && (maxp == 0 || ms.maxp < maxp) { - lm := uint64(ms.maxp) - ms.fss.IterFast(func(subj []byte, ss *SimpleState) bool { - if ss.Msgs > lm { - ms.enforcePerSubjectLimit(bytesToString(subj), ss) - } - return true - }) - } - ms.mu.Unlock() - - if cfg.MaxAge != 0 { - ms.expireMsgs() - } - return nil -} - -// Stores a raw message with expected sequence number and timestamp. -// Lock should be held. -func (ms *memStore) storeRawMsg(subj string, hdr, msg []byte, seq uint64, ts, ttl int64) error { - if ms.msgs == nil { - return ErrStoreClosed - } - - // Tracking by subject. - var ss *SimpleState - var asl bool - if len(subj) > 0 { - var ok bool - if ss, ok = ms.fss.Find(stringToBytes(subj)); ok { - asl = ms.maxp > 0 && ss.Msgs >= uint64(ms.maxp) - } - } - - // Check if we are discarding new messages when we reach the limit. - if ms.cfg.Discard == DiscardNew { - if asl && ms.cfg.DiscardNewPer { - return ErrMaxMsgsPerSubject - } - // If we are discard new and limits policy and clustered, we do the enforcement - // above and should not disqualify the message here since it could cause replicas to drift. - if ms.cfg.Retention == LimitsPolicy || ms.cfg.Replicas == 1 { - if ms.cfg.MaxMsgs > 0 && ms.state.Msgs >= uint64(ms.cfg.MaxMsgs) { - // If we are tracking max messages per subject and are at the limit we will replace, so this is ok. - if !asl { - return ErrMaxMsgs - } - } - if ms.cfg.MaxBytes > 0 && ms.state.Bytes+memStoreMsgSize(subj, hdr, msg) >= uint64(ms.cfg.MaxBytes) { - if !asl { - return ErrMaxBytes - } - // If we are here we are at a subject maximum, need to determine if dropping last message gives us enough room. - if ss.firstNeedsUpdate || ss.lastNeedsUpdate { - ms.recalculateForSubj(subj, ss) - } - sm, ok := ms.msgs[ss.First] - if !ok || memStoreMsgSize(sm.subj, sm.hdr, sm.msg) < memStoreMsgSize(subj, hdr, msg) { - return ErrMaxBytes - } - } - } - } - - if seq != ms.state.LastSeq+1 { - if seq > 0 { - return ErrSequenceMismatch - } - seq = ms.state.LastSeq + 1 - } - - // Adjust first if needed. - now := time.Unix(0, ts).UTC() - if ms.state.Msgs == 0 { - ms.state.FirstSeq = seq - ms.state.FirstTime = now - } - - // Make copies - // TODO(dlc) - Maybe be smarter here. - if len(msg) > 0 { - msg = copyBytes(msg) - } - if len(hdr) > 0 { - hdr = copyBytes(hdr) - } - - // FIXME(dlc) - Could pool at this level? - sm := &StoreMsg{subj, nil, nil, make([]byte, 0, len(hdr)+len(msg)), seq, ts} - sm.buf = append(sm.buf, hdr...) - sm.buf = append(sm.buf, msg...) - if len(hdr) > 0 { - sm.hdr = sm.buf[:len(hdr)] - } - sm.msg = sm.buf[len(hdr):] - ms.msgs[seq] = sm - ms.state.Msgs++ - ms.state.Bytes += memStoreMsgSize(subj, hdr, msg) - ms.state.LastSeq = seq - ms.state.LastTime = now - - // Track per subject. - if len(subj) > 0 { - if ss != nil { - ss.Msgs++ - ss.Last = seq - ss.lastNeedsUpdate = false - // Check per subject limits. - if ms.maxp > 0 && ss.Msgs > uint64(ms.maxp) { - ms.enforcePerSubjectLimit(subj, ss) - } - } else { - ms.fss.Insert([]byte(subj), SimpleState{Msgs: 1, First: seq, Last: seq}) - } - } - - // Limits checks and enforcement. - ms.enforceMsgLimit() - ms.enforceBytesLimit() - - // Per-message TTL. - if ms.ttls != nil && ttl > 0 { - expires := time.Duration(ts) + (time.Second * time.Duration(ttl)) - ms.ttls.Add(seq, int64(expires)) - } - - // Check if we have and need the age expiration timer running. - switch { - case ms.ttls != nil && ttl > 0: - ms.resetAgeChk(0) - case ms.ageChk == nil && (ms.cfg.MaxAge > 0 || ms.ttls != nil): - ms.startAgeChk() - } - - return nil -} - -// StoreRawMsg stores a raw message with expected sequence number and timestamp. -func (ms *memStore) StoreRawMsg(subj string, hdr, msg []byte, seq uint64, ts, ttl int64) error { - ms.mu.Lock() - err := ms.storeRawMsg(subj, hdr, msg, seq, ts, ttl) - cb := ms.scb - // Check if first message timestamp requires expiry - // sooner than initial replica expiry timer set to MaxAge when initializing. - if !ms.receivedAny && ms.cfg.MaxAge != 0 && ts > 0 { - ms.receivedAny = true - // Calculate duration when the next expireMsgs should be called. - ms.resetAgeChk(int64(time.Millisecond) * 50) - } - ms.mu.Unlock() - - if err == nil && cb != nil { - cb(1, int64(memStoreMsgSize(subj, hdr, msg)), seq, subj) - } - - return err -} - -// Store stores a message. -func (ms *memStore) StoreMsg(subj string, hdr, msg []byte, ttl int64) (uint64, int64, error) { - ms.mu.Lock() - seq, ts := ms.state.LastSeq+1, time.Now().UnixNano() - err := ms.storeRawMsg(subj, hdr, msg, seq, ts, ttl) - cb := ms.scb - ms.mu.Unlock() - - if err != nil { - seq, ts = 0, 0 - } else if cb != nil { - cb(1, int64(memStoreMsgSize(subj, hdr, msg)), seq, subj) - } - - return seq, ts, err -} - -// SkipMsg will use the next sequence number but not store anything. -func (ms *memStore) SkipMsg() uint64 { - // Grab time. - now := time.Now().UTC() - - ms.mu.Lock() - seq := ms.state.LastSeq + 1 - ms.state.LastSeq = seq - ms.state.LastTime = now - if ms.state.Msgs == 0 { - ms.state.FirstSeq = seq + 1 - ms.state.FirstTime = now - } else { - ms.dmap.Insert(seq) - } - ms.mu.Unlock() - return seq -} - -// Skip multiple msgs. -func (ms *memStore) SkipMsgs(seq uint64, num uint64) error { - // Grab time. - now := time.Now().UTC() - - ms.mu.Lock() - defer ms.mu.Unlock() - - // Check sequence matches our last sequence. - if seq != ms.state.LastSeq+1 { - if seq > 0 { - return ErrSequenceMismatch - } - seq = ms.state.LastSeq + 1 - } - lseq := seq + num - 1 - - ms.state.LastSeq = lseq - ms.state.LastTime = now - if ms.state.Msgs == 0 { - ms.state.FirstSeq, ms.state.FirstTime = lseq+1, now - } else { - for ; seq <= lseq; seq++ { - ms.dmap.Insert(seq) - } - } - return nil -} - -// RegisterStorageUpdates registers a callback for updates to storage changes. -// It will present number of messages and bytes as a signed integer and an -// optional sequence number of the message if a single. -func (ms *memStore) RegisterStorageUpdates(cb StorageUpdateHandler) { - ms.mu.Lock() - ms.scb = cb - ms.mu.Unlock() -} - -// RegisterSubjectDeleteMarkerUpdates registers a callback for updates to new subject delete markers. -func (ms *memStore) RegisterSubjectDeleteMarkerUpdates(cb SubjectDeleteMarkerUpdateHandler) { - ms.mu.Lock() - ms.sdmcb = cb - ms.mu.Unlock() -} - -// GetSeqFromTime looks for the first sequence number that has the message -// with >= timestamp. -// FIXME(dlc) - inefficient. -func (ms *memStore) GetSeqFromTime(t time.Time) uint64 { - ts := t.UnixNano() - ms.mu.RLock() - defer ms.mu.RUnlock() - if len(ms.msgs) == 0 { - return ms.state.LastSeq + 1 - } - if ts <= ms.msgs[ms.state.FirstSeq].ts { - return ms.state.FirstSeq - } - // LastSeq is not guaranteed to be present since last does not go backwards. - var lmsg *StoreMsg - for lseq := ms.state.LastSeq; lseq > ms.state.FirstSeq; lseq-- { - if lmsg = ms.msgs[lseq]; lmsg != nil { - break - } - } - if lmsg == nil { - return ms.state.FirstSeq - } - - last := lmsg.ts - if ts == last { - return ms.state.LastSeq - } - if ts > last { - return ms.state.LastSeq + 1 - } - index := sort.Search(len(ms.msgs), func(i int) bool { - if msg := ms.msgs[ms.state.FirstSeq+uint64(i)]; msg != nil { - return msg.ts >= ts - } - return false - }) - return uint64(index) + ms.state.FirstSeq -} - -// FilteredState will return the SimpleState associated with the filtered subject and a proposed starting sequence. -func (ms *memStore) FilteredState(sseq uint64, subj string) SimpleState { - // This needs to be a write lock, as filteredStateLocked can - // mutate the per-subject state. - ms.mu.Lock() - defer ms.mu.Unlock() - - return ms.filteredStateLocked(sseq, subj, false) -} - -func (ms *memStore) filteredStateLocked(sseq uint64, filter string, lastPerSubject bool) SimpleState { - if sseq < ms.state.FirstSeq { - sseq = ms.state.FirstSeq - } - - // If past the end no results. - if sseq > ms.state.LastSeq { - return SimpleState{} - } - - if filter == _EMPTY_ { - filter = fwcs - } - isAll := filter == fwcs - - // First check if we can optimize this part. - // This means we want all and the starting sequence was before this block. - if isAll && sseq <= ms.state.FirstSeq { - total := ms.state.Msgs - if lastPerSubject { - total = uint64(ms.fss.Size()) - } - return SimpleState{ - Msgs: total, - First: ms.state.FirstSeq, - Last: ms.state.LastSeq, - } - } - - _tsa, _fsa := [32]string{}, [32]string{} - tsa, fsa := _tsa[:0], _fsa[:0] - wc := subjectHasWildcard(filter) - if wc { - fsa = tokenizeSubjectIntoSlice(fsa[:0], filter) - } - // 1. See if we match any subs from fss. - // 2. If we match and the sseq is past ss.Last then we can use meta only. - // 3. If we match we need to do a partial, break and clear any totals and do a full scan like num pending. - - isMatch := func(subj string) bool { - if isAll { - return true - } - if !wc { - return subj == filter - } - tsa = tokenizeSubjectIntoSlice(tsa[:0], subj) - return isSubsetMatchTokenized(tsa, fsa) - } - - var ss SimpleState - update := func(fss *SimpleState) { - msgs, first, last := fss.Msgs, fss.First, fss.Last - if lastPerSubject { - msgs, first = 1, last - } - ss.Msgs += msgs - if ss.First == 0 || first < ss.First { - ss.First = first - } - if last > ss.Last { - ss.Last = last - } - } - - var havePartial bool - var totalSkipped uint64 - // We will track start and end sequences as we go. - ms.fss.Match(stringToBytes(filter), func(subj []byte, fss *SimpleState) { - if fss.firstNeedsUpdate || fss.lastNeedsUpdate { - ms.recalculateForSubj(bytesToString(subj), fss) - } - if sseq <= fss.First { - update(fss) - } else if sseq <= fss.Last { - // We matched but it is a partial. - havePartial = true - // Don't break here, we will update to keep tracking last. - update(fss) - } else { - totalSkipped += fss.Msgs - } - }) - - // If we did not encounter any partials we can return here. - if !havePartial { - return ss - } - - // If we are here we need to scan the msgs. - // Capture first and last sequences for scan and then clear what we had. - first, last := ss.First, ss.Last - // To track if we decide to exclude we need to calculate first. - var needScanFirst bool - if first < sseq { - first = sseq - needScanFirst = true - } - - // Now we want to check if it is better to scan inclusive and recalculate that way - // or leave and scan exclusive and adjust our totals. - // ss.Last is always correct here. - toScan, toExclude := last-first, first-ms.state.FirstSeq+ms.state.LastSeq-ss.Last - var seen map[string]bool - if lastPerSubject { - seen = make(map[string]bool) - } - if toScan < toExclude { - ss.Msgs, ss.First = 0, 0 - - update := func(sm *StoreMsg) { - ss.Msgs++ - if ss.First == 0 { - ss.First = sm.seq - } - if seen != nil { - seen[sm.subj] = true - } - } - // Check if easier to just scan msgs vs the sequence range. - // This can happen with lots of interior deletes. - if last-first > uint64(len(ms.msgs)) { - for _, sm := range ms.msgs { - if sm.seq >= first && sm.seq <= last && !seen[sm.subj] && isMatch(sm.subj) { - update(sm) - } - } - } else { - for seq := first; seq <= last; seq++ { - if sm, ok := ms.msgs[seq]; ok && !seen[sm.subj] && isMatch(sm.subj) { - update(sm) - } - } - } - } else { - // We will adjust from the totals above by scanning what we need to exclude. - ss.First = first - ss.Msgs += totalSkipped - var adjust uint64 - var tss *SimpleState - - update := func(sm *StoreMsg) { - if lastPerSubject { - tss, _ = ms.fss.Find(stringToBytes(sm.subj)) - } - // If we are last per subject, make sure to only adjust if all messages are before our first. - if tss == nil || tss.Last < first { - adjust++ - } - if seen != nil { - seen[sm.subj] = true - } - } - // Check if easier to just scan msgs vs the sequence range. - if first-ms.state.FirstSeq > uint64(len(ms.msgs)) { - for _, sm := range ms.msgs { - if sm.seq < first && !seen[sm.subj] && isMatch(sm.subj) { - update(sm) - } - } - } else { - for seq := ms.state.FirstSeq; seq < first; seq++ { - if sm, ok := ms.msgs[seq]; ok && !seen[sm.subj] && isMatch(sm.subj) { - update(sm) - } - } - } - // Now do range at end. - for seq := last + 1; seq < ms.state.LastSeq; seq++ { - if sm, ok := ms.msgs[seq]; ok && !seen[sm.subj] && isMatch(sm.subj) { - adjust++ - if seen != nil { - seen[sm.subj] = true - } - } - } - ss.Msgs -= adjust - if needScanFirst { - // Check if easier to just scan msgs vs the sequence range. - // Since we will need to scan all of the msgs vs below where we break on the first match, - // we will only do so if a few orders of magnitude lower. - if last-first > 100*uint64(len(ms.msgs)) { - low := ms.state.LastSeq - for _, sm := range ms.msgs { - if sm.seq >= first && sm.seq < last && isMatch(sm.subj) { - if sm.seq < low { - low = sm.seq - } - } - } - if low < ms.state.LastSeq { - ss.First = low - } - } else { - for seq := first; seq < last; seq++ { - if sm, ok := ms.msgs[seq]; ok && isMatch(sm.subj) { - ss.First = seq - break - } - } - } - } - } - - return ss -} - -// SubjectsState returns a map of SimpleState for all matching subjects. -func (ms *memStore) SubjectsState(subject string) map[string]SimpleState { - // This needs to be a write lock, as we can mutate the per-subject state. - ms.mu.Lock() - defer ms.mu.Unlock() - - if ms.fss.Size() == 0 { - return nil - } - - if subject == _EMPTY_ { - subject = fwcs - } - - fss := make(map[string]SimpleState) - ms.fss.Match(stringToBytes(subject), func(subj []byte, ss *SimpleState) { - subjs := string(subj) - if ss.firstNeedsUpdate || ss.lastNeedsUpdate { - ms.recalculateForSubj(subjs, ss) - } - oss := fss[subjs] - if oss.First == 0 { // New - fss[subjs] = *ss - } else { - // Merge here. - oss.Last, oss.Msgs = ss.Last, oss.Msgs+ss.Msgs - fss[subjs] = oss - } - }) - return fss -} - -func (ms *memStore) MultiLastSeqs(filters []string, maxSeq uint64, maxAllowed int) ([]uint64, error) { - ms.mu.RLock() - defer ms.mu.RUnlock() - - if len(ms.msgs) == 0 { - return nil, nil - } - - // Implied last sequence. - if maxSeq == 0 { - maxSeq = ms.state.LastSeq - } - - //subs := make(map[string]*SimpleState) - seqs := make([]uint64, 0, 64) - seen := make(map[uint64]struct{}) - - addIfNotDupe := func(seq uint64) { - if _, ok := seen[seq]; !ok { - seqs = append(seqs, seq) - seen[seq] = struct{}{} - } - } - - for _, filter := range filters { - ms.fss.Match(stringToBytes(filter), func(subj []byte, ss *SimpleState) { - if ss.Last <= maxSeq { - addIfNotDupe(ss.Last) - } else if ss.Msgs > 1 { - // The last is greater than maxSeq. - s := bytesToString(subj) - for seq := maxSeq; seq > 0; seq-- { - if sm, ok := ms.msgs[seq]; ok && sm.subj == s { - addIfNotDupe(seq) - break - } - } - } - }) - // If maxAllowed was sepcified check that we will not exceed that. - if maxAllowed > 0 && len(seqs) > maxAllowed { - return nil, ErrTooManyResults - } - } - slices.Sort(seqs) - return seqs, nil -} - -// SubjectsTotals return message totals per subject. -func (ms *memStore) SubjectsTotals(filterSubject string) map[string]uint64 { - ms.mu.RLock() - defer ms.mu.RUnlock() - - if ms.fss.Size() == 0 { - return nil - } - - _tsa, _fsa := [32]string{}, [32]string{} - tsa, fsa := _tsa[:0], _fsa[:0] - fsa = tokenizeSubjectIntoSlice(fsa[:0], filterSubject) - isAll := filterSubject == _EMPTY_ || filterSubject == fwcs - - fst := make(map[string]uint64) - ms.fss.Match(stringToBytes(filterSubject), func(subj []byte, ss *SimpleState) { - subjs := string(subj) - if isAll { - fst[subjs] = ss.Msgs - } else { - if tsa = tokenizeSubjectIntoSlice(tsa[:0], subjs); isSubsetMatchTokenized(tsa, fsa) { - fst[subjs] = ss.Msgs - } - } - }) - return fst -} - -// NumPending will return the number of pending messages matching the filter subject starting at sequence. -func (ms *memStore) NumPending(sseq uint64, filter string, lastPerSubject bool) (total, validThrough uint64) { - // This needs to be a write lock, as filteredStateLocked can mutate the per-subject state. - ms.mu.Lock() - defer ms.mu.Unlock() - - ss := ms.filteredStateLocked(sseq, filter, lastPerSubject) - return ss.Msgs, ms.state.LastSeq -} - -// NumPending will return the number of pending messages matching any subject in the sublist starting at sequence. -func (ms *memStore) NumPendingMulti(sseq uint64, sl *Sublist, lastPerSubject bool) (total, validThrough uint64) { - if sl == nil { - return ms.NumPending(sseq, fwcs, lastPerSubject) - } - - // This needs to be a write lock, as we can mutate the per-subject state. - ms.mu.Lock() - defer ms.mu.Unlock() - - var ss SimpleState - if sseq < ms.state.FirstSeq { - sseq = ms.state.FirstSeq - } - // If past the end no results. - if sseq > ms.state.LastSeq { - return 0, ms.state.LastSeq - } - - update := func(fss *SimpleState) { - msgs, first, last := fss.Msgs, fss.First, fss.Last - if lastPerSubject { - msgs, first = 1, last - } - ss.Msgs += msgs - if ss.First == 0 || first < ss.First { - ss.First = first - } - if last > ss.Last { - ss.Last = last - } - } - - var havePartial bool - var totalSkipped uint64 - // We will track start and end sequences as we go. - IntersectStree[SimpleState](ms.fss, sl, func(subj []byte, fss *SimpleState) { - if fss.firstNeedsUpdate || fss.lastNeedsUpdate { - ms.recalculateForSubj(bytesToString(subj), fss) - } - if sseq <= fss.First { - update(fss) - } else if sseq <= fss.Last { - // We matched but it is a partial. - havePartial = true - // Don't break here, we will update to keep tracking last. - update(fss) - } else { - totalSkipped += fss.Msgs - } - }) - - // If we did not encounter any partials we can return here. - if !havePartial { - return ss.Msgs, ms.state.LastSeq - } - - // If we are here we need to scan the msgs. - // Capture first and last sequences for scan and then clear what we had. - first, last := ss.First, ss.Last - // To track if we decide to exclude we need to calculate first. - if first < sseq { - first = sseq - } - - // Now we want to check if it is better to scan inclusive and recalculate that way - // or leave and scan exclusive and adjust our totals. - // ss.Last is always correct here. - toScan, toExclude := last-first, first-ms.state.FirstSeq+ms.state.LastSeq-ss.Last - var seen map[string]bool - if lastPerSubject { - seen = make(map[string]bool) - } - if toScan < toExclude { - ss.Msgs, ss.First = 0, 0 - - update := func(sm *StoreMsg) { - ss.Msgs++ - if ss.First == 0 { - ss.First = sm.seq - } - if seen != nil { - seen[sm.subj] = true - } - } - // Check if easier to just scan msgs vs the sequence range. - // This can happen with lots of interior deletes. - if last-first > uint64(len(ms.msgs)) { - for _, sm := range ms.msgs { - if sm.seq >= first && sm.seq <= last && !seen[sm.subj] && sl.HasInterest(sm.subj) { - update(sm) - } - } - } else { - for seq := first; seq <= last; seq++ { - if sm, ok := ms.msgs[seq]; ok && !seen[sm.subj] && sl.HasInterest(sm.subj) { - update(sm) - } - } - } - } else { - // We will adjust from the totals above by scanning what we need to exclude. - ss.First = first - ss.Msgs += totalSkipped - var adjust uint64 - var tss *SimpleState - - update := func(sm *StoreMsg) { - if lastPerSubject { - tss, _ = ms.fss.Find(stringToBytes(sm.subj)) - } - // If we are last per subject, make sure to only adjust if all messages are before our first. - if tss == nil || tss.Last < first { - adjust++ - } - if seen != nil { - seen[sm.subj] = true - } - } - // Check if easier to just scan msgs vs the sequence range. - if first-ms.state.FirstSeq > uint64(len(ms.msgs)) { - for _, sm := range ms.msgs { - if sm.seq < first && !seen[sm.subj] && sl.HasInterest(sm.subj) { - update(sm) - } - } - } else { - for seq := ms.state.FirstSeq; seq < first; seq++ { - if sm, ok := ms.msgs[seq]; ok && !seen[sm.subj] && sl.HasInterest(sm.subj) { - update(sm) - } - } - } - // Now do range at end. - for seq := last + 1; seq < ms.state.LastSeq; seq++ { - if sm, ok := ms.msgs[seq]; ok && !seen[sm.subj] && sl.HasInterest(sm.subj) { - adjust++ - if seen != nil { - seen[sm.subj] = true - } - } - } - ss.Msgs -= adjust - } - - return ss.Msgs, ms.state.LastSeq -} - -// Will check the msg limit for this tracked subject. -// Lock should be held. -func (ms *memStore) enforcePerSubjectLimit(subj string, ss *SimpleState) { - if ms.maxp <= 0 { - return - } - for nmsgs := ss.Msgs; nmsgs > uint64(ms.maxp); nmsgs = ss.Msgs { - if ss.firstNeedsUpdate || ss.lastNeedsUpdate { - ms.recalculateForSubj(subj, ss) - } - if !ms.removeMsg(ss.First, false, _EMPTY_) { - break - } - } -} - -// Will check the msg limit and drop firstSeq msg if needed. -// Lock should be held. -func (ms *memStore) enforceMsgLimit() { - if ms.cfg.Discard != DiscardOld { - return - } - if ms.cfg.MaxMsgs <= 0 || ms.state.Msgs <= uint64(ms.cfg.MaxMsgs) { - return - } - for nmsgs := ms.state.Msgs; nmsgs > uint64(ms.cfg.MaxMsgs); nmsgs = ms.state.Msgs { - ms.deleteFirstMsgOrPanic() - } -} - -// Will check the bytes limit and drop msgs if needed. -// Lock should be held. -func (ms *memStore) enforceBytesLimit() { - if ms.cfg.Discard != DiscardOld { - return - } - if ms.cfg.MaxBytes <= 0 || ms.state.Bytes <= uint64(ms.cfg.MaxBytes) { - return - } - for bs := ms.state.Bytes; bs > uint64(ms.cfg.MaxBytes); bs = ms.state.Bytes { - ms.deleteFirstMsgOrPanic() - } -} - -// Will start the age check timer. -// Lock should be held. -func (ms *memStore) startAgeChk() { - if ms.ageChk != nil { - return - } - if ms.cfg.MaxAge != 0 || ms.ttls != nil { - ms.ageChk = time.AfterFunc(ms.cfg.MaxAge, ms.expireMsgs) - } -} - -// Lock should be held. -func (ms *memStore) resetAgeChk(delta int64) { - var next int64 = math.MaxInt64 - if ms.ttls != nil { - next = ms.ttls.GetNextExpiration(next) - } - - // If there's no MaxAge and there's nothing waiting to be expired then - // don't bother continuing. The next storeRawMsg() will wake us up if - // needs be. - if ms.cfg.MaxAge <= 0 && next == math.MaxInt64 { - clearTimer(&ms.ageChk) - return - } - - // Check to see if we should be firing sooner than MaxAge for an expiring TTL. - fireIn := ms.cfg.MaxAge - if next < math.MaxInt64 { - // Looks like there's a next expiration, use it either if there's no - // MaxAge set or if it looks to be sooner than MaxAge is. - if until := time.Until(time.Unix(0, next)); fireIn == 0 || until < fireIn { - fireIn = until - } - } - - // If not then look at the delta provided (usually gap to next age expiry). - if delta > 0 { - if fireIn == 0 || time.Duration(delta) < fireIn { - fireIn = time.Duration(delta) - } - } - - // Make sure we aren't firing too often either way, otherwise we can - // negatively impact stream ingest performance. - if fireIn < 250*time.Millisecond { - fireIn = 250 * time.Millisecond - } - - if ms.ageChk != nil { - ms.ageChk.Reset(fireIn) - } else { - ms.ageChk = time.AfterFunc(fireIn, ms.expireMsgs) - } -} - -// Lock should be held. -func (ms *memStore) cancelAgeChk() { - if ms.ageChk != nil { - ms.ageChk.Stop() - ms.ageChk = nil - } -} - -// Lock must be held so that nothing else can interleave and write a -// new message on this subject before we get the chance to write the -// delete marker. If the delete marker is written successfully then -// this function returns a callback func to call scb and sdmcb after -// the lock has been released. -func (ms *memStore) subjectDeleteMarkerIfNeeded(subj string, reason string) func() { - if ms.cfg.SubjectDeleteMarkerTTL <= 0 { - return nil - } - if _, ok := ms.fss.Find(stringToBytes(subj)); ok { - // There are still messages left with this subject, - // therefore it wasn't the last message deleted. - return nil - } - // Build the subject delete marker. If no TTL is specified then - // we'll default to 15 minutes — by that time every possible condition - // should have cleared (i.e. ordered consumer timeout, client timeouts, - // route/gateway interruptions, even device/client restarts etc). - ttl := int64(ms.cfg.SubjectDeleteMarkerTTL.Seconds()) - if ttl <= 0 { - return nil - } - var _hdr [128]byte - hdr := fmt.Appendf( - _hdr[:0], - "NATS/1.0\r\n%s: %s\r\n%s: %s\r\n%s: %d\r\n%s: %s\r\n\r\n\r\n", - JSMarkerReason, reason, - JSMessageTTL, time.Duration(ttl)*time.Second, - JSExpectedLastSubjSeq, 0, - JSExpectedLastSubjSeqSubj, subj, - ) - msg := &inMsg{ - subj: subj, - hdr: hdr, - } - sdmcb := ms.sdmcb - return func() { - if sdmcb != nil { - sdmcb(msg) - } - } -} - -// Memstore lock must be held. The caller should call the callback, if non-nil, -// after releasing the memstore lock. -func (ms *memStore) subjectDeleteMarkersAfterOperation(reason string) func() { - if ms.cfg.SubjectDeleteMarkerTTL <= 0 || len(ms.markers) == 0 { - return nil - } - cbs := make([]func(), 0, len(ms.markers)) - for _, subject := range ms.markers { - if cb := ms.subjectDeleteMarkerIfNeeded(subject, reason); cb != nil { - cbs = append(cbs, cb) - } - } - ms.markers = nil - return func() { - for _, cb := range cbs { - cb() - } - } -} - -// Will expire msgs that are too old. -func (ms *memStore) expireMsgs() { - var smv StoreMsg - var sm *StoreMsg - ms.mu.RLock() - maxAge := int64(ms.cfg.MaxAge) - minAge := time.Now().UnixNano() - maxAge - ms.mu.RUnlock() - - if maxAge > 0 { - var seq uint64 - for sm, seq, _ = ms.LoadNextMsg(fwcs, true, 0, &smv); sm != nil && sm.ts <= minAge; sm, seq, _ = ms.LoadNextMsg(fwcs, true, seq+1, &smv) { - if len(sm.hdr) > 0 { - if ttl, err := getMessageTTL(sm.hdr); err == nil && ttl < 0 { - // The message has a negative TTL, therefore it must "never expire". - minAge = time.Now().UnixNano() - maxAge - continue - } - } - ms.mu.Lock() - ms.removeMsg(seq, false, JSMarkerReasonMaxAge) - ms.mu.Unlock() - // Recalculate in case we are expiring a bunch. - minAge = time.Now().UnixNano() - maxAge - } - } - - ms.mu.Lock() - defer ms.mu.Unlock() - - // TODO: Not great that we're holding the lock here, but the timed hash wheel isn't thread-safe. - nextTTL := int64(math.MaxInt64) - if ms.ttls != nil { - ms.ttls.ExpireTasks(func(seq uint64, ts int64) { - ms.removeMsg(seq, false, _EMPTY_) - }) - if maxAge > 0 { - // Only check if we're expiring something in the next MaxAge interval, saves us a bit - // of work if MaxAge will beat us to the next expiry anyway. - nextTTL = ms.ttls.GetNextExpiration(time.Now().Add(time.Duration(maxAge)).UnixNano()) - } else { - nextTTL = ms.ttls.GetNextExpiration(math.MaxInt64) - } - } - - // Only cancel if no message left, not on potential lookup error that would result in sm == nil. - if ms.state.Msgs == 0 && nextTTL == math.MaxInt64 { - ms.cancelAgeChk() - } else { - if sm == nil { - ms.resetAgeChk(0) - } else { - ms.resetAgeChk(sm.ts - minAge) - } - } -} - -// PurgeEx will remove messages based on subject filters, sequence and number of messages to keep. -// Will return the number of purged messages. -func (ms *memStore) PurgeEx(subject string, sequence, keep uint64, _ /* noMarkers */ bool) (purged uint64, err error) { - // TODO: Don't write markers on purge until we have solved performance - // issues with them. - noMarkers := true - - if subject == _EMPTY_ || subject == fwcs { - if keep == 0 && sequence == 0 { - return ms.purge(0, noMarkers) - } - if sequence > 1 { - return ms.compact(sequence, noMarkers) - } else if keep > 0 { - ms.mu.RLock() - msgs, lseq := ms.state.Msgs, ms.state.LastSeq - ms.mu.RUnlock() - if keep >= msgs { - return 0, nil - } - return ms.compact(lseq-keep+1, noMarkers) - } - return 0, nil - - } - eq := compareFn(subject) - if ss := ms.FilteredState(1, subject); ss.Msgs > 0 { - if keep > 0 { - if keep >= ss.Msgs { - return 0, nil - } - ss.Msgs -= keep - } - last := ss.Last - if sequence > 1 { - last = sequence - 1 - } - ms.mu.Lock() - var removeReason string - if !noMarkers { - removeReason = JSMarkerReasonPurge - } - for seq := ss.First; seq <= last; seq++ { - if sm, ok := ms.msgs[seq]; ok && eq(sm.subj, subject) { - if ok := ms.removeMsg(sm.seq, false, removeReason); ok { - purged++ - if purged >= ss.Msgs { - break - } - } - } - } - ms.mu.Unlock() - } - return purged, nil -} - -// Purge will remove all messages from this store. -// Will return the number of purged messages. -func (ms *memStore) Purge() (uint64, error) { - ms.mu.RLock() - first := ms.state.LastSeq + 1 - ms.mu.RUnlock() - return ms.purge(first, false) -} - -func (ms *memStore) purge(fseq uint64, _ /* noMarkers */ bool) (uint64, error) { - // TODO: Don't write markers on purge until we have solved performance - // issues with them. - noMarkers := true - - ms.mu.Lock() - purged := uint64(len(ms.msgs)) - cb := ms.scb - bytes := int64(ms.state.Bytes) - if fseq < ms.state.LastSeq { - ms.mu.Unlock() - return 0, fmt.Errorf("partial purges not supported on memory store") - } - ms.state.FirstSeq = fseq - ms.state.LastSeq = fseq - 1 - ms.state.FirstTime = time.Time{} - ms.state.Bytes = 0 - ms.state.Msgs = 0 - ms.msgs = make(map[uint64]*StoreMsg) - // Subject delete markers if needed. - if !noMarkers && ms.cfg.SubjectDeleteMarkerTTL > 0 { - ms.fss.IterOrdered(func(bsubj []byte, ss *SimpleState) bool { - ms.markers = append(ms.markers, string(bsubj)) - return true - }) - } - ms.fss = stree.NewSubjectTree[SimpleState]() - sdmcb := ms.subjectDeleteMarkersAfterOperation(JSMarkerReasonPurge) - ms.mu.Unlock() - - if cb != nil { - cb(-int64(purged), -bytes, 0, _EMPTY_) - } - if sdmcb != nil { - sdmcb() - } - - return purged, nil -} - -// Compact will remove all messages from this store up to -// but not including the seq parameter. -// Will return the number of purged messages. -func (ms *memStore) Compact(seq uint64) (uint64, error) { - return ms.compact(seq, false) -} - -func (ms *memStore) compact(seq uint64, _ /* noMarkers */ bool) (uint64, error) { - // TODO: Don't write markers on compact until we have solved performance - // issues with them. - noMarkers := true - - if seq == 0 { - return ms.Purge() - } - - var purged, bytes uint64 - - ms.mu.Lock() - cb := ms.scb - if seq <= ms.state.LastSeq { - fseq := ms.state.FirstSeq - // Determine new first sequence. - for ; seq <= ms.state.LastSeq; seq++ { - if sm, ok := ms.msgs[seq]; ok { - ms.state.FirstSeq = seq - ms.state.FirstTime = time.Unix(0, sm.ts).UTC() - break - } - } - for seq := seq - 1; seq >= fseq; seq-- { - if sm := ms.msgs[seq]; sm != nil { - bytes += memStoreMsgSize(sm.subj, sm.hdr, sm.msg) - purged++ - ms.removeSeqPerSubject(sm.subj, seq, !noMarkers && ms.cfg.SubjectDeleteMarkerTTL > 0) - // Must delete message after updating per-subject info, to be consistent with file store. - delete(ms.msgs, seq) - } else if !ms.dmap.IsEmpty() { - ms.dmap.Delete(seq) - } - } - if purged > ms.state.Msgs { - purged = ms.state.Msgs - } - ms.state.Msgs -= purged - if bytes > ms.state.Bytes { - bytes = ms.state.Bytes - } - ms.state.Bytes -= bytes - } else { - // We are compacting past the end of our range. Do purge and set sequences correctly - // such that the next message placed will have seq. - purged = uint64(len(ms.msgs)) - bytes = ms.state.Bytes - ms.state.Bytes = 0 - ms.state.Msgs = 0 - ms.state.FirstSeq = seq - ms.state.FirstTime = time.Time{} - ms.state.LastSeq = seq - 1 - // Subject delete markers if needed. - if !noMarkers && ms.cfg.SubjectDeleteMarkerTTL > 0 { - ms.fss.IterOrdered(func(bsubj []byte, ss *SimpleState) bool { - ms.markers = append(ms.markers, string(bsubj)) - return true - }) - } - // Reset msgs, fss and dmap. - ms.msgs = make(map[uint64]*StoreMsg) - ms.fss = stree.NewSubjectTree[SimpleState]() - ms.dmap.Empty() - } - // Subject delete markers if needed. - sdmcb := ms.subjectDeleteMarkersAfterOperation(JSMarkerReasonPurge) - ms.mu.Unlock() - - if cb != nil { - cb(-int64(purged), -int64(bytes), 0, _EMPTY_) - } - if sdmcb != nil { - sdmcb() - } - - return purged, nil -} - -// Will completely reset our store. -func (ms *memStore) reset() error { - - ms.mu.Lock() - var purged, bytes uint64 - cb := ms.scb - if cb != nil { - for _, sm := range ms.msgs { - purged++ - bytes += memStoreMsgSize(sm.subj, sm.hdr, sm.msg) - } - } - - // Reset - ms.state.FirstSeq = 0 - ms.state.FirstTime = time.Time{} - ms.state.LastSeq = 0 - ms.state.LastTime = time.Now().UTC() - // Update msgs and bytes. - ms.state.Msgs = 0 - ms.state.Bytes = 0 - // Reset msgs, fss and dmap. - ms.msgs = make(map[uint64]*StoreMsg) - ms.fss = stree.NewSubjectTree[SimpleState]() - ms.dmap.Empty() - - ms.mu.Unlock() - - if cb != nil { - cb(-int64(purged), -int64(bytes), 0, _EMPTY_) - } - - return nil -} - -// Truncate will truncate a stream store up to seq. Sequence needs to be valid. -func (ms *memStore) Truncate(seq uint64) error { - // Check for request to reset. - if seq == 0 { - return ms.reset() - } - - var purged, bytes uint64 - - ms.mu.Lock() - lsm, ok := ms.msgs[seq] - if !ok { - ms.mu.Unlock() - return ErrInvalidSequence - } - - for i := ms.state.LastSeq; i > seq; i-- { - if sm := ms.msgs[i]; sm != nil { - purged++ - bytes += memStoreMsgSize(sm.subj, sm.hdr, sm.msg) - ms.removeSeqPerSubject(sm.subj, i, false) - // Must delete message after updating per-subject info, to be consistent with file store. - delete(ms.msgs, i) - } else if !ms.dmap.IsEmpty() { - ms.dmap.Delete(i) - } - } - // Reset last. - ms.state.LastSeq = lsm.seq - ms.state.LastTime = time.Unix(0, lsm.ts).UTC() - // Update msgs and bytes. - if purged > ms.state.Msgs { - purged = ms.state.Msgs - } - ms.state.Msgs -= purged - if bytes > ms.state.Bytes { - bytes = ms.state.Bytes - } - ms.state.Bytes -= bytes - - cb := ms.scb - ms.mu.Unlock() - - if cb != nil { - cb(-int64(purged), -int64(bytes), 0, _EMPTY_) - } - - return nil -} - -func (ms *memStore) deleteFirstMsgOrPanic() { - if !ms.deleteFirstMsg() { - panic("jetstream memstore has inconsistent state, can't find first seq msg") - } -} - -func (ms *memStore) deleteFirstMsg() bool { - // TODO: Currently no markers for these types of limits (max msgs or max bytes) - return ms.removeMsg(ms.state.FirstSeq, false, _EMPTY_) -} - -// LoadMsg will lookup the message by sequence number and return it if found. -func (ms *memStore) LoadMsg(seq uint64, smp *StoreMsg) (*StoreMsg, error) { - ms.mu.RLock() - sm, ok := ms.msgs[seq] - last := ms.state.LastSeq - ms.mu.RUnlock() - - if !ok || sm == nil { - var err = ErrStoreEOF - if seq <= last { - err = ErrStoreMsgNotFound - } - return nil, err - } - - if smp == nil { - smp = new(StoreMsg) - } - sm.copy(smp) - return smp, nil -} - -// LoadLastMsg will return the last message we have that matches a given subject. -// The subject can be a wildcard. -func (ms *memStore) LoadLastMsg(subject string, smp *StoreMsg) (*StoreMsg, error) { - var sm *StoreMsg - var ok bool - - // This needs to be a write lock, as filteredStateLocked can - // mutate the per-subject state. - ms.mu.Lock() - defer ms.mu.Unlock() - - if subject == _EMPTY_ || subject == fwcs { - sm, ok = ms.msgs[ms.state.LastSeq] - } else if subjectIsLiteral(subject) { - var ss *SimpleState - if ss, ok = ms.fss.Find(stringToBytes(subject)); ok && ss.Msgs > 0 { - sm, ok = ms.msgs[ss.Last] - } - } else if ss := ms.filteredStateLocked(1, subject, true); ss.Msgs > 0 { - sm, ok = ms.msgs[ss.Last] - } - if !ok || sm == nil { - return nil, ErrStoreMsgNotFound - } - - if smp == nil { - smp = new(StoreMsg) - } - sm.copy(smp) - return smp, nil -} - -// LoadNextMsgMulti will find the next message matching any entry in the sublist. -func (ms *memStore) LoadNextMsgMulti(sl *Sublist, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error) { - // TODO(dlc) - for now simple linear walk to get started. - ms.mu.RLock() - defer ms.mu.RUnlock() - - if start < ms.state.FirstSeq { - start = ms.state.FirstSeq - } - - // If past the end no results. - if start > ms.state.LastSeq || ms.state.Msgs == 0 { - return nil, ms.state.LastSeq, ErrStoreEOF - } - - // Initial setup. - fseq, lseq := start, ms.state.LastSeq - - for nseq := fseq; nseq <= lseq; nseq++ { - sm, ok := ms.msgs[nseq] - if !ok { - continue - } - if sl.HasInterest(sm.subj) { - if smp == nil { - smp = new(StoreMsg) - } - sm.copy(smp) - return smp, nseq, nil - } - } - return nil, ms.state.LastSeq, ErrStoreEOF -} - -// LoadNextMsg will find the next message matching the filter subject starting at the start sequence. -// The filter subject can be a wildcard. -func (ms *memStore) LoadNextMsg(filter string, wc bool, start uint64, smp *StoreMsg) (*StoreMsg, uint64, error) { - ms.mu.Lock() - defer ms.mu.Unlock() - - if start < ms.state.FirstSeq { - start = ms.state.FirstSeq - } - - // If past the end no results. - if start > ms.state.LastSeq || ms.state.Msgs == 0 { - return nil, ms.state.LastSeq, ErrStoreEOF - } - - if filter == _EMPTY_ { - filter = fwcs - } - isAll := filter == fwcs - - // Skip scan of ms.fss if number of messages in the block are less than - // 1/2 the number of subjects in ms.fss. Or we have a wc and lots of fss entries. - const linearScanMaxFSS = 256 - doLinearScan := isAll || 2*int(ms.state.LastSeq-start) < ms.fss.Size() || (wc && ms.fss.Size() > linearScanMaxFSS) - - // Initial setup. - fseq, lseq := start, ms.state.LastSeq - - if !doLinearScan { - subs := []string{filter} - if wc || isAll { - subs = subs[:0] - ms.fss.Match(stringToBytes(filter), func(subj []byte, val *SimpleState) { - subs = append(subs, string(subj)) - }) - } - fseq, lseq = ms.state.LastSeq, uint64(0) - for _, subj := range subs { - ss, ok := ms.fss.Find(stringToBytes(subj)) - if !ok { - continue - } - if ss.firstNeedsUpdate || ss.lastNeedsUpdate { - ms.recalculateForSubj(subj, ss) - } - if ss.First < fseq { - fseq = ss.First - } - if ss.Last > lseq { - lseq = ss.Last - } - } - if fseq < start { - fseq = start - } - } - - eq := subjectsEqual - if wc { - eq = subjectIsSubsetMatch - } - - for nseq := fseq; nseq <= lseq; nseq++ { - if sm, ok := ms.msgs[nseq]; ok && (isAll || eq(sm.subj, filter)) { - if smp == nil { - smp = new(StoreMsg) - } - sm.copy(smp) - return smp, nseq, nil - } - } - return nil, ms.state.LastSeq, ErrStoreEOF -} - -// Will load the next non-deleted msg starting at the start sequence and walking backwards. -func (ms *memStore) LoadPrevMsg(start uint64, smp *StoreMsg) (sm *StoreMsg, err error) { - ms.mu.RLock() - defer ms.mu.RUnlock() - - if ms.msgs == nil { - return nil, ErrStoreClosed - } - if ms.state.Msgs == 0 || start < ms.state.FirstSeq { - return nil, ErrStoreEOF - } - if start > ms.state.LastSeq { - start = ms.state.LastSeq - } - - for seq := start; seq >= ms.state.FirstSeq; seq-- { - if sm, ok := ms.msgs[seq]; ok { - if smp == nil { - smp = new(StoreMsg) - } - sm.copy(smp) - return smp, nil - } - } - return nil, ErrStoreEOF -} - -// RemoveMsg will remove the message from this store. -// Will return the number of bytes removed. -func (ms *memStore) RemoveMsg(seq uint64) (bool, error) { - ms.mu.Lock() - // TODO: Don't write markers on removes via the API yet, only via limits. - removed := ms.removeMsg(seq, false, _EMPTY_) - ms.mu.Unlock() - return removed, nil -} - -// EraseMsg will remove the message and rewrite its contents. -func (ms *memStore) EraseMsg(seq uint64) (bool, error) { - ms.mu.Lock() - // TODO: Don't write markers on removes via the API yet, only via limits. - removed := ms.removeMsg(seq, true, _EMPTY_) - ms.mu.Unlock() - return removed, nil -} - -// Performs logic to update first sequence number. -// Lock should be held. -func (ms *memStore) updateFirstSeq(seq uint64) { - if seq != ms.state.FirstSeq { - // Interior delete. - return - } - var nsm *StoreMsg - var ok bool - for nseq := ms.state.FirstSeq + 1; nseq <= ms.state.LastSeq; nseq++ { - if nsm, ok = ms.msgs[nseq]; ok { - break - } - } - oldFirst := ms.state.FirstSeq - if nsm != nil { - ms.state.FirstSeq = nsm.seq - ms.state.FirstTime = time.Unix(0, nsm.ts).UTC() - } else { - // Like purge. - ms.state.FirstSeq = ms.state.LastSeq + 1 - ms.state.FirstTime = time.Time{} - } - - if oldFirst == ms.state.FirstSeq-1 { - ms.dmap.Delete(oldFirst) - } else { - for seq := oldFirst; seq < ms.state.FirstSeq; seq++ { - ms.dmap.Delete(seq) - } - } -} - -// Remove a seq from the fss and select new first. -// Lock should be held. -func (ms *memStore) removeSeqPerSubject(subj string, seq uint64, marker bool) bool { - ss, ok := ms.fss.Find(stringToBytes(subj)) - if !ok { - return false - } - if ss.Msgs == 1 { - ms.fss.Delete(stringToBytes(subj)) - if marker { - ms.markers = append(ms.markers, subj) - } - return true - } - ss.Msgs-- - - // Only one left - if ss.Msgs == 1 { - if !ss.lastNeedsUpdate && seq != ss.Last { - ss.First = ss.Last - ss.firstNeedsUpdate = false - return false - } - if !ss.firstNeedsUpdate && seq != ss.First { - ss.Last = ss.First - ss.lastNeedsUpdate = false - return false - } - } - - // We can lazily calculate the first/last sequence when needed. - ss.firstNeedsUpdate = seq == ss.First || ss.firstNeedsUpdate - ss.lastNeedsUpdate = seq == ss.Last || ss.lastNeedsUpdate - - return false -} - -// Will recalculate the first and/or last sequence for this subject. -// Lock should be held. -func (ms *memStore) recalculateForSubj(subj string, ss *SimpleState) { - if ss.firstNeedsUpdate { - tseq := ss.First + 1 - if tseq < ms.state.FirstSeq { - tseq = ms.state.FirstSeq - } - for ; tseq <= ss.Last; tseq++ { - if sm := ms.msgs[tseq]; sm != nil && sm.subj == subj { - ss.First = tseq - ss.firstNeedsUpdate = false - if ss.Msgs == 1 { - ss.Last = tseq - ss.lastNeedsUpdate = false - return - } - break - } - } - } - if ss.lastNeedsUpdate { - tseq := ss.Last - 1 - if tseq > ms.state.LastSeq { - tseq = ms.state.LastSeq - } - for ; tseq >= ss.First; tseq-- { - if sm := ms.msgs[tseq]; sm != nil && sm.subj == subj { - ss.Last = tseq - ss.lastNeedsUpdate = false - if ss.Msgs == 1 { - ss.First = tseq - ss.firstNeedsUpdate = false - } - return - } - } - } -} - -// Removes the message referenced by seq. -// Lock should be held. -func (ms *memStore) removeMsg(seq uint64, secure bool, marker string) bool { - var ss uint64 - sm, ok := ms.msgs[seq] - if !ok { - return false - } - - ss = memStoreMsgSize(sm.subj, sm.hdr, sm.msg) - - if ms.state.Msgs > 0 { - ms.state.Msgs-- - if ss > ms.state.Bytes { - ss = ms.state.Bytes - } - ms.state.Bytes -= ss - } - ms.dmap.Insert(seq) - ms.updateFirstSeq(seq) - - if secure { - if len(sm.hdr) > 0 { - sm.hdr = make([]byte, len(sm.hdr)) - crand.Read(sm.hdr) - } - if len(sm.msg) > 0 { - sm.msg = make([]byte, len(sm.msg)) - crand.Read(sm.msg) - } - sm.seq, sm.ts = 0, 0 - } - - // Remove any per subject tracking. - needMarker := marker != _EMPTY_ && ms.cfg.SubjectDeleteMarkerTTL > 0 && len(getHeader(JSMarkerReason, sm.hdr)) == 0 - wasLast := ms.removeSeqPerSubject(sm.subj, seq, needMarker) - - // Must delete message after updating per-subject info, to be consistent with file store. - delete(ms.msgs, seq) - - // If the deleted message was itself a delete marker then - // don't write out more of them or we'll churn endlessly. - var sdmcb func() - if needMarker && wasLast { - sdmcb = ms.subjectDeleteMarkersAfterOperation(marker) - } - - if ms.scb != nil || sdmcb != nil { - // We do not want to hold any locks here. - ms.mu.Unlock() - if ms.scb != nil { - delta := int64(ss) - ms.scb(-1, -delta, seq, sm.subj) - } - if sdmcb != nil { - sdmcb() - } - ms.mu.Lock() - } - - return ok -} - -// Type returns the type of the underlying store. -func (ms *memStore) Type() StorageType { - return MemoryStorage -} - -// FastState will fill in state with only the following. -// Msgs, Bytes, First and Last Sequence and Time and NumDeleted. -func (ms *memStore) FastState(state *StreamState) { - ms.mu.RLock() - state.Msgs = ms.state.Msgs - state.Bytes = ms.state.Bytes - state.FirstSeq = ms.state.FirstSeq - state.FirstTime = ms.state.FirstTime - state.LastSeq = ms.state.LastSeq - state.LastTime = ms.state.LastTime - if state.LastSeq > state.FirstSeq { - state.NumDeleted = int((state.LastSeq - state.FirstSeq + 1) - state.Msgs) - if state.NumDeleted < 0 { - state.NumDeleted = 0 - } - } - state.Consumers = ms.consumers - state.NumSubjects = ms.fss.Size() - ms.mu.RUnlock() -} - -func (ms *memStore) State() StreamState { - ms.mu.RLock() - defer ms.mu.RUnlock() - - state := ms.state - state.Consumers = ms.consumers - state.NumSubjects = ms.fss.Size() - state.Deleted = nil - - // Calculate interior delete details. - if numDeleted := int((state.LastSeq - state.FirstSeq + 1) - state.Msgs); numDeleted > 0 { - state.Deleted = make([]uint64, 0, numDeleted) - fseq, lseq := state.FirstSeq, state.LastSeq - ms.dmap.Range(func(seq uint64) bool { - if seq < fseq || seq > lseq { - ms.dmap.Delete(seq) - } else { - state.Deleted = append(state.Deleted, seq) - } - return true - }) - } - if len(state.Deleted) > 0 { - state.NumDeleted = len(state.Deleted) - } - - return state -} - -func (ms *memStore) Utilization() (total, reported uint64, err error) { - ms.mu.RLock() - defer ms.mu.RUnlock() - return ms.state.Bytes, ms.state.Bytes, nil -} - -func memStoreMsgSize(subj string, hdr, msg []byte) uint64 { - return uint64(len(subj) + len(hdr) + len(msg) + 16) // 8*2 for seq + age -} - -// Delete is same as Stop for memory store. -func (ms *memStore) Delete() error { - return ms.Stop() -} - -func (ms *memStore) Stop() error { - // These can't come back, so stop is same as Delete. - ms.Purge() - ms.mu.Lock() - if ms.ageChk != nil { - ms.ageChk.Stop() - ms.ageChk = nil - } - ms.msgs = nil - ms.mu.Unlock() - return nil -} - -func (ms *memStore) isClosed() bool { - ms.mu.RLock() - defer ms.mu.RUnlock() - return ms.msgs == nil -} - -type consumerMemStore struct { - mu sync.Mutex - ms StreamStore - cfg ConsumerConfig - state ConsumerState - closed bool -} - -func (ms *memStore) ConsumerStore(name string, cfg *ConsumerConfig) (ConsumerStore, error) { - if ms == nil { - return nil, fmt.Errorf("memstore is nil") - } - if ms.isClosed() { - return nil, ErrStoreClosed - } - if cfg == nil || name == _EMPTY_ { - return nil, fmt.Errorf("bad consumer config") - } - o := &consumerMemStore{ms: ms, cfg: *cfg} - ms.AddConsumer(o) - return o, nil -} - -func (ms *memStore) AddConsumer(o ConsumerStore) error { - ms.mu.Lock() - ms.consumers++ - ms.mu.Unlock() - return nil -} - -func (ms *memStore) RemoveConsumer(o ConsumerStore) error { - ms.mu.Lock() - if ms.consumers > 0 { - ms.consumers-- - } - ms.mu.Unlock() - return nil -} - -func (ms *memStore) Snapshot(_ time.Duration, _, _ bool) (*SnapshotResult, error) { - return nil, fmt.Errorf("no impl") -} - -// Binary encoded state snapshot, >= v2.10 server. -func (ms *memStore) EncodedStreamState(failed uint64) ([]byte, error) { - ms.mu.RLock() - defer ms.mu.RUnlock() - - // Quick calculate num deleted. - numDeleted := int((ms.state.LastSeq - ms.state.FirstSeq + 1) - ms.state.Msgs) - if numDeleted < 0 { - numDeleted = 0 - } - - // Encoded is Msgs, Bytes, FirstSeq, LastSeq, Failed, NumDeleted and optional DeletedBlocks - var buf [1024]byte - buf[0], buf[1] = streamStateMagic, streamStateVersion - n := hdrLen - n += binary.PutUvarint(buf[n:], ms.state.Msgs) - n += binary.PutUvarint(buf[n:], ms.state.Bytes) - n += binary.PutUvarint(buf[n:], ms.state.FirstSeq) - n += binary.PutUvarint(buf[n:], ms.state.LastSeq) - n += binary.PutUvarint(buf[n:], failed) - n += binary.PutUvarint(buf[n:], uint64(numDeleted)) - - b := buf[0:n] - - if numDeleted > 0 { - buf, err := ms.dmap.Encode(nil) - if err != nil { - return nil, err - } - b = append(b, buf...) - } - - return b, nil -} - -// SyncDeleted will make sure this stream has same deleted state as dbs. -func (ms *memStore) SyncDeleted(dbs DeleteBlocks) { - ms.mu.Lock() - defer ms.mu.Unlock() - - // For now we share one dmap, so if we have one entry here check if states are the same. - // Note this will work for any DeleteBlock type, but we expect this to be a dmap too. - if len(dbs) == 1 { - min, max, num := ms.dmap.State() - if pmin, pmax, pnum := dbs[0].State(); pmin == min && pmax == max && pnum == num { - return - } - } - lseq := ms.state.LastSeq - for _, db := range dbs { - // Skip if beyond our current state. - if first, _, _ := db.State(); first > lseq { - continue - } - db.Range(func(seq uint64) bool { - ms.removeMsg(seq, false, _EMPTY_) - return true - }) - } -} - -func (o *consumerMemStore) Update(state *ConsumerState) error { - // Sanity checks. - if state.AckFloor.Consumer > state.Delivered.Consumer { - return fmt.Errorf("bad ack floor for consumer") - } - if state.AckFloor.Stream > state.Delivered.Stream { - return fmt.Errorf("bad ack floor for stream") - } - - // Copy to our state. - var pending map[uint64]*Pending - var redelivered map[uint64]uint64 - if len(state.Pending) > 0 { - pending = make(map[uint64]*Pending, len(state.Pending)) - for seq, p := range state.Pending { - pending[seq] = &Pending{p.Sequence, p.Timestamp} - if seq <= state.AckFloor.Stream || seq > state.Delivered.Stream { - return fmt.Errorf("bad pending entry, sequence [%d] out of range", seq) - } - } - } - if len(state.Redelivered) > 0 { - redelivered = make(map[uint64]uint64, len(state.Redelivered)) - for seq, dc := range state.Redelivered { - redelivered[seq] = dc - } - } - - // Replace our state. - o.mu.Lock() - defer o.mu.Unlock() - - // Check to see if this is an outdated update. - if state.Delivered.Consumer < o.state.Delivered.Consumer || state.AckFloor.Stream < o.state.AckFloor.Stream { - return fmt.Errorf("old update ignored") - } - - o.state.Delivered = state.Delivered - o.state.AckFloor = state.AckFloor - o.state.Pending = pending - o.state.Redelivered = redelivered - - return nil -} - -// SetStarting sets our starting stream sequence. -func (o *consumerMemStore) SetStarting(sseq uint64) error { - o.mu.Lock() - o.state.Delivered.Stream = sseq - o.mu.Unlock() - return nil -} - -// UpdateStarting updates our starting stream sequence. -func (o *consumerMemStore) UpdateStarting(sseq uint64) { - o.mu.Lock() - defer o.mu.Unlock() - - if sseq > o.state.Delivered.Stream { - o.state.Delivered.Stream = sseq - // For AckNone just update delivered and ackfloor at the same time. - if o.cfg.AckPolicy == AckNone { - o.state.AckFloor.Stream = sseq - } - } -} - -// HasState returns if this store has a recorded state. -func (o *consumerMemStore) HasState() bool { - o.mu.Lock() - defer o.mu.Unlock() - // We have a running state. - return o.state.Delivered.Consumer != 0 || o.state.Delivered.Stream != 0 -} - -func (o *consumerMemStore) UpdateDelivered(dseq, sseq, dc uint64, ts int64) error { - o.mu.Lock() - defer o.mu.Unlock() - - if dc != 1 && o.cfg.AckPolicy == AckNone { - return ErrNoAckPolicy - } - - // On restarts the old leader may get a replay from the raft logs that are old. - if dseq <= o.state.AckFloor.Consumer { - return nil - } - - // See if we expect an ack for this. - if o.cfg.AckPolicy != AckNone { - // Need to create pending records here. - if o.state.Pending == nil { - o.state.Pending = make(map[uint64]*Pending) - } - var p *Pending - // Check for an update to a message already delivered. - if sseq <= o.state.Delivered.Stream { - if p = o.state.Pending[sseq]; p != nil { - // Do not update p.Sequence, that should be the original delivery sequence. - p.Timestamp = ts - } - } else { - // Add to pending. - o.state.Pending[sseq] = &Pending{dseq, ts} - } - // Update delivered as needed. - if dseq > o.state.Delivered.Consumer { - o.state.Delivered.Consumer = dseq - } - if sseq > o.state.Delivered.Stream { - o.state.Delivered.Stream = sseq - } - - if dc > 1 { - if maxdc := uint64(o.cfg.MaxDeliver); maxdc > 0 && dc > maxdc { - // Make sure to remove from pending. - delete(o.state.Pending, sseq) - } - if o.state.Redelivered == nil { - o.state.Redelivered = make(map[uint64]uint64) - } - // Only update if greater than what we already have. - if o.state.Redelivered[sseq] < dc-1 { - o.state.Redelivered[sseq] = dc - 1 - } - } - } else { - // For AckNone just update delivered and ackfloor at the same time. - if dseq > o.state.Delivered.Consumer { - o.state.Delivered.Consumer = dseq - o.state.AckFloor.Consumer = dseq - } - if sseq > o.state.Delivered.Stream { - o.state.Delivered.Stream = sseq - o.state.AckFloor.Stream = sseq - } - } - - return nil -} - -func (o *consumerMemStore) UpdateAcks(dseq, sseq uint64) error { - o.mu.Lock() - defer o.mu.Unlock() - - if o.cfg.AckPolicy == AckNone { - return ErrNoAckPolicy - } - - // On restarts the old leader may get a replay from the raft logs that are old. - if dseq <= o.state.AckFloor.Consumer { - return nil - } - - if len(o.state.Pending) == 0 || o.state.Pending[sseq] == nil { - delete(o.state.Redelivered, sseq) - return ErrStoreMsgNotFound - } - - // Check for AckAll here. - if o.cfg.AckPolicy == AckAll { - sgap := sseq - o.state.AckFloor.Stream - o.state.AckFloor.Consumer = dseq - o.state.AckFloor.Stream = sseq - if sgap > uint64(len(o.state.Pending)) { - for seq := range o.state.Pending { - if seq <= sseq { - delete(o.state.Pending, seq) - delete(o.state.Redelivered, seq) - } - } - } else { - for seq := sseq; seq > sseq-sgap && len(o.state.Pending) > 0; seq-- { - delete(o.state.Pending, seq) - delete(o.state.Redelivered, seq) - } - } - return nil - } - - // AckExplicit - - // First delete from our pending state. - if p, ok := o.state.Pending[sseq]; ok { - delete(o.state.Pending, sseq) - if dseq > p.Sequence && p.Sequence > 0 { - dseq = p.Sequence // Use the original. - } - } - - if len(o.state.Pending) == 0 { - o.state.AckFloor.Consumer = o.state.Delivered.Consumer - o.state.AckFloor.Stream = o.state.Delivered.Stream - } else if dseq == o.state.AckFloor.Consumer+1 { - o.state.AckFloor.Consumer = dseq - o.state.AckFloor.Stream = sseq - - if o.state.Delivered.Consumer > dseq { - for ss := sseq + 1; ss <= o.state.Delivered.Stream; ss++ { - if p, ok := o.state.Pending[ss]; ok { - if p.Sequence > 0 { - o.state.AckFloor.Consumer = p.Sequence - 1 - o.state.AckFloor.Stream = ss - 1 - } - break - } - } - } - } - // We do these regardless. - delete(o.state.Redelivered, sseq) - - return nil -} - -func (o *consumerMemStore) UpdateConfig(cfg *ConsumerConfig) error { - o.mu.Lock() - defer o.mu.Unlock() - - // This is mostly unchecked here. We are assuming the upper layers have done sanity checking. - o.cfg = *cfg - return nil -} - -func (o *consumerMemStore) Stop() error { - o.mu.Lock() - o.closed = true - ms := o.ms - o.mu.Unlock() - ms.RemoveConsumer(o) - return nil -} - -func (o *consumerMemStore) Delete() error { - return o.Stop() -} - -func (o *consumerMemStore) StreamDelete() error { - return o.Stop() -} - -func (o *consumerMemStore) State() (*ConsumerState, error) { - return o.stateWithCopy(true) -} - -// This will not copy pending or redelivered, so should only be done under the -// consumer owner's lock. -func (o *consumerMemStore) BorrowState() (*ConsumerState, error) { - return o.stateWithCopy(false) -} - -func (o *consumerMemStore) stateWithCopy(doCopy bool) (*ConsumerState, error) { - o.mu.Lock() - defer o.mu.Unlock() - - if o.closed { - return nil, ErrStoreClosed - } - - state := &ConsumerState{} - - state.Delivered = o.state.Delivered - state.AckFloor = o.state.AckFloor - if len(o.state.Pending) > 0 { - if doCopy { - state.Pending = o.copyPending() - } else { - state.Pending = o.state.Pending - } - } - if len(o.state.Redelivered) > 0 { - if doCopy { - state.Redelivered = o.copyRedelivered() - } else { - state.Redelivered = o.state.Redelivered - } - } - return state, nil -} - -// EncodedState for this consumer store. -func (o *consumerMemStore) EncodedState() ([]byte, error) { - o.mu.Lock() - defer o.mu.Unlock() - - if o.closed { - return nil, ErrStoreClosed - } - - return encodeConsumerState(&o.state), nil -} - -func (o *consumerMemStore) copyPending() map[uint64]*Pending { - pending := make(map[uint64]*Pending, len(o.state.Pending)) - for seq, p := range o.state.Pending { - pending[seq] = &Pending{p.Sequence, p.Timestamp} - } - return pending -} - -func (o *consumerMemStore) copyRedelivered() map[uint64]uint64 { - redelivered := make(map[uint64]uint64, len(o.state.Redelivered)) - for seq, dc := range o.state.Redelivered { - redelivered[seq] = dc - } - return redelivered -} - -// Type returns the type of the underlying store. -func (o *consumerMemStore) Type() StorageType { return MemoryStorage } - -// Templates -type templateMemStore struct{} - -func newTemplateMemStore() *templateMemStore { - return &templateMemStore{} -} - -// No-ops for memstore. -func (ts *templateMemStore) Store(t *streamTemplate) error { return nil } -func (ts *templateMemStore) Delete(t *streamTemplate) error { return nil } diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/monitor.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/monitor.go deleted file mode 100644 index 41270c73..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/monitor.go +++ /dev/null @@ -1,4014 +0,0 @@ -// Copyright 2013-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "cmp" - "crypto/sha256" - "crypto/tls" - "crypto/x509" - "encoding/hex" - "encoding/json" - "expvar" - "fmt" - "net" - "net/http" - "net/url" - "os" - "path/filepath" - "runtime" - "runtime/pprof" - "slices" - "sort" - "strconv" - "strings" - "sync/atomic" - "time" - - "github.com/nats-io/jwt/v2" - "github.com/nats-io/nats-server/v2/server/pse" -) - -// Connz represents detailed information on current client connections. -type Connz struct { - ID string `json:"server_id"` - Now time.Time `json:"now"` - NumConns int `json:"num_connections"` - Total int `json:"total"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Conns []*ConnInfo `json:"connections"` -} - -// ConnzOptions are the options passed to Connz() -type ConnzOptions struct { - // Sort indicates how the results will be sorted. Check SortOpt for possible values. - // Only the sort by connection ID (ByCid) is ascending, all others are descending. - Sort SortOpt `json:"sort"` - - // Username indicates if user names should be included in the results. - Username bool `json:"auth"` - - // Subscriptions indicates if subscriptions should be included in the results. - Subscriptions bool `json:"subscriptions"` - - // SubscriptionsDetail indicates if subscription details should be included in the results - SubscriptionsDetail bool `json:"subscriptions_detail"` - - // Offset is used for pagination. Connz() only returns connections starting at this - // offset from the global results. - Offset int `json:"offset"` - - // Limit is the maximum number of connections that should be returned by Connz(). - Limit int `json:"limit"` - - // Filter for this explicit client connection. - CID uint64 `json:"cid"` - - // Filter for this explicit client connection based on the MQTT client ID - MQTTClient string `json:"mqtt_client"` - - // Filter by connection state. - State ConnState `json:"state"` - - // The below options only apply if auth is true. - - // Filter by username. - User string `json:"user"` - - // Filter by account. - Account string `json:"acc"` - - // Filter by subject interest - FilterSubject string `json:"filter_subject"` -} - -// ConnState is for filtering states of connections. We will only have two, open and closed. -type ConnState int - -const ( - // ConnOpen filters on open clients. - ConnOpen = ConnState(iota) - // ConnClosed filters on closed clients. - ConnClosed - // ConnAll returns all clients. - ConnAll -) - -// ConnInfo has detailed information on a per connection basis. -type ConnInfo struct { - Cid uint64 `json:"cid"` - Kind string `json:"kind,omitempty"` - Type string `json:"type,omitempty"` - IP string `json:"ip"` - Port int `json:"port"` - Start time.Time `json:"start"` - LastActivity time.Time `json:"last_activity"` - Stop *time.Time `json:"stop,omitempty"` - Reason string `json:"reason,omitempty"` - RTT string `json:"rtt,omitempty"` - Uptime string `json:"uptime"` - Idle string `json:"idle"` - Pending int `json:"pending_bytes"` - InMsgs int64 `json:"in_msgs"` - OutMsgs int64 `json:"out_msgs"` - InBytes int64 `json:"in_bytes"` - OutBytes int64 `json:"out_bytes"` - NumSubs uint32 `json:"subscriptions"` - Name string `json:"name,omitempty"` - Lang string `json:"lang,omitempty"` - Version string `json:"version,omitempty"` - TLSVersion string `json:"tls_version,omitempty"` - TLSCipher string `json:"tls_cipher_suite,omitempty"` - TLSPeerCerts []*TLSPeerCert `json:"tls_peer_certs,omitempty"` - TLSFirst bool `json:"tls_first,omitempty"` - AuthorizedUser string `json:"authorized_user,omitempty"` - Account string `json:"account,omitempty"` - Subs []string `json:"subscriptions_list,omitempty"` - SubsDetail []SubDetail `json:"subscriptions_list_detail,omitempty"` - JWT string `json:"jwt,omitempty"` - IssuerKey string `json:"issuer_key,omitempty"` - NameTag string `json:"name_tag,omitempty"` - Tags jwt.TagList `json:"tags,omitempty"` - MQTTClient string `json:"mqtt_client,omitempty"` // This is the MQTT client id - - // Internal - rtt int64 // For fast sorting -} - -// TLSPeerCert contains basic information about a TLS peer certificate -type TLSPeerCert struct { - Subject string `json:"subject,omitempty"` - SubjectPKISha256 string `json:"spki_sha256,omitempty"` - CertSha256 string `json:"cert_sha256,omitempty"` -} - -// DefaultConnListSize is the default size of the connection list. -const DefaultConnListSize = 1024 - -// DefaultSubListSize is the default size of the subscriptions list. -const DefaultSubListSize = 1024 - -const defaultStackBufSize = 10000 - -func newSubsDetailList(client *client) []SubDetail { - subsDetail := make([]SubDetail, 0, len(client.subs)) - for _, sub := range client.subs { - subsDetail = append(subsDetail, newClientSubDetail(sub)) - } - return subsDetail -} - -func newSubsList(client *client) []string { - subs := make([]string, 0, len(client.subs)) - for _, sub := range client.subs { - subs = append(subs, string(sub.subject)) - } - return subs -} - -// Connz returns a Connz struct containing information about connections. -func (s *Server) Connz(opts *ConnzOptions) (*Connz, error) { - var ( - sortOpt = ByCid - auth bool - subs bool - subsDet bool - offset int - limit = DefaultConnListSize - cid = uint64(0) - state = ConnOpen - user string - acc string - a *Account - filter string - mqttCID string - ) - - if opts != nil { - // If no sort option given or sort is by uptime, then sort by cid - if opts.Sort != _EMPTY_ { - sortOpt = opts.Sort - if !sortOpt.IsValid() { - return nil, fmt.Errorf("invalid sorting option: %s", sortOpt) - } - } - - // Auth specifics. - auth = opts.Username - user = opts.User - acc = opts.Account - mqttCID = opts.MQTTClient - - subs = opts.Subscriptions - subsDet = opts.SubscriptionsDetail - offset = opts.Offset - if offset < 0 { - offset = 0 - } - limit = opts.Limit - if limit <= 0 { - limit = DefaultConnListSize - } - // state - state = opts.State - - // ByStop only makes sense on closed connections - if sortOpt == ByStop && state != ConnClosed { - return nil, fmt.Errorf("sort by stop only valid on closed connections") - } - // ByReason is the same. - if sortOpt == ByReason && state != ConnClosed { - return nil, fmt.Errorf("sort by reason only valid on closed connections") - } - // If searching by CID - if opts.CID > 0 { - cid = opts.CID - limit = 1 - } - // If filtering by subject. - if opts.FilterSubject != _EMPTY_ && opts.FilterSubject != fwcs { - if acc == _EMPTY_ { - return nil, fmt.Errorf("filter by subject only valid with account filtering") - } - filter = opts.FilterSubject - } - } - - c := &Connz{ - Offset: offset, - Limit: limit, - Now: time.Now().UTC(), - } - - // Open clients - var openClients []*client - // Hold for closed clients if requested. - var closedClients []*closedClient - - var clist map[uint64]*client - - if acc != _EMPTY_ { - var err error - a, err = s.lookupAccount(acc) - if err != nil { - return c, nil - } - a.mu.RLock() - clist = make(map[uint64]*client, a.numLocalConnections()) - for c := range a.clients { - if c.kind == CLIENT || c.kind == LEAF { - clist[c.cid] = c - } - } - a.mu.RUnlock() - } - - // Walk the open client list with server lock held. - s.mu.RLock() - // Default to all client unless filled in above. - if clist == nil { - clist = s.clients - } - - // copy the server id for monitoring - c.ID = s.info.ID - - // Number of total clients. The resulting ConnInfo array - // may be smaller if pagination is used. - switch state { - case ConnOpen: - c.Total = len(clist) - case ConnClosed: - closedClients = s.closed.closedClients() - c.Total = len(closedClients) - case ConnAll: - c.Total = len(clist) - closedClients = s.closed.closedClients() - c.Total += len(closedClients) - } - - // We may need to filter these connections. - if acc != _EMPTY_ && len(closedClients) > 0 { - var ccc []*closedClient - for _, cc := range closedClients { - if cc.acc != acc { - continue - } - ccc = append(ccc, cc) - } - c.Total -= (len(closedClients) - len(ccc)) - closedClients = ccc - } - - totalClients := c.Total - if cid > 0 { // Meaning we only want 1. - totalClients = 1 - } - if state == ConnOpen || state == ConnAll { - openClients = make([]*client, 0, totalClients) - } - - // Data structures for results. - var conns []ConnInfo // Limits allocs for actual ConnInfos. - var pconns ConnInfos - - switch state { - case ConnOpen: - conns = make([]ConnInfo, totalClients) - pconns = make(ConnInfos, totalClients) - case ConnClosed: - pconns = make(ConnInfos, totalClients) - case ConnAll: - conns = make([]ConnInfo, cap(openClients)) - pconns = make(ConnInfos, totalClients) - } - - // Search by individual CID. - if cid > 0 { - if state == ConnClosed || state == ConnAll { - copyClosed := closedClients - closedClients = nil - for _, cc := range copyClosed { - if cc.Cid == cid { - closedClients = []*closedClient{cc} - break - } - } - } else if state == ConnOpen || state == ConnAll { - client := s.clients[cid] - if client != nil { - openClients = append(openClients, client) - } - } - } else { - // Gather all open clients. - if state == ConnOpen || state == ConnAll { - for _, client := range clist { - // If we have an account specified we need to filter. - if acc != _EMPTY_ && (client.acc == nil || client.acc.Name != acc) { - continue - } - // Do user filtering second - if user != _EMPTY_ && client.getRawAuthUserLock() != user { - continue - } - // Do mqtt client ID filtering next - if mqttCID != _EMPTY_ && client.getMQTTClientID() != mqttCID { - continue - } - openClients = append(openClients, client) - } - } - } - s.mu.RUnlock() - - // Filter by subject now if needed. We do this outside of server lock. - if filter != _EMPTY_ { - var oc []*client - for _, c := range openClients { - c.mu.Lock() - for _, sub := range c.subs { - if SubjectsCollide(filter, string(sub.subject)) { - oc = append(oc, c) - break - } - } - c.mu.Unlock() - openClients = oc - } - } - - // Just return with empty array if nothing here. - if len(openClients) == 0 && len(closedClients) == 0 { - c.Conns = ConnInfos{} - return c, nil - } - - // Now whip through and generate ConnInfo entries - // Open Clients - i := 0 - for _, client := range openClients { - client.mu.Lock() - ci := &conns[i] - ci.fill(client, client.nc, c.Now, auth) - // Fill in subscription data if requested. - if len(client.subs) > 0 { - if subsDet { - ci.SubsDetail = newSubsDetailList(client) - } else if subs { - ci.Subs = newSubsList(client) - } - } - // Fill in user if auth requested. - if auth { - ci.AuthorizedUser = client.getRawAuthUser() - if name := client.acc.GetName(); name != globalAccountName { - ci.Account = name - } - ci.JWT = client.opts.JWT - ci.IssuerKey = issuerForClient(client) - ci.Tags = client.tags - ci.NameTag = client.acc.getNameTag() - } - client.mu.Unlock() - pconns[i] = ci - i++ - } - // Closed Clients - var needCopy bool - if subs || auth { - needCopy = true - } - for _, cc := range closedClients { - // If we have an account specified we need to filter. - if acc != _EMPTY_ && cc.acc != acc { - continue - } - // Do user filtering second - if user != _EMPTY_ && cc.user != user { - continue - } - // Do mqtt client ID filtering next - if mqttCID != _EMPTY_ && cc.MQTTClient != mqttCID { - continue - } - // Copy if needed for any changes to the ConnInfo - if needCopy { - cx := *cc - cc = &cx - } - // Fill in subscription data if requested. - if len(cc.subs) > 0 { - if subsDet { - cc.SubsDetail = cc.subs - } else if subs { - cc.Subs = make([]string, 0, len(cc.subs)) - for _, sub := range cc.subs { - cc.Subs = append(cc.Subs, sub.Subject) - } - } - } - // Fill in user if auth requested. - if auth { - cc.AuthorizedUser = cc.user - if cc.acc != _EMPTY_ && (cc.acc != globalAccountName) { - cc.Account = cc.acc - if acc, err := s.LookupAccount(cc.acc); err == nil { - cc.NameTag = acc.getNameTag() - } - } - } - pconns[i] = &cc.ConnInfo - i++ - } - - // This will trip if we have filtered out client connections. - if len(pconns) != i { - pconns = pconns[:i] - totalClients = i - } - - switch sortOpt { - case ByCid, ByStart: - sort.Sort(byCid{pconns}) - case BySubs: - sort.Sort(sort.Reverse(bySubs{pconns})) - case ByPending: - sort.Sort(sort.Reverse(byPending{pconns})) - case ByOutMsgs: - sort.Sort(sort.Reverse(byOutMsgs{pconns})) - case ByInMsgs: - sort.Sort(sort.Reverse(byInMsgs{pconns})) - case ByOutBytes: - sort.Sort(sort.Reverse(byOutBytes{pconns})) - case ByInBytes: - sort.Sort(sort.Reverse(byInBytes{pconns})) - case ByLast: - sort.Sort(sort.Reverse(byLast{pconns})) - case ByIdle: - sort.Sort(sort.Reverse(byIdle{pconns, c.Now})) - case ByUptime: - sort.Sort(byUptime{pconns, time.Now()}) - case ByStop: - sort.Sort(sort.Reverse(byStop{pconns})) - case ByReason: - sort.Sort(byReason{pconns}) - case ByRTT: - sort.Sort(sort.Reverse(byRTT{pconns})) - } - - minoff := c.Offset - maxoff := c.Offset + c.Limit - - maxIndex := totalClients - - // Make sure these are sane. - if minoff > maxIndex { - minoff = maxIndex - } - if maxoff > maxIndex { - maxoff = maxIndex - } - - // Now pare down to the requested size. - // TODO(dlc) - for very large number of connections we - // could save the whole list in a hash, send hash on first - // request and allow users to use has for subsequent pages. - // Low TTL, say < 1sec. - c.Conns = pconns[minoff:maxoff] - c.NumConns = len(c.Conns) - - return c, nil -} - -// Fills in the ConnInfo from the client. -// client should be locked. -func (ci *ConnInfo) fill(client *client, nc net.Conn, now time.Time, auth bool) { - // For fast sort if required. - rtt := client.getRTT() - ci.rtt = int64(rtt) - - ci.Cid = client.cid - ci.MQTTClient = client.getMQTTClientID() - ci.Kind = client.kindString() - ci.Type = client.clientTypeString() - ci.Start = client.start - ci.LastActivity = client.last - ci.Uptime = myUptime(now.Sub(client.start)) - ci.Idle = myUptime(now.Sub(client.last)) - ci.RTT = rtt.String() - ci.OutMsgs = client.outMsgs - ci.OutBytes = client.outBytes - ci.NumSubs = uint32(len(client.subs)) - ci.Pending = int(client.out.pb) - ci.Name = client.opts.Name - ci.Lang = client.opts.Lang - ci.Version = client.opts.Version - // inMsgs and inBytes are updated outside of the client's lock, so - // we need to use atomic here. - ci.InMsgs = atomic.LoadInt64(&client.inMsgs) - ci.InBytes = atomic.LoadInt64(&client.inBytes) - - // If the connection is gone, too bad, we won't set TLSVersion and TLSCipher. - // Exclude clients that are still doing handshake so we don't block in - // ConnectionState(). - if client.flags.isSet(handshakeComplete) && nc != nil { - if conn, ok := nc.(*tls.Conn); ok { - cs := conn.ConnectionState() - ci.TLSVersion = tlsVersion(cs.Version) - ci.TLSCipher = tlsCipher(cs.CipherSuite) - if auth && len(cs.PeerCertificates) > 0 { - ci.TLSPeerCerts = makePeerCerts(cs.PeerCertificates) - } - ci.TLSFirst = client.flags.isSet(didTLSFirst) - } - } - - if client.port != 0 { - ci.Port = int(client.port) - ci.IP = client.host - } -} - -func makePeerCerts(pc []*x509.Certificate) []*TLSPeerCert { - res := make([]*TLSPeerCert, len(pc)) - for i, c := range pc { - tmp := sha256.Sum256(c.RawSubjectPublicKeyInfo) - ssha := hex.EncodeToString(tmp[:]) - tmp = sha256.Sum256(c.Raw) - csha := hex.EncodeToString(tmp[:]) - res[i] = &TLSPeerCert{Subject: c.Subject.String(), SubjectPKISha256: ssha, CertSha256: csha} - } - return res -} - -// Assume lock is held -func (c *client) getRTT() time.Duration { - if c.rtt == 0 { - // If a real client, go ahead and send ping now to get a value - // for RTT. For tests and telnet, or if client is closing, etc skip. - if c.opts.Lang != _EMPTY_ { - c.sendRTTPingLocked() - } - return 0 - } - var rtt time.Duration - if c.rtt > time.Microsecond && c.rtt < time.Millisecond { - rtt = c.rtt.Truncate(time.Microsecond) - } else { - rtt = c.rtt.Truncate(time.Nanosecond) - } - return rtt -} - -func decodeBool(w http.ResponseWriter, r *http.Request, param string) (bool, error) { - str := r.URL.Query().Get(param) - if str == _EMPTY_ { - return false, nil - } - val, err := strconv.ParseBool(str) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(fmt.Sprintf("Error decoding boolean for '%s': %v", param, err))) - return false, err - } - return val, nil -} - -func decodeUint64(w http.ResponseWriter, r *http.Request, param string) (uint64, error) { - str := r.URL.Query().Get(param) - if str == _EMPTY_ { - return 0, nil - } - val, err := strconv.ParseUint(str, 10, 64) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(fmt.Sprintf("Error decoding uint64 for '%s': %v", param, err))) - return 0, err - } - return val, nil -} - -func decodeInt(w http.ResponseWriter, r *http.Request, param string) (int, error) { - str := r.URL.Query().Get(param) - if str == _EMPTY_ { - return 0, nil - } - val, err := strconv.Atoi(str) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(fmt.Sprintf("Error decoding int for '%s': %v", param, err))) - return 0, err - } - return val, nil -} - -func decodeState(w http.ResponseWriter, r *http.Request) (ConnState, error) { - str := r.URL.Query().Get("state") - if str == _EMPTY_ { - return ConnOpen, nil - } - switch strings.ToLower(str) { - case "open": - return ConnOpen, nil - case "closed": - return ConnClosed, nil - case "any", "all": - return ConnAll, nil - } - // We do not understand intended state here. - w.WriteHeader(http.StatusBadRequest) - err := fmt.Errorf("Error decoding state for %s", str) - w.Write([]byte(err.Error())) - return 0, err -} - -func decodeSubs(w http.ResponseWriter, r *http.Request) (subs bool, subsDet bool, err error) { - subsDet = strings.ToLower(r.URL.Query().Get("subs")) == "detail" - if !subsDet { - subs, err = decodeBool(w, r, "subs") - } - return -} - -// HandleConnz process HTTP requests for connection information. -func (s *Server) HandleConnz(w http.ResponseWriter, r *http.Request) { - sortOpt := SortOpt(r.URL.Query().Get("sort")) - auth, err := decodeBool(w, r, "auth") - if err != nil { - return - } - subs, subsDet, err := decodeSubs(w, r) - if err != nil { - return - } - offset, err := decodeInt(w, r, "offset") - if err != nil { - return - } - limit, err := decodeInt(w, r, "limit") - if err != nil { - return - } - cid, err := decodeUint64(w, r, "cid") - if err != nil { - return - } - state, err := decodeState(w, r) - if err != nil { - return - } - - user := r.URL.Query().Get("user") - acc := r.URL.Query().Get("acc") - mqttCID := r.URL.Query().Get("mqtt_client") - - connzOpts := &ConnzOptions{ - Sort: sortOpt, - Username: auth, - Subscriptions: subs, - SubscriptionsDetail: subsDet, - Offset: offset, - Limit: limit, - CID: cid, - MQTTClient: mqttCID, - State: state, - User: user, - Account: acc, - } - - s.mu.Lock() - s.httpReqStats[ConnzPath]++ - s.mu.Unlock() - - c, err := s.Connz(connzOpts) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - b, err := json.MarshalIndent(c, "", " ") - if err != nil { - s.Errorf("Error marshaling response to /connz request: %v", err) - } - - // Handle response - ResponseHandler(w, r, b) -} - -// Routez represents detailed information on current client connections. -type Routez struct { - ID string `json:"server_id"` - Name string `json:"server_name"` - Now time.Time `json:"now"` - Import *SubjectPermission `json:"import,omitempty"` - Export *SubjectPermission `json:"export,omitempty"` - NumRoutes int `json:"num_routes"` - Routes []*RouteInfo `json:"routes"` -} - -// RoutezOptions are options passed to Routez -type RoutezOptions struct { - // Subscriptions indicates that Routez will return a route's subscriptions - Subscriptions bool `json:"subscriptions"` - // SubscriptionsDetail indicates if subscription details should be included in the results - SubscriptionsDetail bool `json:"subscriptions_detail"` -} - -// RouteInfo has detailed information on a per connection basis. -type RouteInfo struct { - Rid uint64 `json:"rid"` - RemoteID string `json:"remote_id"` - RemoteName string `json:"remote_name"` - DidSolicit bool `json:"did_solicit"` - IsConfigured bool `json:"is_configured"` - IP string `json:"ip"` - Port int `json:"port"` - Start time.Time `json:"start"` - LastActivity time.Time `json:"last_activity"` - RTT string `json:"rtt,omitempty"` - Uptime string `json:"uptime"` - Idle string `json:"idle"` - Import *SubjectPermission `json:"import,omitempty"` - Export *SubjectPermission `json:"export,omitempty"` - Pending int `json:"pending_size"` - InMsgs int64 `json:"in_msgs"` - OutMsgs int64 `json:"out_msgs"` - InBytes int64 `json:"in_bytes"` - OutBytes int64 `json:"out_bytes"` - NumSubs uint32 `json:"subscriptions"` - Subs []string `json:"subscriptions_list,omitempty"` - SubsDetail []SubDetail `json:"subscriptions_list_detail,omitempty"` - Account string `json:"account,omitempty"` - Compression string `json:"compression,omitempty"` -} - -// Routez returns a Routez struct containing information about routes. -func (s *Server) Routez(routezOpts *RoutezOptions) (*Routez, error) { - rs := &Routez{Routes: []*RouteInfo{}} - rs.Now = time.Now().UTC() - - if routezOpts == nil { - routezOpts = &RoutezOptions{} - } - - s.mu.Lock() - rs.NumRoutes = s.numRoutes() - - // copy the server id for monitoring - rs.ID = s.info.ID - - // Check for defined permissions for all connected routes. - if perms := s.getOpts().Cluster.Permissions; perms != nil { - rs.Import = perms.Import - rs.Export = perms.Export - } - rs.Name = s.getOpts().ServerName - - addRoute := func(r *client) { - r.mu.Lock() - ri := &RouteInfo{ - Rid: r.cid, - RemoteID: r.route.remoteID, - RemoteName: r.route.remoteName, - DidSolicit: r.route.didSolicit, - IsConfigured: r.route.routeType == Explicit, - InMsgs: atomic.LoadInt64(&r.inMsgs), - OutMsgs: r.outMsgs, - InBytes: atomic.LoadInt64(&r.inBytes), - OutBytes: r.outBytes, - NumSubs: uint32(len(r.subs)), - Import: r.opts.Import, - Pending: int(r.out.pb), - Export: r.opts.Export, - RTT: r.getRTT().String(), - Start: r.start, - LastActivity: r.last, - Uptime: myUptime(rs.Now.Sub(r.start)), - Idle: myUptime(rs.Now.Sub(r.last)), - Account: string(r.route.accName), - Compression: r.route.compression, - } - - if len(r.subs) > 0 { - if routezOpts.SubscriptionsDetail { - ri.SubsDetail = newSubsDetailList(r) - } else if routezOpts.Subscriptions { - ri.Subs = newSubsList(r) - } - } - - switch conn := r.nc.(type) { - case *net.TCPConn, *tls.Conn: - addr := conn.RemoteAddr().(*net.TCPAddr) - ri.Port = addr.Port - ri.IP = addr.IP.String() - } - r.mu.Unlock() - rs.Routes = append(rs.Routes, ri) - } - - // Walk the list - s.forEachRoute(func(r *client) { - addRoute(r) - }) - s.mu.Unlock() - return rs, nil -} - -// HandleRoutez process HTTP requests for route information. -func (s *Server) HandleRoutez(w http.ResponseWriter, r *http.Request) { - subs, subsDetail, err := decodeSubs(w, r) - if err != nil { - return - } - - opts := RoutezOptions{Subscriptions: subs, SubscriptionsDetail: subsDetail} - - s.mu.Lock() - s.httpReqStats[RoutezPath]++ - s.mu.Unlock() - - // As of now, no error is ever returned. - rs, _ := s.Routez(&opts) - b, err := json.MarshalIndent(rs, "", " ") - if err != nil { - s.Errorf("Error marshaling response to /routez request: %v", err) - } - - // Handle response - ResponseHandler(w, r, b) -} - -// Subsz represents detail information on current connections. -type Subsz struct { - ID string `json:"server_id"` - Now time.Time `json:"now"` - *SublistStats - Total int `json:"total"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Subs []SubDetail `json:"subscriptions_list,omitempty"` -} - -// SubszOptions are the options passed to Subsz. -// As of now, there are no options defined. -type SubszOptions struct { - // Offset is used for pagination. Subsz() only returns connections starting at this - // offset from the global results. - Offset int `json:"offset"` - - // Limit is the maximum number of subscriptions that should be returned by Subsz(). - Limit int `json:"limit"` - - // Subscriptions indicates if subscription details should be included in the results. - Subscriptions bool `json:"subscriptions"` - - // Filter based on this account name. - Account string `json:"account,omitempty"` - - // Test the list against this subject. Needs to be literal since it signifies a publish subject. - // We will only return subscriptions that would match if a message was sent to this subject. - Test string `json:"test,omitempty"` -} - -// SubDetail is for verbose information for subscriptions. -type SubDetail struct { - Account string `json:"account,omitempty"` - AccountTag string `json:"account_tag,omitempty"` - Subject string `json:"subject"` - Queue string `json:"qgroup,omitempty"` - Sid string `json:"sid"` - Msgs int64 `json:"msgs"` - Max int64 `json:"max,omitempty"` - Cid uint64 `json:"cid"` -} - -// Subscription client should be locked and guaranteed to be present. -func newSubDetail(sub *subscription) SubDetail { - sd := newClientSubDetail(sub) - sd.Account = sub.client.acc.GetName() - sd.AccountTag = sub.client.acc.getNameTag() - return sd -} - -// For subs details under clients. -func newClientSubDetail(sub *subscription) SubDetail { - return SubDetail{ - Subject: string(sub.subject), - Queue: string(sub.queue), - Sid: string(sub.sid), - Msgs: sub.nm, - Max: sub.max, - Cid: sub.client.cid, - } -} - -// Subsz returns a Subsz struct containing subjects statistics -func (s *Server) Subsz(opts *SubszOptions) (*Subsz, error) { - var ( - subdetail bool - test bool - offset int - testSub string - filterAcc string - limit = DefaultSubListSize - ) - - if opts != nil { - subdetail = opts.Subscriptions - offset = opts.Offset - if offset < 0 { - offset = 0 - } - limit = opts.Limit - if limit <= 0 { - limit = DefaultSubListSize - } - if opts.Test != _EMPTY_ { - testSub = opts.Test - test = true - if !IsValidLiteralSubject(testSub) { - return nil, fmt.Errorf("invalid test subject, must be valid publish subject: %s", testSub) - } - } - if opts.Account != _EMPTY_ { - filterAcc = opts.Account - } - } - - slStats := &SublistStats{} - - // FIXME(dlc) - Make account aware. - sz := &Subsz{s.info.ID, time.Now().UTC(), slStats, 0, offset, limit, nil} - - if subdetail { - var raw [4096]*subscription - subs := raw[:0] - s.accounts.Range(func(k, v any) bool { - acc := v.(*Account) - if filterAcc != _EMPTY_ && acc.GetName() != filterAcc { - return true - } - slStats.add(acc.sl.Stats()) - acc.sl.localSubs(&subs, false) - return true - }) - - details := make([]SubDetail, len(subs)) - i := 0 - // TODO(dlc) - may be inefficient and could just do normal match when total subs is large and filtering. - for _, sub := range subs { - // Check for filter - if test && !matchLiteral(testSub, string(sub.subject)) { - continue - } - if sub.client == nil { - continue - } - sub.client.mu.Lock() - details[i] = newSubDetail(sub) - sub.client.mu.Unlock() - i++ - } - minoff := sz.Offset - maxoff := sz.Offset + sz.Limit - - maxIndex := i - - // Make sure these are sane. - if minoff > maxIndex { - minoff = maxIndex - } - if maxoff > maxIndex { - maxoff = maxIndex - } - sz.Subs = details[minoff:maxoff] - sz.Total = len(sz.Subs) - } else { - s.accounts.Range(func(k, v any) bool { - acc := v.(*Account) - if filterAcc != _EMPTY_ && acc.GetName() != filterAcc { - return true - } - slStats.add(acc.sl.Stats()) - return true - }) - } - - return sz, nil -} - -// HandleSubsz processes HTTP requests for subjects stats. -func (s *Server) HandleSubsz(w http.ResponseWriter, r *http.Request) { - s.mu.Lock() - s.httpReqStats[SubszPath]++ - s.mu.Unlock() - - subs, err := decodeBool(w, r, "subs") - if err != nil { - return - } - offset, err := decodeInt(w, r, "offset") - if err != nil { - return - } - limit, err := decodeInt(w, r, "limit") - if err != nil { - return - } - testSub := r.URL.Query().Get("test") - // Filtered account. - filterAcc := r.URL.Query().Get("acc") - - subszOpts := &SubszOptions{ - Subscriptions: subs, - Offset: offset, - Limit: limit, - Account: filterAcc, - Test: testSub, - } - - st, err := s.Subsz(subszOpts) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - - var b []byte - - if len(st.Subs) == 0 { - b, err = json.MarshalIndent(st.SublistStats, "", " ") - } else { - b, err = json.MarshalIndent(st, "", " ") - } - if err != nil { - s.Errorf("Error marshaling response to /subscriptionsz request: %v", err) - } - - // Handle response - ResponseHandler(w, r, b) -} - -// HandleStacksz processes HTTP requests for getting stacks -func (s *Server) HandleStacksz(w http.ResponseWriter, r *http.Request) { - // Do not get any lock here that would prevent getting the stacks - // if we were to have a deadlock somewhere. - var defaultBuf [defaultStackBufSize]byte - size := defaultStackBufSize - buf := defaultBuf[:size] - n := 0 - for { - n = runtime.Stack(buf, true) - if n < size { - break - } - size *= 2 - buf = make([]byte, size) - } - // Handle response - ResponseHandler(w, r, buf[:n]) -} - -type IpqueueszStatusIPQ struct { - Pending int `json:"pending"` - InProgress int `json:"in_progress,omitempty"` -} - -type IpqueueszStatus map[string]IpqueueszStatusIPQ - -func (s *Server) Ipqueuesz(opts *IpqueueszOptions) *IpqueueszStatus { - all, qfilter := opts.All, opts.Filter - queues := IpqueueszStatus{} - s.ipQueues.Range(func(k, v any) bool { - var pending, inProgress int - name := k.(string) - queue, ok := v.(interface { - len() int - inProgress() int64 - }) - if ok { - pending = queue.len() - inProgress = int(queue.inProgress()) - } - if !all && (pending == 0 && inProgress == 0) { - return true - } else if qfilter != _EMPTY_ && !strings.Contains(name, qfilter) { - return true - } - queues[name] = IpqueueszStatusIPQ{Pending: pending, InProgress: inProgress} - return true - }) - return &queues -} - -func (s *Server) HandleIPQueuesz(w http.ResponseWriter, r *http.Request) { - all, err := decodeBool(w, r, "all") - if err != nil { - return - } - qfilter := r.URL.Query().Get("queues") - - queues := s.Ipqueuesz(&IpqueueszOptions{ - All: all, - Filter: qfilter, - }) - - b, _ := json.MarshalIndent(queues, "", " ") - ResponseHandler(w, r, b) -} - -// Varz will output server information on the monitoring port at /varz. -type Varz struct { - ID string `json:"server_id"` - Name string `json:"server_name"` - Version string `json:"version"` - Proto int `json:"proto"` - GitCommit string `json:"git_commit,omitempty"` - GoVersion string `json:"go"` - Host string `json:"host"` - Port int `json:"port"` - AuthRequired bool `json:"auth_required,omitempty"` - TLSRequired bool `json:"tls_required,omitempty"` - TLSVerify bool `json:"tls_verify,omitempty"` - TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` - IP string `json:"ip,omitempty"` - ClientConnectURLs []string `json:"connect_urls,omitempty"` - WSConnectURLs []string `json:"ws_connect_urls,omitempty"` - MaxConn int `json:"max_connections"` - MaxSubs int `json:"max_subscriptions,omitempty"` - PingInterval time.Duration `json:"ping_interval"` - MaxPingsOut int `json:"ping_max"` - HTTPHost string `json:"http_host"` - HTTPPort int `json:"http_port"` - HTTPBasePath string `json:"http_base_path"` - HTTPSPort int `json:"https_port"` - AuthTimeout float64 `json:"auth_timeout"` - MaxControlLine int32 `json:"max_control_line"` - MaxPayload int `json:"max_payload"` - MaxPending int64 `json:"max_pending"` - Cluster ClusterOptsVarz `json:"cluster,omitempty"` - Gateway GatewayOptsVarz `json:"gateway,omitempty"` - LeafNode LeafNodeOptsVarz `json:"leaf,omitempty"` - MQTT MQTTOptsVarz `json:"mqtt,omitempty"` - Websocket WebsocketOptsVarz `json:"websocket,omitempty"` - JetStream JetStreamVarz `json:"jetstream,omitempty"` - TLSTimeout float64 `json:"tls_timeout"` - WriteDeadline time.Duration `json:"write_deadline"` - Start time.Time `json:"start"` - Now time.Time `json:"now"` - Uptime string `json:"uptime"` - Mem int64 `json:"mem"` - Cores int `json:"cores"` - MaxProcs int `json:"gomaxprocs"` - CPU float64 `json:"cpu"` - Connections int `json:"connections"` - TotalConnections uint64 `json:"total_connections"` - Routes int `json:"routes"` - Remotes int `json:"remotes"` - Leafs int `json:"leafnodes"` - InMsgs int64 `json:"in_msgs"` - OutMsgs int64 `json:"out_msgs"` - InBytes int64 `json:"in_bytes"` - OutBytes int64 `json:"out_bytes"` - SlowConsumers int64 `json:"slow_consumers"` - Subscriptions uint32 `json:"subscriptions"` - HTTPReqStats map[string]uint64 `json:"http_req_stats"` - ConfigLoadTime time.Time `json:"config_load_time"` - ConfigDigest string `json:"config_digest"` - Tags jwt.TagList `json:"tags,omitempty"` - TrustedOperatorsJwt []string `json:"trusted_operators_jwt,omitempty"` - TrustedOperatorsClaim []*jwt.OperatorClaims `json:"trusted_operators_claim,omitempty"` - SystemAccount string `json:"system_account,omitempty"` - PinnedAccountFail uint64 `json:"pinned_account_fails,omitempty"` - OCSPResponseCache *OCSPResponseCacheVarz `json:"ocsp_peer_cache,omitempty"` - SlowConsumersStats *SlowConsumersStats `json:"slow_consumer_stats"` -} - -// JetStreamVarz contains basic runtime information about jetstream -type JetStreamVarz struct { - Config *JetStreamConfig `json:"config,omitempty"` - Stats *JetStreamStats `json:"stats,omitempty"` - Meta *MetaClusterInfo `json:"meta,omitempty"` - Limits *JSLimitOpts `json:"limits,omitempty"` -} - -// ClusterOptsVarz contains monitoring cluster information -type ClusterOptsVarz struct { - Name string `json:"name,omitempty"` - Host string `json:"addr,omitempty"` - Port int `json:"cluster_port,omitempty"` - AuthTimeout float64 `json:"auth_timeout,omitempty"` - URLs []string `json:"urls,omitempty"` - TLSTimeout float64 `json:"tls_timeout,omitempty"` - TLSRequired bool `json:"tls_required,omitempty"` - TLSVerify bool `json:"tls_verify,omitempty"` - PoolSize int `json:"pool_size,omitempty"` -} - -// GatewayOptsVarz contains monitoring gateway information -type GatewayOptsVarz struct { - Name string `json:"name,omitempty"` - Host string `json:"host,omitempty"` - Port int `json:"port,omitempty"` - AuthTimeout float64 `json:"auth_timeout,omitempty"` - TLSTimeout float64 `json:"tls_timeout,omitempty"` - TLSRequired bool `json:"tls_required,omitempty"` - TLSVerify bool `json:"tls_verify,omitempty"` - Advertise string `json:"advertise,omitempty"` - ConnectRetries int `json:"connect_retries,omitempty"` - Gateways []RemoteGatewayOptsVarz `json:"gateways,omitempty"` - RejectUnknown bool `json:"reject_unknown,omitempty"` // config got renamed to reject_unknown_cluster -} - -// RemoteGatewayOptsVarz contains monitoring remote gateway information -type RemoteGatewayOptsVarz struct { - Name string `json:"name"` - TLSTimeout float64 `json:"tls_timeout,omitempty"` - URLs []string `json:"urls,omitempty"` -} - -// LeafNodeOptsVarz contains monitoring leaf node information -type LeafNodeOptsVarz struct { - Host string `json:"host,omitempty"` - Port int `json:"port,omitempty"` - AuthTimeout float64 `json:"auth_timeout,omitempty"` - TLSTimeout float64 `json:"tls_timeout,omitempty"` - TLSRequired bool `json:"tls_required,omitempty"` - TLSVerify bool `json:"tls_verify,omitempty"` - Remotes []RemoteLeafOptsVarz `json:"remotes,omitempty"` - TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` -} - -// DenyRules Contains lists of subjects not allowed to be imported/exported -type DenyRules struct { - Exports []string `json:"exports,omitempty"` - Imports []string `json:"imports,omitempty"` -} - -// RemoteLeafOptsVarz contains monitoring remote leaf node information -type RemoteLeafOptsVarz struct { - LocalAccount string `json:"local_account,omitempty"` - TLSTimeout float64 `json:"tls_timeout,omitempty"` - URLs []string `json:"urls,omitempty"` - Deny *DenyRules `json:"deny,omitempty"` - TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` -} - -// MQTTOptsVarz contains monitoring MQTT information -type MQTTOptsVarz struct { - Host string `json:"host,omitempty"` - Port int `json:"port,omitempty"` - NoAuthUser string `json:"no_auth_user,omitempty"` - AuthTimeout float64 `json:"auth_timeout,omitempty"` - TLSMap bool `json:"tls_map,omitempty"` - TLSTimeout float64 `json:"tls_timeout,omitempty"` - TLSPinnedCerts []string `json:"tls_pinned_certs,omitempty"` - JsDomain string `json:"js_domain,omitempty"` - AckWait time.Duration `json:"ack_wait,omitempty"` - MaxAckPending uint16 `json:"max_ack_pending,omitempty"` - TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` -} - -// WebsocketOptsVarz contains monitoring websocket information -type WebsocketOptsVarz struct { - Host string `json:"host,omitempty"` - Port int `json:"port,omitempty"` - Advertise string `json:"advertise,omitempty"` - NoAuthUser string `json:"no_auth_user,omitempty"` - JWTCookie string `json:"jwt_cookie,omitempty"` - HandshakeTimeout time.Duration `json:"handshake_timeout,omitempty"` - AuthTimeout float64 `json:"auth_timeout,omitempty"` - NoTLS bool `json:"no_tls,omitempty"` - TLSMap bool `json:"tls_map,omitempty"` - TLSPinnedCerts []string `json:"tls_pinned_certs,omitempty"` - SameOrigin bool `json:"same_origin,omitempty"` - AllowedOrigins []string `json:"allowed_origins,omitempty"` - Compression bool `json:"compression,omitempty"` - TLSOCSPPeerVerify bool `json:"tls_ocsp_peer_verify,omitempty"` -} - -// OCSPResponseCacheVarz contains OCSP response cache information -type OCSPResponseCacheVarz struct { - Type string `json:"cache_type,omitempty"` - Hits int64 `json:"cache_hits,omitempty"` - Misses int64 `json:"cache_misses,omitempty"` - Responses int64 `json:"cached_responses,omitempty"` - Revokes int64 `json:"cached_revoked_responses,omitempty"` - Goods int64 `json:"cached_good_responses,omitempty"` - Unknowns int64 `json:"cached_unknown_responses,omitempty"` -} - -// VarzOptions are the options passed to Varz(). -// Currently, there are no options defined. -type VarzOptions struct{} - -// SlowConsumersStats contains information about the slow consumers from different type of connections. -type SlowConsumersStats struct { - Clients uint64 `json:"clients"` - Routes uint64 `json:"routes"` - Gateways uint64 `json:"gateways"` - Leafs uint64 `json:"leafs"` -} - -func myUptime(d time.Duration) string { - // Just use total seconds for uptime, and display days / years - tsecs := d / time.Second - tmins := tsecs / 60 - thrs := tmins / 60 - tdays := thrs / 24 - tyrs := tdays / 365 - - if tyrs > 0 { - return fmt.Sprintf("%dy%dd%dh%dm%ds", tyrs, tdays%365, thrs%24, tmins%60, tsecs%60) - } - if tdays > 0 { - return fmt.Sprintf("%dd%dh%dm%ds", tdays, thrs%24, tmins%60, tsecs%60) - } - if thrs > 0 { - return fmt.Sprintf("%dh%dm%ds", thrs, tmins%60, tsecs%60) - } - if tmins > 0 { - return fmt.Sprintf("%dm%ds", tmins, tsecs%60) - } - return fmt.Sprintf("%ds", tsecs) -} - -// HandleRoot will show basic info and links to others handlers. -func (s *Server) HandleRoot(w http.ResponseWriter, r *http.Request) { - // This feels dumb to me, but is required: https://code.google.com/p/go/issues/detail?id=4799 - if r.URL.Path != s.httpBasePath { - http.NotFound(w, r) - return - } - s.mu.Lock() - s.httpReqStats[RootPath]++ - s.mu.Unlock() - - // Calculate source url. If git set go directly to that tag, otherwise just main. - var srcUrl string - if gitCommit == _EMPTY_ { - srcUrl = "https://github.com/nats-io/nats-server" - } else if serverVersion != _EMPTY_ { - srcUrl = fmt.Sprintf("https://github.com/nats-io/nats-server/tree/%s", serverVersion) - } else { - srcUrl = fmt.Sprintf("https://github.com/nats-io/nats-server/tree/%s", gitCommit) - } - - fmt.Fprintf(w, ` - - - - - - - - v%s - - -
- General - JetStream - Connections - Accounts - Account Stats - Subscriptions - Routes - LeafNodes - Gateways - Raft Groups - Health Probe - Help - -`, - srcUrl, - VERSION, - s.basePath(VarzPath), - s.basePath(JszPath), - s.basePath(ConnzPath), - s.basePath(AccountzPath), - s.basePath(AccountStatzPath), - s.basePath(SubszPath), - s.basePath(RoutezPath), - s.basePath(LeafzPath), - s.basePath(GatewayzPath), - s.basePath(RaftzPath), - s.basePath(HealthzPath), - ) -} - -func (s *Server) updateJszVarz(js *jetStream, v *JetStreamVarz, doConfig bool) { - if doConfig { - js.mu.RLock() - // We want to snapshot the config since it will then be available outside - // of the js lock. So make a copy first, then point to this copy. - cfg := js.config - v.Config = &cfg - js.mu.RUnlock() - } - v.Stats = js.usageStats() - v.Limits = &s.getOpts().JetStreamLimits - if mg := js.getMetaGroup(); mg != nil { - if ci := s.raftNodeToClusterInfo(mg); ci != nil { - v.Meta = &MetaClusterInfo{Name: ci.Name, Leader: ci.Leader, Peer: getHash(ci.Leader), Size: mg.ClusterSize()} - if ci.Leader == s.info.Name { - v.Meta.Replicas = ci.Replicas - } - if ipq := s.jsAPIRoutedReqs; ipq != nil { - v.Meta.Pending = ipq.len() - } - } - } -} - -// Varz returns a Varz struct containing the server information. -func (s *Server) Varz(varzOpts *VarzOptions) (*Varz, error) { - var rss, vss int64 - var pcpu float64 - - // We want to do that outside of the lock. - pse.ProcUsage(&pcpu, &rss, &vss) - - s.mu.RLock() - // We need to create a new instance of Varz (with no reference - // whatsoever to anything stored in the server) since the user - // has access to the returned value. - v := s.createVarz(pcpu, rss) - s.mu.RUnlock() - - if js := s.getJetStream(); js != nil { - s.updateJszVarz(js, &v.JetStream, true) - } - - return v, nil -} - -// Returns a Varz instance. -// Server lock is held on entry. -func (s *Server) createVarz(pcpu float64, rss int64) *Varz { - info := s.info - opts := s.getOpts() - c := &opts.Cluster - gw := &opts.Gateway - ln := &opts.LeafNode - mqtt := &opts.MQTT - ws := &opts.Websocket - clustTlsReq := c.TLSConfig != nil - gatewayTlsReq := gw.TLSConfig != nil - leafTlsReq := ln.TLSConfig != nil - leafTlsVerify := leafTlsReq && ln.TLSConfig.ClientAuth == tls.RequireAndVerifyClientCert - leafTlsOCSPPeerVerify := s.ocspPeerVerify && leafTlsReq && ln.tlsConfigOpts.OCSPPeerConfig != nil && ln.tlsConfigOpts.OCSPPeerConfig.Verify - mqttTlsOCSPPeerVerify := s.ocspPeerVerify && mqtt.TLSConfig != nil && mqtt.tlsConfigOpts.OCSPPeerConfig != nil && mqtt.tlsConfigOpts.OCSPPeerConfig.Verify - wsTlsOCSPPeerVerify := s.ocspPeerVerify && ws.TLSConfig != nil && ws.tlsConfigOpts.OCSPPeerConfig != nil && ws.tlsConfigOpts.OCSPPeerConfig.Verify - varz := &Varz{ - ID: info.ID, - Version: info.Version, - Proto: info.Proto, - GitCommit: info.GitCommit, - GoVersion: info.GoVersion, - Name: info.Name, - Host: info.Host, - Port: info.Port, - IP: info.IP, - HTTPHost: opts.HTTPHost, - HTTPPort: opts.HTTPPort, - HTTPBasePath: opts.HTTPBasePath, - HTTPSPort: opts.HTTPSPort, - Cluster: ClusterOptsVarz{ - Name: info.Cluster, - Host: c.Host, - Port: c.Port, - AuthTimeout: c.AuthTimeout, - TLSTimeout: c.TLSTimeout, - TLSRequired: clustTlsReq, - TLSVerify: clustTlsReq, - PoolSize: opts.Cluster.PoolSize, - }, - Gateway: GatewayOptsVarz{ - Name: gw.Name, - Host: gw.Host, - Port: gw.Port, - AuthTimeout: gw.AuthTimeout, - TLSTimeout: gw.TLSTimeout, - TLSRequired: gatewayTlsReq, - TLSVerify: gatewayTlsReq, - Advertise: gw.Advertise, - ConnectRetries: gw.ConnectRetries, - Gateways: []RemoteGatewayOptsVarz{}, - RejectUnknown: gw.RejectUnknown, - }, - LeafNode: LeafNodeOptsVarz{ - Host: ln.Host, - Port: ln.Port, - AuthTimeout: ln.AuthTimeout, - TLSTimeout: ln.TLSTimeout, - TLSRequired: leafTlsReq, - TLSVerify: leafTlsVerify, - TLSOCSPPeerVerify: leafTlsOCSPPeerVerify, - Remotes: []RemoteLeafOptsVarz{}, - }, - MQTT: MQTTOptsVarz{ - Host: mqtt.Host, - Port: mqtt.Port, - NoAuthUser: mqtt.NoAuthUser, - AuthTimeout: mqtt.AuthTimeout, - TLSMap: mqtt.TLSMap, - TLSTimeout: mqtt.TLSTimeout, - JsDomain: mqtt.JsDomain, - AckWait: mqtt.AckWait, - MaxAckPending: mqtt.MaxAckPending, - TLSOCSPPeerVerify: mqttTlsOCSPPeerVerify, - }, - Websocket: WebsocketOptsVarz{ - Host: ws.Host, - Port: ws.Port, - Advertise: ws.Advertise, - NoAuthUser: ws.NoAuthUser, - JWTCookie: ws.JWTCookie, - AuthTimeout: ws.AuthTimeout, - NoTLS: ws.NoTLS, - TLSMap: ws.TLSMap, - SameOrigin: ws.SameOrigin, - AllowedOrigins: copyStrings(ws.AllowedOrigins), - Compression: ws.Compression, - HandshakeTimeout: ws.HandshakeTimeout, - TLSOCSPPeerVerify: wsTlsOCSPPeerVerify, - }, - Start: s.start.UTC(), - MaxSubs: opts.MaxSubs, - Cores: runtime.NumCPU(), - MaxProcs: runtime.GOMAXPROCS(0), - Tags: opts.Tags, - TrustedOperatorsJwt: opts.operatorJWT, - TrustedOperatorsClaim: opts.TrustedOperators, - } - // If this is a leaf without cluster, reset the cluster name (that is otherwise - // set to the server name). - if s.leafNoCluster { - varz.Cluster.Name = _EMPTY_ - } - if len(opts.Routes) > 0 { - varz.Cluster.URLs = urlsToStrings(opts.Routes) - } - if l := len(gw.Gateways); l > 0 { - rgwa := make([]RemoteGatewayOptsVarz, l) - for i, r := range gw.Gateways { - rgwa[i] = RemoteGatewayOptsVarz{ - Name: r.Name, - TLSTimeout: r.TLSTimeout, - } - } - varz.Gateway.Gateways = rgwa - } - if l := len(ln.Remotes); l > 0 { - rlna := make([]RemoteLeafOptsVarz, l) - for i, r := range ln.Remotes { - var deny *DenyRules - if len(r.DenyImports) > 0 || len(r.DenyExports) > 0 { - deny = &DenyRules{ - Imports: r.DenyImports, - Exports: r.DenyExports, - } - } - remoteTlsOCSPPeerVerify := s.ocspPeerVerify && r.tlsConfigOpts != nil && r.tlsConfigOpts.OCSPPeerConfig != nil && r.tlsConfigOpts.OCSPPeerConfig.Verify - - rlna[i] = RemoteLeafOptsVarz{ - LocalAccount: r.LocalAccount, - URLs: urlsToStrings(r.URLs), - TLSTimeout: r.TLSTimeout, - Deny: deny, - TLSOCSPPeerVerify: remoteTlsOCSPPeerVerify, - } - } - varz.LeafNode.Remotes = rlna - } - - // Finish setting it up with fields that can be updated during - // configuration reload and runtime. - s.updateVarzConfigReloadableFields(varz) - s.updateVarzRuntimeFields(varz, true, pcpu, rss) - return varz -} - -func urlsToStrings(urls []*url.URL) []string { - sURLs := make([]string, len(urls)) - for i, u := range urls { - sURLs[i] = u.Host - } - return sURLs -} - -// Invoked during configuration reload once options have possibly be changed -// and config load time has been set. If s.varz has not been initialized yet -// (because no pooling of /varz has been made), this function does nothing. -// Server lock is held on entry. -func (s *Server) updateVarzConfigReloadableFields(v *Varz) { - if v == nil { - return - } - opts := s.getOpts() - info := &s.info - v.AuthRequired = info.AuthRequired - v.TLSRequired = info.TLSRequired - v.TLSVerify = info.TLSVerify - v.MaxConn = opts.MaxConn - v.PingInterval = opts.PingInterval - v.MaxPingsOut = opts.MaxPingsOut - v.AuthTimeout = opts.AuthTimeout - v.MaxControlLine = opts.MaxControlLine - v.MaxPayload = int(opts.MaxPayload) - v.MaxPending = opts.MaxPending - v.TLSTimeout = opts.TLSTimeout - v.WriteDeadline = opts.WriteDeadline - v.ConfigLoadTime = s.configTime.UTC() - v.ConfigDigest = opts.configDigest - // Update route URLs if applicable - if s.varzUpdateRouteURLs { - v.Cluster.URLs = urlsToStrings(opts.Routes) - s.varzUpdateRouteURLs = false - } - if s.sys != nil && s.sys.account != nil { - v.SystemAccount = s.sys.account.GetName() - } - v.MQTT.TLSPinnedCerts = getPinnedCertsAsSlice(opts.MQTT.TLSPinnedCerts) - v.Websocket.TLSPinnedCerts = getPinnedCertsAsSlice(opts.Websocket.TLSPinnedCerts) - - v.TLSOCSPPeerVerify = s.ocspPeerVerify && v.TLSRequired && s.opts.tlsConfigOpts != nil && s.opts.tlsConfigOpts.OCSPPeerConfig != nil && s.opts.tlsConfigOpts.OCSPPeerConfig.Verify -} - -func getPinnedCertsAsSlice(certs PinnedCertSet) []string { - if len(certs) == 0 { - return nil - } - res := make([]string, 0, len(certs)) - for cn := range certs { - res = append(res, cn) - } - return res -} - -// Updates the runtime Varz fields, that is, fields that change during -// runtime and that should be updated any time Varz() or polling of /varz -// is done. -// Server lock is held on entry. -func (s *Server) updateVarzRuntimeFields(v *Varz, forceUpdate bool, pcpu float64, rss int64) { - v.Now = time.Now().UTC() - v.Uptime = myUptime(time.Since(s.start)) - v.Mem = rss - v.CPU = pcpu - if l := len(s.info.ClientConnectURLs); l > 0 { - v.ClientConnectURLs = append([]string(nil), s.info.ClientConnectURLs...) - } - if l := len(s.info.WSConnectURLs); l > 0 { - v.WSConnectURLs = append([]string(nil), s.info.WSConnectURLs...) - } - v.Connections = len(s.clients) - v.TotalConnections = s.totalClients - v.Routes = s.numRoutes() - v.Remotes = s.numRemotes() - v.Leafs = len(s.leafs) - v.InMsgs = atomic.LoadInt64(&s.inMsgs) - v.InBytes = atomic.LoadInt64(&s.inBytes) - v.OutMsgs = atomic.LoadInt64(&s.outMsgs) - v.OutBytes = atomic.LoadInt64(&s.outBytes) - v.SlowConsumers = atomic.LoadInt64(&s.slowConsumers) - v.SlowConsumersStats = &SlowConsumersStats{ - Clients: s.NumSlowConsumersClients(), - Routes: s.NumSlowConsumersRoutes(), - Gateways: s.NumSlowConsumersGateways(), - Leafs: s.NumSlowConsumersLeafs(), - } - v.PinnedAccountFail = atomic.LoadUint64(&s.pinnedAccFail) - - // Make sure to reset in case we are re-using. - v.Subscriptions = 0 - s.accounts.Range(func(k, val any) bool { - acc := val.(*Account) - v.Subscriptions += acc.sl.Count() - return true - }) - - v.HTTPReqStats = make(map[string]uint64, len(s.httpReqStats)) - for key, val := range s.httpReqStats { - v.HTTPReqStats[key] = val - } - - // Update Gateway remote urls if applicable - gw := s.gateway - gw.RLock() - if gw.enabled { - for i := 0; i < len(v.Gateway.Gateways); i++ { - g := &v.Gateway.Gateways[i] - rgw := gw.remotes[g.Name] - if rgw != nil { - rgw.RLock() - // forceUpdate is needed if user calls Varz() programmatically, - // since we need to create a new instance every time and the - // gateway's varzUpdateURLs may have been set to false after - // a web /varz inspection. - if forceUpdate || rgw.varzUpdateURLs { - // Make reuse of backend array - g.URLs = g.URLs[:0] - // rgw.urls is a map[string]*url.URL where the key is - // already in the right format (host:port, without any - // user info present). - for u := range rgw.urls { - g.URLs = append(g.URLs, u) - } - rgw.varzUpdateURLs = false - } - rgw.RUnlock() - } else if g.Name == gw.name && len(gw.ownCfgURLs) > 0 { - // This is a remote that correspond to this very same server. - // We report the URLs that were configured (if any). - // Since we don't support changes to the gateway configuration - // at this time, we could do this only if g.URLs has not been already - // set, but let's do it regardless in case we add support for - // gateway config reload. - g.URLs = g.URLs[:0] - g.URLs = append(g.URLs, gw.ownCfgURLs...) - } - } - } - gw.RUnlock() - - if s.ocsprc != nil && s.ocsprc.Type() != "none" { - stats := s.ocsprc.Stats() - if stats != nil { - v.OCSPResponseCache = &OCSPResponseCacheVarz{ - s.ocsprc.Type(), - stats.Hits, - stats.Misses, - stats.Responses, - stats.Revokes, - stats.Goods, - stats.Unknowns, - } - } - } -} - -// HandleVarz will process HTTP requests for server information. -func (s *Server) HandleVarz(w http.ResponseWriter, r *http.Request) { - var rss, vss int64 - var pcpu float64 - - // We want to do that outside of the lock. - pse.ProcUsage(&pcpu, &rss, &vss) - - // In response to http requests, we want to minimize mem copies - // so we use an object stored in the server. Creating/collecting - // server metrics is done under server lock, but we don't want - // to marshal under that lock. Still, we need to prevent concurrent - // http requests to /varz to update s.varz while marshal is - // happening, so we need a new lock that serialize those http - // requests and include marshaling. - s.varzMu.Lock() - - // Use server lock to create/update the server's varz object. - s.mu.Lock() - var created bool - s.httpReqStats[VarzPath]++ - if s.varz == nil { - s.varz = s.createVarz(pcpu, rss) - created = true - } else { - s.updateVarzRuntimeFields(s.varz, false, pcpu, rss) - } - s.mu.Unlock() - // Since locking is jetStream -> Server, need to update jetstream - // varz outside of server lock. - - if js := s.getJetStream(); js != nil { - var v JetStreamVarz - // Work on stack variable - s.updateJszVarz(js, &v, created) - // Now update server's varz - s.mu.RLock() - sv := &s.varz.JetStream - if created { - sv.Config = v.Config - } - sv.Stats = v.Stats - sv.Meta = v.Meta - sv.Limits = v.Limits - s.mu.RUnlock() - } - - // Do the marshaling outside of server lock, but under varzMu lock. - b, err := json.MarshalIndent(s.varz, "", " ") - s.varzMu.Unlock() - - if err != nil { - s.Errorf("Error marshaling response to /varz request: %v", err) - } - - // Handle response - ResponseHandler(w, r, b) -} - -// GatewayzOptions are the options passed to Gatewayz() -type GatewayzOptions struct { - // Name will output only remote gateways with this name - Name string `json:"name"` - - // Accounts indicates if accounts with its interest should be included in the results. - Accounts bool `json:"accounts"` - - // AccountName will limit the list of accounts to that account name (makes Accounts implicit) - AccountName string `json:"account_name"` - - // AccountSubscriptions indicates if subscriptions should be included in the results. - // Note: This is used only if `Accounts` or `AccountName` are specified. - AccountSubscriptions bool `json:"subscriptions"` - - // AccountSubscriptionsDetail indicates if subscription details should be included in the results. - // Note: This is used only if `Accounts` or `AccountName` are specified. - AccountSubscriptionsDetail bool `json:"subscriptions_detail"` -} - -// Gatewayz represents detailed information on Gateways -type Gatewayz struct { - ID string `json:"server_id"` - Now time.Time `json:"now"` - Name string `json:"name,omitempty"` - Host string `json:"host,omitempty"` - Port int `json:"port,omitempty"` - OutboundGateways map[string]*RemoteGatewayz `json:"outbound_gateways"` - InboundGateways map[string][]*RemoteGatewayz `json:"inbound_gateways"` -} - -// RemoteGatewayz represents information about an outbound connection to a gateway -type RemoteGatewayz struct { - IsConfigured bool `json:"configured"` - Connection *ConnInfo `json:"connection,omitempty"` - Accounts []*AccountGatewayz `json:"accounts,omitempty"` -} - -// AccountGatewayz represents interest mode for this account -type AccountGatewayz struct { - Name string `json:"name"` - InterestMode string `json:"interest_mode"` - NoInterestCount int `json:"no_interest_count,omitempty"` - InterestOnlyThreshold int `json:"interest_only_threshold,omitempty"` - TotalSubscriptions int `json:"num_subs,omitempty"` - NumQueueSubscriptions int `json:"num_queue_subs,omitempty"` - Subs []string `json:"subscriptions_list,omitempty"` - SubsDetail []SubDetail `json:"subscriptions_list_detail,omitempty"` -} - -// Gatewayz returns a Gatewayz struct containing information about gateways. -func (s *Server) Gatewayz(opts *GatewayzOptions) (*Gatewayz, error) { - srvID := s.ID() - now := time.Now().UTC() - gw := s.gateway - gw.RLock() - if !gw.enabled || gw.info == nil { - gw.RUnlock() - gwz := &Gatewayz{ - ID: srvID, - Now: now, - OutboundGateways: map[string]*RemoteGatewayz{}, - InboundGateways: map[string][]*RemoteGatewayz{}, - } - return gwz, nil - } - // Here gateways are enabled, so fill up more. - gwz := &Gatewayz{ - ID: srvID, - Now: now, - Name: gw.name, - Host: gw.info.Host, - Port: gw.info.Port, - } - gw.RUnlock() - - gwz.OutboundGateways = s.createOutboundsRemoteGatewayz(opts, now) - gwz.InboundGateways = s.createInboundsRemoteGatewayz(opts, now) - - return gwz, nil -} - -// Based on give options struct, returns if there is a filtered -// Gateway Name and if we should do report Accounts. -// Note that if Accounts is false but AccountName is not empty, -// then Accounts is implicitly set to true. -func getMonitorGWOptions(opts *GatewayzOptions) (string, bool) { - var name string - var accs bool - if opts != nil { - if opts.Name != _EMPTY_ { - name = opts.Name - } - accs = opts.Accounts - if !accs && opts.AccountName != _EMPTY_ { - accs = true - } - } - return name, accs -} - -// Returns a map of gateways outbound connections. -// Based on options, will include a single or all gateways, -// with no/single/or all accounts interest information. -func (s *Server) createOutboundsRemoteGatewayz(opts *GatewayzOptions, now time.Time) map[string]*RemoteGatewayz { - targetGWName, doAccs := getMonitorGWOptions(opts) - - if targetGWName != _EMPTY_ { - c := s.getOutboundGatewayConnection(targetGWName) - if c == nil { - return nil - } - outbounds := make(map[string]*RemoteGatewayz, 1) - _, rgw := createOutboundRemoteGatewayz(c, opts, now, doAccs) - outbounds[targetGWName] = rgw - return outbounds - } - - var connsa [16]*client - var conns = connsa[:0] - - s.getOutboundGatewayConnections(&conns) - - outbounds := make(map[string]*RemoteGatewayz, len(conns)) - for _, c := range conns { - name, rgw := createOutboundRemoteGatewayz(c, opts, now, doAccs) - if rgw != nil { - outbounds[name] = rgw - } - } - return outbounds -} - -// Returns a RemoteGatewayz for a given outbound gw connection -func createOutboundRemoteGatewayz(c *client, opts *GatewayzOptions, now time.Time, doAccs bool) (string, *RemoteGatewayz) { - var name string - var rgw *RemoteGatewayz - - c.mu.Lock() - if c.gw != nil { - rgw = &RemoteGatewayz{} - if doAccs { - rgw.Accounts = createOutboundAccountsGatewayz(opts, c.gw) - } - if c.gw.cfg != nil { - rgw.IsConfigured = !c.gw.cfg.isImplicit() - } - rgw.Connection = &ConnInfo{} - rgw.Connection.fill(c, c.nc, now, false) - name = c.gw.name - } - c.mu.Unlock() - - return name, rgw -} - -// Returns the list of accounts for this outbound gateway connection. -// Based on the options, it will be a single or all accounts for -// this outbound. -func createOutboundAccountsGatewayz(opts *GatewayzOptions, gw *gateway) []*AccountGatewayz { - if gw.outsim == nil { - return nil - } - - var accName string - if opts != nil { - accName = opts.AccountName - } - if accName != _EMPTY_ { - ei, ok := gw.outsim.Load(accName) - if !ok { - return nil - } - a := createAccountOutboundGatewayz(opts, accName, ei) - return []*AccountGatewayz{a} - } - - accs := make([]*AccountGatewayz, 0, 4) - gw.outsim.Range(func(k, v any) bool { - name := k.(string) - a := createAccountOutboundGatewayz(opts, name, v) - accs = append(accs, a) - return true - }) - return accs -} - -// Returns an AccountGatewayz for this gateway outbound connection -func createAccountOutboundGatewayz(opts *GatewayzOptions, name string, ei any) *AccountGatewayz { - a := &AccountGatewayz{ - Name: name, - InterestOnlyThreshold: gatewayMaxRUnsubBeforeSwitch, - } - if ei != nil { - e := ei.(*outsie) - e.RLock() - a.InterestMode = e.mode.String() - a.NoInterestCount = len(e.ni) - a.NumQueueSubscriptions = e.qsubs - a.TotalSubscriptions = int(e.sl.Count()) - if opts.AccountSubscriptions || opts.AccountSubscriptionsDetail { - var subsa [4096]*subscription - subs := subsa[:0] - e.sl.All(&subs) - if opts.AccountSubscriptions { - a.Subs = make([]string, 0, len(subs)) - } else { - a.SubsDetail = make([]SubDetail, 0, len(subs)) - } - for _, sub := range subs { - if opts.AccountSubscriptions { - a.Subs = append(a.Subs, string(sub.subject)) - } else { - a.SubsDetail = append(a.SubsDetail, newClientSubDetail(sub)) - } - } - } - e.RUnlock() - } else { - a.InterestMode = Optimistic.String() - } - return a -} - -// Returns a map of gateways inbound connections. -// Each entry is an array of RemoteGatewayz since a given server -// may have more than one inbound from the same remote gateway. -// Based on options, will include a single or all gateways, -// with no/single/or all accounts interest information. -func (s *Server) createInboundsRemoteGatewayz(opts *GatewayzOptions, now time.Time) map[string][]*RemoteGatewayz { - targetGWName, doAccs := getMonitorGWOptions(opts) - - var connsa [16]*client - var conns = connsa[:0] - s.getInboundGatewayConnections(&conns) - - m := make(map[string][]*RemoteGatewayz) - for _, c := range conns { - c.mu.Lock() - if c.gw != nil && (targetGWName == _EMPTY_ || targetGWName == c.gw.name) { - igws := m[c.gw.name] - if igws == nil { - igws = make([]*RemoteGatewayz, 0, 2) - } - rgw := &RemoteGatewayz{} - if doAccs { - rgw.Accounts = createInboundAccountsGatewayz(opts, c.gw) - } - rgw.Connection = &ConnInfo{} - rgw.Connection.fill(c, c.nc, now, false) - igws = append(igws, rgw) - m[c.gw.name] = igws - } - c.mu.Unlock() - } - return m -} - -// Returns the list of accounts for this inbound gateway connection. -// Based on the options, it will be a single or all accounts for -// this inbound. -func createInboundAccountsGatewayz(opts *GatewayzOptions, gw *gateway) []*AccountGatewayz { - if gw.insim == nil { - return nil - } - - var accName string - if opts != nil { - accName = opts.AccountName - } - if accName != _EMPTY_ { - e, ok := gw.insim[accName] - if !ok { - return nil - } - a := createInboundAccountGatewayz(accName, e) - return []*AccountGatewayz{a} - } - - accs := make([]*AccountGatewayz, 0, 4) - for name, e := range gw.insim { - a := createInboundAccountGatewayz(name, e) - accs = append(accs, a) - } - return accs -} - -// Returns an AccountGatewayz for this gateway inbound connection -func createInboundAccountGatewayz(name string, e *insie) *AccountGatewayz { - a := &AccountGatewayz{ - Name: name, - InterestOnlyThreshold: gatewayMaxRUnsubBeforeSwitch, - } - if e != nil { - a.InterestMode = e.mode.String() - a.NoInterestCount = len(e.ni) - } else { - a.InterestMode = Optimistic.String() - } - return a -} - -// HandleGatewayz process HTTP requests for route information. -func (s *Server) HandleGatewayz(w http.ResponseWriter, r *http.Request) { - s.mu.Lock() - s.httpReqStats[GatewayzPath]++ - s.mu.Unlock() - - subs, subsDet, err := decodeSubs(w, r) - if err != nil { - return - } - accs, err := decodeBool(w, r, "accs") - if err != nil { - return - } - gwName := r.URL.Query().Get("gw_name") - accName := r.URL.Query().Get("acc_name") - if accName != _EMPTY_ { - accs = true - } - - opts := &GatewayzOptions{ - Name: gwName, - Accounts: accs, - AccountName: accName, - AccountSubscriptions: subs, - AccountSubscriptionsDetail: subsDet, - } - gw, err := s.Gatewayz(opts) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - b, err := json.MarshalIndent(gw, "", " ") - if err != nil { - s.Errorf("Error marshaling response to /gatewayz request: %v", err) - } - - // Handle response - ResponseHandler(w, r, b) -} - -// Leafz represents detailed information on Leafnodes. -type Leafz struct { - ID string `json:"server_id"` - Now time.Time `json:"now"` - NumLeafs int `json:"leafnodes"` - Leafs []*LeafInfo `json:"leafs"` -} - -// LeafzOptions are options passed to Leafz -type LeafzOptions struct { - // Subscriptions indicates that Leafz will return a leafnode's subscriptions - Subscriptions bool `json:"subscriptions"` - Account string `json:"account"` -} - -// LeafInfo has detailed information on each remote leafnode connection. -type LeafInfo struct { - Name string `json:"name"` - IsSpoke bool `json:"is_spoke"` - Account string `json:"account"` - IP string `json:"ip"` - Port int `json:"port"` - RTT string `json:"rtt,omitempty"` - InMsgs int64 `json:"in_msgs"` - OutMsgs int64 `json:"out_msgs"` - InBytes int64 `json:"in_bytes"` - OutBytes int64 `json:"out_bytes"` - NumSubs uint32 `json:"subscriptions"` - Subs []string `json:"subscriptions_list,omitempty"` - Compression string `json:"compression,omitempty"` -} - -// Leafz returns a Leafz structure containing information about leafnodes. -func (s *Server) Leafz(opts *LeafzOptions) (*Leafz, error) { - // Grab leafnodes - var lconns []*client - s.mu.Lock() - if len(s.leafs) > 0 { - lconns = make([]*client, 0, len(s.leafs)) - for _, ln := range s.leafs { - if opts != nil && opts.Account != _EMPTY_ { - ln.mu.Lock() - ok := ln.acc.Name == opts.Account - ln.mu.Unlock() - if !ok { - continue - } - } - lconns = append(lconns, ln) - } - } - s.mu.Unlock() - - leafnodes := make([]*LeafInfo, 0, len(lconns)) - - if len(lconns) > 0 { - for _, ln := range lconns { - ln.mu.Lock() - lni := &LeafInfo{ - Name: ln.leaf.remoteServer, - IsSpoke: ln.isSpokeLeafNode(), - Account: ln.acc.Name, - IP: ln.host, - Port: int(ln.port), - RTT: ln.getRTT().String(), - InMsgs: atomic.LoadInt64(&ln.inMsgs), - OutMsgs: ln.outMsgs, - InBytes: atomic.LoadInt64(&ln.inBytes), - OutBytes: ln.outBytes, - NumSubs: uint32(len(ln.subs)), - Compression: ln.leaf.compression, - } - if opts != nil && opts.Subscriptions { - lni.Subs = make([]string, 0, len(ln.subs)) - for _, sub := range ln.subs { - lni.Subs = append(lni.Subs, string(sub.subject)) - } - } - ln.mu.Unlock() - leafnodes = append(leafnodes, lni) - } - } - - return &Leafz{ - ID: s.ID(), - Now: time.Now().UTC(), - NumLeafs: len(leafnodes), - Leafs: leafnodes, - }, nil -} - -// HandleLeafz process HTTP requests for leafnode information. -func (s *Server) HandleLeafz(w http.ResponseWriter, r *http.Request) { - s.mu.Lock() - s.httpReqStats[LeafzPath]++ - s.mu.Unlock() - - subs, err := decodeBool(w, r, "subs") - if err != nil { - return - } - l, err := s.Leafz(&LeafzOptions{subs, r.URL.Query().Get("acc")}) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - b, err := json.MarshalIndent(l, "", " ") - if err != nil { - s.Errorf("Error marshaling response to /leafz request: %v", err) - } - - // Handle response - ResponseHandler(w, r, b) -} - -// Leafz represents detailed information on Leafnodes. -type AccountStatz struct { - ID string `json:"server_id"` - Now time.Time `json:"now"` - Accounts []*AccountStat `json:"account_statz"` -} - -// AccountStatzOptions are options passed to account stats requests. -type AccountStatzOptions struct { - Accounts []string `json:"accounts"` - IncludeUnused bool `json:"include_unused"` -} - -// Leafz returns a AccountStatz structure containing summary information about accounts. -func (s *Server) AccountStatz(opts *AccountStatzOptions) (*AccountStatz, error) { - stz := &AccountStatz{ - ID: s.ID(), - Now: time.Now().UTC(), - Accounts: []*AccountStat{}, - } - if opts == nil || len(opts.Accounts) == 0 { - s.accounts.Range(func(key, a any) bool { - acc := a.(*Account) - acc.mu.RLock() - if opts.IncludeUnused || acc.numLocalConnections() != 0 { - stz.Accounts = append(stz.Accounts, acc.statz()) - } - acc.mu.RUnlock() - return true - }) - } else { - for _, a := range opts.Accounts { - if acc, ok := s.accounts.Load(a); ok { - acc := acc.(*Account) - acc.mu.RLock() - if opts.IncludeUnused || acc.numLocalConnections() != 0 { - stz.Accounts = append(stz.Accounts, acc.statz()) - } - acc.mu.RUnlock() - } - } - } - return stz, nil -} - -// HandleAccountStatz process HTTP requests for statz information of all accounts. -func (s *Server) HandleAccountStatz(w http.ResponseWriter, r *http.Request) { - s.mu.Lock() - s.httpReqStats[AccountStatzPath]++ - s.mu.Unlock() - - unused, err := decodeBool(w, r, "unused") - if err != nil { - return - } - - l, err := s.AccountStatz(&AccountStatzOptions{IncludeUnused: unused}) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - b, err := json.MarshalIndent(l, "", " ") - if err != nil { - s.Errorf("Error marshaling response to %s request: %v", AccountStatzPath, err) - return - } - - // Handle response - ResponseHandler(w, r, b) -} - -// ResponseHandler handles responses for monitoring routes. -func ResponseHandler(w http.ResponseWriter, r *http.Request, data []byte) { - handleResponse(http.StatusOK, w, r, data) -} - -// handleResponse handles responses for monitoring routes with a specific HTTP status code. -func handleResponse(code int, w http.ResponseWriter, r *http.Request, data []byte) { - // Get callback from request - callback := r.URL.Query().Get("callback") - if callback != _EMPTY_ { - // Response for JSONP - w.Header().Set("Content-Type", "application/javascript") - w.WriteHeader(code) - fmt.Fprintf(w, "%s(%s)", callback, data) - } else { - // Otherwise JSON - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Access-Control-Allow-Origin", "*") - w.WriteHeader(code) - w.Write(data) - } -} - -func (reason ClosedState) String() string { - switch reason { - case ClientClosed: - return "Client Closed" - case AuthenticationTimeout: - return "Authentication Timeout" - case AuthenticationViolation: - return "Authentication Failure" - case TLSHandshakeError: - return "TLS Handshake Failure" - case SlowConsumerPendingBytes: - return "Slow Consumer (Pending Bytes)" - case SlowConsumerWriteDeadline: - return "Slow Consumer (Write Deadline)" - case WriteError: - return "Write Error" - case ReadError: - return "Read Error" - case ParseError: - return "Parse Error" - case StaleConnection: - return "Stale Connection" - case ProtocolViolation: - return "Protocol Violation" - case BadClientProtocolVersion: - return "Bad Client Protocol Version" - case WrongPort: - return "Incorrect Port" - case MaxConnectionsExceeded: - return "Maximum Connections Exceeded" - case MaxAccountConnectionsExceeded: - return "Maximum Account Connections Exceeded" - case MaxPayloadExceeded: - return "Maximum Message Payload Exceeded" - case MaxControlLineExceeded: - return "Maximum Control Line Exceeded" - case MaxSubscriptionsExceeded: - return "Maximum Subscriptions Exceeded" - case DuplicateRoute: - return "Duplicate Route" - case RouteRemoved: - return "Route Removed" - case ServerShutdown: - return "Server Shutdown" - case AuthenticationExpired: - return "Authentication Expired" - case WrongGateway: - return "Wrong Gateway" - case MissingAccount: - return "Missing Account" - case Revocation: - return "Credentials Revoked" - case InternalClient: - return "Internal Client" - case MsgHeaderViolation: - return "Message Header Violation" - case NoRespondersRequiresHeaders: - return "No Responders Requires Headers" - case ClusterNameConflict: - return "Cluster Name Conflict" - case DuplicateRemoteLeafnodeConnection: - return "Duplicate Remote LeafNode Connection" - case DuplicateClientID: - return "Duplicate Client ID" - case DuplicateServerName: - return "Duplicate Server Name" - case MinimumVersionRequired: - return "Minimum Version Required" - case ClusterNamesIdentical: - return "Cluster Names Identical" - case Kicked: - return "Kicked" - } - - return "Unknown State" -} - -// AccountzOptions are options passed to Accountz -type AccountzOptions struct { - // Account indicates that Accountz will return details for the account - Account string `json:"account"` -} - -func newExtServiceLatency(l *serviceLatency) *jwt.ServiceLatency { - if l == nil { - return nil - } - return &jwt.ServiceLatency{ - Sampling: jwt.SamplingRate(l.sampling), - Results: jwt.Subject(l.subject), - } -} - -type ExtImport struct { - jwt.Import - Invalid bool `json:"invalid"` - Share bool `json:"share"` - Tracking bool `json:"tracking"` - TrackingHdr http.Header `json:"tracking_header,omitempty"` - Latency *jwt.ServiceLatency `json:"latency,omitempty"` - M1 *ServiceLatency `json:"m1,omitempty"` -} - -type ExtExport struct { - jwt.Export - ApprovedAccounts []string `json:"approved_accounts,omitempty"` - RevokedAct map[string]time.Time `json:"revoked_activations,omitempty"` -} - -type ExtVrIssues struct { - Description string `json:"description"` - Blocking bool `json:"blocking"` - Time bool `json:"time_check"` -} - -type ExtMap map[string][]*MapDest - -type AccountInfo struct { - AccountName string `json:"account_name"` - LastUpdate time.Time `json:"update_time,omitempty"` - IsSystem bool `json:"is_system,omitempty"` - Expired bool `json:"expired"` - Complete bool `json:"complete"` - JetStream bool `json:"jetstream_enabled"` - LeafCnt int `json:"leafnode_connections"` - ClientCnt int `json:"client_connections"` - SubCnt uint32 `json:"subscriptions"` - Mappings ExtMap `json:"mappings,omitempty"` - Exports []ExtExport `json:"exports,omitempty"` - Imports []ExtImport `json:"imports,omitempty"` - Jwt string `json:"jwt,omitempty"` - IssuerKey string `json:"issuer_key,omitempty"` - NameTag string `json:"name_tag,omitempty"` - Tags jwt.TagList `json:"tags,omitempty"` - Claim *jwt.AccountClaims `json:"decoded_jwt,omitempty"` - Vr []ExtVrIssues `json:"validation_result_jwt,omitempty"` - RevokedUser map[string]time.Time `json:"revoked_user,omitempty"` - Sublist *SublistStats `json:"sublist_stats,omitempty"` - Responses map[string]ExtImport `json:"responses,omitempty"` -} - -type Accountz struct { - ID string `json:"server_id"` - Now time.Time `json:"now"` - SystemAccount string `json:"system_account,omitempty"` - Accounts []string `json:"accounts,omitempty"` - Account *AccountInfo `json:"account_detail,omitempty"` -} - -// HandleAccountz process HTTP requests for account information. -func (s *Server) HandleAccountz(w http.ResponseWriter, r *http.Request) { - s.mu.Lock() - s.httpReqStats[AccountzPath]++ - s.mu.Unlock() - if l, err := s.Accountz(&AccountzOptions{r.URL.Query().Get("acc")}); err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - } else if b, err := json.MarshalIndent(l, "", " "); err != nil { - s.Errorf("Error marshaling response to %s request: %v", AccountzPath, err) - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - } else { - ResponseHandler(w, r, b) // Handle response - } -} - -func (s *Server) Accountz(optz *AccountzOptions) (*Accountz, error) { - a := &Accountz{ - ID: s.ID(), - Now: time.Now().UTC(), - } - if sacc := s.SystemAccount(); sacc != nil { - a.SystemAccount = sacc.GetName() - } - if optz == nil || optz.Account == _EMPTY_ { - a.Accounts = []string{} - s.accounts.Range(func(key, value any) bool { - a.Accounts = append(a.Accounts, key.(string)) - return true - }) - return a, nil - } - aInfo, err := s.accountInfo(optz.Account) - if err != nil { - return nil, err - } - a.Account = aInfo - return a, nil -} - -func newExtImport(v *serviceImport) ExtImport { - imp := ExtImport{ - Invalid: true, - Import: jwt.Import{Type: jwt.Service}, - } - if v != nil { - imp.Share = v.share - imp.Tracking = v.tracking - imp.Invalid = v.invalid - imp.Import = jwt.Import{ - Subject: jwt.Subject(v.to), - Account: v.acc.Name, - Type: jwt.Service, - // Deprecated so we duplicate. Use LocalSubject. - To: jwt.Subject(v.from), - LocalSubject: jwt.RenamingSubject(v.from), - } - imp.TrackingHdr = v.trackingHdr - imp.Latency = newExtServiceLatency(v.latency) - if v.m1 != nil { - m1 := *v.m1 - imp.M1 = &m1 - } - } - return imp -} - -func (s *Server) accountInfo(accName string) (*AccountInfo, error) { - var a *Account - if v, ok := s.accounts.Load(accName); !ok { - return nil, fmt.Errorf("Account %s does not exist", accName) - } else { - a = v.(*Account) - } - isSys := a == s.SystemAccount() - a.mu.RLock() - defer a.mu.RUnlock() - var vrIssues []ExtVrIssues - claim, _ := jwt.DecodeAccountClaims(a.claimJWT) // ignore error - if claim != nil { - vr := jwt.ValidationResults{} - claim.Validate(&vr) - vrIssues = make([]ExtVrIssues, len(vr.Issues)) - for i, v := range vr.Issues { - vrIssues[i] = ExtVrIssues{v.Description, v.Blocking, v.TimeCheck} - } - } - collectRevocations := func(revocations map[string]int64) map[string]time.Time { - l := len(revocations) - if l == 0 { - return nil - } - rev := make(map[string]time.Time, l) - for k, v := range revocations { - rev[k] = time.Unix(v, 0) - } - return rev - } - exports := []ExtExport{} - for k, v := range a.exports.services { - e := ExtExport{ - Export: jwt.Export{ - Subject: jwt.Subject(k), - Type: jwt.Service, - }, - ApprovedAccounts: []string{}, - } - if v != nil { - e.Latency = newExtServiceLatency(v.latency) - e.TokenReq = v.tokenReq - e.ResponseType = jwt.ResponseType(v.respType.String()) - for name := range v.approved { - e.ApprovedAccounts = append(e.ApprovedAccounts, name) - } - e.RevokedAct = collectRevocations(v.actsRevoked) - } - exports = append(exports, e) - } - for k, v := range a.exports.streams { - e := ExtExport{ - Export: jwt.Export{ - Subject: jwt.Subject(k), - Type: jwt.Stream, - }, - ApprovedAccounts: []string{}, - } - if v != nil { - e.TokenReq = v.tokenReq - for name := range v.approved { - e.ApprovedAccounts = append(e.ApprovedAccounts, name) - } - e.RevokedAct = collectRevocations(v.actsRevoked) - } - exports = append(exports, e) - } - imports := []ExtImport{} - for _, v := range a.imports.streams { - imp := ExtImport{ - Invalid: true, - Import: jwt.Import{Type: jwt.Stream}, - } - if v != nil { - imp.Invalid = v.invalid - imp.Import = jwt.Import{ - Subject: jwt.Subject(v.from), - Account: v.acc.Name, - Type: jwt.Stream, - LocalSubject: jwt.RenamingSubject(v.to), - } - } - imports = append(imports, imp) - } - for _, v := range a.imports.services { - imports = append(imports, newExtImport(v)) - } - responses := map[string]ExtImport{} - for k, v := range a.exports.responses { - responses[k] = newExtImport(v) - } - mappings := ExtMap{} - for _, m := range a.mappings { - var dests []*MapDest - var src string - if m == nil { - src = "nil" - if _, ok := mappings[src]; ok { // only set if not present (keep orig in case nil is used) - continue - } - dests = append(dests, &MapDest{}) - } else { - src = m.src - for _, d := range m.dests { - dests = append(dests, &MapDest{d.tr.dest, d.weight, _EMPTY_}) - } - for c, cd := range m.cdests { - for _, d := range cd { - dests = append(dests, &MapDest{d.tr.dest, d.weight, c}) - } - } - } - mappings[src] = dests - } - return &AccountInfo{ - AccountName: accName, - LastUpdate: a.updated.UTC(), - IsSystem: isSys, - Expired: a.expired.Load(), - Complete: !a.incomplete, - JetStream: a.js != nil, - LeafCnt: a.numLocalLeafNodes(), - ClientCnt: a.numLocalConnections(), - SubCnt: a.sl.Count(), - Mappings: mappings, - Exports: exports, - Imports: imports, - Jwt: a.claimJWT, - IssuerKey: a.Issuer, - NameTag: a.getNameTagLocked(), - Tags: a.tags, - Claim: claim, - Vr: vrIssues, - RevokedUser: collectRevocations(a.usersRevoked), - Sublist: a.sl.Stats(), - Responses: responses, - }, nil -} - -// JSzOptions are options passed to Jsz -type JSzOptions struct { - Account string `json:"account,omitempty"` - Accounts bool `json:"accounts,omitempty"` - Streams bool `json:"streams,omitempty"` - Consumer bool `json:"consumer,omitempty"` - Config bool `json:"config,omitempty"` - LeaderOnly bool `json:"leader_only,omitempty"` - Offset int `json:"offset,omitempty"` - Limit int `json:"limit,omitempty"` - RaftGroups bool `json:"raft,omitempty"` - StreamLeaderOnly bool `json:"stream_leader_only,omitempty"` -} - -// HealthzOptions are options passed to Healthz -type HealthzOptions struct { - // Deprecated: Use JSEnabledOnly instead - JSEnabled bool `json:"js-enabled,omitempty"` - JSEnabledOnly bool `json:"js-enabled-only,omitempty"` - JSServerOnly bool `json:"js-server-only,omitempty"` - JSMetaOnly bool `json:"js-meta-only,omitempty"` - Account string `json:"account,omitempty"` - Stream string `json:"stream,omitempty"` - Consumer string `json:"consumer,omitempty"` - Details bool `json:"details,omitempty"` -} - -// ProfilezOptions are options passed to Profilez -type ProfilezOptions struct { - Name string `json:"name"` - Debug int `json:"debug"` - Duration time.Duration `json:"duration,omitempty"` -} - -// IpqueueszOptions are options passed to Ipqueuesz -type IpqueueszOptions struct { - All bool `json:"all"` - Filter string `json:"filter"` -} - -// RaftzOptions are options passed to Raftz -type RaftzOptions struct { - AccountFilter string `json:"account"` - GroupFilter string `json:"group"` -} - -// StreamDetail shows information about the stream state and its consumers. -type StreamDetail struct { - Name string `json:"name"` - Created time.Time `json:"created"` - Cluster *ClusterInfo `json:"cluster,omitempty"` - Config *StreamConfig `json:"config,omitempty"` - State StreamState `json:"state,omitempty"` - Consumer []*ConsumerInfo `json:"consumer_detail,omitempty"` - Mirror *StreamSourceInfo `json:"mirror,omitempty"` - Sources []*StreamSourceInfo `json:"sources,omitempty"` - RaftGroup string `json:"stream_raft_group,omitempty"` - ConsumerRaftGroups []*RaftGroupDetail `json:"consumer_raft_groups,omitempty"` -} - -// RaftGroupDetail shows information details about the Raft group. -type RaftGroupDetail struct { - Name string `json:"name"` - RaftGroup string `json:"raft_group,omitempty"` -} - -type AccountDetail struct { - Name string `json:"name"` - Id string `json:"id"` - JetStreamStats - Streams []StreamDetail `json:"stream_detail,omitempty"` -} - -// MetaClusterInfo shows information about the meta group. -type MetaClusterInfo struct { - Name string `json:"name,omitempty"` - Leader string `json:"leader,omitempty"` - Peer string `json:"peer,omitempty"` - Replicas []*PeerInfo `json:"replicas,omitempty"` - Size int `json:"cluster_size"` - Pending int `json:"pending"` -} - -// JSInfo has detailed information on JetStream. -type JSInfo struct { - ID string `json:"server_id"` - Now time.Time `json:"now"` - Disabled bool `json:"disabled,omitempty"` - Config JetStreamConfig `json:"config,omitempty"` - Limits *JSLimitOpts `json:"limits,omitempty"` - JetStreamStats - Streams int `json:"streams"` - Consumers int `json:"consumers"` - Messages uint64 `json:"messages"` - Bytes uint64 `json:"bytes"` - Meta *MetaClusterInfo `json:"meta_cluster,omitempty"` - - // aggregate raft info - AccountDetails []*AccountDetail `json:"account_details,omitempty"` -} - -func (s *Server) accountDetail(jsa *jsAccount, optStreams, optConsumers, optCfg, optRaft, optStreamLeader bool) *AccountDetail { - jsa.mu.RLock() - acc := jsa.account - name := acc.GetName() - id := name - if acc.nameTag != _EMPTY_ { - name = acc.nameTag - } - jsa.usageMu.RLock() - totalMem, totalStore := jsa.storageTotals() - detail := AccountDetail{ - Name: name, - Id: id, - JetStreamStats: JetStreamStats{ - Memory: totalMem, - Store: totalStore, - API: JetStreamAPIStats{ - Total: jsa.apiTotal, - Errors: jsa.apiErrors, - }, - }, - Streams: make([]StreamDetail, 0, len(jsa.streams)), - } - if reserved, ok := jsa.limits[_EMPTY_]; ok { - detail.JetStreamStats.ReservedMemory = uint64(reserved.MaxMemory) - detail.JetStreamStats.ReservedStore = uint64(reserved.MaxStore) - } - jsa.usageMu.RUnlock() - - var streams []*stream - if optStreams { - for _, stream := range jsa.streams { - streams = append(streams, stream) - } - } - jsa.mu.RUnlock() - - if js := s.getJetStream(); js != nil && optStreams { - for _, stream := range streams { - rgroup := stream.raftGroup() - ci := js.clusterInfo(rgroup) - var cfg *StreamConfig - if optCfg { - c := stream.config() - cfg = &c - } - // Skip if we are only looking for stream leaders. - if optStreamLeader && ci != nil && ci.Leader != s.Name() { - continue - } - sdet := StreamDetail{ - Name: stream.name(), - Created: stream.createdTime(), - State: stream.state(), - Cluster: ci, - Config: cfg, - Mirror: stream.mirrorInfo(), - Sources: stream.sourcesInfo(), - } - if optRaft && rgroup != nil { - sdet.RaftGroup = rgroup.Name - sdet.ConsumerRaftGroups = make([]*RaftGroupDetail, 0) - } - if optConsumers { - for _, consumer := range stream.getPublicConsumers() { - cInfo := consumer.info() - if cInfo == nil { - continue - } - if !optCfg { - cInfo.Config = nil - } - sdet.Consumer = append(sdet.Consumer, cInfo) - if optRaft { - crgroup := consumer.raftGroup() - if crgroup != nil { - sdet.ConsumerRaftGroups = append(sdet.ConsumerRaftGroups, - &RaftGroupDetail{cInfo.Name, crgroup.Name}, - ) - } - } - } - } - detail.Streams = append(detail.Streams, sdet) - } - } - return &detail -} - -func (s *Server) JszAccount(opts *JSzOptions) (*AccountDetail, error) { - js := s.getJetStream() - if js == nil { - return nil, fmt.Errorf("jetstream not enabled") - } - acc := opts.Account - account, ok := s.accounts.Load(acc) - if !ok { - return nil, fmt.Errorf("account %q not found", acc) - } - js.mu.RLock() - jsa, ok := js.accounts[account.(*Account).Name] - js.mu.RUnlock() - if !ok { - return nil, fmt.Errorf("account %q not jetstream enabled", acc) - } - return s.accountDetail(jsa, opts.Streams, opts.Consumer, opts.Config, opts.RaftGroups, opts.StreamLeaderOnly), nil -} - -// helper to get cluster info from node via dummy group -func (s *Server) raftNodeToClusterInfo(node RaftNode) *ClusterInfo { - if node == nil { - return nil - } - peers := node.Peers() - peerList := make([]string, len(peers)) - for i, p := range peers { - peerList[i] = p.ID - } - group := &raftGroup{ - Name: _EMPTY_, - Peers: peerList, - node: node, - } - return s.getJetStream().clusterInfo(group) -} - -// Jsz returns a Jsz structure containing information about JetStream. -func (s *Server) Jsz(opts *JSzOptions) (*JSInfo, error) { - // set option defaults - if opts == nil { - opts = &JSzOptions{} - } - if opts.Limit == 0 { - opts.Limit = 1024 - } - if opts.Consumer { - opts.Streams = true - } - if opts.Streams && opts.Account == _EMPTY_ { - opts.Accounts = true - } - - jsi := &JSInfo{ - ID: s.ID(), - Now: time.Now().UTC(), - } - - js := s.getJetStream() - if js == nil || !js.isEnabled() { - if opts.LeaderOnly { - return nil, fmt.Errorf("%w: not leader", errSkipZreq) - } - - jsi.Disabled = true - return jsi, nil - } - - jsi.Limits = &s.getOpts().JetStreamLimits - - js.mu.RLock() - isLeader := js.cluster == nil || js.cluster.isLeader() - js.mu.RUnlock() - - if opts.LeaderOnly && !isLeader { - return nil, fmt.Errorf("%w: not leader", errSkipZreq) - } - - var accounts []*jsAccount - - js.mu.RLock() - jsi.Config = js.config - for _, info := range js.accounts { - accounts = append(accounts, info) - } - js.mu.RUnlock() - - if mg := js.getMetaGroup(); mg != nil { - if ci := s.raftNodeToClusterInfo(mg); ci != nil { - jsi.Meta = &MetaClusterInfo{Name: ci.Name, Leader: ci.Leader, Peer: getHash(ci.Leader), Size: mg.ClusterSize()} - if isLeader { - jsi.Meta.Replicas = ci.Replicas - } - if ipq := s.jsAPIRoutedReqs; ipq != nil { - jsi.Meta.Pending = ipq.len() - } - } - } - - jsi.JetStreamStats = *js.usageStats() - - filterIdx := -1 - for i, jsa := range accounts { - if jsa.acc().GetName() == opts.Account { - filterIdx = i - } - jsa.mu.RLock() - streams := make([]*stream, 0, len(jsa.streams)) - for _, stream := range jsa.streams { - streams = append(streams, stream) - } - jsa.mu.RUnlock() - jsi.Streams += len(streams) - for _, stream := range streams { - streamState := stream.state() - jsi.Messages += streamState.Msgs - jsi.Bytes += streamState.Bytes - jsi.Consumers += streamState.Consumers - } - } - - // filter logic - if filterIdx != -1 { - accounts = []*jsAccount{accounts[filterIdx]} - } else if opts.Accounts { - if opts.Offset != 0 { - slices.SortFunc(accounts, func(i, j *jsAccount) int { return cmp.Compare(i.acc().Name, j.acc().Name) }) - if opts.Offset > len(accounts) { - accounts = []*jsAccount{} - } else { - accounts = accounts[opts.Offset:] - } - } - if opts.Limit != 0 { - if opts.Limit < len(accounts) { - accounts = accounts[:opts.Limit] - } - } - } else { - accounts = []*jsAccount{} - } - if len(accounts) > 0 { - jsi.AccountDetails = make([]*AccountDetail, 0, len(accounts)) - } - // if wanted, obtain accounts/streams/consumer - for _, jsa := range accounts { - detail := s.accountDetail(jsa, opts.Streams, opts.Consumer, opts.Config, opts.RaftGroups, opts.StreamLeaderOnly) - jsi.AccountDetails = append(jsi.AccountDetails, detail) - } - return jsi, nil -} - -// HandleJsz process HTTP requests for jetstream information. -func (s *Server) HandleJsz(w http.ResponseWriter, r *http.Request) { - s.mu.Lock() - s.httpReqStats[JszPath]++ - s.mu.Unlock() - accounts, err := decodeBool(w, r, "accounts") - if err != nil { - return - } - streams, err := decodeBool(w, r, "streams") - if err != nil { - return - } - consumers, err := decodeBool(w, r, "consumers") - if err != nil { - return - } - config, err := decodeBool(w, r, "config") - if err != nil { - return - } - offset, err := decodeInt(w, r, "offset") - if err != nil { - return - } - limit, err := decodeInt(w, r, "limit") - if err != nil { - return - } - leader, err := decodeBool(w, r, "leader-only") - if err != nil { - return - } - rgroups, err := decodeBool(w, r, "raft") - if err != nil { - return - } - - sleader, err := decodeBool(w, r, "stream-leader-only") - if err != nil { - return - } - - l, err := s.Jsz(&JSzOptions{ - Account: r.URL.Query().Get("acc"), - Accounts: accounts, - Streams: streams, - Consumer: consumers, - Config: config, - LeaderOnly: leader, - Offset: offset, - Limit: limit, - RaftGroups: rgroups, - StreamLeaderOnly: sleader, - }) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(err.Error())) - return - } - b, err := json.MarshalIndent(l, "", " ") - if err != nil { - s.Errorf("Error marshaling response to /jsz request: %v", err) - } - - // Handle response - ResponseHandler(w, r, b) -} - -type HealthStatus struct { - Status string `json:"status"` - StatusCode int `json:"status_code,omitempty"` - Error string `json:"error,omitempty"` - Errors []HealthzError `json:"errors,omitempty"` -} - -type HealthzError struct { - Type HealthZErrorType `json:"type"` - Account string `json:"account,omitempty"` - Stream string `json:"stream,omitempty"` - Consumer string `json:"consumer,omitempty"` - Error string `json:"error,omitempty"` -} - -type HealthZErrorType int - -const ( - HealthzErrorConn HealthZErrorType = iota - HealthzErrorBadRequest - HealthzErrorJetStream - HealthzErrorAccount - HealthzErrorStream - HealthzErrorConsumer -) - -func (t HealthZErrorType) String() string { - switch t { - case HealthzErrorConn: - return "CONNECTION" - case HealthzErrorBadRequest: - return "BAD_REQUEST" - case HealthzErrorJetStream: - return "JETSTREAM" - case HealthzErrorAccount: - return "ACCOUNT" - case HealthzErrorStream: - return "STREAM" - case HealthzErrorConsumer: - return "CONSUMER" - default: - return "unknown" - } -} - -func (t HealthZErrorType) MarshalJSON() ([]byte, error) { - return json.Marshal(t.String()) -} - -func (t *HealthZErrorType) UnmarshalJSON(data []byte) error { - switch string(data) { - case `"CONNECTION"`: - *t = HealthzErrorConn - case `"BAD_REQUEST"`: - *t = HealthzErrorBadRequest - case `"JETSTREAM"`: - *t = HealthzErrorJetStream - case `"ACCOUNT"`: - *t = HealthzErrorAccount - case `"STREAM"`: - *t = HealthzErrorStream - case `"CONSUMER"`: - *t = HealthzErrorConsumer - default: - return fmt.Errorf("unknown healthz error type %q", data) - } - return nil -} - -// https://datatracker.ietf.org/doc/html/draft-inadarei-api-health-check -func (s *Server) HandleHealthz(w http.ResponseWriter, r *http.Request) { - s.mu.Lock() - s.httpReqStats[HealthzPath]++ - s.mu.Unlock() - - jsEnabled, err := decodeBool(w, r, "js-enabled") - if err != nil { - return - } - if jsEnabled { - s.Warnf("Healthcheck: js-enabled deprecated, use js-enabled-only instead") - } - jsEnabledOnly, err := decodeBool(w, r, "js-enabled-only") - if err != nil { - return - } - jsServerOnly, err := decodeBool(w, r, "js-server-only") - if err != nil { - return - } - jsMetaOnly, err := decodeBool(w, r, "js-meta-only") - if err != nil { - return - } - - includeDetails, err := decodeBool(w, r, "details") - if err != nil { - return - } - - hs := s.healthz(&HealthzOptions{ - JSEnabled: jsEnabled, - JSEnabledOnly: jsEnabledOnly, - JSServerOnly: jsServerOnly, - JSMetaOnly: jsMetaOnly, - Account: r.URL.Query().Get("account"), - Stream: r.URL.Query().Get("stream"), - Consumer: r.URL.Query().Get("consumer"), - Details: includeDetails, - }) - - code := hs.StatusCode - if hs.Error != _EMPTY_ { - s.Warnf("Healthcheck failed: %q", hs.Error) - } else if len(hs.Errors) != 0 { - s.Warnf("Healthcheck failed: %d errors", len(hs.Errors)) - } - // Remove StatusCode from JSON representation when responding via HTTP - // since this is already in the response. - hs.StatusCode = 0 - b, err := json.Marshal(hs) - if err != nil { - s.Errorf("Error marshaling response to /healthz request: %v", err) - } - - handleResponse(code, w, r, b) -} - -// Generate health status. -func (s *Server) healthz(opts *HealthzOptions) *HealthStatus { - var health = &HealthStatus{Status: "ok"} - - // set option defaults - if opts == nil { - opts = &HealthzOptions{} - } - details := opts.Details - defer func() { - // for response with details enabled, set status to either "error" or "ok" - if details { - if len(health.Errors) != 0 { - health.Status = "error" - } else { - health.Status = "ok" - } - } - // if no specific status code was set, set it based on the presence of errors - if health.StatusCode == 0 { - if health.Error != _EMPTY_ || len(health.Errors) != 0 { - health.StatusCode = http.StatusServiceUnavailable - } else { - health.StatusCode = http.StatusOK - } - } - }() - - if opts.Account == _EMPTY_ && opts.Stream != _EMPTY_ { - health.StatusCode = http.StatusBadRequest - if !details { - health.Status = "error" - health.Error = fmt.Sprintf("%q must not be empty when checking stream health", "account") - } else { - health.Errors = append(health.Errors, HealthzError{ - Type: HealthzErrorBadRequest, - Error: fmt.Sprintf("%q must not be empty when checking stream health", "account"), - }) - } - return health - } - - if opts.Stream == _EMPTY_ && opts.Consumer != _EMPTY_ { - health.StatusCode = http.StatusBadRequest - if !details { - health.Status = "error" - health.Error = fmt.Sprintf("%q must not be empty when checking consumer health", "stream") - } else { - health.Errors = append(health.Errors, HealthzError{ - Type: HealthzErrorBadRequest, - Error: fmt.Sprintf("%q must not be empty when checking consumer health", "stream"), - }) - } - return health - } - - if err := s.readyForConnections(time.Millisecond); err != nil { - health.StatusCode = http.StatusInternalServerError - health.Status = "error" - if !details { - health.Error = err.Error() - } else { - health.Errors = append(health.Errors, HealthzError{ - Type: HealthzErrorConn, - Error: err.Error(), - }) - } - return health - } - - // If JSServerOnly is true, then do not check further accounts, streams and consumers. - if opts.JSServerOnly { - return health - } - - sopts := s.getOpts() - - // If JS is not enabled in the config, we stop. - if !sopts.JetStream { - return health - } - - // Access the Jetstream state to perform additional checks. - js := s.getJetStream() - const na = "unavailable" - if !js.isEnabled() { - health.StatusCode = http.StatusServiceUnavailable - health.Status = na - if !details { - health.Error = NewJSNotEnabledError().Error() - } else { - health.Errors = append(health.Errors, HealthzError{ - Type: HealthzErrorJetStream, - Error: NewJSNotEnabledError().Error(), - }) - } - return health - } - // Only check if JS is enabled, skip meta and asset check. - if opts.JSEnabledOnly || opts.JSEnabled { - return health - } - - // Clustered JetStream - js.mu.RLock() - cc := js.cluster - js.mu.RUnlock() - - // Currently single server we make sure the streams were recovered. - if cc == nil { - sdir := js.config.StoreDir - // Whip through account folders and pull each stream name. - fis, _ := os.ReadDir(sdir) - var accFound, streamFound, consumerFound bool - for _, fi := range fis { - if fi.Name() == snapStagingDir { - continue - } - if opts.Account != _EMPTY_ { - if fi.Name() != opts.Account { - continue - } - accFound = true - } - acc, err := s.LookupAccount(fi.Name()) - if err != nil { - if !details { - health.Status = na - health.Error = fmt.Sprintf("JetStream account '%s' could not be resolved", fi.Name()) - return health - } - health.Errors = append(health.Errors, HealthzError{ - Type: HealthzErrorAccount, - Account: fi.Name(), - Error: fmt.Sprintf("JetStream account '%s' could not be resolved", fi.Name()), - }) - continue - } - sfis, _ := os.ReadDir(filepath.Join(sdir, fi.Name(), "streams")) - for _, sfi := range sfis { - if opts.Stream != _EMPTY_ { - if sfi.Name() != opts.Stream { - continue - } - streamFound = true - } - stream := sfi.Name() - s, err := acc.lookupStream(stream) - if err != nil { - if !details { - health.Status = na - health.Error = fmt.Sprintf("JetStream stream '%s > %s' could not be recovered", acc, stream) - return health - } - health.Errors = append(health.Errors, HealthzError{ - Type: HealthzErrorStream, - Account: acc.Name, - Stream: stream, - Error: fmt.Sprintf("JetStream stream '%s > %s' could not be recovered", acc, stream), - }) - continue - } - if streamFound { - // if consumer option is passed, verify that the consumer exists on stream - if opts.Consumer != _EMPTY_ { - for _, cons := range s.consumers { - if cons.name == opts.Consumer { - consumerFound = true - break - } - } - } - break - } - } - if accFound { - break - } - } - if opts.Account != _EMPTY_ && !accFound { - health.StatusCode = http.StatusNotFound - if !details { - health.Status = na - health.Error = fmt.Sprintf("JetStream account %q not found", opts.Account) - } else { - health.Errors = []HealthzError{ - { - Type: HealthzErrorAccount, - Account: opts.Account, - Error: fmt.Sprintf("JetStream account %q not found", opts.Account), - }, - } - } - return health - } - if opts.Stream != _EMPTY_ && !streamFound { - health.StatusCode = http.StatusNotFound - if !details { - health.Status = na - health.Error = fmt.Sprintf("JetStream stream %q not found on account %q", opts.Stream, opts.Account) - } else { - health.Errors = []HealthzError{ - { - Type: HealthzErrorStream, - Account: opts.Account, - Stream: opts.Stream, - Error: fmt.Sprintf("JetStream stream %q not found on account %q", opts.Stream, opts.Account), - }, - } - } - return health - } - if opts.Consumer != _EMPTY_ && !consumerFound { - health.StatusCode = http.StatusNotFound - if !details { - health.Status = na - health.Error = fmt.Sprintf("JetStream consumer %q not found for stream %q on account %q", opts.Consumer, opts.Stream, opts.Account) - } else { - health.Errors = []HealthzError{ - { - Type: HealthzErrorConsumer, - Account: opts.Account, - Stream: opts.Stream, - Consumer: opts.Consumer, - Error: fmt.Sprintf("JetStream consumer %q not found for stream %q on account %q", opts.Consumer, opts.Stream, opts.Account), - }, - } - } - } - return health - } - - // If we are here we want to check for any assets assigned to us. - var meta RaftNode - js.mu.RLock() - meta = cc.meta - js.mu.RUnlock() - - // If no meta leader. - if meta == nil || meta.GroupLeader() == _EMPTY_ { - if !details { - health.Status = na - health.Error = "JetStream has not established contact with a meta leader" - } else { - health.Errors = []HealthzError{ - { - Type: HealthzErrorJetStream, - Error: "JetStream has not established contact with a meta leader", - }, - } - } - return health - } - - // If we are not current with the meta leader. - if !meta.Healthy() { - if !details { - health.Status = na - health.Error = "JetStream is not current with the meta leader" - } else { - health.Errors = []HealthzError{ - { - Type: HealthzErrorJetStream, - Error: "JetStream is not current with the meta leader", - }, - } - } - return health - } - - // Are we still recovering meta layer? - if js.isMetaRecovering() { - if !details { - health.Status = na - health.Error = "JetStream is still recovering meta layer" - - } else { - health.Errors = []HealthzError{ - { - Type: HealthzErrorJetStream, - Error: "JetStream is still recovering meta layer", - }, - } - } - return health - } - - // Skips doing full healthz and only checks the meta leader. - if opts.JSMetaOnly { - return health - } - - // Range across all accounts, the streams assigned to them, and the consumers. - // If they are assigned to this server check their status. - ourID := meta.ID() - - // Copy the meta layer so we do not need to hold the js read lock for an extended period of time. - var streams map[string]map[string]*streamAssignment - js.mu.RLock() - if opts.Account == _EMPTY_ { - // Collect all relevant streams and consumers. - streams = make(map[string]map[string]*streamAssignment, len(cc.streams)) - for acc, asa := range cc.streams { - nasa := make(map[string]*streamAssignment) - for stream, sa := range asa { - // If we are a member and we are not being restored, select for check. - if sa.Group.isMember(ourID) && sa.Restore == nil { - csa := sa.copyGroup() - csa.consumers = make(map[string]*consumerAssignment) - for consumer, ca := range sa.consumers { - if ca.Group.isMember(ourID) { - // Use original here. Not a copy. - csa.consumers[consumer] = ca - } - } - nasa[stream] = csa - } - } - streams[acc] = nasa - } - } else { - streams = make(map[string]map[string]*streamAssignment, 1) - asa, ok := cc.streams[opts.Account] - if !ok { - health.StatusCode = http.StatusNotFound - if !details { - health.Status = na - health.Error = fmt.Sprintf("JetStream account %q not found", opts.Account) - } else { - health.Errors = []HealthzError{ - { - Type: HealthzErrorAccount, - Account: opts.Account, - Error: fmt.Sprintf("JetStream account %q not found", opts.Account), - }, - } - } - js.mu.RUnlock() - return health - } - nasa := make(map[string]*streamAssignment) - if opts.Stream != _EMPTY_ { - sa, ok := asa[opts.Stream] - if !ok || !sa.Group.isMember(ourID) { - health.StatusCode = http.StatusNotFound - if !details { - health.Status = na - health.Error = fmt.Sprintf("JetStream stream %q not found on account %q", opts.Stream, opts.Account) - } else { - health.Errors = []HealthzError{ - { - Type: HealthzErrorStream, - Account: opts.Account, - Stream: opts.Stream, - Error: fmt.Sprintf("JetStream stream %q not found on account %q", opts.Stream, opts.Account), - }, - } - } - js.mu.RUnlock() - return health - } - csa := sa.copyGroup() - csa.consumers = make(map[string]*consumerAssignment) - var consumerFound bool - for consumer, ca := range sa.consumers { - if opts.Consumer != _EMPTY_ { - if consumer != opts.Consumer || !ca.Group.isMember(ourID) { - continue - } - consumerFound = true - } - // If we are a member and we are not being restored, select for check. - if sa.Group.isMember(ourID) && sa.Restore == nil { - csa.consumers[consumer] = ca - } - if consumerFound { - break - } - } - if opts.Consumer != _EMPTY_ && !consumerFound { - health.StatusCode = http.StatusNotFound - if !details { - health.Status = na - health.Error = fmt.Sprintf("JetStream consumer %q not found for stream %q on account %q", opts.Consumer, opts.Stream, opts.Account) - } else { - health.Errors = []HealthzError{ - { - Type: HealthzErrorConsumer, - Account: opts.Account, - Stream: opts.Stream, - Consumer: opts.Consumer, - Error: fmt.Sprintf("JetStream consumer %q not found for stream %q on account %q", opts.Consumer, opts.Stream, opts.Account), - }, - } - } - js.mu.RUnlock() - return health - } - nasa[opts.Stream] = csa - } else { - for stream, sa := range asa { - // If we are a member and we are not being restored, select for check. - if sa.Group.isMember(ourID) && sa.Restore == nil { - csa := sa.copyGroup() - csa.consumers = make(map[string]*consumerAssignment) - for consumer, ca := range sa.consumers { - if ca.Group.isMember(ourID) { - csa.consumers[consumer] = ca - } - } - nasa[stream] = csa - } - } - } - streams[opts.Account] = nasa - } - js.mu.RUnlock() - - // Use our copy to traverse so we do not need to hold the js lock. - for accName, asa := range streams { - acc, err := s.LookupAccount(accName) - if err != nil && len(asa) > 0 { - if !details { - health.Status = na - health.Error = fmt.Sprintf("JetStream can not lookup account %q: %v", accName, err) - return health - } - health.Errors = append(health.Errors, HealthzError{ - Type: HealthzErrorAccount, - Account: accName, - Error: fmt.Sprintf("JetStream can not lookup account %q: %v", accName, err), - }) - continue - } - - for stream, sa := range asa { - // Make sure we can look up - if err := js.isStreamHealthy(acc, sa); err != nil { - if !details { - health.Status = na - health.Error = fmt.Sprintf("JetStream stream '%s > %s' is not current: %s", accName, stream, err) - return health - } - health.Errors = append(health.Errors, HealthzError{ - Type: HealthzErrorStream, - Account: accName, - Stream: stream, - Error: fmt.Sprintf("JetStream stream '%s > %s' is not current: %s", accName, stream, err), - }) - continue - } - mset, _ := acc.lookupStream(stream) - // Now check consumers. - for consumer, ca := range sa.consumers { - if err := js.isConsumerHealthy(mset, consumer, ca); err != nil { - if !details { - health.Status = na - health.Error = fmt.Sprintf("JetStream consumer '%s > %s > %s' is not current: %s", acc, stream, consumer, err) - return health - } - health.Errors = append(health.Errors, HealthzError{ - Type: HealthzErrorConsumer, - Account: accName, - Stream: stream, - Consumer: consumer, - Error: fmt.Sprintf("JetStream consumer '%s > %s > %s' is not current: %s", acc, stream, consumer, err), - }) - } - } - } - } - // Success. - return health -} - -type ExpvarzStatus struct { - Memstats json.RawMessage `json:"memstats"` - Cmdline json.RawMessage `json:"cmdline"` -} - -func (s *Server) expvarz(_ *ExpvarzEventOptions) *ExpvarzStatus { - var stat ExpvarzStatus - - const memStatsKey = "memstats" - const cmdLineKey = "cmdline" - - expvar.Do(func(v expvar.KeyValue) { - switch v.Key { - case memStatsKey: - stat.Memstats = json.RawMessage(v.Value.String()) - - case cmdLineKey: - stat.Cmdline = json.RawMessage(v.Value.String()) - } - }) - - return &stat -} - -type ProfilezStatus struct { - Profile []byte `json:"profile"` - Error string `json:"error"` -} - -func (s *Server) profilez(opts *ProfilezOptions) *ProfilezStatus { - var buffer bytes.Buffer - switch opts.Name { - case _EMPTY_: - return &ProfilezStatus{ - Error: "Profile name not specified", - } - case "cpu": - if opts.Duration <= 0 || opts.Duration > 15*time.Second { - return &ProfilezStatus{ - Error: fmt.Sprintf("Duration %s should be between 0s and 15s", opts.Duration), - } - } - if err := pprof.StartCPUProfile(&buffer); err != nil { - return &ProfilezStatus{ - Error: fmt.Sprintf("Failed to start CPU profile: %s", err), - } - } - time.Sleep(opts.Duration) - pprof.StopCPUProfile() - default: - profile := pprof.Lookup(opts.Name) - if profile == nil { - return &ProfilezStatus{ - Error: fmt.Sprintf("Profile %q not found", opts.Name), - } - } - if err := profile.WriteTo(&buffer, opts.Debug); err != nil { - return &ProfilezStatus{ - Error: fmt.Sprintf("Profile %q error: %s", opts.Name, err), - } - } - } - return &ProfilezStatus{ - Profile: buffer.Bytes(), - } -} - -type RaftzGroup struct { - ID string `json:"id"` - State string `json:"state"` - Size int `json:"size"` - QuorumNeeded int `json:"quorum_needed"` - Observer bool `json:"observer,omitempty"` - Paused bool `json:"paused,omitempty"` - Committed uint64 `json:"committed"` - Applied uint64 `json:"applied"` - CatchingUp bool `json:"catching_up,omitempty"` - Leader string `json:"leader,omitempty"` - EverHadLeader bool `json:"ever_had_leader"` - Term uint64 `json:"term"` - Vote string `json:"voted_for,omitempty"` - PTerm uint64 `json:"pterm"` - PIndex uint64 `json:"pindex"` - IPQPropLen int `json:"ipq_proposal_len"` - IPQEntryLen int `json:"ipq_entry_len"` - IPQRespLen int `json:"ipq_resp_len"` - IPQApplyLen int `json:"ipq_apply_len"` - WAL StreamState `json:"wal"` - WALError error `json:"wal_error,omitempty"` - Peers map[string]RaftzGroupPeer `json:"peers"` -} - -type RaftzGroupPeer struct { - Name string `json:"name"` - Known bool `json:"known"` - LastReplicatedIndex uint64 `json:"last_replicated_index,omitempty"` - LastSeen string `json:"last_seen,omitempty"` -} - -type RaftzStatus map[string]map[string]RaftzGroup - -func (s *Server) HandleRaftz(w http.ResponseWriter, r *http.Request) { - if s.raftNodes == nil { - w.WriteHeader(404) - w.Write([]byte("No Raft nodes registered")) - return - } - - groups := s.Raftz(&RaftzOptions{ - AccountFilter: r.URL.Query().Get("acc"), - GroupFilter: r.URL.Query().Get("group"), - }) - - if groups == nil { - w.WriteHeader(404) - w.Write([]byte("No Raft nodes returned, check supplied filters")) - return - } - - b, _ := json.MarshalIndent(groups, "", " ") - ResponseHandler(w, r, b) -} - -func (s *Server) Raftz(opts *RaftzOptions) *RaftzStatus { - afilter, gfilter := opts.AccountFilter, opts.GroupFilter - - if afilter == _EMPTY_ { - if sys := s.SystemAccount(); sys != nil { - afilter = sys.Name - } else { - return nil - } - } - - groups := map[string]RaftNode{} - infos := RaftzStatus{} // account -> group ID - - s.rnMu.RLock() - if gfilter != _EMPTY_ { - if rg, ok := s.raftNodes[gfilter]; ok && rg != nil { - if n, ok := rg.(*raft); ok { - if n.accName == afilter { - groups[gfilter] = rg - } - } - } - } else { - for name, rg := range s.raftNodes { - if rg == nil { - continue - } - if n, ok := rg.(*raft); ok { - if n.accName != afilter { - continue - } - groups[name] = rg - } - } - } - s.rnMu.RUnlock() - - for name, rg := range groups { - n, ok := rg.(*raft) - if n == nil || !ok { - continue - } - if _, ok := infos[n.accName]; !ok { - infos[n.accName] = map[string]RaftzGroup{} - } - // Only take the lock once, using the public RaftNode functions would - // cause us to take and release the locks over and over again. - n.RLock() - info := RaftzGroup{ - ID: n.id, - State: RaftState(n.state.Load()).String(), - Size: n.csz, - QuorumNeeded: n.qn, - Observer: n.observer, - Paused: n.paused, - Committed: n.commit, - Applied: n.applied, - CatchingUp: n.catchup != nil, - Leader: n.leader, - EverHadLeader: n.pleader.Load(), - Term: n.term, - Vote: n.vote, - PTerm: n.pterm, - PIndex: n.pindex, - IPQPropLen: n.prop.len(), - IPQEntryLen: n.entry.len(), - IPQRespLen: n.resp.len(), - IPQApplyLen: n.apply.len(), - WALError: n.werr, - Peers: map[string]RaftzGroupPeer{}, - } - n.wal.FastState(&info.WAL) - for id, p := range n.peers { - if id == n.id { - continue - } - peer := RaftzGroupPeer{ - Name: s.serverNameForNode(id), - Known: p.kp, - LastReplicatedIndex: p.li, - } - if p.ts > 0 { - peer.LastSeen = time.Since(time.Unix(0, p.ts)).String() - } - info.Peers[id] = peer - } - n.RUnlock() - infos[n.accName][name] = info - } - - return &infos -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/monitor_sort_opts.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/monitor_sort_opts.go deleted file mode 100644 index 6ab1095b..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/monitor_sort_opts.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2013-2023 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "time" -) - -// ConnInfos represents a connection info list. We use pointers since it will be sorted. -type ConnInfos []*ConnInfo - -// For sorting -// Len returns length for sorting. -func (cl ConnInfos) Len() int { return len(cl) } - -// Swap will sawap the elements. -func (cl ConnInfos) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] } - -// SortOpt is a helper type to sort clients -type SortOpt string - -// Possible sort options -const ( - ByCid SortOpt = "cid" // By connection ID - ByStart SortOpt = "start" // By connection start time, same as CID - BySubs SortOpt = "subs" // By number of subscriptions - ByPending SortOpt = "pending" // By amount of data in bytes waiting to be sent to client - ByOutMsgs SortOpt = "msgs_to" // By number of messages sent - ByInMsgs SortOpt = "msgs_from" // By number of messages received - ByOutBytes SortOpt = "bytes_to" // By amount of bytes sent - ByInBytes SortOpt = "bytes_from" // By amount of bytes received - ByLast SortOpt = "last" // By the last activity - ByIdle SortOpt = "idle" // By the amount of inactivity - ByUptime SortOpt = "uptime" // By the amount of time connections exist - ByStop SortOpt = "stop" // By the stop time for a closed connection - ByReason SortOpt = "reason" // By the reason for a closed connection - ByRTT SortOpt = "rtt" // By the round trip time -) - -// Individual sort options provide the Less for sort.Interface. Len and Swap are on cList. -// CID -type byCid struct{ ConnInfos } - -func (l byCid) Less(i, j int) bool { return l.ConnInfos[i].Cid < l.ConnInfos[j].Cid } - -// Number of Subscriptions -type bySubs struct{ ConnInfos } - -func (l bySubs) Less(i, j int) bool { return l.ConnInfos[i].NumSubs < l.ConnInfos[j].NumSubs } - -// Pending Bytes -type byPending struct{ ConnInfos } - -func (l byPending) Less(i, j int) bool { return l.ConnInfos[i].Pending < l.ConnInfos[j].Pending } - -// Outbound Msgs -type byOutMsgs struct{ ConnInfos } - -func (l byOutMsgs) Less(i, j int) bool { return l.ConnInfos[i].OutMsgs < l.ConnInfos[j].OutMsgs } - -// Inbound Msgs -type byInMsgs struct{ ConnInfos } - -func (l byInMsgs) Less(i, j int) bool { return l.ConnInfos[i].InMsgs < l.ConnInfos[j].InMsgs } - -// Outbound Bytes -type byOutBytes struct{ ConnInfos } - -func (l byOutBytes) Less(i, j int) bool { return l.ConnInfos[i].OutBytes < l.ConnInfos[j].OutBytes } - -// Inbound Bytes -type byInBytes struct{ ConnInfos } - -func (l byInBytes) Less(i, j int) bool { return l.ConnInfos[i].InBytes < l.ConnInfos[j].InBytes } - -// Last Activity -type byLast struct{ ConnInfos } - -func (l byLast) Less(i, j int) bool { - return l.ConnInfos[i].LastActivity.UnixNano() < l.ConnInfos[j].LastActivity.UnixNano() -} - -// Idle time -type byIdle struct { - ConnInfos - now time.Time -} - -func (l byIdle) Less(i, j int) bool { - return l.now.Sub(l.ConnInfos[i].LastActivity) < l.now.Sub(l.ConnInfos[j].LastActivity) -} - -// Uptime -type byUptime struct { - ConnInfos - now time.Time -} - -func (l byUptime) Less(i, j int) bool { - ci := l.ConnInfos[i] - cj := l.ConnInfos[j] - var upi, upj time.Duration - if ci.Stop == nil || ci.Stop.IsZero() { - upi = l.now.Sub(ci.Start) - } else { - upi = ci.Stop.Sub(ci.Start) - } - if cj.Stop == nil || cj.Stop.IsZero() { - upj = l.now.Sub(cj.Start) - } else { - upj = cj.Stop.Sub(cj.Start) - } - return upi < upj -} - -// Stop -type byStop struct{ ConnInfos } - -func (l byStop) Less(i, j int) bool { - ciStop := l.ConnInfos[i].Stop - cjStop := l.ConnInfos[j].Stop - return ciStop.Before(*cjStop) -} - -// Reason -type byReason struct{ ConnInfos } - -func (l byReason) Less(i, j int) bool { - return l.ConnInfos[i].Reason < l.ConnInfos[j].Reason -} - -// RTT - Default is descending -type byRTT struct{ ConnInfos } - -func (l byRTT) Less(i, j int) bool { return l.ConnInfos[i].rtt < l.ConnInfos[j].rtt } - -// IsValid determines if a sort option is valid -func (s SortOpt) IsValid() bool { - switch s { - case _EMPTY_, ByCid, ByStart, BySubs, ByPending, ByOutMsgs, ByInMsgs, ByOutBytes, ByInBytes, ByLast, ByIdle, ByUptime, ByStop, ByReason, ByRTT: - return true - default: - return false - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/mqtt.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/mqtt.go deleted file mode 100644 index e76b7168..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/mqtt.go +++ /dev/null @@ -1,5789 +0,0 @@ -// Copyright 2020-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "cmp" - "crypto/tls" - "encoding/binary" - "encoding/json" - "errors" - "fmt" - "io" - "net" - "net/http" - "slices" - "strconv" - "strings" - "sync" - "time" - "unicode/utf8" - - "github.com/nats-io/nuid" -) - -// References to "spec" here is from https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.pdf - -const ( - mqttPacketConnect = byte(0x10) - mqttPacketConnectAck = byte(0x20) - mqttPacketPub = byte(0x30) - mqttPacketPubAck = byte(0x40) - mqttPacketPubRec = byte(0x50) - mqttPacketPubRel = byte(0x60) - mqttPacketPubComp = byte(0x70) - mqttPacketSub = byte(0x80) - mqttPacketSubAck = byte(0x90) - mqttPacketUnsub = byte(0xa0) - mqttPacketUnsubAck = byte(0xb0) - mqttPacketPing = byte(0xc0) - mqttPacketPingResp = byte(0xd0) - mqttPacketDisconnect = byte(0xe0) - mqttPacketMask = byte(0xf0) - mqttPacketFlagMask = byte(0x0f) - - mqttProtoLevel = byte(0x4) - - // Connect flags - mqttConnFlagReserved = byte(0x1) - mqttConnFlagCleanSession = byte(0x2) - mqttConnFlagWillFlag = byte(0x04) - mqttConnFlagWillQoS = byte(0x18) - mqttConnFlagWillRetain = byte(0x20) - mqttConnFlagPasswordFlag = byte(0x40) - mqttConnFlagUsernameFlag = byte(0x80) - - // Publish flags - mqttPubFlagRetain = byte(0x01) - mqttPubFlagQoS = byte(0x06) - mqttPubFlagDup = byte(0x08) - mqttPubQos1 = byte(0x1 << 1) - mqttPubQoS2 = byte(0x2 << 1) - - // Subscribe flags - mqttSubscribeFlags = byte(0x2) - mqttSubAckFailure = byte(0x80) - - // Unsubscribe flags - mqttUnsubscribeFlags = byte(0x2) - - // ConnAck returned codes - mqttConnAckRCConnectionAccepted = byte(0x0) - mqttConnAckRCUnacceptableProtocolVersion = byte(0x1) - mqttConnAckRCIdentifierRejected = byte(0x2) - mqttConnAckRCServerUnavailable = byte(0x3) - mqttConnAckRCBadUserOrPassword = byte(0x4) - mqttConnAckRCNotAuthorized = byte(0x5) - mqttConnAckRCQoS2WillRejected = byte(0x10) - - // Maximum payload size of a control packet - mqttMaxPayloadSize = 0xFFFFFFF - - // Topic/Filter characters - mqttTopicLevelSep = '/' - mqttSingleLevelWC = '+' - mqttMultiLevelWC = '#' - mqttReservedPre = '$' - - // This is appended to the sid of a subscription that is - // created on the upper level subject because of the MQTT - // wildcard '#' semantic. - mqttMultiLevelSidSuffix = " fwc" - - // This is the prefix for NATS subscriptions subjects associated as delivery - // subject of JS consumer. We want to make them unique so will prevent users - // MQTT subscriptions to start with this. - mqttSubPrefix = "$MQTT.sub." - - // Stream name for MQTT messages on a given account - mqttStreamName = "$MQTT_msgs" - mqttStreamSubjectPrefix = "$MQTT.msgs." - - // Stream name for MQTT retained messages on a given account - mqttRetainedMsgsStreamName = "$MQTT_rmsgs" - mqttRetainedMsgsStreamSubject = "$MQTT.rmsgs." - - // Stream name for MQTT sessions on a given account - mqttSessStreamName = "$MQTT_sess" - mqttSessStreamSubjectPrefix = "$MQTT.sess." - - // Stream name prefix for MQTT sessions on a given account - mqttSessionsStreamNamePrefix = "$MQTT_sess_" - - // Stream name and subject for incoming MQTT QoS2 messages - mqttQoS2IncomingMsgsStreamName = "$MQTT_qos2in" - mqttQoS2IncomingMsgsStreamSubjectPrefix = "$MQTT.qos2.in." - - // Stream name and subjects for outgoing MQTT QoS (PUBREL) messages - mqttOutStreamName = "$MQTT_out" - mqttOutSubjectPrefix = "$MQTT.out." - mqttPubRelSubjectPrefix = "$MQTT.out.pubrel." - mqttPubRelDeliverySubjectPrefix = "$MQTT.deliver.pubrel." - mqttPubRelConsumerDurablePrefix = "$MQTT_PUBREL_" - - // As per spec, MQTT server may not redeliver QoS 1 and 2 messages to - // clients, except after client reconnects. However, NATS Server will - // redeliver unacknowledged messages after this default interval. This can - // be changed with the server.Options.MQTT.AckWait option. - mqttDefaultAckWait = 30 * time.Second - - // This is the default for the outstanding number of pending QoS 1 - // messages sent to a session with QoS 1 subscriptions. - mqttDefaultMaxAckPending = 1024 - - // A session's list of subscriptions cannot have a cumulative MaxAckPending - // of more than this limit. - mqttMaxAckTotalLimit = 0xFFFF - - // Prefix of the reply subject for JS API requests. - mqttJSARepliesPrefix = "$MQTT.JSA." - - // Those are tokens that are used for the reply subject of JS API requests. - // For instance "$MQTT.JSA..SC." is the reply subject - // for a request to create a stream (where is the server name hash), - // while "$MQTT.JSA..SL." is for a stream lookup, etc... - mqttJSAIdTokenPos = 3 - mqttJSATokenPos = 4 - mqttJSAClientIDPos = 5 - mqttJSAStreamCreate = "SC" - mqttJSAStreamUpdate = "SU" - mqttJSAStreamLookup = "SL" - mqttJSAStreamDel = "SD" - mqttJSAConsumerCreate = "CC" - mqttJSAConsumerLookup = "CL" - mqttJSAConsumerDel = "CD" - mqttJSAMsgStore = "MS" - mqttJSAMsgLoad = "ML" - mqttJSAMsgDelete = "MD" - mqttJSASessPersist = "SP" - mqttJSARetainedMsgDel = "RD" - mqttJSAStreamNames = "SN" - - // This is how long to keep a client in the flappers map before closing the - // connection. This prevent quick reconnect from those clients that keep - // wanting to connect with a client ID already in use. - mqttSessFlappingJailDur = time.Second - - // This is how frequently the timer to cleanup the sessions flappers map is firing. - mqttSessFlappingCleanupInterval = 5 * time.Second - - // Default retry delay if transfer of old session streams to new one fails - mqttDefaultTransferRetry = 5 * time.Second - - // For Websocket URLs - mqttWSPath = "/mqtt" - - mqttInitialPubHeader = 16 // An overkill, should need 7 bytes max - mqttProcessSubTooLong = 100 * time.Millisecond - mqttDefaultRetainedCacheTTL = 2 * time.Minute - mqttRetainedTransferTimeout = 10 * time.Second -) - -const ( - sparkbNBIRTH = "NBIRTH" - sparkbDBIRTH = "DBIRTH" - sparkbNDEATH = "NDEATH" - sparkbDDEATH = "DDEATH" -) - -var ( - sparkbNamespaceTopicPrefix = []byte("spBv1.0/") - sparkbCertificatesTopicPrefix = []byte("$sparkplug/certificates/") -) - -var ( - mqttPingResponse = []byte{mqttPacketPingResp, 0x0} - mqttProtoName = []byte("MQTT") - mqttOldProtoName = []byte("MQIsdp") - mqttSessJailDur = mqttSessFlappingJailDur - mqttFlapCleanItvl = mqttSessFlappingCleanupInterval - mqttJSAPITimeout = 4 * time.Second - mqttRetainedCacheTTL = mqttDefaultRetainedCacheTTL -) - -var ( - errMQTTNotWebsocketPort = errors.New("MQTT clients over websocket must connect to the Websocket port, not the MQTT port") - errMQTTTopicFilterCannotBeEmpty = errors.New("topic filter cannot be empty") - errMQTTMalformedVarInt = errors.New("malformed variable int") - errMQTTSecondConnectPacket = errors.New("received a second CONNECT packet") - errMQTTServerNameMustBeSet = errors.New("mqtt requires server name to be explicitly set") - errMQTTUserMixWithUsersNKeys = errors.New("mqtt authentication username not compatible with presence of users/nkeys") - errMQTTTokenMixWIthUsersNKeys = errors.New("mqtt authentication token not compatible with presence of users/nkeys") - errMQTTAckWaitMustBePositive = errors.New("ack wait must be a positive value") - errMQTTStandaloneNeedsJetStream = errors.New("mqtt requires JetStream to be enabled if running in standalone mode") - errMQTTConnFlagReserved = errors.New("connect flags reserved bit not set to 0") - errMQTTWillAndRetainFlag = errors.New("if Will flag is set to 0, Will Retain flag must be 0 too") - errMQTTPasswordFlagAndNoUser = errors.New("password flag set but username flag is not") - errMQTTCIDEmptyNeedsCleanFlag = errors.New("when client ID is empty, clean session flag must be set to 1") - errMQTTEmptyWillTopic = errors.New("empty Will topic not allowed") - errMQTTEmptyUsername = errors.New("empty user name not allowed") - errMQTTTopicIsEmpty = errors.New("topic cannot be empty") - errMQTTPacketIdentifierIsZero = errors.New("packet identifier cannot be 0") - errMQTTUnsupportedCharacters = errors.New("character ' ' not supported for MQTT topics") - errMQTTInvalidSession = errors.New("invalid MQTT session") -) - -type srvMQTT struct { - listener net.Listener - listenerErr error - authOverride bool - sessmgr mqttSessionManager -} - -type mqttSessionManager struct { - mu sync.RWMutex - sessions map[string]*mqttAccountSessionManager // key is account name -} - -var testDisableRMSCache = false - -type mqttAccountSessionManager struct { - mu sync.RWMutex - sessions map[string]*mqttSession // key is MQTT client ID - sessByHash map[string]*mqttSession // key is MQTT client ID hash - sessLocked map[string]struct{} // key is MQTT client ID and indicate that a session can not be taken by a new client at this time - flappers map[string]int64 // When connection connects with client ID already in use - flapTimer *time.Timer // Timer to perform some cleanup of the flappers map - sl *Sublist // sublist allowing to find retained messages for given subscription - retmsgs map[string]*mqttRetainedMsgRef // retained messages - rmsCache *sync.Map // map[subject]mqttRetainedMsg - jsa mqttJSA - rrmLastSeq uint64 // Restore retained messages expected last sequence - rrmDoneCh chan struct{} // To notify the caller that all retained messages have been loaded - domainTk string // Domain (with trailing "."), or possibly empty. This is added to session subject. -} - -type mqttJSAResponse struct { - reply string // will be used to map to the original request in jsa.NewRequestExMulti - value any -} - -type mqttJSA struct { - mu sync.Mutex - id string - c *client - sendq *ipQueue[*mqttJSPubMsg] - rplyr string - replies sync.Map // [string]chan *mqttJSAResponse - nuid *nuid.NUID - quitCh chan struct{} - domain string // Domain or possibly empty. This is added to session subject. - domainSet bool // covers if domain was set, even to empty -} - -type mqttJSPubMsg struct { - subj string - reply string - hdr int - msg []byte -} - -type mqttRetMsgDel struct { - Subject string `json:"subject"` - Seq uint64 `json:"seq"` -} - -type mqttSession struct { - // subsMu is a "quick" version of the session lock, sufficient for the QoS0 - // callback. It only guarantees that a new subscription is initialized, and - // its retained messages if any have been queued up for delivery. The QoS12 - // callback uses the session lock. - mu sync.Mutex - subsMu sync.RWMutex - - id string // client ID - idHash string // client ID hash - c *client - jsa *mqttJSA - subs map[string]byte // Key is MQTT SUBSCRIBE filter, value is the subscription QoS - cons map[string]*ConsumerConfig - pubRelConsumer *ConsumerConfig - pubRelSubscribed bool - pubRelDeliverySubject string - pubRelDeliverySubjectB []byte - pubRelSubject string - seq uint64 - - // pendingPublish maps packet identifiers (PI) to JetStream ACK subjects for - // QoS1 and 2 PUBLISH messages pending delivery to the session's client. - pendingPublish map[uint16]*mqttPending - - // pendingPubRel maps PIs to JetStream ACK subjects for QoS2 PUBREL - // messages pending delivery to the session's client. - pendingPubRel map[uint16]*mqttPending - - // cpending maps delivery attempts (that come with a JS ACK subject) to - // existing PIs. - cpending map[string]map[uint64]uint16 // composite key: jsDur, sseq - - // "Last used" publish packet identifier (PI). starting point searching for the next available. - last_pi uint16 - - // Maximum number of pending acks for this session. - maxp uint16 - tmaxack int - clean bool - domainTk string -} - -type mqttPersistedSession struct { - Origin string `json:"origin,omitempty"` - ID string `json:"id,omitempty"` - Clean bool `json:"clean,omitempty"` - Subs map[string]byte `json:"subs,omitempty"` - Cons map[string]*ConsumerConfig `json:"cons,omitempty"` - PubRel *ConsumerConfig `json:"pubrel,omitempty"` -} - -type mqttRetainedMsg struct { - Origin string `json:"origin,omitempty"` - Subject string `json:"subject,omitempty"` - Topic string `json:"topic,omitempty"` - Msg []byte `json:"msg,omitempty"` - Flags byte `json:"flags,omitempty"` - Source string `json:"source,omitempty"` - - expiresFromCache time.Time -} - -type mqttRetainedMsgRef struct { - sseq uint64 - floor uint64 - sub *subscription -} - -// mqttSub contains fields associated with a MQTT subscription, and is added to -// the main subscription struct for MQTT message delivery subscriptions. The -// delivery callbacks may get invoked before sub.mqtt is set up, so they should -// acquire either sess.mu or sess.subsMu before accessing it. -type mqttSub struct { - // The sub's QOS and the JS durable name. They can change when - // re-subscribing, and are used in the delivery callbacks. They can be - // quickly accessed using sess.subsMu.RLock, or under the main session lock. - qos byte - jsDur string - - // Pending serialization of retained messages to be sent when subscription - // is registered. The sub's delivery callbacks must wait until `prm` is - // ready (can block on sess.mu for that, too). - prm [][]byte - - // If this subscription needs to be checked for being reserved. E.g. '#' or - // '*' or '*/'. It is set up at the time of subscription and is immutable - // after that. - reserved bool -} - -type mqtt struct { - r *mqttReader - cp *mqttConnectProto - pp *mqttPublish - asm *mqttAccountSessionManager // quick reference to account session manager, immutable after processConnect() - sess *mqttSession // quick reference to session, immutable after processConnect() - cid string // client ID - - // rejectQoS2Pub tells the MQTT client to not accept QoS2 PUBLISH, instead - // error and terminate the connection. - rejectQoS2Pub bool - - // downgradeQOS2Sub tells the MQTT client to downgrade QoS2 SUBSCRIBE - // requests to QoS1. - downgradeQoS2Sub bool -} - -type mqttPending struct { - sseq uint64 // stream sequence - jsAckSubject string // the ACK subject to send the ack to - jsDur string // JS durable name -} - -type mqttConnectProto struct { - rd time.Duration - will *mqttWill - flags byte -} - -type mqttIOReader interface { - io.Reader - SetReadDeadline(time.Time) error -} - -type mqttReader struct { - reader mqttIOReader - buf []byte - pos int - pstart int - pbuf []byte -} - -type mqttWriter struct { - bytes.Buffer -} - -type mqttWill struct { - topic []byte - subject []byte - mapped []byte - message []byte - qos byte - retain bool -} - -type mqttFilter struct { - filter string - qos byte - // Used only for tracing and should not be used after parsing of (un)sub protocols. - ttopic []byte -} - -type mqttPublish struct { - topic []byte - subject []byte - mapped []byte - msg []byte - sz int - pi uint16 - flags byte -} - -// When we re-encode incoming MQTT PUBLISH messages for NATS delivery, we add -// the following headers: -// - "Nmqtt-Pub" (*always) indicates that the message originated from MQTT, and -// contains the original message QoS. -// - "Nmqtt-Subject" contains the original MQTT subject from mqttParsePub. -// - "Nmqtt-Mapped" contains the mapping during mqttParsePub. -// -// When we submit a PUBREL for delivery, we add a "Nmqtt-PubRel" header that -// contains the PI. -const ( - // NATS header that indicates that the message originated from MQTT and - // stores the published message QOS. - mqttNatsHeader = "Nmqtt-Pub" - - // NATS headers to store retained message metadata (along with the original - // message as binary). - mqttNatsRetainedMessageTopic = "Nmqtt-RTopic" - mqttNatsRetainedMessageOrigin = "Nmqtt-ROrigin" - mqttNatsRetainedMessageFlags = "Nmqtt-RFlags" - mqttNatsRetainedMessageSource = "Nmqtt-RSource" - - // NATS header that indicates that the message is an MQTT PubRel and stores - // the PI. - mqttNatsPubRelHeader = "Nmqtt-PubRel" - - // NATS headers to store the original MQTT subject and the subject mapping. - mqttNatsHeaderSubject = "Nmqtt-Subject" - mqttNatsHeaderMapped = "Nmqtt-Mapped" -) - -type mqttParsedPublishNATSHeader struct { - qos byte - subject []byte - mapped []byte -} - -func (s *Server) startMQTT() { - if s.isShuttingDown() { - return - } - - sopts := s.getOpts() - o := &sopts.MQTT - - var hl net.Listener - var err error - - port := o.Port - if port == -1 { - port = 0 - } - hp := net.JoinHostPort(o.Host, strconv.Itoa(port)) - s.mu.Lock() - s.mqtt.sessmgr.sessions = make(map[string]*mqttAccountSessionManager) - hl, err = net.Listen("tcp", hp) - s.mqtt.listenerErr = err - if err != nil { - s.mu.Unlock() - s.Fatalf("Unable to listen for MQTT connections: %v", err) - return - } - if port == 0 { - o.Port = hl.Addr().(*net.TCPAddr).Port - } - s.mqtt.listener = hl - scheme := "mqtt" - if o.TLSConfig != nil { - scheme = "tls" - } - s.Noticef("Listening for MQTT clients on %s://%s:%d", scheme, o.Host, o.Port) - go s.acceptConnections(hl, "MQTT", func(conn net.Conn) { s.createMQTTClient(conn, nil) }, nil) - s.mu.Unlock() -} - -// This is similar to createClient() but has some modifications specifi to MQTT clients. -// The comments have been kept to minimum to reduce code size. Check createClient() for -// more details. -func (s *Server) createMQTTClient(conn net.Conn, ws *websocket) *client { - opts := s.getOpts() - - maxPay := int32(opts.MaxPayload) - maxSubs := int32(opts.MaxSubs) - if maxSubs == 0 { - maxSubs = -1 - } - now := time.Now() - - mqtt := &mqtt{ - rejectQoS2Pub: opts.MQTT.rejectQoS2Pub, - downgradeQoS2Sub: opts.MQTT.downgradeQoS2Sub, - } - c := &client{srv: s, nc: conn, mpay: maxPay, msubs: maxSubs, start: now, last: now, mqtt: mqtt, ws: ws} - c.headers = true - c.mqtt.pp = &mqttPublish{} - // MQTT clients don't send NATS CONNECT protocols. So make it an "echo" - // client, but disable verbose and pedantic (by not setting them). - c.opts.Echo = true - - c.registerWithAccount(s.globalAccount()) - - s.mu.Lock() - // Check auth, override if applicable. - authRequired := s.info.AuthRequired || s.mqtt.authOverride - s.totalClients++ - s.mu.Unlock() - - c.mu.Lock() - if authRequired { - c.flags.set(expectConnect) - } - c.initClient() - c.Debugf("Client connection created") - c.mu.Unlock() - - s.mu.Lock() - if !s.isRunning() || s.ldm { - if s.isShuttingDown() { - conn.Close() - } - s.mu.Unlock() - return c - } - - if opts.MaxConn > 0 && len(s.clients) >= opts.MaxConn { - s.mu.Unlock() - c.maxConnExceeded() - return nil - } - s.clients[c.cid] = c - - // Websocket TLS handshake is already done when getting to this function. - tlsRequired := opts.MQTT.TLSConfig != nil && ws == nil - s.mu.Unlock() - - c.mu.Lock() - - // In case connection has already been closed - if c.isClosed() { - c.mu.Unlock() - c.closeConnection(WriteError) - return nil - } - - var pre []byte - if tlsRequired && opts.AllowNonTLS { - pre = make([]byte, 4) - c.nc.SetReadDeadline(time.Now().Add(secondsToDuration(opts.MQTT.TLSTimeout))) - n, _ := io.ReadFull(c.nc, pre[:]) - c.nc.SetReadDeadline(time.Time{}) - pre = pre[:n] - if n > 0 && pre[0] == 0x16 { - tlsRequired = true - } else { - tlsRequired = false - } - } - - if tlsRequired { - if len(pre) > 0 { - c.nc = &tlsMixConn{c.nc, bytes.NewBuffer(pre)} - pre = nil - } - - // Perform server-side TLS handshake. - if err := c.doTLSServerHandshake(tlsHandshakeMQTT, opts.MQTT.TLSConfig, opts.MQTT.TLSTimeout, opts.MQTT.TLSPinnedCerts); err != nil { - c.mu.Unlock() - return nil - } - } - - if authRequired { - timeout := opts.AuthTimeout - // Possibly override with MQTT specific value. - if opts.MQTT.AuthTimeout != 0 { - timeout = opts.MQTT.AuthTimeout - } - c.setAuthTimer(secondsToDuration(timeout)) - } - - // No Ping timer for MQTT clients... - - s.startGoRoutine(func() { c.readLoop(pre) }) - s.startGoRoutine(func() { c.writeLoop() }) - - if tlsRequired { - c.Debugf("TLS handshake complete") - cs := c.nc.(*tls.Conn).ConnectionState() - c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tlsCipher(cs.CipherSuite)) - } - - c.mu.Unlock() - - return c -} - -// Given the mqtt options, we check if any auth configuration -// has been provided. If so, possibly create users/nkey users and -// store them in s.mqtt.users/nkeys. -// Also update a boolean that indicates if auth is required for -// mqtt clients. -// Server lock is held on entry. -func (s *Server) mqttConfigAuth(opts *MQTTOpts) { - mqtt := &s.mqtt - // If any of those is specified, we consider that there is an override. - mqtt.authOverride = opts.Username != _EMPTY_ || opts.Token != _EMPTY_ || opts.NoAuthUser != _EMPTY_ -} - -// Validate the mqtt related options. -func validateMQTTOptions(o *Options) error { - mo := &o.MQTT - // If no port is defined, we don't care about other options - if mo.Port == 0 { - return nil - } - // We have to force the server name to be explicitly set and be unique when - // in cluster mode. - if o.ServerName == _EMPTY_ && (o.Cluster.Port != 0 || o.Gateway.Port != 0) { - return errMQTTServerNameMustBeSet - } - // If there is a NoAuthUser, we need to have Users defined and - // the user to be present. - if mo.NoAuthUser != _EMPTY_ { - if err := validateNoAuthUser(o, mo.NoAuthUser); err != nil { - return err - } - } - // Token/Username not possible if there are users/nkeys - if len(o.Users) > 0 || len(o.Nkeys) > 0 { - if mo.Username != _EMPTY_ { - return errMQTTUserMixWithUsersNKeys - } - if mo.Token != _EMPTY_ { - return errMQTTTokenMixWIthUsersNKeys - } - } - if mo.AckWait < 0 { - return errMQTTAckWaitMustBePositive - } - // If strictly standalone and there is no JS enabled, then it won't work... - // For leafnodes, we could either have remote(s) and it would be ok, or no - // remote but accept from a remote side that has "hub" property set, which - // then would ok too. So we fail only if we have no leafnode config at all. - if !o.JetStream && o.Cluster.Port == 0 && o.Gateway.Port == 0 && - o.LeafNode.Port == 0 && len(o.LeafNode.Remotes) == 0 { - return errMQTTStandaloneNeedsJetStream - } - if err := validatePinnedCerts(mo.TLSPinnedCerts); err != nil { - return fmt.Errorf("mqtt: %v", err) - } - if mo.ConsumerReplicas > 0 && mo.StreamReplicas > 0 && mo.ConsumerReplicas > mo.StreamReplicas { - return fmt.Errorf("mqtt: consumer_replicas (%v) cannot be higher than stream_replicas (%v)", - mo.ConsumerReplicas, mo.StreamReplicas) - } - return nil -} - -// Returns true if this connection is from a MQTT client. -// Lock held on entry. -func (c *client) isMqtt() bool { - return c.mqtt != nil -} - -// If this is an MQTT client, returns the session client ID, -// otherwise returns the empty string. -// Lock held on entry -func (c *client) getMQTTClientID() string { - if !c.isMqtt() { - return _EMPTY_ - } - return c.mqtt.cid -} - -// Parse protocols inside the given buffer. -// This is invoked from the readLoop. -func (c *client) mqttParse(buf []byte) error { - c.mu.Lock() - s := c.srv - trace := c.trace - connected := c.flags.isSet(connectReceived) - mqtt := c.mqtt - r := mqtt.r - var rd time.Duration - if mqtt.cp != nil { - rd = mqtt.cp.rd - if rd > 0 { - r.reader.SetReadDeadline(time.Time{}) - } - } - hasMappings := c.in.flags.isSet(hasMappings) - c.mu.Unlock() - - r.reset(buf) - - var err error - var b byte - var pl int - var complete bool - - for err == nil && r.hasMore() { - - // Keep track of the starting of the packet, in case we have a partial - r.pstart = r.pos - - // Read packet type and flags - if b, err = r.readByte("packet type"); err != nil { - break - } - - // Packet type - pt := b & mqttPacketMask - - // If client was not connected yet, the first packet must be - // a mqttPacketConnect otherwise we fail the connection. - if !connected && pt != mqttPacketConnect { - // If the buffer indicates that it may be a websocket handshake - // but the client is not websocket, it means that the client - // connected to the MQTT port instead of the Websocket port. - if bytes.HasPrefix(buf, []byte("GET ")) && !c.isWebsocket() { - err = errMQTTNotWebsocketPort - } else { - err = fmt.Errorf("the first packet should be a CONNECT (%v), got %v", mqttPacketConnect, pt) - } - break - } - - pl, complete, err = r.readPacketLen() - if err != nil || !complete { - break - } - - switch pt { - // Packets that we receive back when we act as the "sender": PUBACK, - // PUBREC, PUBCOMP. - case mqttPacketPubAck: - var pi uint16 - pi, err = mqttParsePIPacket(r) - if trace { - c.traceInOp("PUBACK", errOrTrace(err, fmt.Sprintf("pi=%v", pi))) - } - if err == nil { - err = c.mqttProcessPubAck(pi) - } - - case mqttPacketPubRec: - var pi uint16 - pi, err = mqttParsePIPacket(r) - if trace { - c.traceInOp("PUBREC", errOrTrace(err, fmt.Sprintf("pi=%v", pi))) - } - if err == nil { - err = c.mqttProcessPubRec(pi) - } - - case mqttPacketPubComp: - var pi uint16 - pi, err = mqttParsePIPacket(r) - if trace { - c.traceInOp("PUBCOMP", errOrTrace(err, fmt.Sprintf("pi=%v", pi))) - } - if err == nil { - c.mqttProcessPubComp(pi) - } - - // Packets where we act as the "receiver": PUBLISH, PUBREL, SUBSCRIBE, UNSUBSCRIBE. - case mqttPacketPub: - pp := c.mqtt.pp - pp.flags = b & mqttPacketFlagMask - err = c.mqttParsePub(r, pl, pp, hasMappings) - if trace { - c.traceInOp("PUBLISH", errOrTrace(err, mqttPubTrace(pp))) - if err == nil { - c.mqttTraceMsg(pp.msg) - } - } - if err == nil { - err = s.mqttProcessPub(c, pp, trace) - } - - case mqttPacketPubRel: - var pi uint16 - pi, err = mqttParsePIPacket(r) - if trace { - c.traceInOp("PUBREL", errOrTrace(err, fmt.Sprintf("pi=%v", pi))) - } - if err == nil { - err = s.mqttProcessPubRel(c, pi, trace) - } - - case mqttPacketSub: - var pi uint16 // packet identifier - var filters []*mqttFilter - var subs []*subscription - pi, filters, err = c.mqttParseSubs(r, b, pl) - if trace { - c.traceInOp("SUBSCRIBE", errOrTrace(err, mqttSubscribeTrace(pi, filters))) - } - if err == nil { - subs, err = c.mqttProcessSubs(filters) - if err == nil && trace { - c.traceOutOp("SUBACK", []byte(fmt.Sprintf("pi=%v", pi))) - } - } - if err == nil { - c.mqttEnqueueSubAck(pi, filters) - c.mqttSendRetainedMsgsToNewSubs(subs) - } - - case mqttPacketUnsub: - var pi uint16 // packet identifier - var filters []*mqttFilter - pi, filters, err = c.mqttParseUnsubs(r, b, pl) - if trace { - c.traceInOp("UNSUBSCRIBE", errOrTrace(err, mqttUnsubscribeTrace(pi, filters))) - } - if err == nil { - err = c.mqttProcessUnsubs(filters) - if err == nil && trace { - c.traceOutOp("UNSUBACK", []byte(fmt.Sprintf("pi=%v", pi))) - } - } - if err == nil { - c.mqttEnqueueUnsubAck(pi) - } - - // Packets that we get both as a receiver and sender: PING, CONNECT, DISCONNECT - case mqttPacketPing: - if trace { - c.traceInOp("PINGREQ", nil) - } - c.mqttEnqueuePingResp() - if trace { - c.traceOutOp("PINGRESP", nil) - } - - case mqttPacketConnect: - // It is an error to receive a second connect packet - if connected { - err = errMQTTSecondConnectPacket - break - } - var rc byte - var cp *mqttConnectProto - var sessp bool - rc, cp, err = c.mqttParseConnect(r, hasMappings) - // Add the client id to the client's string, regardless of error. - // We may still get the client_id if the call above fails somewhere - // after parsing the client ID itself. - c.ncs.Store(fmt.Sprintf("%s - %q", c, c.mqtt.cid)) - if trace && cp != nil { - c.traceInOp("CONNECT", errOrTrace(err, c.mqttConnectTrace(cp))) - } - if rc != 0 { - c.mqttEnqueueConnAck(rc, sessp) - if trace { - c.traceOutOp("CONNACK", []byte(fmt.Sprintf("sp=%v rc=%v", sessp, rc))) - } - } else if err == nil { - if err = s.mqttProcessConnect(c, cp, trace); err != nil { - err = fmt.Errorf("unable to connect: %v", err) - } else { - // Add this debug statement so users running in Debug mode - // will have the client id printed here for the first time. - c.Debugf("Client connected") - connected = true - rd = cp.rd - } - } - - case mqttPacketDisconnect: - if trace { - c.traceInOp("DISCONNECT", nil) - } - // Normal disconnect, we need to discard the will. - // Spec [MQTT-3.1.2-8] - c.mu.Lock() - if c.mqtt.cp != nil { - c.mqtt.cp.will = nil - } - c.mu.Unlock() - s.mqttHandleClosedClient(c) - c.closeConnection(ClientClosed) - return nil - - default: - err = fmt.Errorf("received unknown packet type %d", pt>>4) - } - } - if err == nil && rd > 0 { - r.reader.SetReadDeadline(time.Now().Add(rd)) - } - return err -} - -func (c *client) mqttTraceMsg(msg []byte) { - maxTrace := c.srv.getOpts().MaxTracedMsgLen - if maxTrace > 0 && len(msg) > maxTrace { - c.Tracef("<<- MSG_PAYLOAD: [\"%s...\"]", msg[:maxTrace]) - } else { - c.Tracef("<<- MSG_PAYLOAD: [%q]", msg) - } -} - -// The MQTT client connection has been closed, or the DISCONNECT packet was received. -// For a "clean" session, we will delete the session, otherwise, simply removing -// the binding. We will also send the "will" message if applicable. -// -// Runs from the client's readLoop. -// No lock held on entry. -func (s *Server) mqttHandleClosedClient(c *client) { - c.mu.Lock() - asm := c.mqtt.asm - sess := c.mqtt.sess - c.mu.Unlock() - - // If asm or sess are nil, it means that we have failed a client - // before it was associated with a session, so nothing more to do. - if asm == nil || sess == nil { - return - } - - // Add this session to the locked map for the rest of the execution. - if err := asm.lockSession(sess, c); err != nil { - return - } - defer asm.unlockSession(sess) - - asm.mu.Lock() - // Clear the client from the session, but session may stay. - sess.mu.Lock() - sess.c = nil - doClean := sess.clean - sess.mu.Unlock() - // If it was a clean session, then we remove from the account manager, - // and we will call clear() outside of any lock. - if doClean { - asm.removeSession(sess, false) - } - // Remove in case it was in the flappers map. - asm.removeSessFromFlappers(sess.id) - asm.mu.Unlock() - - // This needs to be done outside of any lock. - if doClean { - if err := sess.clear(true); err != nil { - c.Errorf(err.Error()) - } - } - - // Now handle the "will". This function will be a no-op if there is no "will" to send. - s.mqttHandleWill(c) -} - -// Updates the MaxAckPending for all MQTT sessions, updating the -// JetStream consumers and updating their max ack pending and forcing -// a expiration of pending messages. -// -// Runs from a server configuration reload routine. -// No lock held on entry. -func (s *Server) mqttUpdateMaxAckPending(newmaxp uint16) { - msm := &s.mqtt.sessmgr - s.accounts.Range(func(k, _ any) bool { - accName := k.(string) - msm.mu.RLock() - asm := msm.sessions[accName] - msm.mu.RUnlock() - if asm == nil { - // Move to next account - return true - } - asm.mu.RLock() - for _, sess := range asm.sessions { - sess.mu.Lock() - sess.maxp = newmaxp - sess.mu.Unlock() - } - asm.mu.RUnlock() - return true - }) -} - -func (s *Server) mqttGetJSAForAccount(acc string) *mqttJSA { - sm := &s.mqtt.sessmgr - - sm.mu.RLock() - asm := sm.sessions[acc] - sm.mu.RUnlock() - - if asm == nil { - return nil - } - - asm.mu.RLock() - jsa := &asm.jsa - asm.mu.RUnlock() - return jsa -} - -func (s *Server) mqttStoreQoSMsgForAccountOnNewSubject(hdr int, msg []byte, acc, subject string) { - if s == nil || hdr <= 0 { - return - } - h := mqttParsePublishNATSHeader(msg[:hdr]) - if h == nil || h.qos == 0 { - return - } - jsa := s.mqttGetJSAForAccount(acc) - if jsa == nil { - return - } - jsa.storeMsg(mqttStreamSubjectPrefix+subject, hdr, msg) -} - -func mqttParsePublishNATSHeader(headerBytes []byte) *mqttParsedPublishNATSHeader { - if len(headerBytes) == 0 { - return nil - } - - pubValue := getHeader(mqttNatsHeader, headerBytes) - if len(pubValue) == 0 { - return nil - } - return &mqttParsedPublishNATSHeader{ - qos: pubValue[0] - '0', - subject: getHeader(mqttNatsHeaderSubject, headerBytes), - mapped: getHeader(mqttNatsHeaderMapped, headerBytes), - } -} - -func mqttParsePubRelNATSHeader(headerBytes []byte) uint16 { - if len(headerBytes) == 0 { - return 0 - } - - pubrelValue := getHeader(mqttNatsPubRelHeader, headerBytes) - if len(pubrelValue) == 0 { - return 0 - } - pi, _ := strconv.Atoi(string(pubrelValue)) - return uint16(pi) -} - -// Returns the MQTT sessions manager for a given account. -// If new, creates the required JetStream streams/consumers -// for handling of sessions and messages. -func (s *Server) getOrCreateMQTTAccountSessionManager(c *client) (*mqttAccountSessionManager, error) { - sm := &s.mqtt.sessmgr - - c.mu.Lock() - acc := c.acc - c.mu.Unlock() - accName := acc.GetName() - - sm.mu.RLock() - asm, ok := sm.sessions[accName] - sm.mu.RUnlock() - - if ok { - return asm, nil - } - - // We will pass the quitCh to the account session manager if we happen to create it. - s.mu.Lock() - quitCh := s.quitCh - s.mu.Unlock() - - // Not found, now take the write lock and check again - sm.mu.Lock() - defer sm.mu.Unlock() - asm, ok = sm.sessions[accName] - if ok { - return asm, nil - } - // Need to create one here. - asm, err := s.mqttCreateAccountSessionManager(acc, quitCh) - if err != nil { - return nil, err - } - sm.sessions[accName] = asm - return asm, nil -} - -// Creates JS streams/consumers for handling of sessions and messages for this account. -// -// Global session manager lock is held on entry. -func (s *Server) mqttCreateAccountSessionManager(acc *Account, quitCh chan struct{}) (*mqttAccountSessionManager, error) { - var err error - - accName := acc.GetName() - - opts := s.getOpts() - c := s.createInternalAccountClient() - c.acc = acc - - id := s.NodeName() - replicas := opts.MQTT.StreamReplicas - if replicas <= 0 { - replicas = s.mqttDetermineReplicas() - } - qname := fmt.Sprintf("[ACC:%s] MQTT ", accName) - as := &mqttAccountSessionManager{ - sessions: make(map[string]*mqttSession), - sessByHash: make(map[string]*mqttSession), - sessLocked: make(map[string]struct{}), - flappers: make(map[string]int64), - jsa: mqttJSA{ - id: id, - c: c, - rplyr: mqttJSARepliesPrefix + id + ".", - sendq: newIPQueue[*mqttJSPubMsg](s, qname+"send"), - nuid: nuid.New(), - quitCh: quitCh, - }, - } - if !testDisableRMSCache { - as.rmsCache = &sync.Map{} - } - // TODO record domain name in as here - - // The domain to communicate with may be required for JS calls. - // Search from specific (per account setting) to generic (mqtt setting) - if opts.JsAccDefaultDomain != nil { - if d, ok := opts.JsAccDefaultDomain[accName]; ok { - if d != _EMPTY_ { - as.jsa.domain = d - } - as.jsa.domainSet = true - } - // in case domain was set to empty, check if there are more generic domain overwrites - } - if as.jsa.domain == _EMPTY_ { - if d := opts.MQTT.JsDomain; d != _EMPTY_ { - as.jsa.domain = d - as.jsa.domainSet = true - } - } - // We need to include the domain in the subject prefix used to store sessions in the $MQTT_sess stream. - if as.jsa.domainSet { - if as.jsa.domain != _EMPTY_ { - as.domainTk = as.jsa.domain + "." - } - } else if d := s.getOpts().JetStreamDomain; d != _EMPTY_ { - as.domainTk = d + "." - } - if as.jsa.domainSet { - s.Noticef("Creating MQTT streams/consumers with replicas %v for account %q in domain %q", replicas, accName, as.jsa.domain) - } else { - s.Noticef("Creating MQTT streams/consumers with replicas %v for account %q", replicas, accName) - } - - var subs []*subscription - var success bool - closeCh := make(chan struct{}) - - defer func() { - if success { - return - } - for _, sub := range subs { - c.processUnsub(sub.sid) - } - close(closeCh) - }() - - // We create all subscriptions before starting the go routine that will do - // sends otherwise we could get races. - // Note that using two different clients (one for the subs, one for the - // sends) would cause other issues such as registration of recent subs in - // the "sub" client would be invisible to the check for GW routed replies - // (shouldMapReplyForGatewaySend) since the client there would be the "sender". - - jsa := &as.jsa - sid := int64(1) - // This is a subscription that will process all JS API replies. We could split to - // individual subscriptions if needed, but since there is a bit of common code, - // that seemed like a good idea to be all in one place. - if err := as.createSubscription(jsa.rplyr+">", - as.processJSAPIReplies, &sid, &subs); err != nil { - return nil, err - } - - // We will listen for replies to session persist requests so that we can - // detect the use of a session with the same client ID anywhere in the cluster. - // `$MQTT.JSA.{js-id}.SP.{client-id-hash}.{uuid}` - if err := as.createSubscription(mqttJSARepliesPrefix+"*."+mqttJSASessPersist+".*.*", - as.processSessionPersist, &sid, &subs); err != nil { - return nil, err - } - - // We create the subscription on "$MQTT.sub." to limit the subjects - // that a user would allow permissions on. - rmsubj := mqttSubPrefix + nuid.Next() - if err := as.createSubscription(rmsubj, as.processRetainedMsg, &sid, &subs); err != nil { - return nil, err - } - - // Create a subscription to be notified of retained messages delete requests. - rmdelsubj := mqttJSARepliesPrefix + "*." + mqttJSARetainedMsgDel - if err := as.createSubscription(rmdelsubj, as.processRetainedMsgDel, &sid, &subs); err != nil { - return nil, err - } - - // No more creation of subscriptions past this point otherwise RACEs may happen. - - // Start the go routine that will send JS API requests. - s.startGoRoutine(func() { - defer s.grWG.Done() - as.sendJSAPIrequests(s, c, accName, closeCh) - }) - - // Start the go routine that will clean up cached retained messages that expired. - if as.rmsCache != nil { - s.startGoRoutine(func() { - defer s.grWG.Done() - as.cleanupRetainedMessageCache(s, closeCh) - }) - } - - lookupStream := func(stream, txt string) (*StreamInfo, error) { - si, err := jsa.lookupStream(stream) - if err != nil { - if IsNatsErr(err, JSStreamNotFoundErr) { - return nil, nil - } - return nil, fmt.Errorf("lookup %s stream for account %q: %v", txt, accName, err) - } - if opts.MQTT.StreamReplicas == 0 { - return si, nil - } - sr := 1 - if si.Cluster != nil { - sr += len(si.Cluster.Replicas) - } - if replicas != sr { - s.Warnf("MQTT %s stream replicas mismatch: current is %v but configuration is %v for '%s > %s'", - txt, sr, replicas, accName, stream) - } - return si, nil - } - - if si, err := lookupStream(mqttSessStreamName, "sessions"); err != nil { - return nil, err - } else if si == nil { - // Create the stream for the sessions. - cfg := &StreamConfig{ - Name: mqttSessStreamName, - Subjects: []string{mqttSessStreamSubjectPrefix + as.domainTk + ">"}, - Storage: FileStorage, - Retention: LimitsPolicy, - Replicas: replicas, - MaxMsgsPer: 1, - } - if _, created, err := jsa.createStream(cfg); err == nil && created { - as.transferUniqueSessStreamsToMuxed(s) - } else if isErrorOtherThan(err, JSStreamNameExistErr) { - return nil, fmt.Errorf("create sessions stream for account %q: %v", accName, err) - } - } - - if si, err := lookupStream(mqttStreamName, "messages"); err != nil { - return nil, err - } else if si == nil { - // Create the stream for the messages. - cfg := &StreamConfig{ - Name: mqttStreamName, - Subjects: []string{mqttStreamSubjectPrefix + ">"}, - Storage: FileStorage, - Retention: InterestPolicy, - Replicas: replicas, - } - if _, _, err := jsa.createStream(cfg); isErrorOtherThan(err, JSStreamNameExistErr) { - return nil, fmt.Errorf("create messages stream for account %q: %v", accName, err) - } - } - - if si, err := lookupStream(mqttQoS2IncomingMsgsStreamName, "QoS2 incoming messages"); err != nil { - return nil, err - } else if si == nil { - // Create the stream for the incoming QoS2 messages that have not been - // PUBREL-ed by the sender. Subject is - // "$MQTT.qos2..", the .PI is to achieve exactly - // once for each PI. - cfg := &StreamConfig{ - Name: mqttQoS2IncomingMsgsStreamName, - Subjects: []string{mqttQoS2IncomingMsgsStreamSubjectPrefix + ">"}, - Storage: FileStorage, - Retention: LimitsPolicy, - Discard: DiscardNew, - MaxMsgsPer: 1, - DiscardNewPer: true, - Replicas: replicas, - } - if _, _, err := jsa.createStream(cfg); isErrorOtherThan(err, JSStreamNameExistErr) { - return nil, fmt.Errorf("create QoS2 incoming messages stream for account %q: %v", accName, err) - } - } - - if si, err := lookupStream(mqttOutStreamName, "QoS2 outgoing PUBREL"); err != nil { - return nil, err - } else if si == nil { - // Create the stream for the incoming QoS2 messages that have not been - // PUBREL-ed by the sender. NATS messages are submitted as - // "$MQTT.pubrel." - cfg := &StreamConfig{ - Name: mqttOutStreamName, - Subjects: []string{mqttOutSubjectPrefix + ">"}, - Storage: FileStorage, - Retention: InterestPolicy, - Replicas: replicas, - } - if _, _, err := jsa.createStream(cfg); isErrorOtherThan(err, JSStreamNameExistErr) { - return nil, fmt.Errorf("create QoS2 outgoing PUBREL stream for account %q: %v", accName, err) - } - } - - // This is the only case where we need "si" after lookup/create - needToTransfer := true - si, err := lookupStream(mqttRetainedMsgsStreamName, "retained messages") - switch { - case err != nil: - return nil, err - - case si == nil: - // Create the stream for retained messages. - cfg := &StreamConfig{ - Name: mqttRetainedMsgsStreamName, - Subjects: []string{mqttRetainedMsgsStreamSubject + ">"}, - Storage: FileStorage, - Retention: LimitsPolicy, - Replicas: replicas, - MaxMsgsPer: 1, - } - // We will need "si" outside of this block. - si, _, err = jsa.createStream(cfg) - if err != nil { - if isErrorOtherThan(err, JSStreamNameExistErr) { - return nil, fmt.Errorf("create retained messages stream for account %q: %v", accName, err) - } - // Suppose we had a race and the stream was actually created by another - // node, we really need "si" after that, so lookup the stream again here. - si, err = lookupStream(mqttRetainedMsgsStreamName, "retained messages") - if err != nil { - return nil, err - } - } - needToTransfer = false - - default: - needToTransfer = si.Config.MaxMsgsPer != 1 - } - - // Doing this check outside of above if/else due to possible race when - // creating the stream. - wantedSubj := mqttRetainedMsgsStreamSubject + ">" - if len(si.Config.Subjects) != 1 || si.Config.Subjects[0] != wantedSubj { - // Update only the Subjects at this stage, not MaxMsgsPer yet. - si.Config.Subjects = []string{wantedSubj} - if si, err = jsa.updateStream(&si.Config); err != nil { - return nil, fmt.Errorf("failed to update stream config: %w", err) - } - } - - transferRMS := func() error { - if !needToTransfer { - return nil - } - - as.transferRetainedToPerKeySubjectStream(s) - - // We need another lookup to have up-to-date si.State values in order - // to load all retained messages. - si, err = lookupStream(mqttRetainedMsgsStreamName, "retained messages") - if err != nil { - return err - } - needToTransfer = false - return nil - } - - // Attempt to transfer all "single subject" retained messages to new - // subjects. It may fail, will log its own error; ignore it the first time - // and proceed to updating MaxMsgsPer. Then we invoke transferRMS() again, - // which will get another chance to resolve the error; if not we bail there. - if err = transferRMS(); err != nil { - return nil, err - } - - // Now, if the stream does not have MaxMsgsPer set to 1, and there are no - // more messages on the single $MQTT.rmsgs subject, update the stream again. - if si.Config.MaxMsgsPer != 1 { - si.Config.MaxMsgsPer = 1 - // We will need an up-to-date si, so don't use local variable here. - if si, err = jsa.updateStream(&si.Config); err != nil { - return nil, fmt.Errorf("failed to update stream config: %w", err) - } - } - - // If we failed the first time, there is now at most one lingering message - // in the old subject. Try again (it will be a NO-OP if succeeded the first - // time). - if err = transferRMS(); err != nil { - return nil, err - } - - var lastSeq uint64 - var rmDoneCh chan struct{} - st := si.State - if st.Msgs > 0 { - lastSeq = st.LastSeq - if lastSeq > 0 { - rmDoneCh = make(chan struct{}) - as.rrmLastSeq = lastSeq - as.rrmDoneCh = rmDoneCh - } - } - - // Opportunistically delete the old (legacy) consumer, from v2.10.10 and - // before. Ignore any errors that might arise. - rmLegacyDurName := mqttRetainedMsgsStreamName + "_" + jsa.id - jsa.deleteConsumer(mqttRetainedMsgsStreamName, rmLegacyDurName, true) - - // Create a new, uniquely names consumer for retained messages for this - // server. The prior one will expire eventually. - ccfg := &CreateConsumerRequest{ - Stream: mqttRetainedMsgsStreamName, - Config: ConsumerConfig{ - Name: mqttRetainedMsgsStreamName + "_" + nuid.Next(), - FilterSubject: mqttRetainedMsgsStreamSubject + ">", - DeliverSubject: rmsubj, - ReplayPolicy: ReplayInstant, - AckPolicy: AckNone, - InactiveThreshold: 5 * time.Minute, - }, - } - if _, err := jsa.createEphemeralConsumer(ccfg); err != nil { - return nil, fmt.Errorf("create retained messages consumer for account %q: %v", accName, err) - } - - if lastSeq > 0 { - ttl := time.NewTimer(mqttJSAPITimeout) - defer ttl.Stop() - - select { - case <-rmDoneCh: - case <-ttl.C: - s.Warnf("Timing out waiting to load %v retained messages", st.Msgs) - case <-quitCh: - return nil, ErrServerNotRunning - } - } - - // Set this so that on defer we don't cleanup. - success = true - - return as, nil -} - -func (s *Server) mqttDetermineReplicas() int { - // If not clustered, then replica will be 1. - if !s.JetStreamIsClustered() { - return 1 - } - opts := s.getOpts() - replicas := 0 - for _, u := range opts.Routes { - host := u.Hostname() - // If this is an IP just add one. - if net.ParseIP(host) != nil { - replicas++ - } else { - addrs, _ := net.LookupHost(host) - replicas += len(addrs) - } - } - if replicas < 1 { - replicas = 1 - } else if replicas > 3 { - replicas = 3 - } - return replicas -} - -////////////////////////////////////////////////////////////////////////////// -// -// JS APIs related functions -// -////////////////////////////////////////////////////////////////////////////// - -func (jsa *mqttJSA) newRequest(kind, subject string, hdr int, msg []byte) (any, error) { - return jsa.newRequestEx(kind, subject, _EMPTY_, hdr, msg, mqttJSAPITimeout) -} - -func (jsa *mqttJSA) prefixDomain(subject string) string { - if jsa.domain != _EMPTY_ { - // rewrite js api prefix with domain - if sub := strings.TrimPrefix(subject, JSApiPrefix+"."); sub != subject { - subject = fmt.Sprintf("$JS.%s.API.%s", jsa.domain, sub) - } - } - return subject -} - -func (jsa *mqttJSA) newRequestEx(kind, subject, cidHash string, hdr int, msg []byte, timeout time.Duration) (any, error) { - responses, err := jsa.newRequestExMulti(kind, subject, cidHash, []int{hdr}, [][]byte{msg}, timeout) - if err != nil { - return nil, err - } - if len(responses) != 1 { - return nil, fmt.Errorf("unreachable: invalid number of responses (%d)", len(responses)) - } - return responses[0].value, nil -} - -// newRequestExMulti sends multiple messages on the same subject and waits for -// all responses. It returns the same number of responses in the same order as -// msgs parameter. In case of a timeout it returns an error as well as all -// responses received as a sparsely populated array, matching msgs, with nils -// for the values that have not yet been received. -// -// Note that each response may represent an error and should be inspected as -// such by the caller. -func (jsa *mqttJSA) newRequestExMulti(kind, subject, cidHash string, hdrs []int, msgs [][]byte, timeout time.Duration) ([]*mqttJSAResponse, error) { - if len(hdrs) != len(msgs) { - return nil, fmt.Errorf("unreachable: invalid number of messages (%d) or header offsets (%d)", len(msgs), len(hdrs)) - } - responseCh := make(chan *mqttJSAResponse, len(msgs)) - - // Generate and queue all outgoing requests, have all results reported to - // responseCh, and store a map of reply subjects to the original subjects' - // indices. - r2i := map[string]int{} - for i, msg := range msgs { - hdr := hdrs[i] - var sb strings.Builder - // Either we use nuid.Next() which uses a global lock, or our own nuid object, but - // then it needs to be "write" protected. This approach will reduce across account - // contention since we won't use the global nuid's lock. - jsa.mu.Lock() - uid := jsa.nuid.Next() - sb.WriteString(jsa.rplyr) - jsa.mu.Unlock() - - sb.WriteString(kind) - sb.WriteByte(btsep) - if cidHash != _EMPTY_ { - sb.WriteString(cidHash) - sb.WriteByte(btsep) - } - sb.WriteString(uid) - reply := sb.String() - - // Add responseCh to the reply channel map. It will be cleaned out on - // timeout (see below), or in processJSAPIReplies upon receiving the - // response. - jsa.replies.Store(reply, responseCh) - - subject = jsa.prefixDomain(subject) - jsa.sendq.push(&mqttJSPubMsg{ - subj: subject, - reply: reply, - hdr: hdr, - msg: msg, - }) - r2i[reply] = i - } - - // Wait for all responses to come back, or for the timeout to expire. We - // don't want to use time.After() which causes memory growth because the - // timer can't be stopped and will need to expire to then be garbage - // collected. - c := 0 - responses := make([]*mqttJSAResponse, len(msgs)) - start := time.Now() - t := time.NewTimer(timeout) - defer t.Stop() - for { - select { - case r := <-responseCh: - i := r2i[r.reply] - responses[i] = r - c++ - if c == len(msgs) { - return responses, nil - } - - case <-jsa.quitCh: - return nil, ErrServerNotRunning - - case <-t.C: - var reply string - now := time.Now() - for reply = range r2i { // preserve the last value for Errorf - jsa.replies.Delete(reply) - } - - if len(msgs) == 1 { - return responses, fmt.Errorf("timeout after %v: request type %q on %q (reply=%q)", now.Sub(start), kind, subject, reply) - } else { - return responses, fmt.Errorf("timeout after %v: request type %q on %q: got %d out of %d", now.Sub(start), kind, subject, c, len(msgs)) - } - } - } -} - -func (jsa *mqttJSA) sendAck(ackSubject string) { - // We pass -1 for the hdr so that the send loop does not need to - // add the "client info" header. This is not a JS API request per se. - jsa.sendMsg(ackSubject, nil) -} - -func (jsa *mqttJSA) sendMsg(subj string, msg []byte) { - if subj == _EMPTY_ { - return - } - jsa.sendq.push(&mqttJSPubMsg{subj: subj, msg: msg, hdr: -1}) -} - -func (jsa *mqttJSA) createEphemeralConsumer(cfg *CreateConsumerRequest) (*JSApiConsumerCreateResponse, error) { - cfgb, err := json.Marshal(cfg) - if err != nil { - return nil, err - } - subj := fmt.Sprintf(JSApiConsumerCreateT, cfg.Stream) - ccri, err := jsa.newRequest(mqttJSAConsumerCreate, subj, 0, cfgb) - if err != nil { - return nil, err - } - ccr := ccri.(*JSApiConsumerCreateResponse) - return ccr, ccr.ToError() -} - -func (jsa *mqttJSA) createDurableConsumer(cfg *CreateConsumerRequest) (*JSApiConsumerCreateResponse, error) { - cfgb, err := json.Marshal(cfg) - if err != nil { - return nil, err - } - subj := fmt.Sprintf(JSApiDurableCreateT, cfg.Stream, cfg.Config.Durable) - ccri, err := jsa.newRequest(mqttJSAConsumerCreate, subj, 0, cfgb) - if err != nil { - return nil, err - } - ccr := ccri.(*JSApiConsumerCreateResponse) - return ccr, ccr.ToError() -} - -// if noWait is specified, does not wait for the JS response, returns nil -func (jsa *mqttJSA) deleteConsumer(streamName, consName string, noWait bool) (*JSApiConsumerDeleteResponse, error) { - subj := fmt.Sprintf(JSApiConsumerDeleteT, streamName, consName) - if noWait { - jsa.sendMsg(subj, nil) - return nil, nil - } - - cdri, err := jsa.newRequest(mqttJSAConsumerDel, subj, 0, nil) - if err != nil { - return nil, err - } - cdr := cdri.(*JSApiConsumerDeleteResponse) - return cdr, cdr.ToError() -} - -func (jsa *mqttJSA) createStream(cfg *StreamConfig) (*StreamInfo, bool, error) { - cfgb, err := json.Marshal(cfg) - if err != nil { - return nil, false, err - } - scri, err := jsa.newRequest(mqttJSAStreamCreate, fmt.Sprintf(JSApiStreamCreateT, cfg.Name), 0, cfgb) - if err != nil { - return nil, false, err - } - scr := scri.(*JSApiStreamCreateResponse) - return scr.StreamInfo, scr.DidCreate, scr.ToError() -} - -func (jsa *mqttJSA) updateStream(cfg *StreamConfig) (*StreamInfo, error) { - cfgb, err := json.Marshal(cfg) - if err != nil { - return nil, err - } - scri, err := jsa.newRequest(mqttJSAStreamUpdate, fmt.Sprintf(JSApiStreamUpdateT, cfg.Name), 0, cfgb) - if err != nil { - return nil, err - } - scr := scri.(*JSApiStreamUpdateResponse) - return scr.StreamInfo, scr.ToError() -} - -func (jsa *mqttJSA) lookupStream(name string) (*StreamInfo, error) { - slri, err := jsa.newRequest(mqttJSAStreamLookup, fmt.Sprintf(JSApiStreamInfoT, name), 0, nil) - if err != nil { - return nil, err - } - slr := slri.(*JSApiStreamInfoResponse) - return slr.StreamInfo, slr.ToError() -} - -func (jsa *mqttJSA) deleteStream(name string) (bool, error) { - sdri, err := jsa.newRequest(mqttJSAStreamDel, fmt.Sprintf(JSApiStreamDeleteT, name), 0, nil) - if err != nil { - return false, err - } - sdr := sdri.(*JSApiStreamDeleteResponse) - return sdr.Success, sdr.ToError() -} - -func (jsa *mqttJSA) loadLastMsgFor(streamName string, subject string) (*StoredMsg, error) { - mreq := &JSApiMsgGetRequest{LastFor: subject} - req, err := json.Marshal(mreq) - if err != nil { - return nil, err - } - lmri, err := jsa.newRequest(mqttJSAMsgLoad, fmt.Sprintf(JSApiMsgGetT, streamName), 0, req) - if err != nil { - return nil, err - } - lmr := lmri.(*JSApiMsgGetResponse) - return lmr.Message, lmr.ToError() -} - -func (jsa *mqttJSA) loadLastMsgForMulti(streamName string, subjects []string) ([]*JSApiMsgGetResponse, error) { - marshaled := make([][]byte, 0, len(subjects)) - headerBytes := make([]int, 0, len(subjects)) - for _, subject := range subjects { - mreq := &JSApiMsgGetRequest{LastFor: subject} - bb, err := json.Marshal(mreq) - if err != nil { - return nil, err - } - marshaled = append(marshaled, bb) - headerBytes = append(headerBytes, 0) - } - - all, err := jsa.newRequestExMulti(mqttJSAMsgLoad, fmt.Sprintf(JSApiMsgGetT, streamName), _EMPTY_, headerBytes, marshaled, mqttJSAPITimeout) - // all has the same order as subjects, preserve it as we unmarshal - responses := make([]*JSApiMsgGetResponse, len(all)) - for i, v := range all { - if v != nil { - responses[i] = v.value.(*JSApiMsgGetResponse) - } - } - return responses, err -} - -func (jsa *mqttJSA) loadNextMsgFor(streamName string, subject string) (*StoredMsg, error) { - mreq := &JSApiMsgGetRequest{NextFor: subject} - req, err := json.Marshal(mreq) - if err != nil { - return nil, err - } - lmri, err := jsa.newRequest(mqttJSAMsgLoad, fmt.Sprintf(JSApiMsgGetT, streamName), 0, req) - if err != nil { - return nil, err - } - lmr := lmri.(*JSApiMsgGetResponse) - return lmr.Message, lmr.ToError() -} - -func (jsa *mqttJSA) loadMsg(streamName string, seq uint64) (*StoredMsg, error) { - mreq := &JSApiMsgGetRequest{Seq: seq} - req, err := json.Marshal(mreq) - if err != nil { - return nil, err - } - lmri, err := jsa.newRequest(mqttJSAMsgLoad, fmt.Sprintf(JSApiMsgGetT, streamName), 0, req) - if err != nil { - return nil, err - } - lmr := lmri.(*JSApiMsgGetResponse) - return lmr.Message, lmr.ToError() -} - -func (jsa *mqttJSA) storeMsg(subject string, headers int, msg []byte) (*JSPubAckResponse, error) { - return jsa.storeMsgWithKind(mqttJSAMsgStore, subject, headers, msg) -} - -func (jsa *mqttJSA) storeMsgWithKind(kind, subject string, headers int, msg []byte) (*JSPubAckResponse, error) { - smri, err := jsa.newRequest(kind, subject, headers, msg) - if err != nil { - return nil, err - } - smr := smri.(*JSPubAckResponse) - return smr, smr.ToError() -} - -func (jsa *mqttJSA) storeSessionMsg(domainTk, cidHash string, hdr int, msg []byte) (*JSPubAckResponse, error) { - // Compute subject where the session is being stored - subject := mqttSessStreamSubjectPrefix + domainTk + cidHash - - // Passing cidHash will add it to the JS reply subject, so that we can use - // it in processSessionPersist. - smri, err := jsa.newRequestEx(mqttJSASessPersist, subject, cidHash, hdr, msg, mqttJSAPITimeout) - if err != nil { - return nil, err - } - smr := smri.(*JSPubAckResponse) - return smr, smr.ToError() -} - -func (jsa *mqttJSA) loadSessionMsg(domainTk, cidHash string) (*StoredMsg, error) { - subject := mqttSessStreamSubjectPrefix + domainTk + cidHash - return jsa.loadLastMsgFor(mqttSessStreamName, subject) -} - -func (jsa *mqttJSA) deleteMsg(stream string, seq uint64, wait bool) error { - dreq := JSApiMsgDeleteRequest{Seq: seq, NoErase: true} - req, _ := json.Marshal(dreq) - subj := jsa.prefixDomain(fmt.Sprintf(JSApiMsgDeleteT, stream)) - if !wait { - jsa.sendq.push(&mqttJSPubMsg{ - subj: subj, - msg: req, - }) - return nil - } - dmi, err := jsa.newRequest(mqttJSAMsgDelete, subj, 0, req) - if err != nil { - return err - } - dm := dmi.(*JSApiMsgDeleteResponse) - return dm.ToError() -} - -////////////////////////////////////////////////////////////////////////////// -// -// Account Sessions Manager related functions -// -////////////////////////////////////////////////////////////////////////////// - -// Returns true if `err` is not nil and does not match the api error with ErrorIdentifier id -func isErrorOtherThan(err error, id ErrorIdentifier) bool { - return err != nil && !IsNatsErr(err, id) -} - -// Process JS API replies. -// -// Can run from various go routines (consumer's loop, system send loop, etc..). -func (as *mqttAccountSessionManager) processJSAPIReplies(_ *subscription, pc *client, _ *Account, subject, _ string, msg []byte) { - token := tokenAt(subject, mqttJSATokenPos) - if token == _EMPTY_ { - return - } - jsa := &as.jsa - chi, ok := jsa.replies.Load(subject) - if !ok { - return - } - jsa.replies.Delete(subject) - ch := chi.(chan *mqttJSAResponse) - out := func(value any) { - ch <- &mqttJSAResponse{reply: subject, value: value} - } - switch token { - case mqttJSAStreamCreate: - var resp = &JSApiStreamCreateResponse{} - if err := json.Unmarshal(msg, resp); err != nil { - resp.Error = NewJSInvalidJSONError(err) - } - out(resp) - case mqttJSAStreamUpdate: - var resp = &JSApiStreamUpdateResponse{} - if err := json.Unmarshal(msg, resp); err != nil { - resp.Error = NewJSInvalidJSONError(err) - } - out(resp) - case mqttJSAStreamLookup: - var resp = &JSApiStreamInfoResponse{} - if err := json.Unmarshal(msg, &resp); err != nil { - resp.Error = NewJSInvalidJSONError(err) - } - out(resp) - case mqttJSAStreamDel: - var resp = &JSApiStreamDeleteResponse{} - if err := json.Unmarshal(msg, &resp); err != nil { - resp.Error = NewJSInvalidJSONError(err) - } - out(resp) - case mqttJSAConsumerCreate: - var resp = &JSApiConsumerCreateResponse{} - if err := json.Unmarshal(msg, resp); err != nil { - resp.Error = NewJSInvalidJSONError(err) - } - out(resp) - case mqttJSAConsumerDel: - var resp = &JSApiConsumerDeleteResponse{} - if err := json.Unmarshal(msg, resp); err != nil { - resp.Error = NewJSInvalidJSONError(err) - } - out(resp) - case mqttJSAMsgStore, mqttJSASessPersist: - var resp = &JSPubAckResponse{} - if err := json.Unmarshal(msg, resp); err != nil { - resp.Error = NewJSInvalidJSONError(err) - } - out(resp) - case mqttJSAMsgLoad: - var resp = &JSApiMsgGetResponse{} - if err := json.Unmarshal(msg, &resp); err != nil { - resp.Error = NewJSInvalidJSONError(err) - } - out(resp) - case mqttJSAStreamNames: - var resp = &JSApiStreamNamesResponse{} - if err := json.Unmarshal(msg, resp); err != nil { - resp.Error = NewJSInvalidJSONError(err) - } - out(resp) - case mqttJSAMsgDelete: - var resp = &JSApiMsgDeleteResponse{} - if err := json.Unmarshal(msg, resp); err != nil { - resp.Error = NewJSInvalidJSONError(err) - } - out(resp) - default: - pc.Warnf("Unknown reply code %q", token) - } -} - -// This will both load all retained messages and process updates from the cluster. -// -// Run from various go routines (JS consumer, etc..). -// No lock held on entry. -func (as *mqttAccountSessionManager) processRetainedMsg(_ *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - h, m := c.msgParts(rmsg) - rm, err := mqttDecodeRetainedMessage(h, m) - if err != nil { - return - } - // If lastSeq is 0 (nothing to recover, or done doing it) and this is - // from our own server, ignore. - as.mu.RLock() - if as.rrmLastSeq == 0 && rm.Origin == as.jsa.id { - as.mu.RUnlock() - return - } - as.mu.RUnlock() - - // At this point we either recover from our own server, or process a remote retained message. - seq, _, _ := ackReplyInfo(reply) - - // Handle this retained message, no need to copy the bytes. - as.handleRetainedMsg(rm.Subject, &mqttRetainedMsgRef{sseq: seq}, rm, false) - - // If we were recovering (lastSeq > 0), then check if we are done. - as.mu.Lock() - if as.rrmLastSeq > 0 && seq >= as.rrmLastSeq { - as.rrmLastSeq = 0 - close(as.rrmDoneCh) - as.rrmDoneCh = nil - } - as.mu.Unlock() -} - -func (as *mqttAccountSessionManager) processRetainedMsgDel(_ *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - idHash := tokenAt(subject, 3) - if idHash == _EMPTY_ || idHash == as.jsa.id { - return - } - _, msg := c.msgParts(rmsg) - if len(msg) < LEN_CR_LF { - return - } - var drm mqttRetMsgDel - if err := json.Unmarshal(msg, &drm); err != nil { - return - } - as.handleRetainedMsgDel(drm.Subject, drm.Seq) -} - -// This will receive all JS API replies for a request to store a session record, -// including the reply for our own server, which we will ignore. -// This allows us to detect that some application somewhere else in the cluster -// is connecting with the same client ID, and therefore we need to close the -// connection that is currently using this client ID. -// -// Can run from various go routines (system send loop, etc..). -// No lock held on entry. -func (as *mqttAccountSessionManager) processSessionPersist(_ *subscription, pc *client, _ *Account, subject, _ string, rmsg []byte) { - // Ignore our own responses here (they are handled elsewhere) - if tokenAt(subject, mqttJSAIdTokenPos) == as.jsa.id { - return - } - cIDHash := tokenAt(subject, mqttJSAClientIDPos) - _, msg := pc.msgParts(rmsg) - if len(msg) < LEN_CR_LF { - return - } - var par = &JSPubAckResponse{} - if err := json.Unmarshal(msg, par); err != nil { - return - } - if err := par.Error; err != nil { - return - } - as.mu.RLock() - // Note that as.domainTk includes a terminal '.', so strip to compare to PubAck.Domain. - dl := len(as.domainTk) - if dl > 0 { - dl-- - } - ignore := par.Domain != as.domainTk[:dl] - as.mu.RUnlock() - if ignore { - return - } - - as.mu.Lock() - defer as.mu.Unlock() - sess, ok := as.sessByHash[cIDHash] - if !ok { - return - } - // If our current session's stream sequence is higher, it means that this - // update is stale, so we don't do anything here. - sess.mu.Lock() - ignore = par.Sequence < sess.seq - sess.mu.Unlock() - if ignore { - return - } - as.removeSession(sess, false) - sess.mu.Lock() - if ec := sess.c; ec != nil { - as.addSessToFlappers(sess.id) - ec.Warnf("Closing because a remote connection has started with the same client ID: %q", sess.id) - // Disassociate the client from the session so that on client close, - // nothing will be done with regards to cleaning up the session, - // such as deleting stream, etc.. - sess.c = nil - // Remove in separate go routine. - go ec.closeConnection(DuplicateClientID) - } - sess.mu.Unlock() -} - -// Adds this client ID to the flappers map, and if needed start the timer -// for map cleanup. -// -// Lock held on entry. -func (as *mqttAccountSessionManager) addSessToFlappers(clientID string) { - as.flappers[clientID] = time.Now().UnixNano() - if as.flapTimer == nil { - as.flapTimer = time.AfterFunc(mqttFlapCleanItvl, func() { - as.mu.Lock() - defer as.mu.Unlock() - // In case of shutdown, this will be nil - if as.flapTimer == nil { - return - } - now := time.Now().UnixNano() - for cID, tm := range as.flappers { - if now-tm > int64(mqttSessJailDur) { - delete(as.flappers, cID) - } - } - as.flapTimer.Reset(mqttFlapCleanItvl) - }) - } -} - -// Remove this client ID from the flappers map. -// -// Lock held on entry. -func (as *mqttAccountSessionManager) removeSessFromFlappers(clientID string) { - delete(as.flappers, clientID) - // Do not stop/set timer to nil here. Better leave the timer run at its - // regular interval and detect that there is nothing to do. The timer - // will be stopped on shutdown. -} - -// Helper to create a subscription. It updates the sid and array of subscriptions. -func (as *mqttAccountSessionManager) createSubscription(subject string, cb msgHandler, sid *int64, subs *[]*subscription) error { - sub, err := as.jsa.c.processSub([]byte(subject), nil, []byte(strconv.FormatInt(*sid, 10)), cb, false) - if err != nil { - return err - } - *sid++ - *subs = append(*subs, sub) - return nil -} - -// A timer loop to cleanup up expired cached retained messages for a given MQTT account. -// The closeCh is used by the caller to be able to interrupt this routine -// if the rest of the initialization fails, since the quitCh is really -// only used when the server shutdown. -// -// No lock held on entry. -func (as *mqttAccountSessionManager) cleanupRetainedMessageCache(s *Server, closeCh chan struct{}) { - tt := time.NewTicker(mqttRetainedCacheTTL) - defer tt.Stop() - for { - select { - case <-tt.C: - // Set a limit to the number of retained messages to scan since we - // lock as for it. Since the map enumeration gives random order we - // should eventually clean up everything. - i, maxScan := 0, 10*1000 - now := time.Now() - as.rmsCache.Range(func(key, value any) bool { - rm := value.(*mqttRetainedMsg) - if now.After(rm.expiresFromCache) { - as.rmsCache.Delete(key) - } - i++ - return i < maxScan - }) - - case <-closeCh: - return - case <-s.quitCh: - return - } - } -} - -// Loop to send JS API requests for a given MQTT account. -// The closeCh is used by the caller to be able to interrupt this routine -// if the rest of the initialization fails, since the quitCh is really -// only used when the server shutdown. -// -// No lock held on entry. -func (as *mqttAccountSessionManager) sendJSAPIrequests(s *Server, c *client, accName string, closeCh chan struct{}) { - var cluster string - if s.JetStreamEnabled() && !as.jsa.domainSet { - // Only request the own cluster when it is clear that - cluster = s.cachedClusterName() - } - as.mu.RLock() - sendq := as.jsa.sendq - quitCh := as.jsa.quitCh - ci := ClientInfo{Account: accName, Cluster: cluster} - as.mu.RUnlock() - - // The account session manager does not have a suhtdown API per-se, instead, - // we will cleanup things when this go routine exits after detecting that the - // server is shutdown or the initialization of the account manager failed. - defer func() { - as.mu.Lock() - if as.flapTimer != nil { - as.flapTimer.Stop() - as.flapTimer = nil - } - as.mu.Unlock() - }() - - b, _ := json.Marshal(ci) - hdrStart := bytes.Buffer{} - hdrStart.WriteString(hdrLine) - http.Header{ClientInfoHdr: []string{string(b)}}.Write(&hdrStart) - hdrStart.WriteString(CR_LF) - hdrStart.WriteString(CR_LF) - hdrb := hdrStart.Bytes() - - for { - select { - case <-sendq.ch: - pmis := sendq.pop() - for _, r := range pmis { - var nsize int - - msg := r.msg - // If r.hdr is set to -1, it means that there is no need for any header. - if r.hdr != -1 { - bb := bytes.Buffer{} - if r.hdr > 0 { - // This means that the header has been set by the caller and is - // already part of `msg`, so simply set c.pa.hdr to the given value. - c.pa.hdr = r.hdr - nsize = len(msg) - msg = append(msg, _CRLF_...) - } else { - // We need the ClientInfo header, so add it here. - bb.Write(hdrb) - c.pa.hdr = bb.Len() - bb.Write(r.msg) - nsize = bb.Len() - bb.WriteString(_CRLF_) - msg = bb.Bytes() - } - c.pa.hdb = []byte(strconv.Itoa(c.pa.hdr)) - } else { - c.pa.hdr = -1 - c.pa.hdb = nil - nsize = len(msg) - msg = append(msg, _CRLF_...) - } - - c.pa.subject = []byte(r.subj) - c.pa.reply = []byte(r.reply) - c.pa.size = nsize - c.pa.szb = []byte(strconv.Itoa(nsize)) - - c.processInboundClientMsg(msg) - c.flushClients(0) - } - sendq.recycle(&pmis) - - case <-closeCh: - return - case <-quitCh: - return - } - } -} - -// Add/Replace this message from the retained messages map. -// If a message for this topic already existed, the existing record is updated -// with the provided information. -// Lock not held on entry. -func (as *mqttAccountSessionManager) handleRetainedMsg(key string, rf *mqttRetainedMsgRef, rm *mqttRetainedMsg, copyBytesToCache bool) { - as.mu.Lock() - defer as.mu.Unlock() - if as.retmsgs == nil { - as.retmsgs = make(map[string]*mqttRetainedMsgRef) - as.sl = NewSublistWithCache() - } else { - // Check if we already had one retained message. If so, update the existing one. - if erm, exists := as.retmsgs[key]; exists { - // If the new sequence is below the floor or the existing one, - // then ignore the new one. - if rf.sseq <= erm.sseq || rf.sseq <= erm.floor { - return - } - // Capture existing sequence number so we can return it as the old sequence. - erm.sseq = rf.sseq - // Clear the floor - erm.floor = 0 - // If sub is nil, it means that it was removed from sublist following a - // network delete. So need to add it now. - if erm.sub == nil { - erm.sub = &subscription{subject: []byte(key)} - as.sl.Insert(erm.sub) - } - - // Update the in-memory retained message cache but only for messages - // that are already in the cache, i.e. have been (recently) used. - as.setCachedRetainedMsg(key, rm, true, copyBytesToCache) - return - } - } - - rf.sub = &subscription{subject: []byte(key)} - as.retmsgs[key] = rf - as.sl.Insert(rf.sub) -} - -// Removes the retained message for the given `subject` if present, and returns the -// stream sequence it was stored at. It will be 0 if no retained message was removed. -// If a sequence is passed and not 0, then the retained message will be removed only -// if the given sequence is equal or higher to what is stored. -// -// No lock held on entry. -func (as *mqttAccountSessionManager) handleRetainedMsgDel(subject string, seq uint64) uint64 { - var seqToRemove uint64 - as.mu.Lock() - if as.retmsgs == nil { - as.retmsgs = make(map[string]*mqttRetainedMsgRef) - as.sl = NewSublistWithCache() - } - if erm, ok := as.retmsgs[subject]; ok { - if as.rmsCache != nil { - as.rmsCache.Delete(subject) - } - if erm.sub != nil { - as.sl.Remove(erm.sub) - erm.sub = nil - } - // If processing a delete request from the network, then seq will be > 0. - // If that is the case and it is greater or equal to what we have, we need - // to record the floor for this subject. - if seq != 0 && seq >= erm.sseq { - erm.sseq = 0 - erm.floor = seq - } else if seq == 0 { - delete(as.retmsgs, subject) - seqToRemove = erm.sseq - } - } else if seq != 0 { - rf := &mqttRetainedMsgRef{floor: seq} - as.retmsgs[subject] = rf - } - as.mu.Unlock() - return seqToRemove -} - -// First check if this session's client ID is already in the "locked" map, -// which if it is the case means that another client is now bound to this -// session and this should return an error. -// If not in the "locked" map, but the client is not bound with this session, -// then same error is returned. -// Finally, if all checks ok, then the session's ID is added to the "locked" map. -// -// No lock held on entry. -func (as *mqttAccountSessionManager) lockSession(sess *mqttSession, c *client) error { - as.mu.Lock() - defer as.mu.Unlock() - var fail bool - if _, fail = as.sessLocked[sess.id]; !fail { - sess.mu.Lock() - fail = sess.c != c - sess.mu.Unlock() - } - if fail { - return fmt.Errorf("another session is in use with client ID %q", sess.id) - } - as.sessLocked[sess.id] = struct{}{} - return nil -} - -// Remove the session from the "locked" map. -// -// No lock held on entry. -func (as *mqttAccountSessionManager) unlockSession(sess *mqttSession) { - as.mu.Lock() - delete(as.sessLocked, sess.id) - as.mu.Unlock() -} - -// Simply adds the session to the various sessions maps. -// The boolean `lock` indicates if this function should acquire the lock -// prior to adding to the maps. -// -// No lock held on entry. -func (as *mqttAccountSessionManager) addSession(sess *mqttSession, lock bool) { - if lock { - as.mu.Lock() - } - as.sessions[sess.id] = sess - as.sessByHash[sess.idHash] = sess - if lock { - as.mu.Unlock() - } -} - -// Simply removes the session from the various sessions maps. -// The boolean `lock` indicates if this function should acquire the lock -// prior to removing from the maps. -// -// No lock held on entry. -func (as *mqttAccountSessionManager) removeSession(sess *mqttSession, lock bool) { - if lock { - as.mu.Lock() - } - delete(as.sessions, sess.id) - delete(as.sessByHash, sess.idHash) - if lock { - as.mu.Unlock() - } -} - -// Helper to set the sub's mqtt fields and possibly serialize (pre-loaded) -// retained messages. -// -// Session lock held on entry. Acquires the subs lock and holds it for -// the duration. Non-MQTT messages coming into mqttDeliverMsgCbQoS0 will be -// waiting. -func (sess *mqttSession) processQOS12Sub( - c *client, // subscribing client. - subject, sid []byte, isReserved bool, qos byte, jsDurName string, h msgHandler, // subscription parameters. -) (*subscription, error) { - return sess.processSub(c, subject, sid, isReserved, qos, jsDurName, h, false, nil, false, nil) -} - -func (sess *mqttSession) processSub( - c *client, // subscribing client. - subject, sid []byte, isReserved bool, qos byte, jsDurName string, h msgHandler, // subscription parameters. - initShadow bool, // do we need to scan for shadow subscriptions? (not for QOS1+) - rms map[string]*mqttRetainedMsg, // preloaded rms (can be empty, or missing items if errors) - trace bool, // trace serialized retained messages in the log? - as *mqttAccountSessionManager, // needed only for rms serialization. -) (*subscription, error) { - start := time.Now() - defer func() { - elapsed := time.Since(start) - if elapsed > mqttProcessSubTooLong { - c.Warnf("Took too long to process subscription for %q: %v", subject, elapsed) - } - }() - - // Hold subsMu to prevent QOS0 messages callback from doing anything until - // the (MQTT) sub is initialized. - sess.subsMu.Lock() - defer sess.subsMu.Unlock() - - sub, err := c.processSub(subject, nil, sid, h, false) - if err != nil { - // c.processSub already called c.Errorf(), so no need here. - return nil, err - } - subs := []*subscription{sub} - if initShadow { - subs = append(subs, sub.shadow...) - } - for _, ss := range subs { - if ss.mqtt == nil { - // reserved is set only once and once the subscription has been - // created it can be considered immutable. - ss.mqtt = &mqttSub{ - reserved: isReserved, - } - } - // QOS and jsDurName can be changed on an existing subscription, so - // accessing it later requires a lock. - ss.mqtt.qos = qos - ss.mqtt.jsDur = jsDurName - } - - if len(rms) > 0 { - for _, ss := range subs { - as.serializeRetainedMsgsForSub(rms, sess, c, ss, trace) - } - } - - return sub, nil -} - -// Process subscriptions for the given session/client. -// -// When `fromSubProto` is false, it means that this is invoked from the CONNECT -// protocol, when restoring subscriptions that were saved for this session. -// In that case, there is no need to update the session record. -// -// When `fromSubProto` is true, it means that this call is invoked from the -// processing of the SUBSCRIBE protocol, which means that the session needs to -// be updated. It also means that if a subscription on same subject with same -// QoS already exist, we should not be recreating the subscription/JS durable, -// since it was already done when processing the CONNECT protocol. -// -// Runs from the client's readLoop. -// Lock not held on entry, but session is in the locked map. -func (as *mqttAccountSessionManager) processSubs(sess *mqttSession, c *client, - filters []*mqttFilter, fromSubProto, trace bool) ([]*subscription, error) { - - c.mu.Lock() - acc := c.acc - c.mu.Unlock() - - // Helper to determine if we need to create a separate top-level - // subscription for a wildcard. - fwc := func(subject string) (bool, string, string) { - if !mqttNeedSubForLevelUp(subject) { - return false, _EMPTY_, _EMPTY_ - } - // Say subject is "foo.>", remove the ".>" so that it becomes "foo" - fwcsubject := subject[:len(subject)-2] - // Change the sid to "foo fwc" - fwcsid := fwcsubject + mqttMultiLevelSidSuffix - - return true, fwcsubject, fwcsid - } - - rmSubjects := map[string]struct{}{} - // Preload retained messages for all requested subscriptions. Also, since - // it's the first iteration over the filter list, do some cleanup. - for _, f := range filters { - if f.qos > 2 { - f.qos = 2 - } - if c.mqtt.downgradeQoS2Sub && f.qos == 2 { - c.Warnf("Downgrading subscription QoS2 to QoS1 for %q, as configured", f.filter) - f.qos = 1 - } - - // Do not allow subscribing to our internal subjects. - // - // TODO: (levb: not sure why since one can subscribe to `#` and it'll - // include everything; I guess this would discourage? Otherwise another - // candidate for DO NOT DELIVER prefix list). - if strings.HasPrefix(f.filter, mqttSubPrefix) { - f.qos = mqttSubAckFailure - continue - } - - if f.qos == 2 { - if err := sess.ensurePubRelConsumerSubscription(c); err != nil { - c.Errorf("failed to initialize PUBREL processing: %v", err) - f.qos = mqttSubAckFailure - continue - } - } - - // Find retained messages. - if fromSubProto { - addRMSubjects := func(subject string) error { - sub := &subscription{ - client: c, - subject: []byte(subject), - sid: []byte(subject), - } - if err := c.addShadowSubscriptions(acc, sub, false); err != nil { - return err - } - - for _, sub := range append([]*subscription{sub}, sub.shadow...) { - as.addRetainedSubjectsForSubject(rmSubjects, bytesToString(sub.subject)) - for _, ss := range sub.shadow { - as.addRetainedSubjectsForSubject(rmSubjects, bytesToString(ss.subject)) - } - } - return nil - } - - if err := addRMSubjects(f.filter); err != nil { - f.qos = mqttSubAckFailure - continue - } - if need, subject, _ := fwc(f.filter); need { - if err := addRMSubjects(subject); err != nil { - f.qos = mqttSubAckFailure - continue - } - } - } - } - - serializeRMS := len(rmSubjects) > 0 - var rms map[string]*mqttRetainedMsg - if serializeRMS { - // Make the best effort to load retained messages. We will identify - // errors in the next pass. - rms = as.loadRetainedMessages(rmSubjects, c) - } - - // Small helper to add the consumer config to the session. - addJSConsToSess := func(sid string, cc *ConsumerConfig) { - if cc == nil { - return - } - if sess.cons == nil { - sess.cons = make(map[string]*ConsumerConfig) - } - sess.cons[sid] = cc - } - - var err error - subs := make([]*subscription, 0, len(filters)) - for _, f := range filters { - // Skip what's already been identified as a failure. - if f.qos == mqttSubAckFailure { - continue - } - subject := f.filter - bsubject := []byte(subject) - sid := subject - bsid := bsubject - isReserved := isMQTTReservedSubscription(subject) - - var jscons *ConsumerConfig - var jssub *subscription - - // Note that if a subscription already exists on this subject, the - // existing sub is returned. Need to update the qos. - var sub *subscription - var err error - - const processShadowSubs = true - - as.mu.Lock() - sess.mu.Lock() - sub, err = sess.processSub(c, - bsubject, bsid, isReserved, f.qos, // main subject - _EMPTY_, mqttDeliverMsgCbQoS0, // no jsDur for QOS0 - processShadowSubs, - rms, trace, as) - sess.mu.Unlock() - as.mu.Unlock() - - if err != nil { - f.qos = mqttSubAckFailure - sess.cleanupFailedSub(c, sub, jscons, jssub) - continue - } - - // This will create (if not already exist) a JS consumer for - // subscriptions of QoS >= 1. But if a JS consumer already exists and - // the subscription for same subject is now a QoS==0, then the JS - // consumer will be deleted. - jscons, jssub, err = sess.processJSConsumer(c, subject, sid, f.qos, fromSubProto) - if err != nil { - f.qos = mqttSubAckFailure - sess.cleanupFailedSub(c, sub, jscons, jssub) - continue - } - - // Process the wildcard subject if needed. - if need, fwcsubject, fwcsid := fwc(subject); need { - var fwjscons *ConsumerConfig - var fwjssub *subscription - var fwcsub *subscription - - // See note above about existing subscription. - as.mu.Lock() - sess.mu.Lock() - fwcsub, err = sess.processSub(c, - []byte(fwcsubject), []byte(fwcsid), isReserved, f.qos, // FWC (top-level wildcard) subject - _EMPTY_, mqttDeliverMsgCbQoS0, // no jsDur for QOS0 - processShadowSubs, - rms, trace, as) - sess.mu.Unlock() - as.mu.Unlock() - if err != nil { - // c.processSub already called c.Errorf(), so no need here. - f.qos = mqttSubAckFailure - sess.cleanupFailedSub(c, sub, jscons, jssub) - continue - } - - fwjscons, fwjssub, err = sess.processJSConsumer(c, fwcsubject, fwcsid, f.qos, fromSubProto) - if err != nil { - // c.processSub already called c.Errorf(), so no need here. - f.qos = mqttSubAckFailure - sess.cleanupFailedSub(c, sub, jscons, jssub) - sess.cleanupFailedSub(c, fwcsub, fwjscons, fwjssub) - continue - } - - subs = append(subs, fwcsub) - addJSConsToSess(fwcsid, fwjscons) - } - - subs = append(subs, sub) - addJSConsToSess(sid, jscons) - } - - if fromSubProto { - err = sess.update(filters, true) - } - - return subs, err -} - -// Retained publish messages matching this subscription are serialized in the -// subscription's `prm` mqtt writer. This buffer will be queued for outbound -// after the subscription is processed and SUBACK is sent or possibly when -// server processes an incoming published message matching the newly -// registered subscription. -// -// Runs from the client's readLoop. -// Account session manager lock held on entry. -// Session lock held on entry. -func (as *mqttAccountSessionManager) serializeRetainedMsgsForSub(rms map[string]*mqttRetainedMsg, sess *mqttSession, c *client, sub *subscription, trace bool) error { - if len(as.retmsgs) == 0 || len(rms) == 0 { - return nil - } - result := as.sl.ReverseMatch(string(sub.subject)) - if len(result.psubs) == 0 { - return nil - } - toTrace := []mqttPublish{} - for _, psub := range result.psubs { - - rm := rms[string(psub.subject)] - if rm == nil { - // This should not happen since we pre-load messages into rms before - // calling serialize. - continue - } - var pi uint16 - qos := mqttGetQoS(rm.Flags) - if qos > sub.mqtt.qos { - qos = sub.mqtt.qos - } - if c.mqtt.rejectQoS2Pub && qos == 2 { - c.Warnf("Rejecting retained message with QoS2 for subscription %q, as configured", sub.subject) - continue - } - if qos > 0 { - pi = sess.trackPublishRetained() - - // If we failed to get a PI for this message, send it as a QoS0, the - // best we can do? - if pi == 0 { - qos = 0 - } - } - - // Need to use the subject for the retained message, not the `sub` subject. - // We can find the published retained message in rm.sub.subject. - // Set the RETAIN flag: [MQTT-3.3.1-8]. - flags, headerBytes := mqttMakePublishHeader(pi, qos, false, true, []byte(rm.Topic), len(rm.Msg)) - c.mu.Lock() - sub.mqtt.prm = append(sub.mqtt.prm, headerBytes, rm.Msg) - c.mu.Unlock() - if trace { - toTrace = append(toTrace, mqttPublish{ - topic: []byte(rm.Topic), - flags: flags, - pi: pi, - sz: len(rm.Msg), - }) - } - } - for _, pp := range toTrace { - c.traceOutOp("PUBLISH", []byte(mqttPubTrace(&pp))) - } - return nil -} - -// Appends the stored message subjects for all retained message records that -// match the given subscription's `subject` (which could have wildcards). -// -// Account session manager NOT lock held on entry. -func (as *mqttAccountSessionManager) addRetainedSubjectsForSubject(list map[string]struct{}, topSubject string) bool { - as.mu.RLock() - if len(as.retmsgs) == 0 { - as.mu.RUnlock() - return false - } - result := as.sl.ReverseMatch(topSubject) - as.mu.RUnlock() - - added := false - for _, sub := range result.psubs { - subject := string(sub.subject) - if _, ok := list[subject]; ok { - continue - } - list[subject] = struct{}{} - added = true - } - - return added -} - -type warner interface { - Warnf(format string, v ...any) -} - -// Loads a list of retained messages given a list of stored message subjects. -func (as *mqttAccountSessionManager) loadRetainedMessages(subjects map[string]struct{}, w warner) map[string]*mqttRetainedMsg { - rms := make(map[string]*mqttRetainedMsg, len(subjects)) - ss := []string{} - for s := range subjects { - if rm := as.getCachedRetainedMsg(s); rm != nil { - rms[s] = rm - } else { - ss = append(ss, mqttRetainedMsgsStreamSubject+s) - } - } - - if len(ss) == 0 { - return rms - } - - results, err := as.jsa.loadLastMsgForMulti(mqttRetainedMsgsStreamName, ss) - // If an error occurred, warn, but then proceed with what we got. - if err != nil { - w.Warnf("error loading retained messages: %v", err) - } - for i, result := range results { - if result == nil { - continue // skip requests that timed out - } - if result.ToError() != nil { - w.Warnf("failed to load retained message for subject %q: %v", ss[i], err) - continue - } - rm, err := mqttDecodeRetainedMessage(result.Message.Header, result.Message.Data) - if err != nil { - w.Warnf("failed to decode retained message for subject %q: %v", ss[i], err) - continue - } - - // Add the loaded retained message to the cache, and to the results map. - key := ss[i][len(mqttRetainedMsgsStreamSubject):] - as.setCachedRetainedMsg(key, rm, false, false) - rms[key] = rm - } - return rms -} - -// Composes a NATS message for a storeable mqttRetainedMsg. -func mqttEncodeRetainedMessage(rm *mqttRetainedMsg) (natsMsg []byte, headerLen int) { - // No need to encode the subject, we can restore it from topic. - l := len(hdrLine) - l += len(mqttNatsRetainedMessageTopic) + 1 + len(rm.Topic) + 2 // 1 byte for ':', 2 bytes for CRLF - if rm.Origin != _EMPTY_ { - l += len(mqttNatsRetainedMessageOrigin) + 1 + len(rm.Origin) + 2 // 1 byte for ':', 2 bytes for CRLF - } - if rm.Source != _EMPTY_ { - l += len(mqttNatsRetainedMessageSource) + 1 + len(rm.Source) + 2 // 1 byte for ':', 2 bytes for CRLF - } - l += len(mqttNatsRetainedMessageFlags) + 1 + 2 + 2 // 1 byte for ':', 2 bytes for the flags, 2 bytes for CRLF - l += 2 // 2 bytes for the extra CRLF after the header - l += len(rm.Msg) - - buf := bytes.NewBuffer(make([]byte, 0, l)) - - buf.WriteString(hdrLine) - - buf.WriteString(mqttNatsRetainedMessageTopic) - buf.WriteByte(':') - buf.WriteString(rm.Topic) - buf.WriteString(_CRLF_) - - buf.WriteString(mqttNatsRetainedMessageFlags) - buf.WriteByte(':') - buf.WriteString(strconv.FormatUint(uint64(rm.Flags), 16)) - buf.WriteString(_CRLF_) - - if rm.Origin != _EMPTY_ { - buf.WriteString(mqttNatsRetainedMessageOrigin) - buf.WriteByte(':') - buf.WriteString(rm.Origin) - buf.WriteString(_CRLF_) - } - if rm.Source != _EMPTY_ { - buf.WriteString(mqttNatsRetainedMessageSource) - buf.WriteByte(':') - buf.WriteString(rm.Source) - buf.WriteString(_CRLF_) - } - - // End of header, finalize - buf.WriteString(_CRLF_) - headerLen = buf.Len() - buf.Write(rm.Msg) - return buf.Bytes(), headerLen -} - -func mqttDecodeRetainedMessage(h, m []byte) (*mqttRetainedMsg, error) { - fHeader := getHeader(mqttNatsRetainedMessageFlags, h) - if len(fHeader) > 0 { - flags, err := strconv.ParseUint(string(fHeader), 16, 8) - if err != nil { - return nil, fmt.Errorf("invalid retained message flags: %v", err) - } - topic := getHeader(mqttNatsRetainedMessageTopic, h) - subj, _ := mqttToNATSSubjectConversion(topic, false) - return &mqttRetainedMsg{ - Flags: byte(flags), - Subject: string(subj), - Topic: string(topic), - Origin: string(getHeader(mqttNatsRetainedMessageOrigin, h)), - Source: string(getHeader(mqttNatsRetainedMessageSource, h)), - Msg: m, - }, nil - } else { - var rm mqttRetainedMsg - if err := json.Unmarshal(m, &rm); err != nil { - return nil, err - } - return &rm, nil - } -} - -// Creates the session stream (limit msgs of 1) for this client ID if it does -// not already exist. If it exists, recover the single record to rebuild the -// state of the session. If there is a session record but this session is not -// registered in the runtime of this server, then a request is made to the -// owner to close the client associated with this session since specification -// [MQTT-3.1.4-2] specifies that if the ClientId represents a Client already -// connected to the Server then the Server MUST disconnect the existing client. -// -// Runs from the client's readLoop. -// Lock not held on entry, but session is in the locked map. -func (as *mqttAccountSessionManager) createOrRestoreSession(clientID string, opts *Options) (*mqttSession, bool, error) { - jsa := &as.jsa - formatError := func(errTxt string, err error) (*mqttSession, bool, error) { - accName := jsa.c.acc.GetName() - return nil, false, fmt.Errorf("%s for account %q, session %q: %v", errTxt, accName, clientID, err) - } - - hash := getHash(clientID) - smsg, err := jsa.loadSessionMsg(as.domainTk, hash) - if err != nil { - if isErrorOtherThan(err, JSNoMessageFoundErr) { - return formatError("loading session record", err) - } - // Message not found, so reate the session... - // Create a session and indicate that this session did not exist. - sess := mqttSessionCreate(jsa, clientID, hash, 0, opts) - sess.domainTk = as.domainTk - return sess, false, nil - } - // We need to recover the existing record now. - ps := &mqttPersistedSession{} - if err := json.Unmarshal(smsg.Data, ps); err != nil { - return formatError(fmt.Sprintf("unmarshal of session record at sequence %v", smsg.Sequence), err) - } - - // Restore this session (even if we don't own it), the caller will do the right thing. - sess := mqttSessionCreate(jsa, clientID, hash, smsg.Sequence, opts) - sess.domainTk = as.domainTk - sess.clean = ps.Clean - sess.subs = ps.Subs - sess.cons = ps.Cons - sess.pubRelConsumer = ps.PubRel - as.addSession(sess, true) - return sess, true, nil -} - -// Sends a request to delete a message, but does not wait for the response. -// -// No lock held on entry. -func (as *mqttAccountSessionManager) deleteRetainedMsg(seq uint64) { - as.jsa.deleteMsg(mqttRetainedMsgsStreamName, seq, false) -} - -// Sends a message indicating that a retained message on a given subject and stream sequence -// is being removed. -func (as *mqttAccountSessionManager) notifyRetainedMsgDeleted(subject string, seq uint64) { - req := mqttRetMsgDel{ - Subject: subject, - Seq: seq, - } - b, _ := json.Marshal(&req) - jsa := &as.jsa - jsa.sendq.push(&mqttJSPubMsg{ - subj: jsa.rplyr + mqttJSARetainedMsgDel, - msg: b, - }) -} - -func (as *mqttAccountSessionManager) transferUniqueSessStreamsToMuxed(log *Server) { - // Set retry to true, will be set to false on success. - retry := true - defer func() { - if retry { - next := mqttDefaultTransferRetry - log.Warnf("Failed to transfer all MQTT session streams, will try again in %v", next) - time.AfterFunc(next, func() { as.transferUniqueSessStreamsToMuxed(log) }) - } - }() - - jsa := &as.jsa - sni, err := jsa.newRequestEx(mqttJSAStreamNames, JSApiStreams, _EMPTY_, 0, nil, 5*time.Second) - if err != nil { - log.Errorf("Unable to transfer MQTT session streams: %v", err) - return - } - snames := sni.(*JSApiStreamNamesResponse) - if snames.Error != nil { - log.Errorf("Unable to transfer MQTT session streams: %v", snames.ToError()) - return - } - var oldMQTTSessStreams []string - for _, sn := range snames.Streams { - if strings.HasPrefix(sn, mqttSessionsStreamNamePrefix) { - oldMQTTSessStreams = append(oldMQTTSessStreams, sn) - } - } - ns := len(oldMQTTSessStreams) - if ns == 0 { - // Nothing to do - retry = false - return - } - log.Noticef("Transferring %v MQTT session streams...", ns) - for _, sn := range oldMQTTSessStreams { - log.Noticef(" Transferring stream %q to %q", sn, mqttSessStreamName) - smsg, err := jsa.loadLastMsgFor(sn, sn) - if err != nil { - log.Errorf(" Unable to load session record: %v", err) - return - } - ps := &mqttPersistedSession{} - if err := json.Unmarshal(smsg.Data, ps); err != nil { - log.Warnf(" Unable to unmarshal the content of this stream, may not be a legitimate MQTT session stream, skipping") - continue - } - // Store record to MQTT session stream - if _, err := jsa.storeSessionMsg(as.domainTk, getHash(ps.ID), 0, smsg.Data); err != nil { - log.Errorf(" Unable to transfer the session record: %v", err) - return - } - jsa.deleteStream(sn) - } - log.Noticef("Transfer of %v MQTT session streams done!", ns) - retry = false -} - -func (as *mqttAccountSessionManager) transferRetainedToPerKeySubjectStream(log *Server) error { - jsa := &as.jsa - var processed int - var transferred int - - start := time.Now() - deadline := start.Add(mqttRetainedTransferTimeout) - for { - // Try and look up messages on the original undivided "$MQTT.rmsgs" subject. - // If nothing is returned here, we assume to have migrated all old messages. - smsg, err := jsa.loadNextMsgFor(mqttRetainedMsgsStreamName, "$MQTT.rmsgs") - if IsNatsErr(err, JSNoMessageFoundErr) { - // We've ran out of messages to transfer, done. - break - } - if err != nil { - log.Warnf(" Unable to transfer a retained message: failed to load from '$MQTT.rmsgs': %s", err) - return err - } - - // Unmarshal the message so that we can obtain the subject name. Do not - // use mqttDecodeRetainedMessage() here because these messages are from - // older versions, and contain the full JSON encoding in payload. - var rmsg mqttRetainedMsg - if err = json.Unmarshal(smsg.Data, &rmsg); err == nil { - // Store the message again, this time with the new per-key subject. - subject := mqttRetainedMsgsStreamSubject + rmsg.Subject - if _, err = jsa.storeMsg(subject, 0, smsg.Data); err != nil { - log.Errorf(" Unable to transfer the retained message with sequence %d: %v", smsg.Sequence, err) - } - transferred++ - } else { - log.Warnf(" Unable to unmarshal retained message with sequence %d, skipping", smsg.Sequence) - } - - // Delete the original message. - if err := jsa.deleteMsg(mqttRetainedMsgsStreamName, smsg.Sequence, true); err != nil { - log.Errorf(" Unable to clean up the retained message with sequence %d: %v", smsg.Sequence, err) - return err - } - processed++ - - now := time.Now() - if now.After(deadline) { - err := fmt.Errorf("timed out while transferring retained messages from '$MQTT.rmsgs' after %v, %d processed, %d successfully transferred", now.Sub(start), processed, transferred) - log.Noticef(err.Error()) - return err - } - } - if processed > 0 { - log.Noticef("Processed %d messages from '$MQTT.rmsgs', successfully transferred %d in %v", processed, transferred, time.Since(start)) - } else { - log.Debugf("No messages found to transfer from '$MQTT.rmsgs'") - } - return nil -} - -func (as *mqttAccountSessionManager) getCachedRetainedMsg(subject string) *mqttRetainedMsg { - if as.rmsCache == nil { - return nil - } - v, ok := as.rmsCache.Load(subject) - if !ok { - return nil - } - rm := v.(*mqttRetainedMsg) - if rm.expiresFromCache.Before(time.Now()) { - as.rmsCache.Delete(subject) - return nil - } - return rm -} - -func (as *mqttAccountSessionManager) setCachedRetainedMsg(subject string, rm *mqttRetainedMsg, onlyReplace bool, copyBytesToCache bool) { - if as.rmsCache == nil || rm == nil { - return - } - rm.expiresFromCache = time.Now().Add(mqttRetainedCacheTTL) - if onlyReplace { - if _, ok := as.rmsCache.Load(subject); !ok { - return - } - } - if copyBytesToCache { - rm.Msg = copyBytes(rm.Msg) - } - as.rmsCache.Store(subject, rm) -} - -////////////////////////////////////////////////////////////////////////////// -// -// MQTT session related functions -// -////////////////////////////////////////////////////////////////////////////// - -// Returns a new mqttSession object with max ack pending set based on -// option or use mqttDefaultMaxAckPending if no option set. -func mqttSessionCreate(jsa *mqttJSA, id, idHash string, seq uint64, opts *Options) *mqttSession { - maxp := opts.MQTT.MaxAckPending - if maxp == 0 { - maxp = mqttDefaultMaxAckPending - } - - return &mqttSession{ - jsa: jsa, - id: id, - idHash: idHash, - seq: seq, - maxp: maxp, - pubRelSubject: mqttPubRelSubjectPrefix + idHash, - pubRelDeliverySubject: mqttPubRelDeliverySubjectPrefix + idHash, - pubRelDeliverySubjectB: []byte(mqttPubRelDeliverySubjectPrefix + idHash), - } -} - -// Persists a session. Note that if the session's current client does not match -// the given client, nothing is done. -// -// Lock not held on entry. -func (sess *mqttSession) save() error { - sess.mu.Lock() - ps := mqttPersistedSession{ - Origin: sess.jsa.id, - ID: sess.id, - Clean: sess.clean, - Subs: sess.subs, - Cons: sess.cons, - PubRel: sess.pubRelConsumer, - } - b, _ := json.Marshal(&ps) - - domainTk, cidHash := sess.domainTk, sess.idHash - seq := sess.seq - sess.mu.Unlock() - - var hdr int - if seq != 0 { - bb := bytes.Buffer{} - bb.WriteString(hdrLine) - bb.WriteString(JSExpectedLastSubjSeq) - bb.WriteString(":") - bb.WriteString(strconv.FormatInt(int64(seq), 10)) - bb.WriteString(CR_LF) - bb.WriteString(CR_LF) - hdr = bb.Len() - bb.Write(b) - b = bb.Bytes() - } - - resp, err := sess.jsa.storeSessionMsg(domainTk, cidHash, hdr, b) - if err != nil { - return fmt.Errorf("unable to persist session %q (seq=%v): %v", ps.ID, seq, err) - } - sess.mu.Lock() - sess.seq = resp.Sequence - sess.mu.Unlock() - return nil -} - -// Clear the session. -// -// Runs from the client's readLoop. -// Lock not held on entry, but session is in the locked map. -func (sess *mqttSession) clear(noWait bool) error { - var durs []string - var pubRelDur string - - sess.mu.Lock() - id := sess.id - seq := sess.seq - if l := len(sess.cons); l > 0 { - durs = make([]string, 0, l) - } - for sid, cc := range sess.cons { - delete(sess.cons, sid) - durs = append(durs, cc.Durable) - } - if sess.pubRelConsumer != nil { - pubRelDur = sess.pubRelConsumer.Durable - } - - sess.subs = nil - sess.pendingPublish = nil - sess.pendingPubRel = nil - sess.cpending = nil - sess.pubRelConsumer = nil - sess.seq = 0 - sess.tmaxack = 0 - sess.mu.Unlock() - - for _, dur := range durs { - if _, err := sess.jsa.deleteConsumer(mqttStreamName, dur, noWait); isErrorOtherThan(err, JSConsumerNotFoundErr) { - return fmt.Errorf("unable to delete consumer %q for session %q: %v", dur, sess.id, err) - } - } - if pubRelDur != _EMPTY_ { - _, err := sess.jsa.deleteConsumer(mqttOutStreamName, pubRelDur, noWait) - if isErrorOtherThan(err, JSConsumerNotFoundErr) { - return fmt.Errorf("unable to delete consumer %q for session %q: %v", pubRelDur, sess.id, err) - } - } - - if seq > 0 { - err := sess.jsa.deleteMsg(mqttSessStreamName, seq, !noWait) - // Ignore the various errors indicating that the message (or sequence) - // is already deleted, can happen in a cluster. - if isErrorOtherThan(err, JSSequenceNotFoundErrF) { - if isErrorOtherThan(err, JSStreamMsgDeleteFailedF) || !strings.Contains(err.Error(), ErrStoreMsgNotFound.Error()) { - return fmt.Errorf("unable to delete session %q record at sequence %v: %v", id, seq, err) - } - } - } - return nil -} - -// This will update the session record for this client in the account's MQTT -// sessions stream if the session had any change in the subscriptions. -// -// Runs from the client's readLoop. -// Lock not held on entry, but session is in the locked map. -func (sess *mqttSession) update(filters []*mqttFilter, add bool) error { - // Evaluate if we need to persist anything. - var needUpdate bool - for _, f := range filters { - if add { - if f.qos == mqttSubAckFailure { - continue - } - if qos, ok := sess.subs[f.filter]; !ok || qos != f.qos { - if sess.subs == nil { - sess.subs = make(map[string]byte) - } - sess.subs[f.filter] = f.qos - needUpdate = true - } - } else { - if _, ok := sess.subs[f.filter]; ok { - delete(sess.subs, f.filter) - needUpdate = true - } - } - } - var err error - if needUpdate { - err = sess.save() - } - return err -} - -func (sess *mqttSession) bumpPI() uint16 { - var avail bool - next := sess.last_pi - for i := 0; i < 0xFFFF; i++ { - next++ - if next == 0 { - next = 1 - } - - _, usedInPublish := sess.pendingPublish[next] - _, usedInPubRel := sess.pendingPubRel[next] - if !usedInPublish && !usedInPubRel { - sess.last_pi = next - avail = true - break - } - } - if !avail { - return 0 - } - return sess.last_pi -} - -// trackPublishRetained is invoked when a retained (QoS) message is published. -// It need a new PI to be allocated, so we add it to the pendingPublish map, -// with an empty value. Since cpending (not pending) is used to serialize the PI -// mappings, we need to add this PI there as well. Make a unique key by using -// mqttRetainedMsgsStreamName for the durable name, and PI for sseq. -// -// Lock held on entry -func (sess *mqttSession) trackPublishRetained() uint16 { - // Make sure we initialize the tracking maps. - if sess.pendingPublish == nil { - sess.pendingPublish = make(map[uint16]*mqttPending) - } - if sess.cpending == nil { - sess.cpending = make(map[string]map[uint64]uint16) - } - - pi := sess.bumpPI() - if pi == 0 { - return 0 - } - sess.pendingPublish[pi] = &mqttPending{} - - return pi -} - -// trackPublish is invoked when a (QoS) PUBLISH message is to be delivered. It -// detects an untracked (new) message based on its sequence extracted from its -// delivery-time jsAckSubject, and adds it to the tracking maps. Returns a PI to -// use for the message (new, or previously used), and whether this is a -// duplicate delivery attempt. -// -// Lock held on entry -func (sess *mqttSession) trackPublish(jsDur, jsAckSubject string) (uint16, bool) { - var dup bool - var pi uint16 - - if jsAckSubject == _EMPTY_ || jsDur == _EMPTY_ { - return 0, false - } - - // Make sure we initialize the tracking maps. - if sess.pendingPublish == nil { - sess.pendingPublish = make(map[uint16]*mqttPending) - } - if sess.cpending == nil { - sess.cpending = make(map[string]map[uint64]uint16) - } - - // Get the stream sequence and duplicate flag from the ack reply subject. - sseq, _, dcount := ackReplyInfo(jsAckSubject) - if dcount > 1 { - dup = true - } - - var ack *mqttPending - sseqToPi, ok := sess.cpending[jsDur] - if !ok { - sseqToPi = make(map[uint64]uint16) - sess.cpending[jsDur] = sseqToPi - } else { - pi = sseqToPi[sseq] - } - - if pi != 0 { - // There is a possible race between a PUBLISH re-delivery calling us, - // and a PUBREC received already having submitting a PUBREL into JS . If - // so, indicate no need for (re-)delivery by returning a PI of 0. - _, usedForPubRel := sess.pendingPubRel[pi] - if /*dup && */ usedForPubRel { - return 0, false - } - - // We should have a pending JS ACK for this PI. - ack = sess.pendingPublish[pi] - } else { - // sess.maxp will always have a value > 0. - if len(sess.pendingPublish) >= int(sess.maxp) { - // Indicate that we did not assign a packet identifier. - // The caller will not send the message to the subscription - // and JS will redeliver later, based on consumer's AckWait. - return 0, false - } - - pi = sess.bumpPI() - if pi == 0 { - return 0, false - } - - sseqToPi[sseq] = pi - } - - if ack == nil { - sess.pendingPublish[pi] = &mqttPending{ - jsDur: jsDur, - sseq: sseq, - jsAckSubject: jsAckSubject, - } - } else { - ack.jsAckSubject = jsAckSubject - ack.sseq = sseq - ack.jsDur = jsDur - } - - return pi, dup -} - -// Stops a PI from being tracked as a PUBLISH. It can still be in use for a -// pending PUBREL. -// -// Lock held on entry -func (sess *mqttSession) untrackPublish(pi uint16) (jsAckSubject string) { - ack, ok := sess.pendingPublish[pi] - if !ok { - return _EMPTY_ - } - - delete(sess.pendingPublish, pi) - if len(sess.pendingPublish) == 0 { - sess.last_pi = 0 - } - - if len(sess.cpending) != 0 && ack.jsDur != _EMPTY_ { - if sseqToPi := sess.cpending[ack.jsDur]; sseqToPi != nil { - delete(sseqToPi, ack.sseq) - } - } - - return ack.jsAckSubject -} - -// trackAsPubRel is invoked in 2 cases: (a) when we receive a PUBREC and we need -// to change from tracking the PI as a PUBLISH to a PUBREL; and (b) when we -// attempt to deliver the PUBREL to record the JS ack subject for it. -// -// Lock held on entry -func (sess *mqttSession) trackAsPubRel(pi uint16, jsAckSubject string) { - if sess.pubRelConsumer == nil { - // The cosumer MUST be set up already. - return - } - jsDur := sess.pubRelConsumer.Durable - - if sess.pendingPubRel == nil { - sess.pendingPubRel = make(map[uint16]*mqttPending) - } - - if jsAckSubject == _EMPTY_ { - sess.pendingPubRel[pi] = &mqttPending{ - jsDur: jsDur, - } - return - } - - sseq, _, _ := ackReplyInfo(jsAckSubject) - - if sess.cpending == nil { - sess.cpending = make(map[string]map[uint64]uint16) - } - sseqToPi := sess.cpending[jsDur] - if sseqToPi == nil { - sseqToPi = make(map[uint64]uint16) - sess.cpending[jsDur] = sseqToPi - } - sseqToPi[sseq] = pi - sess.pendingPubRel[pi] = &mqttPending{ - jsDur: sess.pubRelConsumer.Durable, - sseq: sseq, - jsAckSubject: jsAckSubject, - } -} - -// Stops a PI from being tracked as a PUBREL. -// -// Lock held on entry -func (sess *mqttSession) untrackPubRel(pi uint16) (jsAckSubject string) { - ack, ok := sess.pendingPubRel[pi] - if !ok { - return _EMPTY_ - } - - delete(sess.pendingPubRel, pi) - - if sess.pubRelConsumer != nil && len(sess.cpending) > 0 { - if sseqToPi := sess.cpending[ack.jsDur]; sseqToPi != nil { - delete(sseqToPi, ack.sseq) - } - } - - return ack.jsAckSubject -} - -// Sends a consumer delete request, but does not wait for response. -// -// Lock not held on entry. -func (sess *mqttSession) deleteConsumer(cc *ConsumerConfig) { - sess.mu.Lock() - sess.tmaxack -= cc.MaxAckPending - sess.jsa.deleteConsumer(mqttStreamName, cc.Durable, true) - sess.mu.Unlock() -} - -////////////////////////////////////////////////////////////////////////////// -// -// CONNECT protocol related functions -// -////////////////////////////////////////////////////////////////////////////// - -// Parse the MQTT connect protocol -func (c *client) mqttParseConnect(r *mqttReader, hasMappings bool) (byte, *mqttConnectProto, error) { - // Protocol name - proto, err := r.readBytes("protocol name", false) - if err != nil { - return 0, nil, err - } - - // Spec [MQTT-3.1.2-1] - if !bytes.Equal(proto, mqttProtoName) { - // Check proto name against v3.1 to report better error - if bytes.Equal(proto, mqttOldProtoName) { - return 0, nil, fmt.Errorf("older protocol %q not supported", proto) - } - return 0, nil, fmt.Errorf("expected connect packet with protocol name %q, got %q", mqttProtoName, proto) - } - - // Protocol level - level, err := r.readByte("protocol level") - if err != nil { - return 0, nil, err - } - // Spec [MQTT-3.1.2-2] - if level != mqttProtoLevel { - return mqttConnAckRCUnacceptableProtocolVersion, nil, fmt.Errorf("unacceptable protocol version of %v", level) - } - - cp := &mqttConnectProto{} - // Connect flags - cp.flags, err = r.readByte("flags") - if err != nil { - return 0, nil, err - } - - // Spec [MQTT-3.1.2-3] - if cp.flags&mqttConnFlagReserved != 0 { - return 0, nil, errMQTTConnFlagReserved - } - - var hasWill bool - wqos := (cp.flags & mqttConnFlagWillQoS) >> 3 - wretain := cp.flags&mqttConnFlagWillRetain != 0 - // Spec [MQTT-3.1.2-11] - if cp.flags&mqttConnFlagWillFlag == 0 { - // Spec [MQTT-3.1.2-13] - if wqos != 0 { - return 0, nil, fmt.Errorf("if Will flag is set to 0, Will QoS must be 0 too, got %v", wqos) - } - // Spec [MQTT-3.1.2-15] - if wretain { - return 0, nil, errMQTTWillAndRetainFlag - } - } else { - // Spec [MQTT-3.1.2-14] - if wqos == 3 { - return 0, nil, fmt.Errorf("if Will flag is set to 1, Will QoS can be 0, 1 or 2, got %v", wqos) - } - hasWill = true - } - - if c.mqtt.rejectQoS2Pub && hasWill && wqos == 2 { - return mqttConnAckRCQoS2WillRejected, nil, fmt.Errorf("server does not accept QoS2 for Will messages") - } - - // Spec [MQTT-3.1.2-19] - hasUser := cp.flags&mqttConnFlagUsernameFlag != 0 - // Spec [MQTT-3.1.2-21] - hasPassword := cp.flags&mqttConnFlagPasswordFlag != 0 - // Spec [MQTT-3.1.2-22] - if !hasUser && hasPassword { - return 0, nil, errMQTTPasswordFlagAndNoUser - } - - // Keep alive - var ka uint16 - ka, err = r.readUint16("keep alive") - if err != nil { - return 0, nil, err - } - // Spec [MQTT-3.1.2-24] - if ka > 0 { - cp.rd = time.Duration(float64(ka)*1.5) * time.Second - } - - // Payload starts here and order is mandated by: - // Spec [MQTT-3.1.3-1]: client ID, will topic, will message, username, password - - // Client ID - c.mqtt.cid, err = r.readString("client ID") - if err != nil { - return 0, nil, err - } - // Spec [MQTT-3.1.3-7] - if c.mqtt.cid == _EMPTY_ { - if cp.flags&mqttConnFlagCleanSession == 0 { - return mqttConnAckRCIdentifierRejected, nil, errMQTTCIDEmptyNeedsCleanFlag - } - // Spec [MQTT-3.1.3-6] - c.mqtt.cid = nuid.Next() - } - // Spec [MQTT-3.1.3-4] and [MQTT-3.1.3-9] - if !utf8.ValidString(c.mqtt.cid) { - return mqttConnAckRCIdentifierRejected, nil, fmt.Errorf("invalid utf8 for client ID: %q", c.mqtt.cid) - } - - if hasWill { - cp.will = &mqttWill{ - qos: wqos, - retain: wretain, - } - var topic []byte - // Need to make a copy since we need to hold to this topic after the - // parsing of this protocol. - topic, err = r.readBytes("Will topic", true) - if err != nil { - return 0, nil, err - } - if len(topic) == 0 { - return 0, nil, errMQTTEmptyWillTopic - } - if !utf8.Valid(topic) { - return 0, nil, fmt.Errorf("invalid utf8 for Will topic %q", topic) - } - // Convert MQTT topic to NATS subject - cp.will.subject, err = mqttTopicToNATSPubSubject(topic) - if err != nil { - return 0, nil, err - } - // Check for subject mapping. - if hasMappings { - // For selectMappedSubject to work, we need to have c.pa.subject set. - // If there is a change, c.pa.mapped will be set after the call. - c.pa.subject = cp.will.subject - if changed := c.selectMappedSubject(); changed { - // We need to keep track of the NATS subject/mapped in the `cp` structure. - cp.will.subject = c.pa.subject - cp.will.mapped = c.pa.mapped - // We also now need to map the original MQTT topic to the new topic - // based on the new subject. - topic = natsSubjectToMQTTTopic(cp.will.subject) - } - // Reset those now. - c.pa.subject, c.pa.mapped = nil, nil - } - cp.will.topic = topic - // Now "will" message. - // Ask for a copy since we need to hold to this after parsing of this protocol. - cp.will.message, err = r.readBytes("Will message", true) - if err != nil { - return 0, nil, err - } - } - - if hasUser { - c.opts.Username, err = r.readString("user name") - if err != nil { - return 0, nil, err - } - if c.opts.Username == _EMPTY_ { - return mqttConnAckRCBadUserOrPassword, nil, errMQTTEmptyUsername - } - // Spec [MQTT-3.1.3-11] - if !utf8.ValidString(c.opts.Username) { - return mqttConnAckRCBadUserOrPassword, nil, fmt.Errorf("invalid utf8 for user name %q", c.opts.Username) - } - } - - if hasPassword { - c.opts.Password, err = r.readString("password") - if err != nil { - return 0, nil, err - } - c.opts.Token = c.opts.Password - c.opts.JWT = c.opts.Password - } - return 0, cp, nil -} - -func (c *client) mqttConnectTrace(cp *mqttConnectProto) string { - trace := fmt.Sprintf("clientID=%s", c.mqtt.cid) - if cp.rd > 0 { - trace += fmt.Sprintf(" keepAlive=%v", cp.rd) - } - if cp.will != nil { - trace += fmt.Sprintf(" will=(topic=%s QoS=%v retain=%v)", - cp.will.topic, cp.will.qos, cp.will.retain) - } - if cp.flags&mqttConnFlagCleanSession != 0 { - trace += " clean" - } - if c.opts.Username != _EMPTY_ { - trace += fmt.Sprintf(" username=%s", c.opts.Username) - } - if c.opts.Password != _EMPTY_ { - trace += " password=****" - } - return trace -} - -// Process the CONNECT packet. -// -// For the first session on the account, an account session manager will be created, -// along with the JetStream streams/consumer necessary for the working of MQTT. -// -// The session, identified by a client ID, will be registered, or if already existing, -// will be resumed. If the session exists but is associated with an existing client, -// the old client is evicted, as per the specifications. -// -// Due to specific locking requirements around JS API requests, we cannot hold some -// locks for the entire duration of processing of some protocols, therefore, we use -// a map that registers the client ID in a "locked" state. If a different client tries -// to connect and the server detects that the client ID is in that map, it will try -// a little bit until it is not, or fail the new client, since we can't protect -// processing of protocols in the original client. This is not expected to happen often. -// -// Runs from the client's readLoop. -// No lock held on entry. -func (s *Server) mqttProcessConnect(c *client, cp *mqttConnectProto, trace bool) error { - sendConnAck := func(rc byte, sessp bool) { - c.mqttEnqueueConnAck(rc, sessp) - if trace { - c.traceOutOp("CONNACK", []byte(fmt.Sprintf("sp=%v rc=%v", sessp, rc))) - } - } - - c.mu.Lock() - cid := c.mqtt.cid - c.clearAuthTimer() - c.mu.Unlock() - if !s.isClientAuthorized(c) { - if trace { - c.traceOutOp("CONNACK", []byte(fmt.Sprintf("sp=%v rc=%v", false, mqttConnAckRCNotAuthorized))) - } - c.authViolation() - return ErrAuthentication - } - // Now that we are authenticated, we have the client bound to the account. - // Get the account's level MQTT sessions manager. If it does not exists yet, - // this will create it along with the streams where sessions and messages - // are stored. - asm, err := s.getOrCreateMQTTAccountSessionManager(c) - if err != nil { - return err - } - - // Most of the session state is altered only in the readLoop so does not - // need locking. For things that can be access in the readLoop and in - // callbacks, we will use explicit locking. - // To prevent other clients to connect with the same client ID, we will - // add the client ID to a "locked" map so that the connect somewhere else - // is put on hold. - // This keep track of how many times this client is detecting that its - // client ID is in the locked map. After a short amount, the server will - // fail this inbound client. - locked := 0 - -CHECK: - asm.mu.Lock() - // Check if different applications keep trying to connect with the same - // client ID at the same time. - if tm, ok := asm.flappers[cid]; ok { - // If the last time it tried to connect was more than 1 sec ago, - // then accept and remove from flappers map. - if time.Now().UnixNano()-tm > int64(mqttSessJailDur) { - asm.removeSessFromFlappers(cid) - } else { - // Will hold this client for a second and then close it. We - // do this so that if the client has a reconnect feature we - // don't end-up with very rapid flapping between apps. - // We need to wait in place and not schedule the connection - // close because if this is a misbehaved client that does - // not wait for the CONNACK and sends other protocols, the - // server would not have a fully setup client and may panic. - asm.mu.Unlock() - select { - case <-s.quitCh: - case <-time.After(mqttSessJailDur): - } - c.closeConnection(DuplicateClientID) - return ErrConnectionClosed - } - } - // If an existing session is in the process of processing some packet, we can't - // evict the old client just yet. So try again to see if the state clears, but - // if it does not, then we have no choice but to fail the new client instead of - // the old one. - if _, ok := asm.sessLocked[cid]; ok { - asm.mu.Unlock() - if locked++; locked == 10 { - return fmt.Errorf("other session with client ID %q is in the process of connecting", cid) - } - time.Sleep(100 * time.Millisecond) - goto CHECK - } - - // Register this client ID the "locked" map for the duration if this function. - asm.sessLocked[cid] = struct{}{} - // And remove it on exit, regardless of error or not. - defer func() { - asm.mu.Lock() - delete(asm.sessLocked, cid) - asm.mu.Unlock() - }() - - // Is the client requesting a clean session or not. - cleanSess := cp.flags&mqttConnFlagCleanSession != 0 - // Session present? Assume false, will be set to true only when applicable. - sessp := false - // Do we have an existing session for this client ID - es, exists := asm.sessions[cid] - asm.mu.Unlock() - - // The session is not in the map, but may be on disk, so try to recover - // or create the stream if not. - if !exists { - es, exists, err = asm.createOrRestoreSession(cid, s.getOpts()) - if err != nil { - return err - } - } - if exists { - // Clear the session if client wants a clean session. - // Also, Spec [MQTT-3.2.2-1]: don't report session present - if cleanSess || es.clean { - // Spec [MQTT-3.1.2-6]: If CleanSession is set to 1, the Client and - // Server MUST discard any previous Session and start a new one. - // This Session lasts as long as the Network Connection. State data - // associated with this Session MUST NOT be reused in any subsequent - // Session. - if err := es.clear(false); err != nil { - asm.removeSession(es, true) - return err - } - } else { - // Report to the client that the session was present - sessp = true - } - // Spec [MQTT-3.1.4-2]. If the ClientId represents a Client already - // connected to the Server then the Server MUST disconnect the existing - // client. - // Bind with the new client. This needs to be protected because can be - // accessed outside of the readLoop. - es.mu.Lock() - ec := es.c - es.c = c - es.clean = cleanSess - es.mu.Unlock() - if ec != nil { - // Remove "will" of existing client before closing - ec.mu.Lock() - ec.mqtt.cp.will = nil - ec.mu.Unlock() - // Add to the map of the flappers - asm.mu.Lock() - asm.addSessToFlappers(cid) - asm.mu.Unlock() - c.Warnf("Replacing old client %q since both have the same client ID %q", ec, cid) - // Close old client in separate go routine - go ec.closeConnection(DuplicateClientID) - } - } else { - // Spec [MQTT-3.2.2-3]: if the Server does not have stored Session state, - // it MUST set Session Present to 0 in the CONNACK packet. - es.mu.Lock() - es.c, es.clean = c, cleanSess - es.mu.Unlock() - // Now add this new session into the account sessions - asm.addSession(es, true) - } - // We would need to save only if it did not exist previously, but we save - // always in case we are running in cluster mode. This will notify other - // running servers that this session is being used. - if err := es.save(); err != nil { - asm.removeSession(es, true) - return err - } - c.mu.Lock() - c.flags.set(connectReceived) - c.mqtt.cp = cp - c.mqtt.asm = asm - c.mqtt.sess = es - c.mu.Unlock() - - // Spec [MQTT-3.2.0-1]: CONNACK must be the first protocol sent to the session. - sendConnAck(mqttConnAckRCConnectionAccepted, sessp) - - // Process possible saved subscriptions. - if l := len(es.subs); l > 0 { - filters := make([]*mqttFilter, 0, l) - for subject, qos := range es.subs { - filters = append(filters, &mqttFilter{filter: subject, qos: qos}) - } - if _, err := asm.processSubs(es, c, filters, false, trace); err != nil { - return err - } - } - return nil -} - -func (c *client) mqttEnqueueConnAck(rc byte, sessionPresent bool) { - proto := [4]byte{mqttPacketConnectAck, 2, 0, rc} - c.mu.Lock() - // Spec [MQTT-3.2.2-4]. If return code is different from 0, then - // session present flag must be set to 0. - if rc == 0 { - if sessionPresent { - proto[2] = 1 - } - } - c.enqueueProto(proto[:]) - c.mu.Unlock() -} - -func (s *Server) mqttHandleWill(c *client) { - c.mu.Lock() - if c.mqtt.cp == nil { - c.mu.Unlock() - return - } - will := c.mqtt.cp.will - if will == nil { - c.mu.Unlock() - return - } - pp := c.mqtt.pp - pp.topic = will.topic - pp.subject = will.subject - pp.mapped = will.mapped - pp.msg = will.message - pp.sz = len(will.message) - pp.pi = 0 - pp.flags = will.qos << 1 - if will.retain { - pp.flags |= mqttPubFlagRetain - } - c.mu.Unlock() - s.mqttInitiateMsgDelivery(c, pp) - c.flushClients(0) -} - -////////////////////////////////////////////////////////////////////////////// -// -// PUBLISH protocol related functions -// -////////////////////////////////////////////////////////////////////////////// - -func (c *client) mqttParsePub(r *mqttReader, pl int, pp *mqttPublish, hasMappings bool) error { - qos := mqttGetQoS(pp.flags) - if qos > 2 { - return fmt.Errorf("QoS=%v is invalid in MQTT", qos) - } - - if c.mqtt.rejectQoS2Pub && qos == 2 { - return fmt.Errorf("QoS=2 is disabled for PUBLISH messages") - } - - // Keep track of where we are when starting to read the variable header - start := r.pos - - var err error - pp.topic, err = r.readBytes("topic", false) - if err != nil { - return err - } - if len(pp.topic) == 0 { - return errMQTTTopicIsEmpty - } - // Convert the topic to a NATS subject. This call will also check that - // there is no MQTT wildcards (Spec [MQTT-3.3.2-2] and [MQTT-4.7.1-1]) - // Note that this may not result in a copy if there is no conversion. - // It is good because after the message is processed we won't have a - // reference to the buffer and we save a copy. - pp.subject, err = mqttTopicToNATSPubSubject(pp.topic) - if err != nil { - return err - } - - // Check for subject mapping. - if hasMappings { - // For selectMappedSubject to work, we need to have c.pa.subject set. - // If there is a change, c.pa.mapped will be set after the call. - c.pa.subject = pp.subject - if changed := c.selectMappedSubject(); changed { - // We need to keep track of the NATS subject/mapped in the `pp` structure. - pp.subject = c.pa.subject - pp.mapped = c.pa.mapped - // We also now need to map the original MQTT topic to the new topic - // based on the new subject. - pp.topic = natsSubjectToMQTTTopic(pp.subject) - } - // Reset those now. - c.pa.subject, c.pa.mapped = nil, nil - } - - if qos > 0 { - pp.pi, err = r.readUint16("packet identifier") - if err != nil { - return err - } - if pp.pi == 0 { - return fmt.Errorf("with QoS=%v, packet identifier cannot be 0", qos) - } - } else { - pp.pi = 0 - } - - // The message payload will be the total packet length minus - // what we have consumed for the variable header - pp.sz = pl - (r.pos - start) - if pp.sz > 0 { - start = r.pos - r.pos += pp.sz - pp.msg = r.buf[start:r.pos] - } else { - pp.msg = nil - } - return nil -} - -func mqttPubTrace(pp *mqttPublish) string { - dup := pp.flags&mqttPubFlagDup != 0 - qos := mqttGetQoS(pp.flags) - retain := mqttIsRetained(pp.flags) - var piStr string - if pp.pi > 0 { - piStr = fmt.Sprintf(" pi=%v", pp.pi) - } - return fmt.Sprintf("%s dup=%v QoS=%v retain=%v size=%v%s", - pp.topic, dup, qos, retain, pp.sz, piStr) -} - -// Composes a NATS message from a MQTT PUBLISH packet. The message includes an -// internal header containint the original packet's QoS, and for QoS2 packets -// the original subject. -// -// Example (QoS2, subject: "foo.bar"): -// -// NATS/1.0\r\n -// Nmqtt-Pub:2foo.bar\r\n -// \r\n -func mqttNewDeliverableMessage(pp *mqttPublish, encodePP bool) (natsMsg []byte, headerLen int) { - size := len(hdrLine) + - len(mqttNatsHeader) + 2 + 2 + // 2 for ':', and 2 for CRLF - 2 + // end-of-header CRLF - pp.sz - if encodePP { - size += len(mqttNatsHeaderSubject) + 1 + // +1 for ':' - len(pp.subject) + 2 // 2 for CRLF - - if len(pp.mapped) > 0 { - size += len(mqttNatsHeaderMapped) + 1 + // +1 for ':' - len(pp.mapped) + 2 // 2 for CRLF - } - } - buf := bytes.NewBuffer(make([]byte, 0, size)) - - qos := mqttGetQoS(pp.flags) - - buf.WriteString(hdrLine) - buf.WriteString(mqttNatsHeader) - buf.WriteByte(':') - buf.WriteByte(qos + '0') - buf.WriteString(_CRLF_) - - if encodePP { - buf.WriteString(mqttNatsHeaderSubject) - buf.WriteByte(':') - buf.Write(pp.subject) - buf.WriteString(_CRLF_) - - if len(pp.mapped) > 0 { - buf.WriteString(mqttNatsHeaderMapped) - buf.WriteByte(':') - buf.Write(pp.mapped) - buf.WriteString(_CRLF_) - } - } - - // End of header - buf.WriteString(_CRLF_) - - headerLen = buf.Len() - - buf.Write(pp.msg) - return buf.Bytes(), headerLen -} - -// Composes a NATS message for a pending PUBREL packet. The message includes an -// internal header containing the PI for PUBREL/PUBCOMP. -// -// Example (PI:123): -// -// NATS/1.0\r\n -// Nmqtt-PubRel:123\r\n -// \r\n -func mqttNewDeliverablePubRel(pi uint16) (natsMsg []byte, headerLen int) { - size := len(hdrLine) + - len(mqttNatsPubRelHeader) + 6 + 2 + // 6 for ':65535', and 2 for CRLF - 2 // end-of-header CRLF - buf := bytes.NewBuffer(make([]byte, 0, size)) - buf.WriteString(hdrLine) - buf.WriteString(mqttNatsPubRelHeader) - buf.WriteByte(':') - buf.WriteString(strconv.FormatInt(int64(pi), 10)) - buf.WriteString(_CRLF_) - buf.WriteString(_CRLF_) - return buf.Bytes(), buf.Len() -} - -// Process the PUBLISH packet. -// -// Runs from the client's readLoop. -// No lock held on entry. -func (s *Server) mqttProcessPub(c *client, pp *mqttPublish, trace bool) error { - qos := mqttGetQoS(pp.flags) - - switch qos { - case 0: - return s.mqttInitiateMsgDelivery(c, pp) - - case 1: - // [MQTT-4.3.2-2]. Initiate onward delivery of the Application Message, - // Send PUBACK. - // - // The receiver is not required to complete delivery of the Application - // Message before sending the PUBACK. When its original sender receives - // the PUBACK packet, ownership of the Application Message is - // transferred to the receiver. - err := s.mqttInitiateMsgDelivery(c, pp) - if err == nil { - c.mqttEnqueuePubResponse(mqttPacketPubAck, pp.pi, trace) - } - return err - - case 2: - // [MQTT-4.3.3-2]. Method A, Store message, send PUBREC. - // - // The receiver is not required to complete delivery of the Application - // Message before sending the PUBREC or PUBCOMP. When its original - // sender receives the PUBREC packet, ownership of the Application - // Message is transferred to the receiver. - err := s.mqttStoreQoS2MsgOnce(c, pp) - if err == nil { - c.mqttEnqueuePubResponse(mqttPacketPubRec, pp.pi, trace) - } - return err - - default: - return fmt.Errorf("unreachable: invalid QoS in mqttProcessPub: %v", qos) - } -} - -func (s *Server) mqttInitiateMsgDelivery(c *client, pp *mqttPublish) error { - natsMsg, headerLen := mqttNewDeliverableMessage(pp, false) - - // Set the client's pubarg for processing. - c.pa.subject = pp.subject - c.pa.mapped = pp.mapped - c.pa.reply = nil - c.pa.hdr = headerLen - c.pa.hdb = []byte(strconv.FormatInt(int64(c.pa.hdr), 10)) - c.pa.size = len(natsMsg) - c.pa.szb = []byte(strconv.FormatInt(int64(c.pa.size), 10)) - defer func() { - c.pa.subject = nil - c.pa.mapped = nil - c.pa.reply = nil - c.pa.hdr = -1 - c.pa.hdb = nil - c.pa.size = 0 - c.pa.szb = nil - }() - - _, permIssue := c.processInboundClientMsg(natsMsg) - if permIssue { - return nil - } - - // If QoS 0 messages don't need to be stored, other (1 and 2) do. Store them - // JetStream under "$MQTT.msgs." - if qos := mqttGetQoS(pp.flags); qos == 0 { - return nil - } - - // We need to call flushClients now since this we may have called c.addToPCD - // with destination clients (possibly a route). Without calling flushClients - // the following call may then be stuck waiting for a reply that may never - // come because the destination is not flushed (due to c.out.fsp > 0, - // see addToPCD and writeLoop for details). - c.flushClients(0) - - _, err := c.mqtt.sess.jsa.storeMsg(mqttStreamSubjectPrefix+string(c.pa.subject), headerLen, natsMsg) - - return err -} - -var mqttMaxMsgErrPattern = fmt.Sprintf("%s (%v)", ErrMaxMsgsPerSubject.Error(), JSStreamStoreFailedF) - -func (s *Server) mqttStoreQoS2MsgOnce(c *client, pp *mqttPublish) error { - // `true` means encode the MQTT PUBLISH packet in the NATS message header. - natsMsg, headerLen := mqttNewDeliverableMessage(pp, true) - - // Do not broadcast the message until it has been deduplicated and released - // by the sender. Instead store this QoS2 message as - // "$MQTT.qos2..". If the message is a duplicate, we get back - // a ErrMaxMsgsPerSubject, otherwise it does not change the flow, still need - // to send a PUBREC back to the client. The original subject (translated - // from MQTT topic) is included in the NATS header of the stored message to - // use for latter delivery. - _, err := c.mqtt.sess.jsa.storeMsg(c.mqttQoS2InternalSubject(pp.pi), headerLen, natsMsg) - - // TODO: would prefer a more robust and performant way of checking the - // error, but it comes back wrapped as an API result. - if err != nil && - (isErrorOtherThan(err, JSStreamStoreFailedF) || err.Error() != mqttMaxMsgErrPattern) { - return err - } - - return nil -} - -func (c *client) mqttQoS2InternalSubject(pi uint16) string { - return mqttQoS2IncomingMsgsStreamSubjectPrefix + c.mqtt.cid + "." + strconv.FormatUint(uint64(pi), 10) -} - -// Process a PUBREL packet (QoS2, acting as Receiver). -// -// Runs from the client's readLoop. -// No lock held on entry. -func (s *Server) mqttProcessPubRel(c *client, pi uint16, trace bool) error { - // Once done with the processing, send a PUBCOMP back to the client. - defer c.mqttEnqueuePubResponse(mqttPacketPubComp, pi, trace) - - // See if there is a message pending for this pi. All failures are treated - // as "not found". - asm := c.mqtt.asm - stored, _ := asm.jsa.loadLastMsgFor(mqttQoS2IncomingMsgsStreamName, c.mqttQoS2InternalSubject(pi)) - - if stored == nil { - // No message found, nothing to do. - return nil - } - // Best attempt to delete the message from the QoS2 stream. - asm.jsa.deleteMsg(mqttQoS2IncomingMsgsStreamName, stored.Sequence, true) - - // only MQTT QoS2 messages should be here, and they must have a subject. - h := mqttParsePublishNATSHeader(stored.Header) - if h == nil || h.qos != 2 || len(h.subject) == 0 { - return errors.New("invalid message in QoS2 PUBREL stream") - } - - pp := &mqttPublish{ - topic: natsSubjectToMQTTTopic(h.subject), - subject: h.subject, - mapped: h.mapped, - msg: stored.Data, - sz: len(stored.Data), - pi: pi, - flags: h.qos << 1, - } - - return s.mqttInitiateMsgDelivery(c, pp) -} - -// Invoked when processing an inbound client message. If the "retain" flag is -// set, the message is stored so it can be later resent to (re)starting -// subscriptions that match the subject. -// -// Invoked from the MQTT publisher's readLoop. No client lock is held on entry. -func (c *client) mqttHandlePubRetain() { - pp := c.mqtt.pp - retainMQTT := mqttIsRetained(pp.flags) - isBirth, _, isCertificate := sparkbParseBirthDeathTopic(pp.topic) - retainSparkbBirth := isBirth && !isCertificate - - // [tck-id-topics-nbirth-mqtt] NBIRTH messages MUST be published with MQTT - // QoS equal to 0 and retain equal to false. - // - // [tck-id-conformance-mqtt-aware-nbirth-mqtt-retain] A Sparkplug Aware MQTT - // Server MUST make NBIRTH messages available on the topic: - // $sparkplug/certificates/namespace/group_id/NBIRTH/edge_node_id with the - // MQTT retain flag set to true. - if retainMQTT == retainSparkbBirth { - // (retainSparkbBirth && retainMQTT) : not valid, so ignore altogether. - // (!retainSparkbBirth && !retainMQTT) : nothing to do. - return - } - - asm := c.mqtt.asm - key := string(pp.subject) - - // Always clear the retain flag to deliver a normal published message. - defer func() { - pp.flags &= ^mqttPubFlagRetain - }() - - // Spec [MQTT-3.3.1-11]. Payload of size 0 removes the retained message, but - // should still be delivered as a normal message. - if pp.sz == 0 { - if seqToRemove := asm.handleRetainedMsgDel(key, 0); seqToRemove > 0 { - asm.deleteRetainedMsg(seqToRemove) - asm.notifyRetainedMsgDeleted(key, seqToRemove) - } - return - } - - rm := &mqttRetainedMsg{ - Origin: asm.jsa.id, - Msg: pp.msg, // will copy these bytes later as we process rm. - Flags: pp.flags, - Source: c.opts.Username, - } - - if retainSparkbBirth { - // [tck-id-conformance-mqtt-aware-store] A Sparkplug Aware MQTT Server - // MUST store NBIRTH and DBIRTH messages as they pass through the MQTT - // Server. - // - // [tck-id-conformance-mqtt-aware-nbirth-mqtt-topic]. A Sparkplug Aware - // MQTT Server MUST make NBIRTH messages available on a topic of the - // form: $sparkplug/certificates/namespace/group_id/NBIRTH/edge_node_id - // - // [tck-id-conformance-mqtt-aware-dbirth-mqtt-topic] A Sparkplug Aware - // MQTT Server MUST make DBIRTH messages available on a topic of the - // form: - // $sparkplug/certificates/namespace/group_id/DBIRTH/edge_node_id/device_id - topic := append(sparkbCertificatesTopicPrefix, pp.topic...) - subject, _ := mqttTopicToNATSPubSubject(topic) - rm.Topic = string(topic) - rm.Subject = string(subject) - - // will use to save the retained message. - key = string(subject) - - // Store the retained message with the RETAIN flag set. - rm.Flags |= mqttPubFlagRetain - - // Copy the payload out of pp since we will be sending the message - // asynchronously. - msg := make([]byte, pp.sz) - copy(msg, pp.msg[:pp.sz]) - asm.jsa.sendMsg(key, msg) - - } else { // isRetained - // Spec [MQTT-3.3.1-5]. Store the retained message with its QoS. - // - // When coming from a publish protocol, `pp` is referencing a stack - // variable that itself possibly references the read buffer. - rm.Topic = string(pp.topic) - } - - // Set the key to the subject of the message for retained, or the composed - // $sparkplug subject for sparkB. - rm.Subject = key - rmBytes, hdr := mqttEncodeRetainedMessage(rm) // will copy the payload bytes - smr, err := asm.jsa.storeMsg(mqttRetainedMsgsStreamSubject+key, hdr, rmBytes) - if err == nil { - // Update the new sequence. - rf := &mqttRetainedMsgRef{ - sseq: smr.Sequence, - } - // Add/update the map. `true` to copy the payload bytes if needs to - // update rmsCache. - asm.handleRetainedMsg(key, rf, rm, true) - } else { - c.mu.Lock() - acc := c.acc - c.mu.Unlock() - c.Errorf("unable to store retained message for account %q, subject %q: %v", - acc.GetName(), key, err) - } -} - -// After a config reload, it is possible that the source of a publish retained -// message is no longer allowed to publish on the given topic. If that is the -// case, the retained message is removed from the map and will no longer be -// sent to (re)starting subscriptions. -// -// Server lock MUST NOT be held on entry. -func (s *Server) mqttCheckPubRetainedPerms() { - sm := &s.mqtt.sessmgr - sm.mu.RLock() - done := len(sm.sessions) == 0 - sm.mu.RUnlock() - - if done { - return - } - - s.mu.Lock() - users := make(map[string]*User, len(s.users)) - for un, u := range s.users { - users[un] = u - } - s.mu.Unlock() - - // First get a list of all of the sessions. - sm.mu.RLock() - asms := make([]*mqttAccountSessionManager, 0, len(sm.sessions)) - for _, asm := range sm.sessions { - asms = append(asms, asm) - } - sm.mu.RUnlock() - - type retainedMsg struct { - subj string - rmsg *mqttRetainedMsgRef - } - - // For each session we will obtain a list of retained messages. - var _rms [128]retainedMsg - rms := _rms[:0] - for _, asm := range asms { - // Get all of the retained messages. Then we will sort them so - // that they are in sequence order, which should help the file - // store to not have to load out-of-order blocks so often. - asm.mu.RLock() - rms = rms[:0] // reuse slice - for subj, rf := range asm.retmsgs { - rms = append(rms, retainedMsg{ - subj: subj, - rmsg: rf, - }) - } - asm.mu.RUnlock() - slices.SortFunc(rms, func(i, j retainedMsg) int { return cmp.Compare(i.rmsg.sseq, j.rmsg.sseq) }) - - perms := map[string]*perm{} - deletes := map[string]uint64{} - for _, rf := range rms { - jsm, err := asm.jsa.loadMsg(mqttRetainedMsgsStreamName, rf.rmsg.sseq) - if err != nil || jsm == nil { - continue - } - rm, err := mqttDecodeRetainedMessage(jsm.Header, jsm.Data) - if err != nil { - continue - } - if rm.Source == _EMPTY_ { - continue - } - // Lookup source from global users. - u := users[rm.Source] - if u != nil { - p, ok := perms[rm.Source] - if !ok { - p = generatePubPerms(u.Permissions) - perms[rm.Source] = p - } - // If there is permission and no longer allowed to publish in - // the subject, remove the publish retained message from the map. - if p != nil && !pubAllowed(p, rf.subj) { - u = nil - } - } - - // Not present or permissions have changed such that the source can't - // publish on that subject anymore: remove it from the map. - if u == nil { - asm.mu.Lock() - delete(asm.retmsgs, rf.subj) - asm.sl.Remove(rf.rmsg.sub) - asm.mu.Unlock() - deletes[rf.subj] = rf.rmsg.sseq - } - } - - for subject, seq := range deletes { - asm.deleteRetainedMsg(seq) - asm.notifyRetainedMsgDeleted(subject, seq) - } - } -} - -// Helper to generate only pub permissions from a Permissions object -func generatePubPerms(perms *Permissions) *perm { - var p *perm - if perms.Publish.Allow != nil { - p = &perm{} - p.allow = NewSublistWithCache() - for _, pubSubject := range perms.Publish.Allow { - sub := &subscription{subject: []byte(pubSubject)} - p.allow.Insert(sub) - } - } - if len(perms.Publish.Deny) > 0 { - if p == nil { - p = &perm{} - } - p.deny = NewSublistWithCache() - for _, pubSubject := range perms.Publish.Deny { - sub := &subscription{subject: []byte(pubSubject)} - p.deny.Insert(sub) - } - } - return p -} - -// Helper that checks if given `perms` allow to publish on the given `subject` -func pubAllowed(perms *perm, subject string) bool { - allowed := true - if perms.allow != nil { - np, _ := perms.allow.NumInterest(subject) - allowed = np != 0 - } - // If we have a deny list and are currently allowed, check that as well. - if allowed && perms.deny != nil { - np, _ := perms.deny.NumInterest(subject) - allowed = np == 0 - } - return allowed -} - -func (c *client) mqttEnqueuePubResponse(packetType byte, pi uint16, trace bool) { - proto := [4]byte{packetType, 0x2, 0, 0} - proto[2] = byte(pi >> 8) - proto[3] = byte(pi) - - // Bits 3,2,1 and 0 of the fixed header in the PUBREL Control Packet are - // reserved and MUST be set to 0,0,1 and 0 respectively. The Server MUST treat - // any other value as malformed and close the Network Connection [MQTT-3.6.1-1]. - if packetType == mqttPacketPubRel { - proto[0] |= 0x2 - } - - c.mu.Lock() - c.enqueueProto(proto[:4]) - c.mu.Unlock() - - if trace { - name := "(???)" - switch packetType { - case mqttPacketPubAck: - name = "PUBACK" - case mqttPacketPubRec: - name = "PUBREC" - case mqttPacketPubRel: - name = "PUBREL" - case mqttPacketPubComp: - name = "PUBCOMP" - } - c.traceOutOp(name, []byte(fmt.Sprintf("pi=%v", pi))) - } -} - -func mqttParsePIPacket(r *mqttReader) (uint16, error) { - pi, err := r.readUint16("packet identifier") - if err != nil { - return 0, err - } - if pi == 0 { - return 0, errMQTTPacketIdentifierIsZero - } - return pi, nil -} - -// Process a PUBACK (QoS1) or a PUBREC (QoS2) packet, acting as Sender. Set -// isPubRec to false to process as a PUBACK. -// -// Runs from the client's readLoop. No lock held on entry. -func (c *client) mqttProcessPublishReceived(pi uint16, isPubRec bool) (err error) { - sess := c.mqtt.sess - if sess == nil { - return errMQTTInvalidSession - } - - var jsAckSubject string - sess.mu.Lock() - // Must be the same client, and the session must have been setup for QoS2. - if sess.c != c { - sess.mu.Unlock() - return errMQTTInvalidSession - } - if isPubRec { - // The JS ACK subject for the PUBREL will be filled in at the delivery - // attempt. - sess.trackAsPubRel(pi, _EMPTY_) - } - jsAckSubject = sess.untrackPublish(pi) - sess.mu.Unlock() - - if isPubRec { - natsMsg, headerLen := mqttNewDeliverablePubRel(pi) - _, err = sess.jsa.storeMsg(sess.pubRelSubject, headerLen, natsMsg) - if err != nil { - // Failure to send out PUBREL will terminate the connection. - return err - } - } - - // Send the ack to JS to remove the pending message from the consumer. - sess.jsa.sendAck(jsAckSubject) - return nil -} - -func (c *client) mqttProcessPubAck(pi uint16) error { - return c.mqttProcessPublishReceived(pi, false) -} - -func (c *client) mqttProcessPubRec(pi uint16) error { - return c.mqttProcessPublishReceived(pi, true) -} - -// Runs from the client's readLoop. No lock held on entry. -func (c *client) mqttProcessPubComp(pi uint16) { - sess := c.mqtt.sess - if sess == nil { - return - } - - var jsAckSubject string - sess.mu.Lock() - if sess.c != c { - sess.mu.Unlock() - return - } - jsAckSubject = sess.untrackPubRel(pi) - sess.mu.Unlock() - - // Send the ack to JS to remove the pending message from the consumer. - sess.jsa.sendAck(jsAckSubject) -} - -// Return the QoS from the given PUBLISH protocol's flags -func mqttGetQoS(flags byte) byte { - return flags & mqttPubFlagQoS >> 1 -} - -func mqttIsRetained(flags byte) bool { - return flags&mqttPubFlagRetain != 0 -} - -func sparkbParseBirthDeathTopic(topic []byte) (isBirth, isDeath, isCertificate bool) { - if bytes.HasPrefix(topic, sparkbCertificatesTopicPrefix) { - isCertificate = true - topic = topic[len(sparkbCertificatesTopicPrefix):] - } - if !bytes.HasPrefix(topic, sparkbNamespaceTopicPrefix) { - return false, false, false - } - topic = topic[len(sparkbNamespaceTopicPrefix):] - - parts := bytes.Split(topic, []byte{'/'}) - if len(parts) < 3 || len(parts) > 4 { - return false, false, false - } - typ := bytesToString(parts[1]) - switch typ { - case sparkbNBIRTH, sparkbDBIRTH: - isBirth = true - case sparkbNDEATH, sparkbDDEATH: - isDeath = true - default: - return false, false, false - } - return isBirth, isDeath, isCertificate -} - -////////////////////////////////////////////////////////////////////////////// -// -// SUBSCRIBE related functions -// -////////////////////////////////////////////////////////////////////////////// - -func (c *client) mqttParseSubs(r *mqttReader, b byte, pl int) (uint16, []*mqttFilter, error) { - return c.mqttParseSubsOrUnsubs(r, b, pl, true) -} - -func (c *client) mqttParseSubsOrUnsubs(r *mqttReader, b byte, pl int, sub bool) (uint16, []*mqttFilter, error) { - var expectedFlag byte - var action string - if sub { - expectedFlag = mqttSubscribeFlags - } else { - expectedFlag = mqttUnsubscribeFlags - action = "un" - } - // Spec [MQTT-3.8.1-1], [MQTT-3.10.1-1] - if rf := b & 0xf; rf != expectedFlag { - return 0, nil, fmt.Errorf("wrong %ssubscribe reserved flags: %x", action, rf) - } - pi, err := r.readUint16("packet identifier") - if err != nil { - return 0, nil, fmt.Errorf("reading packet identifier: %v", err) - } - end := r.pos + (pl - 2) - var filters []*mqttFilter - for r.pos < end { - // Don't make a copy now because, this will happen during conversion - // or when processing the sub. - topic, err := r.readBytes("topic filter", false) - if err != nil { - return 0, nil, err - } - if len(topic) == 0 { - return 0, nil, errMQTTTopicFilterCannotBeEmpty - } - // Spec [MQTT-3.8.3-1], [MQTT-3.10.3-1] - if !utf8.Valid(topic) { - return 0, nil, fmt.Errorf("invalid utf8 for topic filter %q", topic) - } - var qos byte - // We are going to report if we had an error during the conversion, - // but we don't fail the parsing. When processing the sub, we will - // have an error then, and the processing of subs code will send - // the proper mqttSubAckFailure flag for this given subscription. - filter, err := mqttFilterToNATSSubject(topic) - if err != nil { - c.Errorf("invalid topic %q: %v", topic, err) - } - if sub { - qos, err = r.readByte("QoS") - if err != nil { - return 0, nil, err - } - // Spec [MQTT-3-8.3-4]. - if qos > 2 { - return 0, nil, fmt.Errorf("subscribe QoS value must be 0, 1 or 2, got %v", qos) - } - } - f := &mqttFilter{ttopic: topic, filter: string(filter), qos: qos} - filters = append(filters, f) - } - // Spec [MQTT-3.8.3-3], [MQTT-3.10.3-2] - if len(filters) == 0 { - return 0, nil, fmt.Errorf("%ssubscribe protocol must contain at least 1 topic filter", action) - } - return pi, filters, nil -} - -func mqttSubscribeTrace(pi uint16, filters []*mqttFilter) string { - var sep string - sb := &strings.Builder{} - sb.WriteString("[") - for i, f := range filters { - sb.WriteString(sep) - sb.Write(f.ttopic) - sb.WriteString(" (") - sb.WriteString(f.filter) - sb.WriteString(") QoS=") - sb.WriteString(fmt.Sprintf("%v", f.qos)) - if i == 0 { - sep = ", " - } - } - sb.WriteString(fmt.Sprintf("] pi=%v", pi)) - return sb.String() -} - -// For a MQTT QoS0 subscription, we create a single NATS subscription on the -// actual subject, for instance "foo.bar". -// -// For a MQTT QoS1+ subscription, we create 2 subscriptions, one on "foo.bar" -// (as for QoS0, but sub.mqtt.qos will be 1 or 2), and one on the subject -// "$MQTT.sub." which is the delivery subject of the JS durable consumer -// with the filter subject "$MQTT.msgs.foo.bar". -// -// This callback delivers messages to the client as QoS0 messages, either -// because: (a) they have been produced as MQTT QoS0 messages (and therefore -// only this callback can receive them); (b) they are MQTT QoS1+ published -// messages but this callback is for a subscription that is QoS0; or (c) the -// published messages come from (other) NATS publishers on the subject. -// -// This callback must reject a message if it is known to be a QoS1+ published -// message and this is the callback for a QoS1+ subscription because in that -// case, it will be handled by the other callback. This avoid getting duplicate -// deliveries. -func mqttDeliverMsgCbQoS0(sub *subscription, pc *client, _ *Account, subject, reply string, rmsg []byte) { - if pc.kind == JETSTREAM && len(reply) > 0 && strings.HasPrefix(reply, jsAckPre) { - return - } - - // This is the client associated with the subscription. - cc := sub.client - - // This is immutable - sess := cc.mqtt.sess - - // Lock here, otherwise we may be called with sub.mqtt == nil. Ignore - // wildcard subscriptions if this subject starts with '$', per Spec - // [MQTT-4.7.2-1]. - sess.subsMu.RLock() - subQoS := sub.mqtt.qos - ignore := mqttMustIgnoreForReservedSub(sub, subject) - sess.subsMu.RUnlock() - - if ignore { - return - } - - hdr, msg := pc.msgParts(rmsg) - var topic []byte - if pc.isMqtt() { - // This is an MQTT publisher directly connected to this server. - - // Check the subscription's QoS. If the message was published with a - // QoS>0 and the sub has the QoS>0 then the message will be delivered by - // mqttDeliverMsgCbQoS12. - msgQoS := mqttGetQoS(pc.mqtt.pp.flags) - if subQoS > 0 && msgQoS > 0 { - return - } - topic = pc.mqtt.pp.topic - // Check for service imports where subject mapping is in play. - if len(pc.pa.mapped) > 0 && len(pc.pa.psi) > 0 { - topic = natsSubjectStrToMQTTTopic(subject) - } - - } else { - // Non MQTT client, could be NATS publisher, or ROUTER, etc.. - h := mqttParsePublishNATSHeader(hdr) - - // Check the subscription's QoS. If the message was published with a - // QoS>0 (in the header) and the sub has the QoS>0 then the message will - // be delivered by mqttDeliverMsgCbQoS12. - if subQoS > 0 && h != nil && h.qos > 0 { - return - } - - // If size is more than what a MQTT client can handle, we should probably reject, - // for now just truncate. - if len(msg) > mqttMaxPayloadSize { - msg = msg[:mqttMaxPayloadSize] - } - topic = natsSubjectStrToMQTTTopic(subject) - } - - // Message never has a packet identifier nor is marked as duplicate. - pc.mqttEnqueuePublishMsgTo(cc, sub, 0, 0, false, topic, msg) -} - -// This is the callback attached to a JS durable subscription for a MQTT QoS 1+ -// sub. Only JETSTREAM should be sending a message to this subject (the delivery -// subject associated with the JS durable consumer), but in cluster mode, this -// can be coming from a route, gw, etc... We make sure that if this is the case, -// the message contains a NATS/MQTT header that indicates that this is a -// published QoS1+ message. -func mqttDeliverMsgCbQoS12(sub *subscription, pc *client, _ *Account, subject, reply string, rmsg []byte) { - // Message on foo.bar is stored under $MQTT.msgs.foo.bar, so the subject has to be - // at least as long as the stream subject prefix "$MQTT.msgs.", and after removing - // the prefix, has to be at least 1 character long. - if len(subject) < len(mqttStreamSubjectPrefix)+1 { - return - } - - hdr, msg := pc.msgParts(rmsg) - h := mqttParsePublishNATSHeader(hdr) - if pc.kind != JETSTREAM && (h == nil || h.qos == 0) { - // MQTT QoS 0 messages must be ignored, they will be delivered by the - // other callback, the direct NATS subscription. All JETSTREAM messages - // will have the header. - return - } - - // This is the client associated with the subscription. - cc := sub.client - - // This is immutable - sess := cc.mqtt.sess - - // We lock to check some of the subscription's fields and if we need to keep - // track of pending acks, etc. There is no need to acquire the subsMu RLock - // since sess.Lock is overarching for modifying subscriptions. - sess.mu.Lock() - if sess.c != cc || sub.mqtt == nil { - sess.mu.Unlock() - return - } - - // In this callback we handle only QoS-published messages to QoS - // subscriptions. Ignore if either is 0, will be delivered by the other - // callback, mqttDeliverMsgCbQos1. - var qos byte - if h != nil { - qos = h.qos - } - if qos > sub.mqtt.qos { - qos = sub.mqtt.qos - } - if qos == 0 { - sess.mu.Unlock() - return - } - - // Check for reserved subject violation. If so, we will send the ack to - // remove the message, and do nothing else. - strippedSubj := subject[len(mqttStreamSubjectPrefix):] - if mqttMustIgnoreForReservedSub(sub, strippedSubj) { - sess.mu.Unlock() - sess.jsa.sendAck(reply) - return - } - - pi, dup := sess.trackPublish(sub.mqtt.jsDur, reply) - sess.mu.Unlock() - - if pi == 0 { - // We have reached max pending, don't send the message now. - // JS will cause a redelivery and if by then the number of pending - // messages has fallen below threshold, the message will be resent. - return - } - - originalTopic := natsSubjectStrToMQTTTopic(strippedSubj) - pc.mqttEnqueuePublishMsgTo(cc, sub, pi, qos, dup, originalTopic, msg) -} - -func mqttDeliverPubRelCb(sub *subscription, pc *client, _ *Account, subject, reply string, rmsg []byte) { - if sub.client.mqtt == nil || sub.client.mqtt.sess == nil || reply == _EMPTY_ { - return - } - - hdr, _ := pc.msgParts(rmsg) - pi := mqttParsePubRelNATSHeader(hdr) - if pi == 0 { - return - } - - // This is the client associated with the subscription. - cc := sub.client - - // This is immutable - sess := cc.mqtt.sess - - sess.mu.Lock() - if sess.c != cc || sess.pubRelConsumer == nil { - sess.mu.Unlock() - return - } - sess.trackAsPubRel(pi, reply) - trace := cc.trace - sess.mu.Unlock() - - cc.mqttEnqueuePubResponse(mqttPacketPubRel, pi, trace) -} - -// The MQTT Server MUST NOT match Topic Filters starting with a wildcard -// character (# or +) with Topic Names beginning with a $ character, Spec -// [MQTT-4.7.2-1]. We will return true if there is a violation. -// -// Session or subMu lock must be held on entry to protect access to sub.mqtt. -func mqttMustIgnoreForReservedSub(sub *subscription, subject string) bool { - // If the subject does not start with $ nothing to do here. - if !sub.mqtt.reserved || len(subject) == 0 || subject[0] != mqttReservedPre { - return false - } - return true -} - -// Check if a sub is a reserved wildcard. E.g. '#', '*', or '*/" prefix. -func isMQTTReservedSubscription(subject string) bool { - if len(subject) == 1 && (subject[0] == fwc || subject[0] == pwc) { - return true - } - // Match "*.<>" - if len(subject) > 1 && (subject[0] == pwc && subject[1] == btsep) { - return true - } - return false -} - -func sparkbReplaceDeathTimestamp(msg []byte) []byte { - const VARINT = 0 - const TIMESTAMP = 1 - - orig := msg - buf := bytes.NewBuffer(make([]byte, 0, len(msg)+16)) // 16 bytes should be enough if we need to add a timestamp - writeDeathTimestamp := func() { - // [tck-id-conformance-mqtt-aware-ndeath-timestamp] A Sparkplug Aware - // MQTT Server MAY replace the timestamp of NDEATH messages. If it does, - // it MUST set the timestamp to the UTC time at which it attempts to - // deliver the NDEATH to subscribed clients - // - // sparkB spec: 6.4.1. Google Protocol Buffer Schema - // optional uint64 timestamp = 1; // Timestamp at message sending time - // - // SparkplugB timestamps are milliseconds since epoch, represented as - // uint64 in go, transmitted as protobuf varint. - ts := uint64(time.Now().UnixMilli()) - buf.Write(protoEncodeVarint(TIMESTAMP<<3 | VARINT)) - buf.Write(protoEncodeVarint(ts)) - } - - for len(msg) > 0 { - fieldNumericID, fieldType, size, err := protoScanField(msg) - if err != nil { - return orig - } - if fieldType != VARINT || fieldNumericID != TIMESTAMP { - // Add the field as is - buf.Write(msg[:size]) - msg = msg[size:] - continue - } - - writeDeathTimestamp() - - // Add the rest of the message as is, we are done - buf.Write(msg[size:]) - return buf.Bytes() - } - - // Add timestamp if we did not find one. - writeDeathTimestamp() - - return buf.Bytes() -} - -// Common function to mqtt delivery callbacks to serialize and send the message -// to the `cc` client. -func (c *client) mqttEnqueuePublishMsgTo(cc *client, sub *subscription, pi uint16, qos byte, dup bool, topic, msg []byte) { - // [tck-id-conformance-mqtt-aware-nbirth-mqtt-retain] A Sparkplug Aware - // MQTT Server MUST make NBIRTH messages available on the topic: - // $sparkplug/certificates/namespace/group_id/NBIRTH/edge_node_id with - // the MQTT retain flag set to true - // - // [tck-id-conformance-mqtt-aware-dbirth-mqtt-retain] A Sparkplug Aware - // MQTT Server MUST make DBIRTH messages available on the topic: - // $sparkplug/certificates/namespace/group_id/DBIRTH/edge_node_id/device_id - // with the MQTT retain flag set to true - // - // $sparkplug/certificates messages are sent as NATS messages, so we - // need to add the retain flag when sending them to MQTT clients. - - retain := false - isBirth, isDeath, isCertificate := sparkbParseBirthDeathTopic(topic) - if isBirth && qos == 0 { - retain = isCertificate - } else if isDeath && !isCertificate { - msg = sparkbReplaceDeathTimestamp(msg) - } - - flags, headerBytes := mqttMakePublishHeader(pi, qos, dup, retain, topic, len(msg)) - - cc.mu.Lock() - if sub.mqtt.prm != nil { - for _, data := range sub.mqtt.prm { - cc.queueOutbound(data) - } - sub.mqtt.prm = nil - } - cc.queueOutbound(headerBytes) - cc.queueOutbound(msg) - c.addToPCD(cc) - trace := cc.trace - cc.mu.Unlock() - - if trace { - pp := mqttPublish{ - topic: topic, - flags: flags, - pi: pi, - sz: len(msg), - } - cc.traceOutOp("PUBLISH", []byte(mqttPubTrace(&pp))) - } -} - -// Serializes to the given writer the message for the given subject. -func (w *mqttWriter) WritePublishHeader(pi uint16, qos byte, dup, retained bool, topic []byte, msgLen int) byte { - // Compute len (will have to add packet id if message is sent as QoS>=1) - pkLen := 2 + len(topic) + msgLen - var flags byte - - // Set flags for dup/retained/qos1 - if dup { - flags |= mqttPubFlagDup - } - if retained { - flags |= mqttPubFlagRetain - } - if qos > 0 { - pkLen += 2 - flags |= qos << 1 - } - - w.WriteByte(mqttPacketPub | flags) - w.WriteVarInt(pkLen) - w.WriteBytes(topic) - if qos > 0 { - w.WriteUint16(pi) - } - - return flags -} - -// Serializes to the given writer the message for the given subject. -func mqttMakePublishHeader(pi uint16, qos byte, dup, retained bool, topic []byte, msgLen int) (byte, []byte) { - headerBuf := newMQTTWriter(mqttInitialPubHeader + len(topic)) - flags := headerBuf.WritePublishHeader(pi, qos, dup, retained, topic, msgLen) - return flags, headerBuf.Bytes() -} - -// Process the SUBSCRIBE packet. -// -// Process the list of subscriptions and update the given filter -// with the QoS that has been accepted (or failure). -// -// Spec [MQTT-3.8.4-3] says that if an exact same subscription is -// found, it needs to be replaced with the new one (possibly updating -// the qos) and that the flow of publications must not be interrupted, -// which I read as the replacement cannot be a "remove then add" if there -// is a chance that in between the 2 actions, published messages -// would be "lost" because there would not be any matching subscription. -// -// Run from client's readLoop. -// No lock held on entry. -func (c *client) mqttProcessSubs(filters []*mqttFilter) ([]*subscription, error) { - // Those things are immutable, but since processing subs is not - // really in the fast path, let's get them under the client lock. - c.mu.Lock() - asm := c.mqtt.asm - sess := c.mqtt.sess - trace := c.trace - c.mu.Unlock() - - if err := asm.lockSession(sess, c); err != nil { - return nil, err - } - defer asm.unlockSession(sess) - return asm.processSubs(sess, c, filters, true, trace) -} - -// Cleanup that is performed in processSubs if there was an error. -// -// Runs from client's readLoop. -// Lock not held on entry, but session is in the locked map. -func (sess *mqttSession) cleanupFailedSub(c *client, sub *subscription, cc *ConsumerConfig, jssub *subscription) { - if sub != nil { - c.processUnsub(sub.sid) - } - if jssub != nil { - c.processUnsub(jssub.sid) - } - if cc != nil { - sess.deleteConsumer(cc) - } -} - -// Make sure we are set up to deliver PUBREL messages to this QoS2-subscribed -// session. -func (sess *mqttSession) ensurePubRelConsumerSubscription(c *client) error { - opts := c.srv.getOpts() - ackWait := opts.MQTT.AckWait - if ackWait == 0 { - ackWait = mqttDefaultAckWait - } - maxAckPending := int(opts.MQTT.MaxAckPending) - if maxAckPending == 0 { - maxAckPending = mqttDefaultMaxAckPending - } - - sess.mu.Lock() - pubRelSubscribed := sess.pubRelSubscribed - pubRelSubject := sess.pubRelSubject - pubRelDeliverySubjectB := sess.pubRelDeliverySubjectB - pubRelDeliverySubject := sess.pubRelDeliverySubject - pubRelConsumer := sess.pubRelConsumer - tmaxack := sess.tmaxack - idHash := sess.idHash - id := sess.id - sess.mu.Unlock() - - // Subscribe before the consumer is created so we don't loose any messages. - if !pubRelSubscribed { - _, err := c.processSub(pubRelDeliverySubjectB, nil, pubRelDeliverySubjectB, - mqttDeliverPubRelCb, false) - if err != nil { - c.Errorf("Unable to create subscription for JetStream consumer on %q: %v", pubRelDeliverySubject, err) - return err - } - pubRelSubscribed = true - } - - // Create the consumer if needed. - if pubRelConsumer == nil { - // Check that the limit of subs' maxAckPending are not going over the limit - if after := tmaxack + maxAckPending; after > mqttMaxAckTotalLimit { - return fmt.Errorf("max_ack_pending for all consumers would be %v which exceeds the limit of %v", - after, mqttMaxAckTotalLimit) - } - - ccr := &CreateConsumerRequest{ - Stream: mqttOutStreamName, - Config: ConsumerConfig{ - DeliverSubject: pubRelDeliverySubject, - Durable: mqttPubRelConsumerDurablePrefix + idHash, - AckPolicy: AckExplicit, - DeliverPolicy: DeliverNew, - FilterSubject: pubRelSubject, - AckWait: ackWait, - MaxAckPending: maxAckPending, - MemoryStorage: opts.MQTT.ConsumerMemoryStorage, - }, - } - if opts.MQTT.ConsumerInactiveThreshold > 0 { - ccr.Config.InactiveThreshold = opts.MQTT.ConsumerInactiveThreshold - } - if _, err := sess.jsa.createDurableConsumer(ccr); err != nil { - c.Errorf("Unable to add JetStream consumer for PUBREL for client %q: err=%v", id, err) - return err - } - pubRelConsumer = &ccr.Config - tmaxack += maxAckPending - } - - sess.mu.Lock() - sess.pubRelSubscribed = pubRelSubscribed - sess.pubRelConsumer = pubRelConsumer - sess.tmaxack = tmaxack - sess.mu.Unlock() - - return nil -} - -// When invoked with a QoS of 0, looks for an existing JS durable consumer for -// the given sid and if one is found, delete the JS durable consumer and unsub -// the NATS subscription on the delivery subject. -// -// With a QoS > 0, creates or update the existing JS durable consumer along with -// its NATS subscription on a delivery subject. -// -// Session lock is acquired and released as needed. Session is in the locked -// map. -func (sess *mqttSession) processJSConsumer(c *client, subject, sid string, - qos byte, fromSubProto bool) (*ConsumerConfig, *subscription, error) { - - sess.mu.Lock() - cc, exists := sess.cons[sid] - tmaxack := sess.tmaxack - idHash := sess.idHash - sess.mu.Unlock() - - // Check if we are already a JS consumer for this SID. - if exists { - // If current QoS is 0, it means that we need to delete the existing - // one (that was QoS > 0) - if qos == 0 { - // The JS durable consumer's delivery subject is on a NUID of - // the form: mqttSubPrefix + . It is also used as the sid - // for the NATS subscription, so use that for the lookup. - c.mu.Lock() - sub := c.subs[cc.DeliverSubject] - c.mu.Unlock() - - sess.mu.Lock() - delete(sess.cons, sid) - sess.mu.Unlock() - - sess.deleteConsumer(cc) - if sub != nil { - c.processUnsub(sub.sid) - } - return nil, nil, nil - } - // If this is called when processing SUBSCRIBE protocol, then if - // the JS consumer already exists, we are done (it was created - // during the processing of CONNECT). - if fromSubProto { - return nil, nil, nil - } - } - // Here it means we don't have a JS consumer and if we are QoS 0, - // we have nothing to do. - if qos == 0 { - return nil, nil, nil - } - var err error - var inbox string - if exists { - inbox = cc.DeliverSubject - } else { - inbox = mqttSubPrefix + nuid.Next() - opts := c.srv.getOpts() - ackWait := opts.MQTT.AckWait - if ackWait == 0 { - ackWait = mqttDefaultAckWait - } - maxAckPending := int(opts.MQTT.MaxAckPending) - if maxAckPending == 0 { - maxAckPending = mqttDefaultMaxAckPending - } - - // Check that the limit of subs' maxAckPending are not going over the limit - if after := tmaxack + maxAckPending; after > mqttMaxAckTotalLimit { - return nil, nil, fmt.Errorf("max_ack_pending for all consumers would be %v which exceeds the limit of %v", - after, mqttMaxAckTotalLimit) - } - - durName := idHash + "_" + nuid.Next() - ccr := &CreateConsumerRequest{ - Stream: mqttStreamName, - Config: ConsumerConfig{ - DeliverSubject: inbox, - Durable: durName, - AckPolicy: AckExplicit, - DeliverPolicy: DeliverNew, - FilterSubject: mqttStreamSubjectPrefix + subject, - AckWait: ackWait, - MaxAckPending: maxAckPending, - MemoryStorage: opts.MQTT.ConsumerMemoryStorage, - }, - } - if opts.MQTT.ConsumerInactiveThreshold > 0 { - ccr.Config.InactiveThreshold = opts.MQTT.ConsumerInactiveThreshold - } - if _, err := sess.jsa.createDurableConsumer(ccr); err != nil { - c.Errorf("Unable to add JetStream consumer for subscription on %q: err=%v", subject, err) - return nil, nil, err - } - cc = &ccr.Config - tmaxack += maxAckPending - } - - // This is an internal subscription on subject like "$MQTT.sub." that is setup - // for the JS durable's deliver subject. - sess.mu.Lock() - sess.tmaxack = tmaxack - sub, err := sess.processQOS12Sub(c, []byte(inbox), []byte(inbox), - isMQTTReservedSubscription(subject), qos, cc.Durable, mqttDeliverMsgCbQoS12) - sess.mu.Unlock() - - if err != nil { - sess.deleteConsumer(cc) - c.Errorf("Unable to create subscription for JetStream consumer on %q: %v", subject, err) - return nil, nil, err - } - return cc, sub, nil -} - -// Queues the published retained messages for each subscription and signals -// the writeLoop. -func (c *client) mqttSendRetainedMsgsToNewSubs(subs []*subscription) { - c.mu.Lock() - for _, sub := range subs { - if sub.mqtt != nil && sub.mqtt.prm != nil { - for _, data := range sub.mqtt.prm { - c.queueOutbound(data) - } - sub.mqtt.prm = nil - } - } - c.flushSignal() - c.mu.Unlock() -} - -func (c *client) mqttEnqueueSubAck(pi uint16, filters []*mqttFilter) { - w := newMQTTWriter(7 + len(filters)) - w.WriteByte(mqttPacketSubAck) - // packet length is 2 (for packet identifier) and 1 byte per filter. - w.WriteVarInt(2 + len(filters)) - w.WriteUint16(pi) - for _, f := range filters { - w.WriteByte(f.qos) - } - c.mu.Lock() - c.enqueueProto(w.Bytes()) - c.mu.Unlock() -} - -////////////////////////////////////////////////////////////////////////////// -// -// UNSUBSCRIBE related functions -// -////////////////////////////////////////////////////////////////////////////// - -func (c *client) mqttParseUnsubs(r *mqttReader, b byte, pl int) (uint16, []*mqttFilter, error) { - return c.mqttParseSubsOrUnsubs(r, b, pl, false) -} - -// Process the UNSUBSCRIBE packet. -// -// Given the list of topics, this is going to unsubscribe the low level NATS subscriptions -// and delete the JS durable consumers when applicable. -// -// Runs from the client's readLoop. -// No lock held on entry. -func (c *client) mqttProcessUnsubs(filters []*mqttFilter) error { - // Those things are immutable, but since processing unsubs is not - // really in the fast path, let's get them under the client lock. - c.mu.Lock() - asm := c.mqtt.asm - sess := c.mqtt.sess - c.mu.Unlock() - - if err := asm.lockSession(sess, c); err != nil { - return err - } - defer asm.unlockSession(sess) - - removeJSCons := func(sid string) { - cc, ok := sess.cons[sid] - if ok { - delete(sess.cons, sid) - sess.deleteConsumer(cc) - // Need lock here since these are accessed by callbacks - sess.mu.Lock() - if seqPis, ok := sess.cpending[cc.Durable]; ok { - delete(sess.cpending, cc.Durable) - for _, pi := range seqPis { - delete(sess.pendingPublish, pi) - } - if len(sess.pendingPublish) == 0 { - sess.last_pi = 0 - } - } - sess.mu.Unlock() - } - } - for _, f := range filters { - sid := f.filter - // Remove JS Consumer if one exists for this sid - removeJSCons(sid) - if err := c.processUnsub([]byte(sid)); err != nil { - c.Errorf("error unsubscribing from %q: %v", sid, err) - } - if mqttNeedSubForLevelUp(sid) { - subject := sid[:len(sid)-2] - sid = subject + mqttMultiLevelSidSuffix - removeJSCons(sid) - if err := c.processUnsub([]byte(sid)); err != nil { - c.Errorf("error unsubscribing from %q: %v", subject, err) - } - } - } - return sess.update(filters, false) -} - -func (c *client) mqttEnqueueUnsubAck(pi uint16) { - w := newMQTTWriter(4) - w.WriteByte(mqttPacketUnsubAck) - w.WriteVarInt(2) - w.WriteUint16(pi) - c.mu.Lock() - c.enqueueProto(w.Bytes()) - c.mu.Unlock() -} - -func mqttUnsubscribeTrace(pi uint16, filters []*mqttFilter) string { - var sep string - sb := strings.Builder{} - sb.WriteString("[") - for i, f := range filters { - sb.WriteString(sep) - sb.Write(f.ttopic) - sb.WriteString(" (") - sb.WriteString(f.filter) - sb.WriteString(")") - if i == 0 { - sep = ", " - } - } - sb.WriteString(fmt.Sprintf("] pi=%v", pi)) - return sb.String() -} - -////////////////////////////////////////////////////////////////////////////// -// -// PINGREQ/PINGRESP related functions -// -////////////////////////////////////////////////////////////////////////////// - -func (c *client) mqttEnqueuePingResp() { - c.mu.Lock() - c.enqueueProto(mqttPingResponse) - c.mu.Unlock() -} - -////////////////////////////////////////////////////////////////////////////// -// -// Trace functions -// -////////////////////////////////////////////////////////////////////////////// - -func errOrTrace(err error, trace string) []byte { - if err != nil { - return []byte(err.Error()) - } - return []byte(trace) -} - -////////////////////////////////////////////////////////////////////////////// -// -// Subject/Topic conversion functions -// -////////////////////////////////////////////////////////////////////////////// - -// Converts an MQTT Topic Name to a NATS Subject (used by PUBLISH) -// See mqttToNATSSubjectConversion() for details. -func mqttTopicToNATSPubSubject(mt []byte) ([]byte, error) { - return mqttToNATSSubjectConversion(mt, false) -} - -// Converts an MQTT Topic Filter to a NATS Subject (used by SUBSCRIBE) -// See mqttToNATSSubjectConversion() for details. -func mqttFilterToNATSSubject(filter []byte) ([]byte, error) { - return mqttToNATSSubjectConversion(filter, true) -} - -// Converts an MQTT Topic Name or Filter to a NATS Subject. -// In MQTT: -// - a Topic Name does not have wildcard (PUBLISH uses only topic names). -// - a Topic Filter can include wildcards (SUBSCRIBE uses those). -// - '+' and '#' are wildcard characters (single and multiple levels respectively) -// - '/' is the topic level separator. -// -// Conversion that occurs: -// - '/' is replaced with '/.' if it is the first character in mt -// - '/' is replaced with './' if the last or next character in mt is '/' -// For instance, foo//bar would become foo./.bar -// - '/' is replaced with '.' for all other conditions (foo/bar -> foo.bar) -// - '.' is replaced with '//'. -// - ' ' cause an error to be returned. -// -// If there is no need to convert anything (say "foo" remains "foo"), then -// the no memory is allocated and the returned slice is the original `mt`. -func mqttToNATSSubjectConversion(mt []byte, wcOk bool) ([]byte, error) { - var cp bool - var j int - res := mt - - makeCopy := func(i int) { - cp = true - res = make([]byte, 0, len(mt)+10) - if i > 0 { - res = append(res, mt[:i]...) - } - } - - end := len(mt) - 1 - for i := 0; i < len(mt); i++ { - switch mt[i] { - case mqttTopicLevelSep: - if i == 0 || res[j-1] == btsep { - if !cp { - makeCopy(0) - } - res = append(res, mqttTopicLevelSep, btsep) - j++ - } else if i == end || mt[i+1] == mqttTopicLevelSep { - if !cp { - makeCopy(i) - } - res = append(res, btsep, mqttTopicLevelSep) - j++ - } else { - if !cp { - makeCopy(i) - } - res = append(res, btsep) - } - case ' ': - // As of now, we cannot support ' ' in the MQTT topic/filter. - return nil, errMQTTUnsupportedCharacters - case btsep: - if !cp { - makeCopy(i) - } - res = append(res, mqttTopicLevelSep, mqttTopicLevelSep) - j++ - case mqttSingleLevelWC, mqttMultiLevelWC: - if !wcOk { - // Spec [MQTT-3.3.2-2] and [MQTT-4.7.1-1] - // The wildcard characters can be used in Topic Filters, but MUST NOT be used within a Topic Name - return nil, fmt.Errorf("wildcards not allowed in publish's topic: %q", mt) - } - if !cp { - makeCopy(i) - } - if mt[i] == mqttSingleLevelWC { - res = append(res, pwc) - } else { - res = append(res, fwc) - } - default: - if cp { - res = append(res, mt[i]) - } - } - j++ - } - if cp && res[j-1] == btsep { - res = append(res, mqttTopicLevelSep) - j++ - } - return res[:j], nil -} - -// Converts a NATS subject to MQTT topic. This is for publish -// messages only, so there is no checking for wildcards. -// Rules are reversed of mqttToNATSSubjectConversion. -func natsSubjectStrToMQTTTopic(subject string) []byte { - return natsSubjectToMQTTTopic(stringToBytes(subject)) -} - -func natsSubjectToMQTTTopic(subject []byte) []byte { - topic := make([]byte, len(subject)) - end := len(subject) - 1 - var j int - for i := 0; i < len(subject); i++ { - switch subject[i] { - case mqttTopicLevelSep: - if i < end { - switch c := subject[i+1]; c { - case btsep, mqttTopicLevelSep: - if c == btsep { - topic[j] = mqttTopicLevelSep - } else { - topic[j] = btsep - } - j++ - i++ - default: - } - } - case btsep: - topic[j] = mqttTopicLevelSep - j++ - default: - topic[j] = subject[i] - j++ - } - } - return topic[:j] -} - -// Returns true if the subject has more than 1 token and ends with ".>" -func mqttNeedSubForLevelUp(subject string) bool { - if len(subject) < 3 { - return false - } - end := len(subject) - if subject[end-2] == '.' && subject[end-1] == fwc { - return true - } - return false -} - -////////////////////////////////////////////////////////////////////////////// -// -// MQTT Reader functions -// -////////////////////////////////////////////////////////////////////////////// - -func (r *mqttReader) reset(buf []byte) { - if l := len(r.pbuf); l > 0 { - tmp := make([]byte, l+len(buf)) - copy(tmp, r.pbuf) - copy(tmp[l:], buf) - buf = tmp - r.pbuf = nil - } - r.buf = buf - r.pos = 0 - r.pstart = 0 -} - -func (r *mqttReader) hasMore() bool { - return r.pos != len(r.buf) -} - -func (r *mqttReader) readByte(field string) (byte, error) { - if r.pos == len(r.buf) { - return 0, fmt.Errorf("error reading %s: %v", field, io.EOF) - } - b := r.buf[r.pos] - r.pos++ - return b, nil -} - -func (r *mqttReader) readPacketLen() (int, bool, error) { - return r.readPacketLenWithCheck(true) -} - -func (r *mqttReader) readPacketLenWithCheck(check bool) (int, bool, error) { - m := 1 - v := 0 - for { - var b byte - if r.pos != len(r.buf) { - b = r.buf[r.pos] - r.pos++ - } else { - break - } - v += int(b&0x7f) * m - if (b & 0x80) == 0 { - if check && r.pos+v > len(r.buf) { - break - } - return v, true, nil - } - m *= 0x80 - if m > 0x200000 { - return 0, false, errMQTTMalformedVarInt - } - } - r.pbuf = make([]byte, len(r.buf)-r.pstart) - copy(r.pbuf, r.buf[r.pstart:]) - return 0, false, nil -} - -func (r *mqttReader) readString(field string) (string, error) { - var s string - bs, err := r.readBytes(field, false) - if err == nil { - s = string(bs) - } - return s, err -} - -func (r *mqttReader) readBytes(field string, cp bool) ([]byte, error) { - luint, err := r.readUint16(field) - if err != nil { - return nil, err - } - l := int(luint) - if l == 0 { - return nil, nil - } - start := r.pos - if start+l > len(r.buf) { - return nil, fmt.Errorf("error reading %s: %v", field, io.ErrUnexpectedEOF) - } - r.pos += l - b := r.buf[start:r.pos] - if cp { - b = copyBytes(b) - } - return b, nil -} - -func (r *mqttReader) readUint16(field string) (uint16, error) { - if len(r.buf)-r.pos < 2 { - return 0, fmt.Errorf("error reading %s: %v", field, io.ErrUnexpectedEOF) - } - start := r.pos - r.pos += 2 - return binary.BigEndian.Uint16(r.buf[start:r.pos]), nil -} - -////////////////////////////////////////////////////////////////////////////// -// -// MQTT Writer functions -// -////////////////////////////////////////////////////////////////////////////// - -func (w *mqttWriter) WriteUint16(i uint16) { - w.WriteByte(byte(i >> 8)) - w.WriteByte(byte(i)) -} - -func (w *mqttWriter) WriteString(s string) { - w.WriteBytes([]byte(s)) -} - -func (w *mqttWriter) WriteBytes(bs []byte) { - w.WriteUint16(uint16(len(bs))) - w.Write(bs) -} - -func (w *mqttWriter) WriteVarInt(value int) { - for { - b := byte(value & 0x7f) - value >>= 7 - if value > 0 { - b |= 0x80 - } - w.WriteByte(b) - if value == 0 { - break - } - } -} - -func newMQTTWriter(cap int) *mqttWriter { - w := &mqttWriter{} - w.Grow(cap) - return w -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/msgtrace.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/msgtrace.go deleted file mode 100644 index e3e73421..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/msgtrace.go +++ /dev/null @@ -1,846 +0,0 @@ -// Copyright 2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "encoding/json" - "fmt" - "math/rand" - "strconv" - "strings" - "sync/atomic" - "time" -) - -const ( - MsgTraceDest = "Nats-Trace-Dest" - MsgTraceHop = "Nats-Trace-Hop" - MsgTraceOriginAccount = "Nats-Trace-Origin-Account" - MsgTraceOnly = "Nats-Trace-Only" - - // External trace header. Note that this header is normally in lower - // case (https://www.w3.org/TR/trace-context/#header-name). Vendors - // MUST expect the header in any case (upper, lower, mixed), and - // SHOULD send the header name in lowercase. - traceParentHdr = "traceparent" -) - -type MsgTraceType string - -// Type of message trace events in the MsgTraceEvents list. -// This is needed to unmarshal the list. -const ( - MsgTraceIngressType = "in" - MsgTraceSubjectMappingType = "sm" - MsgTraceStreamExportType = "se" - MsgTraceServiceImportType = "si" - MsgTraceJetStreamType = "js" - MsgTraceEgressType = "eg" -) - -type MsgTraceEvent struct { - Server ServerInfo `json:"server"` - Request MsgTraceRequest `json:"request"` - Hops int `json:"hops,omitempty"` - Events MsgTraceEvents `json:"events"` -} - -type MsgTraceRequest struct { - // We are not making this an http.Header so that header name case is preserved. - Header map[string][]string `json:"header,omitempty"` - MsgSize int `json:"msgsize,omitempty"` -} - -type MsgTraceEvents []MsgTrace - -type MsgTrace interface { - new() MsgTrace - typ() MsgTraceType -} - -type MsgTraceBase struct { - Type MsgTraceType `json:"type"` - Timestamp time.Time `json:"ts"` -} - -type MsgTraceIngress struct { - MsgTraceBase - Kind int `json:"kind"` - CID uint64 `json:"cid"` - Name string `json:"name,omitempty"` - Account string `json:"acc"` - Subject string `json:"subj"` - Error string `json:"error,omitempty"` -} - -type MsgTraceSubjectMapping struct { - MsgTraceBase - MappedTo string `json:"to"` -} - -type MsgTraceStreamExport struct { - MsgTraceBase - Account string `json:"acc"` - To string `json:"to"` -} - -type MsgTraceServiceImport struct { - MsgTraceBase - Account string `json:"acc"` - From string `json:"from"` - To string `json:"to"` -} - -type MsgTraceJetStream struct { - MsgTraceBase - Stream string `json:"stream"` - Subject string `json:"subject,omitempty"` - NoInterest bool `json:"nointerest,omitempty"` - Error string `json:"error,omitempty"` -} - -type MsgTraceEgress struct { - MsgTraceBase - Kind int `json:"kind"` - CID uint64 `json:"cid"` - Name string `json:"name,omitempty"` - Hop string `json:"hop,omitempty"` - Account string `json:"acc,omitempty"` - Subscription string `json:"sub,omitempty"` - Queue string `json:"queue,omitempty"` - Error string `json:"error,omitempty"` - - // This is for applications that unmarshal the trace events - // and want to link an egress to route/leaf/gateway with - // the MsgTraceEvent from that server. - Link *MsgTraceEvent `json:"-"` -} - -// ------------------------------------------------------------- - -func (t MsgTraceBase) typ() MsgTraceType { return t.Type } -func (MsgTraceIngress) new() MsgTrace { return &MsgTraceIngress{} } -func (MsgTraceSubjectMapping) new() MsgTrace { return &MsgTraceSubjectMapping{} } -func (MsgTraceStreamExport) new() MsgTrace { return &MsgTraceStreamExport{} } -func (MsgTraceServiceImport) new() MsgTrace { return &MsgTraceServiceImport{} } -func (MsgTraceJetStream) new() MsgTrace { return &MsgTraceJetStream{} } -func (MsgTraceEgress) new() MsgTrace { return &MsgTraceEgress{} } - -var msgTraceInterfaces = map[MsgTraceType]MsgTrace{ - MsgTraceIngressType: MsgTraceIngress{}, - MsgTraceSubjectMappingType: MsgTraceSubjectMapping{}, - MsgTraceStreamExportType: MsgTraceStreamExport{}, - MsgTraceServiceImportType: MsgTraceServiceImport{}, - MsgTraceJetStreamType: MsgTraceJetStream{}, - MsgTraceEgressType: MsgTraceEgress{}, -} - -func (t *MsgTraceEvents) UnmarshalJSON(data []byte) error { - var raw []json.RawMessage - err := json.Unmarshal(data, &raw) - if err != nil { - return err - } - *t = make(MsgTraceEvents, len(raw)) - var tt MsgTraceBase - for i, r := range raw { - if err = json.Unmarshal(r, &tt); err != nil { - return err - } - tr, ok := msgTraceInterfaces[tt.Type] - if !ok { - return fmt.Errorf("unknown trace type %v", tt.Type) - } - te := tr.new() - if err := json.Unmarshal(r, te); err != nil { - return err - } - (*t)[i] = te - } - return nil -} - -func getTraceAs[T MsgTrace](e any) *T { - v, ok := e.(*T) - if ok { - return v - } - return nil -} - -func (t *MsgTraceEvent) Ingress() *MsgTraceIngress { - if len(t.Events) < 1 { - return nil - } - return getTraceAs[MsgTraceIngress](t.Events[0]) -} - -func (t *MsgTraceEvent) SubjectMapping() *MsgTraceSubjectMapping { - for _, e := range t.Events { - if e.typ() == MsgTraceSubjectMappingType { - return getTraceAs[MsgTraceSubjectMapping](e) - } - } - return nil -} - -func (t *MsgTraceEvent) StreamExports() []*MsgTraceStreamExport { - var se []*MsgTraceStreamExport - for _, e := range t.Events { - if e.typ() == MsgTraceStreamExportType { - se = append(se, getTraceAs[MsgTraceStreamExport](e)) - } - } - return se -} - -func (t *MsgTraceEvent) ServiceImports() []*MsgTraceServiceImport { - var si []*MsgTraceServiceImport - for _, e := range t.Events { - if e.typ() == MsgTraceServiceImportType { - si = append(si, getTraceAs[MsgTraceServiceImport](e)) - } - } - return si -} - -func (t *MsgTraceEvent) JetStream() *MsgTraceJetStream { - for _, e := range t.Events { - if e.typ() == MsgTraceJetStreamType { - return getTraceAs[MsgTraceJetStream](e) - } - } - return nil -} - -func (t *MsgTraceEvent) Egresses() []*MsgTraceEgress { - var eg []*MsgTraceEgress - for _, e := range t.Events { - if e.typ() == MsgTraceEgressType { - eg = append(eg, getTraceAs[MsgTraceEgress](e)) - } - } - return eg -} - -const ( - errMsgTraceOnlyNoSupport = "Not delivered because remote does not support message tracing" - errMsgTraceNoSupport = "Message delivered but remote does not support message tracing so no trace event generated from there" - errMsgTraceNoEcho = "Not delivered because of no echo" - errMsgTracePubViolation = "Not delivered because publish denied for this subject" - errMsgTraceSubDeny = "Not delivered because subscription denies this subject" - errMsgTraceSubClosed = "Not delivered because subscription is closed" - errMsgTraceClientClosed = "Not delivered because client is closed" - errMsgTraceAutoSubExceeded = "Not delivered because auto-unsubscribe exceeded" - errMsgTraceFastProdNoStall = "Not delivered because fast producer not stalled and consumer is slow" -) - -type msgTrace struct { - ready int32 - srv *Server - acc *Account - // Origin account name, set only if acc is nil when acc lookup failed. - oan string - dest string - event *MsgTraceEvent - js *MsgTraceJetStream - hop string - nhop string - tonly bool // Will only trace the message, not do delivery. - ct compressionType -} - -// This will be false outside of the tests, so when building the server binary, -// any code where you see `if msgTraceRunInTests` statement will be compiled -// out, so this will have no performance penalty. -var ( - msgTraceRunInTests bool - msgTraceCheckSupport bool -) - -// Returns the message trace object, if message is being traced, -// and `true` if we want to only trace, not actually deliver the message. -func (c *client) isMsgTraceEnabled() (*msgTrace, bool) { - t := c.pa.trace - if t == nil { - return nil, false - } - return t, t.tonly -} - -// For LEAF/ROUTER/GATEWAY, return false if the remote does not support -// message tracing (important if the tracing requests trace-only). -func (c *client) msgTraceSupport() bool { - // Exclude client connection from the protocol check. - return c.kind == CLIENT || c.opts.Protocol >= MsgTraceProto -} - -func getConnName(c *client) string { - switch c.kind { - case ROUTER: - if n := c.route.remoteName; n != _EMPTY_ { - return n - } - case GATEWAY: - if n := c.gw.remoteName; n != _EMPTY_ { - return n - } - case LEAF: - if n := c.leaf.remoteServer; n != _EMPTY_ { - return n - } - } - return c.opts.Name -} - -func getCompressionType(cts string) compressionType { - if cts == _EMPTY_ { - return noCompression - } - cts = strings.ToLower(cts) - if strings.Contains(cts, "snappy") || strings.Contains(cts, "s2") { - return snappyCompression - } - if strings.Contains(cts, "gzip") { - return gzipCompression - } - return unsupportedCompression -} - -func (c *client) initMsgTrace() *msgTrace { - // The code in the "if" statement is only running in test mode. - if msgTraceRunInTests { - // Check the type of client that tries to initialize a trace struct. - if !(c.kind == CLIENT || c.kind == ROUTER || c.kind == GATEWAY || c.kind == LEAF) { - panic(fmt.Sprintf("Unexpected client type %q trying to initialize msgTrace", c.kindString())) - } - // In some tests, we want to make a server behave like an old server - // and so even if a trace header is received, we want the server to - // simply ignore it. - if msgTraceCheckSupport { - if c.srv == nil || c.srv.getServerProto() < MsgTraceProto { - return nil - } - } - } - if c.pa.hdr <= 0 { - return nil - } - hdr := c.msgBuf[:c.pa.hdr] - headers, external := genHeaderMapIfTraceHeadersPresent(hdr) - if len(headers) == 0 { - return nil - } - // Little helper to give us the first value of a given header, or _EMPTY_ - // if key is not present. - getHdrVal := func(key string) string { - vv, ok := headers[key] - if !ok { - return _EMPTY_ - } - return vv[0] - } - ct := getCompressionType(getHdrVal(acceptEncodingHeader)) - var ( - dest string - traceOnly bool - ) - // Check for traceOnly only if not external. - if !external { - if to := getHdrVal(MsgTraceOnly); to != _EMPTY_ { - tos := strings.ToLower(to) - switch tos { - case "1", "true", "on": - traceOnly = true - } - } - dest = getHdrVal(MsgTraceDest) - // Check the destination to see if this is a valid public subject. - if !IsValidPublishSubject(dest) { - // We still have to return a msgTrace object (if traceOnly is set) - // because if we don't, the message will end-up being delivered to - // applications, which may break them. We report the error in any case. - c.Errorf("Destination %q is not valid, won't be able to trace events", dest) - if !traceOnly { - // We can bail, tracing will be disabled for this message. - return nil - } - } - } - var ( - // Account to use when sending the trace event - acc *Account - // Ingress' account name - ian string - // Origin account name - oan string - // The hop "id", taken from headers only when not from CLIENT - hop string - ) - if c.kind == ROUTER || c.kind == GATEWAY || c.kind == LEAF { - // The ingress account name will always be c.pa.account, but `acc` may - // be different if we have an origin account header. - if c.kind == LEAF { - ian = c.acc.GetName() - } else { - ian = string(c.pa.account) - } - // The remote will have set the origin account header only if the - // message changed account (think of service imports). - oan = getHdrVal(MsgTraceOriginAccount) - if oan == _EMPTY_ { - // For LEAF or ROUTER with pinned-account, we can use the c.acc. - if c.kind == LEAF || (c.kind == ROUTER && len(c.route.accName) > 0) { - acc = c.acc - } else { - // We will lookup account with c.pa.account (or ian). - oan = ian - } - } - // Unless we already got the account, we need to look it up. - if acc == nil { - // We don't want to do account resolving here. - if acci, ok := c.srv.accounts.Load(oan); ok { - acc = acci.(*Account) - // Since we have looked-up the account, we don't need oan, so - // clear it in case it was set. - oan = _EMPTY_ - } else { - // We still have to return a msgTrace object (if traceOnly is set) - // because if we don't, the message will end-up being delivered to - // applications, which may break them. We report the error in any case. - c.Errorf("Account %q was not found, won't be able to trace events", oan) - if !traceOnly { - // We can bail, tracing will be disabled for this message. - return nil - } - } - } - // Check the hop header - hop = getHdrVal(MsgTraceHop) - } else { - acc = c.acc - ian = acc.GetName() - } - // If external, we need to have the account's trace destination set, - // otherwise, we are not enabling tracing. - if external { - var sampling int - if acc != nil { - dest, sampling = acc.getTraceDestAndSampling() - } - if dest == _EMPTY_ { - // No account destination, no tracing for external trace headers. - return nil - } - // Check sampling, but only from origin server. - if c.kind == CLIENT && !sample(sampling) { - // Need to desactivate the traceParentHdr so that if the message - // is routed, it does possibly trigger a trace there. - disableTraceHeaders(c, hdr) - return nil - } - } - c.pa.trace = &msgTrace{ - srv: c.srv, - acc: acc, - oan: oan, - dest: dest, - ct: ct, - hop: hop, - event: &MsgTraceEvent{ - Request: MsgTraceRequest{ - Header: headers, - MsgSize: c.pa.size, - }, - Events: append(MsgTraceEvents(nil), &MsgTraceIngress{ - MsgTraceBase: MsgTraceBase{ - Type: MsgTraceIngressType, - Timestamp: time.Now(), - }, - Kind: c.kind, - CID: c.cid, - Name: getConnName(c), - Account: ian, - Subject: string(c.pa.subject), - }), - }, - tonly: traceOnly, - } - return c.pa.trace -} - -func sample(sampling int) bool { - // Option parsing should ensure that sampling is [1..100], but consider - // any value outside of this range to be 100%. - if sampling <= 0 || sampling >= 100 { - return true - } - return rand.Int31n(100) <= int32(sampling) -} - -// This function will return the header as a map (instead of http.Header because -// we want to preserve the header names' case) and a boolean that indicates if -// the headers have been lifted due to the presence of the external trace header -// only. -// Note that because of the traceParentHdr, the search is done in a case -// insensitive way, but if the header is found, it is rewritten in lower case -// as suggested by the spec, but also to make it easier to disable the header -// when needed. -func genHeaderMapIfTraceHeadersPresent(hdr []byte) (map[string][]string, bool) { - - var ( - _keys = [64][]byte{} - _vals = [64][]byte{} - m map[string][]string - traceDestHdrFound bool - traceParentHdrFound bool - ) - // Skip the hdrLine - if !bytes.HasPrefix(hdr, stringToBytes(hdrLine)) { - return nil, false - } - - traceDestHdrAsBytes := stringToBytes(MsgTraceDest) - traceParentHdrAsBytes := stringToBytes(traceParentHdr) - crLFAsBytes := stringToBytes(CR_LF) - dashAsBytes := stringToBytes("-") - - keys := _keys[:0] - vals := _vals[:0] - - for i := len(hdrLine); i < len(hdr); { - // Search for key/val delimiter - del := bytes.IndexByte(hdr[i:], ':') - if del < 0 { - break - } - keyStart := i - key := hdr[keyStart : keyStart+del] - i += del + 1 - valStart := i - nl := bytes.Index(hdr[valStart:], crLFAsBytes) - if nl < 0 { - break - } - if len(key) > 0 { - val := bytes.Trim(hdr[valStart:valStart+nl], " \t") - vals = append(vals, val) - - // Check for the external trace header. - if bytes.EqualFold(key, traceParentHdrAsBytes) { - // Rewrite the header using lower case if needed. - if !bytes.Equal(key, traceParentHdrAsBytes) { - copy(hdr[keyStart:], traceParentHdrAsBytes) - } - // We will now check if the value has sampling or not. - // TODO(ik): Not sure if this header can have multiple values - // or not, and if so, what would be the rule to check for - // sampling. What is done here is to check them all until we - // found one with sampling. - if !traceParentHdrFound { - tk := bytes.Split(val, dashAsBytes) - if len(tk) == 4 && len([]byte(tk[3])) == 2 { - if hexVal, err := strconv.ParseInt(bytesToString(tk[3]), 16, 8); err == nil { - if hexVal&0x1 == 0x1 { - traceParentHdrFound = true - } - } - } - } - // Add to the keys with the external trace header in lower case. - keys = append(keys, traceParentHdrAsBytes) - } else { - // Is the key the Nats-Trace-Dest header? - if bytes.EqualFold(key, traceDestHdrAsBytes) { - traceDestHdrFound = true - } - // Add to the keys and preserve the key's case - keys = append(keys, key) - } - } - i += nl + 2 - } - if !traceDestHdrFound && !traceParentHdrFound { - return nil, false - } - m = make(map[string][]string, len(keys)) - for i, k := range keys { - hname := string(k) - m[hname] = append(m[hname], string(vals[i])) - } - return m, !traceDestHdrFound && traceParentHdrFound -} - -// Special case where we create a trace event before parsing the message. -// This is for cases where the connection will be closed when detecting -// an error during early message processing (for instance max payload). -func (c *client) initAndSendIngressErrEvent(hdr []byte, dest string, ingressError error) { - if ingressError == nil { - return - } - ct := getAcceptEncoding(hdr) - t := &msgTrace{ - srv: c.srv, - acc: c.acc, - dest: dest, - ct: ct, - event: &MsgTraceEvent{ - Request: MsgTraceRequest{MsgSize: c.pa.size}, - Events: append(MsgTraceEvents(nil), &MsgTraceIngress{ - MsgTraceBase: MsgTraceBase{ - Type: MsgTraceIngressType, - Timestamp: time.Now(), - }, - Kind: c.kind, - CID: c.cid, - Name: getConnName(c), - Error: ingressError.Error(), - }), - }, - } - t.sendEvent() -} - -// Returns `true` if message tracing is enabled and we are tracing only, -// that is, we are not going to deliver the inbound message, returns -// `false` otherwise (no tracing, or tracing and message delivery). -func (t *msgTrace) traceOnly() bool { - return t != nil && t.tonly -} - -func (t *msgTrace) setOriginAccountHeaderIfNeeded(c *client, acc *Account, msg []byte) []byte { - var oan string - // If t.acc is set, only check that, not t.oan. - if t.acc != nil { - if t.acc != acc { - oan = t.acc.GetName() - } - } else if t.oan != acc.GetName() { - oan = t.oan - } - if oan != _EMPTY_ { - msg = c.setHeader(MsgTraceOriginAccount, oan, msg) - } - return msg -} - -func (t *msgTrace) setHopHeader(c *client, msg []byte) []byte { - e := t.event - e.Hops++ - if len(t.hop) > 0 { - t.nhop = fmt.Sprintf("%s.%d", t.hop, e.Hops) - } else { - t.nhop = fmt.Sprintf("%d", e.Hops) - } - return c.setHeader(MsgTraceHop, t.nhop, msg) -} - -// Will look for the MsgTraceSendTo and traceParentHdr headers and change the first -// character to an 'X' so that if this message is sent to a remote, the remote -// will not initialize tracing since it won't find the actual trace headers. -// The function returns the position of the headers so it can efficiently be -// re-enabled by calling enableTraceHeaders. -// Note that if `msg` can be either the header alone or the full message -// (header and payload). This function will use c.pa.hdr to limit the -// search to the header section alone. -func disableTraceHeaders(c *client, msg []byte) []int { - // Code largely copied from getHeader(), except that we don't need the value - if c.pa.hdr <= 0 { - return []int{-1, -1} - } - hdr := msg[:c.pa.hdr] - headers := [2]string{MsgTraceDest, traceParentHdr} - positions := [2]int{-1, -1} - for i := 0; i < 2; i++ { - key := stringToBytes(headers[i]) - pos := bytes.Index(hdr, key) - if pos < 0 { - continue - } - // Make sure this key does not have additional prefix. - if pos < 2 || hdr[pos-1] != '\n' || hdr[pos-2] != '\r' { - continue - } - index := pos + len(key) - if index >= len(hdr) { - continue - } - if hdr[index] != ':' { - continue - } - // Disable the trace by altering the first character of the header - hdr[pos] = 'X' - positions[i] = pos - } - // Return the positions of those characters so we can re-enable the headers. - return positions[:2] -} - -// Changes back the character at the given position `pos` in the `msg` -// byte slice to the first character of the MsgTraceSendTo header. -func enableTraceHeaders(msg []byte, positions []int) { - firstChar := [2]byte{MsgTraceDest[0], traceParentHdr[0]} - for i, pos := range positions { - if pos == -1 { - continue - } - msg[pos] = firstChar[i] - } -} - -func (t *msgTrace) setIngressError(err string) { - if i := t.event.Ingress(); i != nil { - i.Error = err - } -} - -func (t *msgTrace) addSubjectMappingEvent(subj []byte) { - if t == nil { - return - } - t.event.Events = append(t.event.Events, &MsgTraceSubjectMapping{ - MsgTraceBase: MsgTraceBase{ - Type: MsgTraceSubjectMappingType, - Timestamp: time.Now(), - }, - MappedTo: string(subj), - }) -} - -func (t *msgTrace) addEgressEvent(dc *client, sub *subscription, err string) { - if t == nil { - return - } - e := &MsgTraceEgress{ - MsgTraceBase: MsgTraceBase{ - Type: MsgTraceEgressType, - Timestamp: time.Now(), - }, - Kind: dc.kind, - CID: dc.cid, - Name: getConnName(dc), - Hop: t.nhop, - Error: err, - } - t.nhop = _EMPTY_ - // Specific to CLIENT connections... - if dc.kind == CLIENT { - // Set the subscription's subject and possibly queue name. - e.Subscription = string(sub.subject) - if len(sub.queue) > 0 { - e.Queue = string(sub.queue) - } - } - if dc.kind == CLIENT || dc.kind == LEAF { - if i := t.event.Ingress(); i != nil { - // If the Ingress' account is different from the destination's - // account, add the account name into the Egress trace event. - // This would happen with service imports. - if dcAccName := dc.acc.GetName(); dcAccName != i.Account { - e.Account = dcAccName - } - } - } - t.event.Events = append(t.event.Events, e) -} - -func (t *msgTrace) addStreamExportEvent(dc *client, to []byte) { - if t == nil { - return - } - dc.mu.Lock() - accName := dc.acc.GetName() - dc.mu.Unlock() - t.event.Events = append(t.event.Events, &MsgTraceStreamExport{ - MsgTraceBase: MsgTraceBase{ - Type: MsgTraceStreamExportType, - Timestamp: time.Now(), - }, - Account: accName, - To: string(to), - }) -} - -func (t *msgTrace) addServiceImportEvent(accName, from, to string) { - if t == nil { - return - } - t.event.Events = append(t.event.Events, &MsgTraceServiceImport{ - MsgTraceBase: MsgTraceBase{ - Type: MsgTraceServiceImportType, - Timestamp: time.Now(), - }, - Account: accName, - From: from, - To: to, - }) -} - -func (t *msgTrace) addJetStreamEvent(streamName string) { - if t == nil { - return - } - t.js = &MsgTraceJetStream{ - MsgTraceBase: MsgTraceBase{ - Type: MsgTraceJetStreamType, - Timestamp: time.Now(), - }, - Stream: streamName, - } - t.event.Events = append(t.event.Events, t.js) -} - -func (t *msgTrace) updateJetStreamEvent(subject string, noInterest bool) { - if t == nil { - return - } - // JetStream event should have been created in addJetStreamEvent - if t.js == nil { - return - } - t.js.Subject = subject - t.js.NoInterest = noInterest - // Update the timestamp since this is more accurate than when it - // was first added in addJetStreamEvent(). - t.js.Timestamp = time.Now() -} - -func (t *msgTrace) sendEventFromJetStream(err error) { - if t == nil { - return - } - // JetStream event should have been created in addJetStreamEvent - if t.js == nil { - return - } - if err != nil { - t.js.Error = err.Error() - } - t.sendEvent() -} - -func (t *msgTrace) sendEvent() { - if t == nil { - return - } - if t.js != nil { - ready := atomic.AddInt32(&t.ready, 1) == 2 - if !ready { - return - } - } - t.srv.sendInternalAccountSysMsg(t.acc, t.dest, &t.event.Server, t.event, t.ct) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/nkey.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/nkey.go deleted file mode 100644 index 0e5d0ee0..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/nkey.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2018-2023 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - crand "crypto/rand" - "encoding/base64" -) - -// Raw length of the nonce challenge -const ( - nonceRawLen = 11 - nonceLen = 15 // base64.RawURLEncoding.EncodedLen(nonceRawLen) -) - -// NonceRequired tells us if we should send a nonce. -func (s *Server) NonceRequired() bool { - s.mu.Lock() - defer s.mu.Unlock() - return s.nonceRequired() -} - -// nonceRequired tells us if we should send a nonce. -// Lock should be held on entry. -func (s *Server) nonceRequired() bool { - return s.getOpts().AlwaysEnableNonce || len(s.nkeys) > 0 || s.trustedKeys != nil -} - -// Generate a nonce for INFO challenge. -// Assumes server lock is held -func (s *Server) generateNonce(n []byte) { - var raw [nonceRawLen]byte - data := raw[:] - crand.Read(data) - base64.RawURLEncoding.Encode(n, data) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ocsp.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ocsp.go deleted file mode 100644 index cf3b1504..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ocsp.go +++ /dev/null @@ -1,992 +0,0 @@ -// Copyright 2021-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "crypto/sha256" - "crypto/tls" - "crypto/x509" - "encoding/asn1" - "encoding/base64" - "encoding/pem" - "errors" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "strings" - "sync" - "time" - - "golang.org/x/crypto/ocsp" - - "github.com/nats-io/nats-server/v2/server/certidp" - "github.com/nats-io/nats-server/v2/server/certstore" -) - -const ( - defaultOCSPStoreDir = "ocsp" - defaultOCSPCheckInterval = 24 * time.Hour - minOCSPCheckInterval = 2 * time.Minute -) - -type OCSPMode uint8 - -const ( - // OCSPModeAuto staples a status, only if "status_request" is set in cert. - OCSPModeAuto OCSPMode = iota - - // OCSPModeAlways enforces OCSP stapling for certs and shuts down the server in - // case a server is revoked or cannot get OCSP staples. - OCSPModeAlways - - // OCSPModeNever disables OCSP stapling even if cert has Must-Staple flag. - OCSPModeNever - - // OCSPModeMust honors the Must-Staple flag from a certificate but also causing shutdown - // in case the certificate has been revoked. - OCSPModeMust -) - -// OCSPMonitor monitors the state of a staple per certificate. -type OCSPMonitor struct { - kind string - mu sync.Mutex - raw []byte - srv *Server - certFile string - resp *ocsp.Response - hc *http.Client - stopCh chan struct{} - Leaf *x509.Certificate - Issuer *x509.Certificate - - shutdownOnRevoke bool -} - -func (oc *OCSPMonitor) getNextRun() time.Duration { - oc.mu.Lock() - nextUpdate := oc.resp.NextUpdate - oc.mu.Unlock() - - now := time.Now() - if nextUpdate.IsZero() { - // If response is missing NextUpdate, we check the day after. - // Technically, if NextUpdate is missing, we can try whenever. - // https://tools.ietf.org/html/rfc6960#section-4.2.2.1 - return defaultOCSPCheckInterval - } - dur := nextUpdate.Sub(now) / 2 - - // If negative, then wait a couple of minutes before getting another staple. - if dur < 0 { - return minOCSPCheckInterval - } - - return dur -} - -func (oc *OCSPMonitor) getStatus() ([]byte, *ocsp.Response, error) { - raw, resp := oc.getCacheStatus() - if len(raw) > 0 && resp != nil { - // Check if the OCSP is still valid. - if err := validOCSPResponse(resp); err == nil { - return raw, resp, nil - } - } - var err error - raw, resp, err = oc.getLocalStatus() - if err == nil { - return raw, resp, nil - } - - return oc.getRemoteStatus() -} - -func (oc *OCSPMonitor) getCacheStatus() ([]byte, *ocsp.Response) { - oc.mu.Lock() - defer oc.mu.Unlock() - return oc.raw, oc.resp -} - -func (oc *OCSPMonitor) getLocalStatus() ([]byte, *ocsp.Response, error) { - opts := oc.srv.getOpts() - storeDir := opts.StoreDir - if storeDir == _EMPTY_ { - return nil, nil, fmt.Errorf("store_dir not set") - } - - // This key must be based upon the current full certificate, not the public key, - // so MUST be on the full raw certificate and not an SPKI or other reduced form. - key := fmt.Sprintf("%x", sha256.Sum256(oc.Leaf.Raw)) - - oc.mu.Lock() - raw, err := os.ReadFile(filepath.Join(storeDir, defaultOCSPStoreDir, key)) - oc.mu.Unlock() - if err != nil { - return nil, nil, err - } - - resp, err := ocsp.ParseResponse(raw, oc.Issuer) - if err != nil { - return nil, nil, fmt.Errorf("failed to get local status: %w", err) - } - if err := validOCSPResponse(resp); err != nil { - return nil, nil, err - } - - // Cache the response. - oc.mu.Lock() - oc.raw = raw - oc.resp = resp - oc.mu.Unlock() - - return raw, resp, nil -} - -func (oc *OCSPMonitor) getRemoteStatus() ([]byte, *ocsp.Response, error) { - opts := oc.srv.getOpts() - var overrideURLs []string - if config := opts.OCSPConfig; config != nil { - overrideURLs = config.OverrideURLs - } - getRequestBytes := func(u string, reqDER []byte, hc *http.Client) ([]byte, error) { - reqEnc := base64.StdEncoding.EncodeToString(reqDER) - u = fmt.Sprintf("%s/%s", u, reqEnc) - start := time.Now() - resp, err := hc.Get(u) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - oc.srv.Debugf("Received OCSP response (method=GET, status=%v, url=%s, duration=%.3fs)", - resp.StatusCode, u, time.Since(start).Seconds()) - if resp.StatusCode > 299 { - return nil, fmt.Errorf("non-ok http status on GET request (reqlen=%d): %d", len(reqEnc), resp.StatusCode) - } - return io.ReadAll(resp.Body) - } - postRequestBytes := func(u string, body []byte, hc *http.Client) ([]byte, error) { - hreq, err := http.NewRequest("POST", u, bytes.NewReader(body)) - if err != nil { - return nil, err - } - hreq.Header.Add("Content-Type", "application/ocsp-request") - hreq.Header.Add("Accept", "application/ocsp-response") - - start := time.Now() - resp, err := hc.Do(hreq) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - oc.srv.Debugf("Received OCSP response (method=POST, status=%v, url=%s, duration=%.3fs)", - resp.StatusCode, u, time.Since(start).Seconds()) - if resp.StatusCode > 299 { - return nil, fmt.Errorf("non-ok http status on POST request (reqlen=%d): %d", len(body), resp.StatusCode) - } - return io.ReadAll(resp.Body) - } - - // Request documentation: - // https://tools.ietf.org/html/rfc6960#appendix-A.1 - - reqDER, err := ocsp.CreateRequest(oc.Leaf, oc.Issuer, nil) - if err != nil { - return nil, nil, err - } - - responders := oc.Leaf.OCSPServer - if len(overrideURLs) > 0 { - responders = overrideURLs - } - if len(responders) == 0 { - return nil, nil, fmt.Errorf("no available ocsp servers") - } - - oc.mu.Lock() - hc := oc.hc - oc.mu.Unlock() - - var raw []byte - for _, u := range responders { - var postErr, getErr error - u = strings.TrimSuffix(u, "/") - // Prefer to make POST requests first. - raw, postErr = postRequestBytes(u, reqDER, hc) - if postErr == nil { - err = nil - break - } else { - // Fallback to use a GET request. - raw, getErr = getRequestBytes(u, reqDER, hc) - if getErr == nil { - err = nil - break - } else { - err = errors.Join(postErr, getErr) - } - } - } - if err != nil { - return nil, nil, fmt.Errorf("exhausted ocsp servers: %w", err) - } - resp, err := ocsp.ParseResponse(raw, oc.Issuer) - if err != nil { - return nil, nil, fmt.Errorf("failed to get remote status: %w", err) - } - if err := validOCSPResponse(resp); err != nil { - return nil, nil, err - } - - if storeDir := opts.StoreDir; storeDir != _EMPTY_ { - key := fmt.Sprintf("%x", sha256.Sum256(oc.Leaf.Raw)) - if err := oc.writeOCSPStatus(storeDir, key, raw); err != nil { - return nil, nil, fmt.Errorf("failed to write ocsp status: %w", err) - } - } - - oc.mu.Lock() - oc.raw = raw - oc.resp = resp - oc.mu.Unlock() - - return raw, resp, nil -} - -func (oc *OCSPMonitor) run() { - s := oc.srv - s.mu.Lock() - quitCh := s.quitCh - s.mu.Unlock() - - var doShutdown bool - defer func() { - // Need to decrement before shuting down, otherwise shutdown - // would be stuck waiting on grWG to go down to 0. - s.grWG.Done() - if doShutdown { - s.Shutdown() - } - }() - - oc.mu.Lock() - shutdownOnRevoke := oc.shutdownOnRevoke - certFile := oc.certFile - stopCh := oc.stopCh - kind := oc.kind - oc.mu.Unlock() - - var nextRun time.Duration - _, resp, err := oc.getStatus() - if err == nil && resp.Status == ocsp.Good { - nextRun = oc.getNextRun() - t := resp.NextUpdate.Format(time.RFC3339Nano) - s.Noticef( - "Found OCSP status for %s certificate at '%s': good, next update %s, checking again in %s", - kind, certFile, t, nextRun, - ) - } else if err == nil && shutdownOnRevoke { - // If resp.Status is ocsp.Revoked, ocsp.Unknown, or any other value. - s.Errorf("Found OCSP status for %s certificate at '%s': %s", kind, certFile, ocspStatusString(resp.Status)) - doShutdown = true - return - } - - for { - // On reload, if the certificate changes then need to stop this monitor. - select { - case <-time.After(nextRun): - case <-stopCh: - // In case of reload and have to restart the OCSP stapling monitoring. - return - case <-quitCh: - // Server quit channel. - return - } - _, resp, err := oc.getRemoteStatus() - if err != nil { - nextRun = oc.getNextRun() - s.Errorf("Bad OCSP status update for certificate '%s': %s, trying again in %v", certFile, err, nextRun) - continue - } - - switch n := resp.Status; n { - case ocsp.Good: - nextRun = oc.getNextRun() - t := resp.NextUpdate.Format(time.RFC3339Nano) - s.Noticef( - "Received OCSP status for %s certificate '%s': good, next update %s, checking again in %s", - kind, certFile, t, nextRun, - ) - continue - default: - s.Errorf("Received OCSP status for %s certificate '%s': %s", kind, certFile, ocspStatusString(n)) - if shutdownOnRevoke { - doShutdown = true - } - return - } - } -} - -func (oc *OCSPMonitor) stop() { - oc.mu.Lock() - stopCh := oc.stopCh - oc.mu.Unlock() - stopCh <- struct{}{} -} - -// NewOCSPMonitor takes a TLS configuration then wraps it with the callbacks set for OCSP verification -// along with a monitor that will periodically fetch OCSP staples. -func (srv *Server) NewOCSPMonitor(config *tlsConfigKind) (*tls.Config, *OCSPMonitor, error) { - kind := config.kind - tc := config.tlsConfig - tcOpts := config.tlsOpts - opts := srv.getOpts() - oc := opts.OCSPConfig - - // We need to track the CA certificate in case the CA is not present - // in the chain to be able to verify the signature of the OCSP staple. - var ( - certFile string - caFile string - ) - if kind == kindStringMap[CLIENT] { - tcOpts = opts.tlsConfigOpts - if opts.TLSCert != _EMPTY_ { - certFile = opts.TLSCert - } - if opts.TLSCaCert != _EMPTY_ { - caFile = opts.TLSCaCert - } - } - if tcOpts != nil { - certFile = tcOpts.CertFile - caFile = tcOpts.CaFile - } - - // NOTE: Currently OCSP Stapling is enabled only for the first certificate found. - var mon *OCSPMonitor - for _, currentCert := range tc.Certificates { - // Create local copy since this will be used in the GetCertificate callback. - cert := currentCert - - // This is normally non-nil, but can still be nil here when in tests - // or in some embedded scenarios. - if cert.Leaf == nil { - if len(cert.Certificate) <= 0 { - return nil, nil, fmt.Errorf("no certificate found") - } - var err error - cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) - if err != nil { - return nil, nil, fmt.Errorf("error parsing certificate: %v", err) - } - } - var shutdownOnRevoke bool - mustStaple := hasOCSPStatusRequest(cert.Leaf) - if oc != nil { - switch { - case oc.Mode == OCSPModeNever: - if mustStaple { - srv.Warnf("Certificate at '%s' has MustStaple but OCSP is disabled", certFile) - } - return tc, nil, nil - case oc.Mode == OCSPModeAlways: - // Start the monitor for this cert even if it does not have - // the MustStaple flag and shutdown the server in case the - // staple ever gets revoked. - mustStaple = true - shutdownOnRevoke = true - case oc.Mode == OCSPModeMust && mustStaple: - shutdownOnRevoke = true - case oc.Mode == OCSPModeAuto && !mustStaple: - // "status_request" MustStaple flag not set in certificate. No need to do anything. - return tc, nil, nil - } - } - if !mustStaple { - // No explicit OCSP config and cert does not have MustStaple flag either. - return tc, nil, nil - } - - if err := srv.setupOCSPStapleStoreDir(); err != nil { - return nil, nil, err - } - - // TODO: Add OCSP 'responder_cert' option in case CA cert not available. - issuer, err := getOCSPIssuer(caFile, cert.Certificate) - if err != nil { - return nil, nil, err - } - - mon = &OCSPMonitor{ - kind: kind, - srv: srv, - hc: &http.Client{Timeout: 30 * time.Second}, - shutdownOnRevoke: shutdownOnRevoke, - certFile: certFile, - stopCh: make(chan struct{}, 1), - Leaf: cert.Leaf, - Issuer: issuer, - } - - // Get the certificate status from the memory, then remote OCSP responder. - if _, resp, err := mon.getStatus(); err != nil { - return nil, nil, fmt.Errorf("bad OCSP status update for certificate at '%s': %s", certFile, err) - } else if resp != nil && resp.Status != ocsp.Good && shutdownOnRevoke { - return nil, nil, fmt.Errorf("found existing OCSP status for certificate at '%s': %s", certFile, ocspStatusString(resp.Status)) - } - - // Callbacks below will be in charge of returning the certificate instead, - // so this has to be nil. - tc.Certificates = nil - - // GetCertificate returns a certificate that's presented to a client. - tc.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { - ccert := cert - raw, _, err := mon.getStatus() - if err != nil { - return nil, err - } - return &tls.Certificate{ - OCSPStaple: raw, - Certificate: ccert.Certificate, - PrivateKey: ccert.PrivateKey, - SupportedSignatureAlgorithms: ccert.SupportedSignatureAlgorithms, - SignedCertificateTimestamps: ccert.SignedCertificateTimestamps, - Leaf: ccert.Leaf, - }, nil - } - - // Check whether need to verify staples from a peer router or gateway connection. - switch kind { - case kindStringMap[ROUTER], kindStringMap[GATEWAY]: - tc.VerifyConnection = func(s tls.ConnectionState) error { - oresp := s.OCSPResponse - if oresp == nil { - return fmt.Errorf("%s peer missing OCSP Staple", kind) - } - - // Peer connections will verify the response of the staple. - if len(s.VerifiedChains) == 0 { - return fmt.Errorf("%s peer missing TLS verified chains", kind) - } - - chain := s.VerifiedChains[0] - peerLeaf := chain[0] - peerIssuer := certidp.GetLeafIssuerCert(chain, 0) - if peerIssuer == nil { - return fmt.Errorf("failed to get issuer certificate for %s peer", kind) - } - - // Response signature of issuer or issuer delegate is checked in the library parse - resp, err := ocsp.ParseResponseForCert(oresp, peerLeaf, peerIssuer) - if err != nil { - return fmt.Errorf("failed to parse OCSP response from %s peer: %w", kind, err) - } - - // If signer was issuer delegate double-check issuer delegate authorization - if resp.Certificate != nil { - ok := false - for _, eku := range resp.Certificate.ExtKeyUsage { - if eku == x509.ExtKeyUsageOCSPSigning { - ok = true - break - } - } - if !ok { - return fmt.Errorf("OCSP staple's signer missing authorization by CA to act as OCSP signer") - } - } - - // Check that the OCSP response is effective, take defaults for clockskew and default validity - peerOpts := certidp.OCSPPeerConfig{ClockSkew: -1, TTLUnsetNextUpdate: -1} - sLog := certidp.Log{Debugf: srv.Debugf} - if !certidp.OCSPResponseCurrent(resp, &peerOpts, &sLog) { - return fmt.Errorf("OCSP staple from %s peer not current", kind) - } - - if resp.Status != ocsp.Good { - return fmt.Errorf("bad status for OCSP Staple from %s peer: %s", kind, ocspStatusString(resp.Status)) - } - - return nil - } - - // When server makes a peer connection, need to also present an OCSP Staple. - tc.GetClientCertificate = func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) { - ccert := cert - raw, _, err := mon.getStatus() - if err != nil { - return nil, err - } - // NOTE: crypto/tls.sendClientCertificate internally also calls getClientCertificate - // so if for some reason these callbacks are triggered concurrently during a reconnect - // there can be a race. To avoid that, the OCSP monitor lock is used to serialize access - // to the staple which could also change inflight during an update. - mon.mu.Lock() - ccert.OCSPStaple = raw - mon.mu.Unlock() - - return &ccert, nil - } - default: - // GetClientCertificate returns a certificate that's presented to a server. - tc.GetClientCertificate = func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) { - return &cert, nil - } - } - } - return tc, mon, nil -} - -func (s *Server) setupOCSPStapleStoreDir() error { - opts := s.getOpts() - storeDir := opts.StoreDir - if storeDir == _EMPTY_ { - return nil - } - storeDir = filepath.Join(storeDir, defaultOCSPStoreDir) - if stat, err := os.Stat(storeDir); os.IsNotExist(err) { - if err := os.MkdirAll(storeDir, defaultDirPerms); err != nil { - return fmt.Errorf("could not create OCSP storage directory - %v", err) - } - } else if stat == nil || !stat.IsDir() { - return fmt.Errorf("OCSP storage directory is not a directory") - } - return nil -} - -type tlsConfigKind struct { - tlsConfig *tls.Config - tlsOpts *TLSConfigOpts - kind string - isLeafSpoke bool - apply func(*tls.Config) -} - -func (s *Server) configureOCSP() []*tlsConfigKind { - sopts := s.getOpts() - - configs := make([]*tlsConfigKind, 0) - - if config := sopts.TLSConfig; config != nil { - opts := sopts.tlsConfigOpts - o := &tlsConfigKind{ - kind: kindStringMap[CLIENT], - tlsConfig: config, - tlsOpts: opts, - apply: func(tc *tls.Config) { sopts.TLSConfig = tc }, - } - configs = append(configs, o) - } - if config := sopts.Websocket.TLSConfig; config != nil { - opts := sopts.Websocket.tlsConfigOpts - o := &tlsConfigKind{ - kind: kindStringMap[CLIENT], - tlsConfig: config, - tlsOpts: opts, - apply: func(tc *tls.Config) { sopts.Websocket.TLSConfig = tc }, - } - configs = append(configs, o) - } - if config := sopts.MQTT.TLSConfig; config != nil { - opts := sopts.tlsConfigOpts - o := &tlsConfigKind{ - kind: kindStringMap[CLIENT], - tlsConfig: config, - tlsOpts: opts, - apply: func(tc *tls.Config) { sopts.MQTT.TLSConfig = tc }, - } - configs = append(configs, o) - } - if config := sopts.Cluster.TLSConfig; config != nil { - opts := sopts.Cluster.tlsConfigOpts - o := &tlsConfigKind{ - kind: kindStringMap[ROUTER], - tlsConfig: config, - tlsOpts: opts, - apply: func(tc *tls.Config) { sopts.Cluster.TLSConfig = tc }, - } - configs = append(configs, o) - } - if config := sopts.LeafNode.TLSConfig; config != nil { - opts := sopts.LeafNode.tlsConfigOpts - o := &tlsConfigKind{ - kind: kindStringMap[LEAF], - tlsConfig: config, - tlsOpts: opts, - apply: func(tc *tls.Config) { sopts.LeafNode.TLSConfig = tc }, - } - configs = append(configs, o) - } - for _, remote := range sopts.LeafNode.Remotes { - if config := remote.TLSConfig; config != nil { - // Use a copy of the remote here since will be used - // in the apply func callback below. - r, opts := remote, remote.tlsConfigOpts - o := &tlsConfigKind{ - kind: kindStringMap[LEAF], - tlsConfig: config, - tlsOpts: opts, - isLeafSpoke: true, - apply: func(tc *tls.Config) { r.TLSConfig = tc }, - } - configs = append(configs, o) - } - } - if config := sopts.Gateway.TLSConfig; config != nil { - opts := sopts.Gateway.tlsConfigOpts - o := &tlsConfigKind{ - kind: kindStringMap[GATEWAY], - tlsConfig: config, - tlsOpts: opts, - apply: func(tc *tls.Config) { sopts.Gateway.TLSConfig = tc }, - } - configs = append(configs, o) - } - for _, remote := range sopts.Gateway.Gateways { - if config := remote.TLSConfig; config != nil { - gw, opts := remote, remote.tlsConfigOpts - o := &tlsConfigKind{ - kind: kindStringMap[GATEWAY], - tlsConfig: config, - tlsOpts: opts, - apply: func(tc *tls.Config) { gw.TLSConfig = tc }, - } - configs = append(configs, o) - } - } - return configs -} - -func (s *Server) enableOCSP() error { - configs := s.configureOCSP() - - for _, config := range configs { - - // We do not staple Leaf Hub and Leaf Spokes, use ocsp_peer - if config.kind != kindStringMap[LEAF] { - // OCSP Stapling feature, will also enable tls server peer check for gateway and route peers - tc, mon, err := s.NewOCSPMonitor(config) - if err != nil { - return err - } - // Check if an OCSP stapling monitor is required for this certificate. - if mon != nil { - s.ocsps = append(s.ocsps, mon) - - // Override the TLS config with one that follows OCSP stapling - config.apply(tc) - } - } - - // OCSP peer check (client mTLS, leaf mTLS, leaf remote TLS) - if config.kind == kindStringMap[CLIENT] || config.kind == kindStringMap[LEAF] { - tc, plugged, err := s.plugTLSOCSPPeer(config) - if err != nil { - return err - } - if plugged && tc != nil { - s.ocspPeerVerify = true - config.apply(tc) - } - } - } - - return nil -} - -func (s *Server) startOCSPMonitoring() { - s.mu.Lock() - ocsps := s.ocsps - s.mu.Unlock() - if ocsps == nil { - return - } - for _, mon := range ocsps { - m := mon - m.mu.Lock() - kind := m.kind - m.mu.Unlock() - s.Noticef("OCSP Stapling enabled for %s connections", kind) - s.startGoRoutine(func() { m.run() }) - } -} - -func (s *Server) reloadOCSP() error { - if err := s.setupOCSPStapleStoreDir(); err != nil { - return err - } - - s.mu.Lock() - ocsps := s.ocsps - s.mu.Unlock() - - // Stop all OCSP Stapling monitors in case there were any running. - for _, oc := range ocsps { - oc.stop() - } - - configs := s.configureOCSP() - - // Restart the monitors under the new configuration. - ocspm := make([]*OCSPMonitor, 0) - - // Reset server's ocspPeerVerify flag to re-detect at least one plugged OCSP peer - s.mu.Lock() - s.ocspPeerVerify = false - s.mu.Unlock() - s.stopOCSPResponseCache() - - for _, config := range configs { - // We do not staple Leaf Hub and Leaf Spokes, use ocsp_peer - if config.kind != kindStringMap[LEAF] { - tc, mon, err := s.NewOCSPMonitor(config) - if err != nil { - return err - } - // Check if an OCSP stapling monitor is required for this certificate. - if mon != nil { - ocspm = append(ocspm, mon) - - // Apply latest TLS configuration after OCSP monitors have started. - defer config.apply(tc) - } - } - - // OCSP peer check (client mTLS, leaf mTLS, leaf remote TLS) - if config.kind == kindStringMap[CLIENT] || config.kind == kindStringMap[LEAF] { - tc, plugged, err := s.plugTLSOCSPPeer(config) - if err != nil { - return err - } - if plugged && tc != nil { - s.ocspPeerVerify = true - defer config.apply(tc) - } - } - } - - // Replace stopped monitors with the new ones. - s.mu.Lock() - s.ocsps = ocspm - s.mu.Unlock() - - // Dispatch all goroutines once again. - s.startOCSPMonitoring() - - // Init and restart OCSP responder cache - s.stopOCSPResponseCache() - s.initOCSPResponseCache() - s.startOCSPResponseCache() - - return nil -} - -func hasOCSPStatusRequest(cert *x509.Certificate) bool { - // OID for id-pe-tlsfeature defined in RFC here: - // https://datatracker.ietf.org/doc/html/rfc7633 - tlsFeatures := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24} - const statusRequestExt = 5 - - // Example values: - // * [48 3 2 1 5] - seen when creating own certs locally - // * [30 3 2 1 5] - seen in the wild - // Documentation: - // https://tools.ietf.org/html/rfc6066 - - for _, ext := range cert.Extensions { - if !ext.Id.Equal(tlsFeatures) { - continue - } - - var val []int - rest, err := asn1.Unmarshal(ext.Value, &val) - if err != nil || len(rest) > 0 { - return false - } - - for _, n := range val { - if n == statusRequestExt { - return true - } - } - break - } - - return false -} - -// writeOCSPStatus writes an OCSP status to a temporary file then moves it to a -// new path, in an attempt to avoid corrupting existing data. -func (oc *OCSPMonitor) writeOCSPStatus(storeDir, file string, data []byte) error { - storeDir = filepath.Join(storeDir, defaultOCSPStoreDir) - tmp, err := os.CreateTemp(storeDir, "tmp-cert-status") - if err != nil { - return err - } - - if _, err := tmp.Write(data); err != nil { - tmp.Close() - os.Remove(tmp.Name()) - return err - } - if err := tmp.Close(); err != nil { - return err - } - - oc.mu.Lock() - err = os.Rename(tmp.Name(), filepath.Join(storeDir, file)) - oc.mu.Unlock() - if err != nil { - os.Remove(tmp.Name()) - return err - } - - return nil -} - -func parseCertPEM(name string) ([]*x509.Certificate, error) { - data, err := os.ReadFile(name) - if err != nil { - return nil, err - } - - var pemBytes []byte - - var block *pem.Block - for len(data) != 0 { - block, data = pem.Decode(data) - if block == nil { - break - } - if block.Type != "CERTIFICATE" { - return nil, fmt.Errorf("unexpected PEM certificate type: %s", block.Type) - } - - pemBytes = append(pemBytes, block.Bytes...) - } - - return x509.ParseCertificates(pemBytes) -} - -// getOCSPIssuerLocally determines a leaf's issuer from locally configured certificates -func getOCSPIssuerLocally(trustedCAs []*x509.Certificate, certBundle []*x509.Certificate) (*x509.Certificate, error) { - var vOpts x509.VerifyOptions - var leaf *x509.Certificate - trustedCAPool := x509.NewCertPool() - - // Require Leaf as first cert in bundle - if len(certBundle) > 0 { - leaf = certBundle[0] - } else { - return nil, fmt.Errorf("invalid ocsp ca configuration") - } - - // Allow Issuer to be configured as second cert in bundle - if len(certBundle) > 1 { - // The operator may have misconfigured the cert bundle - issuerCandidate := certBundle[1] - err := issuerCandidate.CheckSignature(leaf.SignatureAlgorithm, leaf.RawTBSCertificate, leaf.Signature) - if err != nil { - return nil, fmt.Errorf("invalid issuer configuration: %w", err) - } else { - return issuerCandidate, nil - } - } - - // Operator did not provide the Leaf Issuer in cert bundle second position - // so we will attempt to create at least one ordered verified chain from the - // trusted CA pool. - - // Specify CA trust store to validator; if unset, system trust store used - if len(trustedCAs) > 0 { - for _, ca := range trustedCAs { - trustedCAPool.AddCert(ca) - } - vOpts.Roots = trustedCAPool - } - - return certstore.GetLeafIssuer(leaf, vOpts), nil -} - -// getOCSPIssuer determines an issuer certificate from the cert (bundle) or the file-based CA trust store -func getOCSPIssuer(caFile string, chain [][]byte) (*x509.Certificate, error) { - var issuer *x509.Certificate - var trustedCAs []*x509.Certificate - var certBundle []*x509.Certificate - var err error - - // FIXME(tgb): extend if pluggable CA store provider added to NATS (i.e. other than PEM file) - - // Non-system default CA trust store passed - if caFile != _EMPTY_ { - trustedCAs, err = parseCertPEM(caFile) - if err != nil { - return nil, fmt.Errorf("failed to parse ca_file: %v", err) - } - } - - // Specify bundled intermediate CA store - for _, certBytes := range chain { - cert, err := x509.ParseCertificate(certBytes) - if err != nil { - return nil, fmt.Errorf("failed to parse cert: %v", err) - } - certBundle = append(certBundle, cert) - } - - issuer, err = getOCSPIssuerLocally(trustedCAs, certBundle) - if err != nil || issuer == nil { - return nil, fmt.Errorf("no issuers found") - } - - if !issuer.IsCA { - return nil, fmt.Errorf("%s invalid ca basic constraints: is not ca", issuer.Subject) - } - return issuer, nil -} - -func ocspStatusString(n int) string { - switch n { - case ocsp.Good: - return "good" - case ocsp.Revoked: - return "revoked" - default: - return "unknown" - } -} - -func validOCSPResponse(r *ocsp.Response) error { - // Time validation not handled by ParseResponse. - // https://tools.ietf.org/html/rfc6960#section-4.2.2.1 - if !r.NextUpdate.IsZero() && r.NextUpdate.Before(time.Now()) { - t := r.NextUpdate.Format(time.RFC3339Nano) - return fmt.Errorf("invalid ocsp NextUpdate, is past time: %s", t) - } - if r.ThisUpdate.After(time.Now()) { - t := r.ThisUpdate.Format(time.RFC3339Nano) - return fmt.Errorf("invalid ocsp ThisUpdate, is future time: %s", t) - } - - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ocsp_peer.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ocsp_peer.go deleted file mode 100644 index fd735094..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ocsp_peer.go +++ /dev/null @@ -1,405 +0,0 @@ -// Copyright 2023-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "crypto/tls" - "crypto/x509" - "errors" - "fmt" - "strings" - "time" - - "golang.org/x/crypto/ocsp" - - "github.com/nats-io/nats-server/v2/server/certidp" -) - -func parseOCSPPeer(v any) (pcfg *certidp.OCSPPeerConfig, retError error) { - var lt token - defer convertPanicToError(<, &retError) - tk, v := unwrapValue(v, <) - cm, ok := v.(map[string]any) - if !ok { - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrIllegalPeerOptsConfig, v)} - } - pcfg = certidp.NewOCSPPeerConfig() - retError = nil - for mk, mv := range cm { - tk, mv = unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "verify": - verify, ok := mv.(bool) - if !ok { - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldGeneric, mk)} - } - pcfg.Verify = verify - case "allowed_clockskew": - at := float64(0) - switch mv := mv.(type) { - case int64: - at = float64(mv) - case float64: - at = mv - case string: - d, err := time.ParseDuration(mv) - if err != nil { - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldTypeConversion, "unexpected type")} - } - at = d.Seconds() - default: - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldTypeConversion, "unexpected type")} - } - if at >= 0 { - pcfg.ClockSkew = at - } - case "ca_timeout": - at := float64(0) - switch mv := mv.(type) { - case int64: - at = float64(mv) - case float64: - at = mv - case string: - d, err := time.ParseDuration(mv) - if err != nil { - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldTypeConversion, err)} - } - at = d.Seconds() - default: - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldTypeConversion, "unexpected type")} - } - if at >= 0 { - pcfg.Timeout = at - } - case "cache_ttl_when_next_update_unset": - at := float64(0) - switch mv := mv.(type) { - case int64: - at = float64(mv) - case float64: - at = mv - case string: - d, err := time.ParseDuration(mv) - if err != nil { - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldTypeConversion, err)} - } - at = d.Seconds() - default: - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldTypeConversion, "unexpected type")} - } - if at >= 0 { - pcfg.TTLUnsetNextUpdate = at - } - case "warn_only": - warnOnly, ok := mv.(bool) - if !ok { - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldGeneric, mk)} - } - pcfg.WarnOnly = warnOnly - case "unknown_is_good": - unknownIsGood, ok := mv.(bool) - if !ok { - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldGeneric, mk)} - } - pcfg.UnknownIsGood = unknownIsGood - case "allow_when_ca_unreachable": - allowWhenCAUnreachable, ok := mv.(bool) - if !ok { - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldGeneric, mk)} - } - pcfg.AllowWhenCAUnreachable = allowWhenCAUnreachable - default: - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldGeneric, mk)} - } - } - return pcfg, nil -} - -func peerFromVerifiedChains(chains [][]*x509.Certificate) *x509.Certificate { - if len(chains) == 0 || len(chains[0]) == 0 { - return nil - } - return chains[0][0] -} - -// plugTLSOCSPPeer will plug the TLS handshake lifecycle for client mTLS connections and Leaf connections -func (s *Server) plugTLSOCSPPeer(config *tlsConfigKind) (*tls.Config, bool, error) { - if config == nil || config.tlsConfig == nil { - return nil, false, errors.New(certidp.ErrUnableToPlugTLSEmptyConfig) - } - kind := config.kind - isSpoke := config.isLeafSpoke - tcOpts := config.tlsOpts - if tcOpts == nil || tcOpts.OCSPPeerConfig == nil || !tcOpts.OCSPPeerConfig.Verify { - return nil, false, nil - } - s.Debugf(certidp.DbgPlugTLSForKind, config.kind) - // peer is a tls client - if kind == kindStringMap[CLIENT] || (kind == kindStringMap[LEAF] && !isSpoke) { - if !tcOpts.Verify { - return nil, false, errors.New(certidp.ErrMTLSRequired) - } - return s.plugClientTLSOCSPPeer(config) - } - // peer is a tls server - if kind == kindStringMap[LEAF] && isSpoke { - return s.plugServerTLSOCSPPeer(config) - } - return nil, false, nil -} - -func (s *Server) plugClientTLSOCSPPeer(config *tlsConfigKind) (*tls.Config, bool, error) { - if config == nil || config.tlsConfig == nil || config.tlsOpts == nil { - return nil, false, errors.New(certidp.ErrUnableToPlugTLSClient) - } - tc := config.tlsConfig - tcOpts := config.tlsOpts - kind := config.kind - if tcOpts.OCSPPeerConfig == nil || !tcOpts.OCSPPeerConfig.Verify { - return tc, false, nil - } - tc.VerifyConnection = func(cs tls.ConnectionState) error { - if !s.tlsClientOCSPValid(cs.VerifiedChains, tcOpts.OCSPPeerConfig) { - s.sendOCSPPeerRejectEvent(kind, peerFromVerifiedChains(cs.VerifiedChains), certidp.MsgTLSClientRejectConnection) - return errors.New(certidp.MsgTLSClientRejectConnection) - } - return nil - } - return tc, true, nil -} - -func (s *Server) plugServerTLSOCSPPeer(config *tlsConfigKind) (*tls.Config, bool, error) { - if config == nil || config.tlsConfig == nil || config.tlsOpts == nil { - return nil, false, errors.New(certidp.ErrUnableToPlugTLSServer) - } - tc := config.tlsConfig - tcOpts := config.tlsOpts - kind := config.kind - if tcOpts.OCSPPeerConfig == nil || !tcOpts.OCSPPeerConfig.Verify { - return tc, false, nil - } - tc.VerifyConnection = func(cs tls.ConnectionState) error { - if !s.tlsServerOCSPValid(cs.VerifiedChains, tcOpts.OCSPPeerConfig) { - s.sendOCSPPeerRejectEvent(kind, peerFromVerifiedChains(cs.VerifiedChains), certidp.MsgTLSServerRejectConnection) - return errors.New(certidp.MsgTLSServerRejectConnection) - } - return nil - } - return tc, true, nil -} - -// tlsServerOCSPValid evaluates verified chains (post successful TLS handshake) against OCSP -// eligibility. A verified chain is considered OCSP Valid if either none of the links are -// OCSP eligible, or current "good" responses from the CA can be obtained for each eligible link. -// Upon first OCSP Valid chain found, the Server is deemed OCSP Valid. If none of the chains are -// OCSP Valid, the Server is deemed OCSP Invalid. A verified self-signed certificate (chain length 1) -// is also considered OCSP Valid. -func (s *Server) tlsServerOCSPValid(chains [][]*x509.Certificate, opts *certidp.OCSPPeerConfig) bool { - s.Debugf(certidp.DbgNumServerChains, len(chains)) - return s.peerOCSPValid(chains, opts) -} - -// tlsClientOCSPValid evaluates verified chains (post successful TLS handshake) against OCSP -// eligibility. A verified chain is considered OCSP Valid if either none of the links are -// OCSP eligible, or current "good" responses from the CA can be obtained for each eligible link. -// Upon first OCSP Valid chain found, the Client is deemed OCSP Valid. If none of the chains are -// OCSP Valid, the Client is deemed OCSP Invalid. A verified self-signed certificate (chain length 1) -// is also considered OCSP Valid. -func (s *Server) tlsClientOCSPValid(chains [][]*x509.Certificate, opts *certidp.OCSPPeerConfig) bool { - s.Debugf(certidp.DbgNumClientChains, len(chains)) - return s.peerOCSPValid(chains, opts) -} - -func (s *Server) peerOCSPValid(chains [][]*x509.Certificate, opts *certidp.OCSPPeerConfig) bool { - peer := peerFromVerifiedChains(chains) - if peer == nil { - s.Errorf(certidp.ErrPeerEmptyAutoReject) - return false - } - for ci, chain := range chains { - s.Debugf(certidp.DbgLinksInChain, ci, len(chain)) - // Self-signed certificate is Client OCSP Valid (no CA) - if len(chain) == 1 { - s.Debugf(certidp.DbgSelfSignedValid, ci) - return true - } - // Check if any of the links in the chain are OCSP eligible - chainEligible := false - var eligibleLinks []*certidp.ChainLink - // Iterate over links skipping the root cert which is not OCSP eligible (self == issuer) - for linkPos := 0; linkPos < len(chain)-1; linkPos++ { - cert := chain[linkPos] - link := &certidp.ChainLink{ - Leaf: cert, - } - if certidp.CertOCSPEligible(link) { - chainEligible = true - issuerCert := certidp.GetLeafIssuerCert(chain, linkPos) - if issuerCert == nil { - // unexpected chain condition, reject Client as OCSP Invalid - return false - } - link.Issuer = issuerCert - eligibleLinks = append(eligibleLinks, link) - } - } - // A trust-store verified chain that is not OCSP eligible is always OCSP Valid - if !chainEligible { - s.Debugf(certidp.DbgValidNonOCSPChain, ci) - return true - } - s.Debugf(certidp.DbgChainIsOCSPEligible, ci, len(eligibleLinks)) - // Chain has at least one OCSP eligible link, so check each eligible link; - // any link with a !good OCSP response chain OCSP Invalid - chainValid := true - for _, link := range eligibleLinks { - // if option selected, good could reflect either ocsp.Good or ocsp.Unknown - if badReason, good := s.certOCSPGood(link, opts); !good { - s.Debugf(badReason) - s.sendOCSPPeerChainlinkInvalidEvent(peer, link.Leaf, badReason) - chainValid = false - break - } - } - if chainValid { - s.Debugf(certidp.DbgChainIsOCSPValid, ci) - return true - } - } - // If we are here, all chains had OCSP eligible links, but none of the chains achieved OCSP valid - s.Debugf(certidp.DbgNoOCSPValidChains) - return false -} - -func (s *Server) certOCSPGood(link *certidp.ChainLink, opts *certidp.OCSPPeerConfig) (string, bool) { - if link == nil || link.Leaf == nil || link.Issuer == nil || link.OCSPWebEndpoints == nil || len(*link.OCSPWebEndpoints) < 1 { - return "Empty chainlink found", false - } - var err error - sLogs := &certidp.Log{ - Debugf: s.Debugf, - Noticef: s.Noticef, - Warnf: s.Warnf, - Errorf: s.Errorf, - Tracef: s.Tracef, - } - fingerprint := certidp.GenerateFingerprint(link.Leaf) - // Used for debug/operator only, not match - subj := certidp.GetSubjectDNForm(link.Leaf) - var rawResp []byte - var ocspr *ocsp.Response - var useCachedResp bool - var rc = s.ocsprc - var cachedRevocation bool - // Check our cache before calling out to the CA OCSP responder - s.Debugf(certidp.DbgCheckingCacheForCert, subj, fingerprint) - if rawResp = rc.Get(fingerprint, sLogs); len(rawResp) > 0 { - // Signature validation of CA's OCSP response occurs in ParseResponse - ocspr, err = ocsp.ParseResponse(rawResp, link.Issuer) - if err == nil && ocspr != nil { - // Check if OCSP Response delegation present and if so is valid - if !certidp.ValidDelegationCheck(link.Issuer, ocspr) { - // Invalid delegation was already in cache, purge it and don't use it - s.Debugf(certidp.MsgCachedOCSPResponseInvalid, subj) - rc.Delete(fingerprint, true, sLogs) - goto AFTERCACHE - } - if certidp.OCSPResponseCurrent(ocspr, opts, sLogs) { - s.Debugf(certidp.DbgCurrentResponseCached, certidp.GetStatusAssertionStr(ocspr.Status)) - useCachedResp = true - } else { - // Cached response is not current, delete it and tidy runtime stats to reflect a miss; - // if preserve_revoked is enabled, the cache will not delete the cached response - s.Debugf(certidp.DbgExpiredResponseCached, certidp.GetStatusAssertionStr(ocspr.Status)) - rc.Delete(fingerprint, true, sLogs) - } - // Regardless of currency, record a cached revocation found in case AllowWhenCAUnreachable is set - if ocspr.Status == ocsp.Revoked { - cachedRevocation = true - } - } else { - // Bogus cached assertion, purge it and don't use it - s.Debugf(certidp.MsgCachedOCSPResponseInvalid, subj, fingerprint) - rc.Delete(fingerprint, true, sLogs) - goto AFTERCACHE - } - } -AFTERCACHE: - if !useCachedResp { - // CA OCSP responder callout needed - rawResp, err = certidp.FetchOCSPResponse(link, opts, sLogs) - if err != nil || rawResp == nil || len(rawResp) == 0 { - s.Warnf(certidp.ErrCAResponderCalloutFail, subj, err) - if opts.WarnOnly { - s.Warnf(certidp.MsgAllowWarnOnlyOccurred, subj) - return _EMPTY_, true - } - if opts.AllowWhenCAUnreachable && !cachedRevocation { - // Link has no cached history of revocation, so allow it to pass - s.Warnf(certidp.MsgAllowWhenCAUnreachableOccurred, subj) - return _EMPTY_, true - } else if opts.AllowWhenCAUnreachable { - // Link has cached but expired revocation so reject when CA is unreachable - s.Warnf(certidp.MsgAllowWhenCAUnreachableOccurredCachedRevoke, subj) - } - return certidp.MsgFailedOCSPResponseFetch, false - } - // Signature validation of CA's OCSP response occurs in ParseResponse - ocspr, err = ocsp.ParseResponse(rawResp, link.Issuer) - if err == nil && ocspr != nil { - // Check if OCSP Response delegation present and if so is valid - if !certidp.ValidDelegationCheck(link.Issuer, ocspr) { - s.Warnf(certidp.MsgOCSPResponseDelegationInvalid, subj) - if opts.WarnOnly { - // Can't use bogus assertion, but warn-only set so allow link to pass - s.Warnf(certidp.MsgAllowWarnOnlyOccurred, subj) - return _EMPTY_, true - } - return fmt.Sprintf(certidp.MsgOCSPResponseDelegationInvalid, subj), false - } - if !certidp.OCSPResponseCurrent(ocspr, opts, sLogs) { - s.Warnf(certidp.ErrNewCAResponseNotCurrent, subj) - if opts.WarnOnly { - // Can't use non-effective assertion, but warn-only set so allow link to pass - s.Warnf(certidp.MsgAllowWarnOnlyOccurred, subj) - return _EMPTY_, true - } - return certidp.MsgOCSPResponseNotEffective, false - } - } else { - s.Errorf(certidp.ErrCAResponseParseFailed, subj, err) - if opts.WarnOnly { - // Can't use bogus assertion, but warn-only set so allow link to pass - s.Warnf(certidp.MsgAllowWarnOnlyOccurred, subj) - return _EMPTY_, true - } - return certidp.MsgFailedOCSPResponseParse, false - } - // cache the valid fetched CA OCSP Response - rc.Put(fingerprint, ocspr, subj, sLogs) - } - - // Whether through valid cache response available or newly fetched valid response, now check the status - if ocspr.Status == ocsp.Revoked || (ocspr.Status == ocsp.Unknown && !opts.UnknownIsGood) { - s.Warnf(certidp.ErrOCSPInvalidPeerLink, subj, certidp.GetStatusAssertionStr(ocspr.Status)) - if opts.WarnOnly { - s.Warnf(certidp.MsgAllowWarnOnlyOccurred, subj) - return _EMPTY_, true - } - return fmt.Sprintf(certidp.MsgOCSPResponseInvalidStatus, certidp.GetStatusAssertionStr(ocspr.Status)), false - } - s.Debugf(certidp.DbgOCSPValidPeerLink, subj) - return _EMPTY_, true -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ocsp_responsecache.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ocsp_responsecache.go deleted file mode 100644 index c3848129..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ocsp_responsecache.go +++ /dev/null @@ -1,636 +0,0 @@ -// Copyright 2023-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "path" - "path/filepath" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/klauspost/compress/s2" - "golang.org/x/crypto/ocsp" - - "github.com/nats-io/nats-server/v2/server/certidp" -) - -const ( - OCSPResponseCacheDefaultDir = "_rc_" - OCSPResponseCacheDefaultFilename = "cache.json" - OCSPResponseCacheDefaultTempFilePrefix = "ocsprc-*" - OCSPResponseCacheMinimumSaveInterval = 1 * time.Second - OCSPResponseCacheDefaultSaveInterval = 5 * time.Minute -) - -type OCSPResponseCacheType int - -const ( - NONE OCSPResponseCacheType = iota + 1 - LOCAL -) - -var OCSPResponseCacheTypeMap = map[string]OCSPResponseCacheType{ - "none": NONE, - "local": LOCAL, -} - -type OCSPResponseCacheConfig struct { - Type OCSPResponseCacheType - LocalStore string - PreserveRevoked bool - SaveInterval float64 -} - -func NewOCSPResponseCacheConfig() *OCSPResponseCacheConfig { - return &OCSPResponseCacheConfig{ - Type: LOCAL, - LocalStore: OCSPResponseCacheDefaultDir, - PreserveRevoked: false, - SaveInterval: OCSPResponseCacheDefaultSaveInterval.Seconds(), - } -} - -type OCSPResponseCacheStats struct { - Responses int64 `json:"size"` - Hits int64 `json:"hits"` - Misses int64 `json:"misses"` - Revokes int64 `json:"revokes"` - Goods int64 `json:"goods"` - Unknowns int64 `json:"unknowns"` -} - -type OCSPResponseCacheItem struct { - Subject string `json:"subject,omitempty"` - CachedAt time.Time `json:"cached_at"` - RespStatus certidp.StatusAssertion `json:"resp_status"` - RespExpires time.Time `json:"resp_expires,omitempty"` - Resp []byte `json:"resp"` -} - -type OCSPResponseCache interface { - Put(key string, resp *ocsp.Response, subj string, log *certidp.Log) - Get(key string, log *certidp.Log) []byte - Delete(key string, miss bool, log *certidp.Log) - Type() string - Start(s *Server) - Stop(s *Server) - Online() bool - Config() *OCSPResponseCacheConfig - Stats() *OCSPResponseCacheStats -} - -// NoOpCache is a no-op implementation of OCSPResponseCache -type NoOpCache struct { - config *OCSPResponseCacheConfig - stats *OCSPResponseCacheStats - online bool - mu *sync.RWMutex -} - -func (c *NoOpCache) Put(_ string, _ *ocsp.Response, _ string, _ *certidp.Log) {} - -func (c *NoOpCache) Get(_ string, _ *certidp.Log) []byte { - return nil -} - -func (c *NoOpCache) Delete(_ string, _ bool, _ *certidp.Log) {} - -func (c *NoOpCache) Start(_ *Server) { - c.mu.Lock() - defer c.mu.Unlock() - c.stats = &OCSPResponseCacheStats{} - c.online = true -} - -func (c *NoOpCache) Stop(_ *Server) { - c.mu.Lock() - defer c.mu.Unlock() - c.online = false -} - -func (c *NoOpCache) Online() bool { - c.mu.RLock() - defer c.mu.RUnlock() - return c.online -} - -func (c *NoOpCache) Type() string { - c.mu.RLock() - defer c.mu.RUnlock() - return "none" -} - -func (c *NoOpCache) Config() *OCSPResponseCacheConfig { - c.mu.RLock() - defer c.mu.RUnlock() - return c.config -} - -func (c *NoOpCache) Stats() *OCSPResponseCacheStats { - c.mu.RLock() - defer c.mu.RUnlock() - return c.stats -} - -// LocalCache is a local file implementation of OCSPResponseCache -type LocalCache struct { - config *OCSPResponseCacheConfig - stats *OCSPResponseCacheStats - online bool - cache map[string]OCSPResponseCacheItem - mu *sync.RWMutex - saveInterval time.Duration - dirty bool - timer *time.Timer -} - -// Put captures a CA OCSP response to the OCSP peer cache indexed by response fingerprint (a hash) -func (c *LocalCache) Put(key string, caResp *ocsp.Response, subj string, log *certidp.Log) { - c.mu.RLock() - if !c.online || caResp == nil || key == "" { - c.mu.RUnlock() - return - } - c.mu.RUnlock() - log.Debugf(certidp.DbgCachingResponse, subj, key) - rawC, err := c.Compress(caResp.Raw) - if err != nil { - log.Errorf(certidp.ErrResponseCompressFail, key, err) - return - } - log.Debugf(certidp.DbgAchievedCompression, float64(len(rawC))/float64(len(caResp.Raw))) - c.mu.Lock() - defer c.mu.Unlock() - // check if we are replacing and do stats - item, ok := c.cache[key] - if ok { - c.adjustStats(-1, item.RespStatus) - } - item = OCSPResponseCacheItem{ - Subject: subj, - CachedAt: time.Now().UTC().Round(time.Second), - RespStatus: certidp.StatusAssertionIntToVal[caResp.Status], - RespExpires: caResp.NextUpdate, - Resp: rawC, - } - c.cache[key] = item - c.adjustStats(1, item.RespStatus) - c.dirty = true -} - -// Get returns a CA OCSP response from the OCSP peer cache matching the response fingerprint (a hash) -func (c *LocalCache) Get(key string, log *certidp.Log) []byte { - c.mu.RLock() - defer c.mu.RUnlock() - if !c.online || key == "" { - return nil - } - val, ok := c.cache[key] - if ok { - atomic.AddInt64(&c.stats.Hits, 1) - log.Debugf(certidp.DbgCacheHit, key) - } else { - atomic.AddInt64(&c.stats.Misses, 1) - log.Debugf(certidp.DbgCacheMiss, key) - return nil - } - resp, err := c.Decompress(val.Resp) - if err != nil { - log.Errorf(certidp.ErrResponseDecompressFail, key, err) - return nil - } - return resp -} - -func (c *LocalCache) adjustStatsHitToMiss() { - atomic.AddInt64(&c.stats.Misses, 1) - atomic.AddInt64(&c.stats.Hits, -1) -} - -func (c *LocalCache) adjustStats(delta int64, rs certidp.StatusAssertion) { - if delta == 0 { - return - } - atomic.AddInt64(&c.stats.Responses, delta) - switch rs { - case ocsp.Good: - atomic.AddInt64(&c.stats.Goods, delta) - case ocsp.Revoked: - atomic.AddInt64(&c.stats.Revokes, delta) - case ocsp.Unknown: - atomic.AddInt64(&c.stats.Unknowns, delta) - } -} - -// Delete removes a CA OCSP response from the OCSP peer cache matching the response fingerprint (a hash) -func (c *LocalCache) Delete(key string, wasMiss bool, log *certidp.Log) { - c.mu.Lock() - defer c.mu.Unlock() - if !c.online || key == "" || c.config == nil { - return - } - item, ok := c.cache[key] - if !ok { - return - } - if item.RespStatus == ocsp.Revoked && c.config.PreserveRevoked { - log.Debugf(certidp.DbgPreservedRevocation, key) - if wasMiss { - c.adjustStatsHitToMiss() - } - return - } - log.Debugf(certidp.DbgDeletingCacheResponse, key) - delete(c.cache, key) - c.adjustStats(-1, item.RespStatus) - if wasMiss { - c.adjustStatsHitToMiss() - } - c.dirty = true -} - -// Start initializes the configured OCSP peer cache, loads a saved cache from disk (if present), and initializes runtime statistics -func (c *LocalCache) Start(s *Server) { - s.Debugf(certidp.DbgStartingCache) - c.loadCache(s) - c.initStats() - c.mu.Lock() - c.online = true - c.mu.Unlock() -} - -func (c *LocalCache) Stop(s *Server) { - c.mu.Lock() - s.Debugf(certidp.DbgStoppingCache) - c.online = false - c.timer.Stop() - c.mu.Unlock() - c.saveCache(s) -} - -func (c *LocalCache) Online() bool { - c.mu.RLock() - defer c.mu.RUnlock() - return c.online -} - -func (c *LocalCache) Type() string { - c.mu.RLock() - defer c.mu.RUnlock() - return "local" -} - -func (c *LocalCache) Config() *OCSPResponseCacheConfig { - c.mu.RLock() - defer c.mu.RUnlock() - return c.config -} - -func (c *LocalCache) Stats() *OCSPResponseCacheStats { - c.mu.RLock() - defer c.mu.RUnlock() - if c.stats == nil { - return nil - } - stats := OCSPResponseCacheStats{ - Responses: c.stats.Responses, - Hits: c.stats.Hits, - Misses: c.stats.Misses, - Revokes: c.stats.Revokes, - Goods: c.stats.Goods, - Unknowns: c.stats.Unknowns, - } - return &stats -} - -func (c *LocalCache) initStats() { - c.mu.Lock() - defer c.mu.Unlock() - c.stats = &OCSPResponseCacheStats{} - c.stats.Hits = 0 - c.stats.Misses = 0 - c.stats.Responses = int64(len(c.cache)) - for _, resp := range c.cache { - switch resp.RespStatus { - case ocsp.Good: - c.stats.Goods++ - case ocsp.Revoked: - c.stats.Revokes++ - case ocsp.Unknown: - c.stats.Unknowns++ - } - } -} - -func (c *LocalCache) Compress(buf []byte) ([]byte, error) { - bodyLen := int64(len(buf)) - var output bytes.Buffer - writer := s2.NewWriter(&output) - input := bytes.NewReader(buf[:bodyLen]) - if n, err := io.CopyN(writer, input, bodyLen); err != nil { - return nil, fmt.Errorf(certidp.ErrCannotWriteCompressed, err) - } else if n != bodyLen { - return nil, fmt.Errorf(certidp.ErrTruncatedWrite, n, bodyLen) - } - if err := writer.Close(); err != nil { - return nil, fmt.Errorf(certidp.ErrCannotCloseWriter, err) - } - return output.Bytes(), nil -} - -func (c *LocalCache) Decompress(buf []byte) ([]byte, error) { - bodyLen := int64(len(buf)) - input := bytes.NewReader(buf[:bodyLen]) - reader := io.NopCloser(s2.NewReader(input)) - output, err := io.ReadAll(reader) - if err != nil { - return nil, fmt.Errorf(certidp.ErrCannotReadCompressed, err) - } - return output, reader.Close() -} - -func (c *LocalCache) loadCache(s *Server) { - d := s.opts.OCSPCacheConfig.LocalStore - if d == _EMPTY_ { - d = OCSPResponseCacheDefaultDir - } - f := OCSPResponseCacheDefaultFilename - store, err := filepath.Abs(path.Join(d, f)) - if err != nil { - s.Errorf(certidp.ErrLoadCacheFail, err) - return - } - s.Debugf(certidp.DbgLoadingCache, store) - c.mu.Lock() - defer c.mu.Unlock() - c.cache = make(map[string]OCSPResponseCacheItem) - dat, err := os.ReadFile(store) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - s.Debugf(certidp.DbgNoCacheFound) - } else { - s.Warnf(certidp.ErrLoadCacheFail, err) - } - return - } - err = json.Unmarshal(dat, &c.cache) - if err != nil { - // make sure clean cache - c.cache = make(map[string]OCSPResponseCacheItem) - s.Warnf(certidp.ErrLoadCacheFail, err) - c.dirty = true - return - } - c.dirty = false -} - -func (c *LocalCache) saveCache(s *Server) { - c.mu.RLock() - dirty := c.dirty - c.mu.RUnlock() - if !dirty { - return - } - s.Debugf(certidp.DbgCacheDirtySave) - var d string - if c.config.LocalStore != _EMPTY_ { - d = c.config.LocalStore - } else { - d = OCSPResponseCacheDefaultDir - } - f := OCSPResponseCacheDefaultFilename - store, err := filepath.Abs(path.Join(d, f)) - if err != nil { - s.Errorf(certidp.ErrSaveCacheFail, err) - return - } - s.Debugf(certidp.DbgSavingCache, store) - if _, err := os.Stat(d); os.IsNotExist(err) { - err = os.Mkdir(d, defaultDirPerms) - if err != nil { - s.Errorf(certidp.ErrSaveCacheFail, err) - return - } - } - tmp, err := os.CreateTemp(d, OCSPResponseCacheDefaultTempFilePrefix) - if err != nil { - s.Errorf(certidp.ErrSaveCacheFail, err) - return - } - defer func() { - tmp.Close() - os.Remove(tmp.Name()) - }() // clean up any temp files - - // RW lock here because we're going to snapshot the cache to disk and mark as clean if successful - c.mu.Lock() - defer c.mu.Unlock() - dat, err := json.MarshalIndent(c.cache, "", " ") - if err != nil { - s.Errorf(certidp.ErrSaveCacheFail, err) - return - } - cacheSize, err := tmp.Write(dat) - if err != nil { - s.Errorf(certidp.ErrSaveCacheFail, err) - return - } - err = tmp.Sync() - if err != nil { - s.Errorf(certidp.ErrSaveCacheFail, err) - return - } - err = tmp.Close() - if err != nil { - s.Errorf(certidp.ErrSaveCacheFail, err) - return - } - // do the final swap and overwrite any old saved peer cache - err = os.Rename(tmp.Name(), store) - if err != nil { - s.Errorf(certidp.ErrSaveCacheFail, err) - return - } - c.dirty = false - s.Debugf(certidp.DbgCacheSaved, cacheSize) -} - -var OCSPResponseCacheUsage = ` -You may enable OCSP peer response cacheing at server configuration root level: - -(If no TLS blocks are configured with OCSP peer verification, ocsp_cache is ignored.) - - ... - # short form enables with defaults - ocsp_cache: true - - # if false or undefined and one or more TLS blocks are configured with OCSP peer verification, "none" is implied - - # long form includes settable options - ocsp_cache { - - # Cache type (default local) - type: local - - # Cache file directory for local-type cache (default _rc_ in current working directory) - local_store: "_rc_" - - # Ignore cache deletes if cached OCSP response is Revoked status (default false) - preserve_revoked: false - - # For local store, interval to save in-memory cache to disk in seconds (default 300 seconds, minimum 1 second) - save_interval: 300 - } - ... - -Note: Cache of server's own OCSP response (staple) is enabled using the 'ocsp' configuration option. -` - -func (s *Server) initOCSPResponseCache() { - // No mTLS OCSP or Leaf OCSP enablements, so no need to init cache - s.mu.RLock() - if !s.ocspPeerVerify { - s.mu.RUnlock() - return - } - s.mu.RUnlock() - so := s.getOpts() - if so.OCSPCacheConfig == nil { - so.OCSPCacheConfig = NewOCSPResponseCacheConfig() - } - var cc = so.OCSPCacheConfig - s.mu.Lock() - defer s.mu.Unlock() - switch cc.Type { - case NONE: - s.ocsprc = &NoOpCache{config: cc, online: true, mu: &sync.RWMutex{}} - case LOCAL: - c := &LocalCache{ - config: cc, - online: false, - cache: make(map[string]OCSPResponseCacheItem), - mu: &sync.RWMutex{}, - dirty: false, - } - c.saveInterval = time.Duration(cc.SaveInterval) * time.Second - c.timer = time.AfterFunc(c.saveInterval, func() { - s.Debugf(certidp.DbgCacheSaveTimerExpired) - c.saveCache(s) - c.timer.Reset(c.saveInterval) - }) - s.ocsprc = c - default: - s.Fatalf(certidp.ErrBadCacheTypeConfig, cc.Type) - } -} - -func (s *Server) startOCSPResponseCache() { - // No mTLS OCSP or Leaf OCSP enablements, so no need to start cache - s.mu.RLock() - if !s.ocspPeerVerify || s.ocsprc == nil { - s.mu.RUnlock() - return - } - s.mu.RUnlock() - - // Could be heavier operation depending on cache implementation - s.ocsprc.Start(s) - if s.ocsprc.Online() { - s.Noticef(certidp.MsgCacheOnline, s.ocsprc.Type()) - } else { - s.Noticef(certidp.MsgCacheOffline, s.ocsprc.Type()) - } -} - -func (s *Server) stopOCSPResponseCache() { - s.mu.RLock() - if s.ocsprc == nil { - s.mu.RUnlock() - return - } - s.mu.RUnlock() - s.ocsprc.Stop(s) -} - -func parseOCSPResponseCache(v any) (pcfg *OCSPResponseCacheConfig, retError error) { - var lt token - defer convertPanicToError(<, &retError) - tk, v := unwrapValue(v, <) - cm, ok := v.(map[string]any) - if !ok { - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrIllegalCacheOptsConfig, v)} - } - pcfg = NewOCSPResponseCacheConfig() - retError = nil - for mk, mv := range cm { - // Again, unwrap token value if line check is required. - tk, mv = unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "type": - cache, ok := mv.(string) - if !ok { - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingCacheOptFieldGeneric, mk)} - } - cacheType, exists := OCSPResponseCacheTypeMap[strings.ToLower(cache)] - if !exists { - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrUnknownCacheType, cache)} - } - pcfg.Type = cacheType - case "local_store": - store, ok := mv.(string) - if !ok { - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingCacheOptFieldGeneric, mk)} - } - pcfg.LocalStore = store - case "preserve_revoked": - preserve, ok := mv.(bool) - if !ok { - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingCacheOptFieldGeneric, mk)} - } - pcfg.PreserveRevoked = preserve - case "save_interval": - at := float64(0) - switch mv := mv.(type) { - case int64: - at = float64(mv) - case float64: - at = mv - case string: - d, err := time.ParseDuration(mv) - if err != nil { - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingPeerOptFieldTypeConversion, err)} - } - at = d.Seconds() - default: - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingCacheOptFieldTypeConversion, "unexpected type")} - } - si := time.Duration(at) * time.Second - if si < OCSPResponseCacheMinimumSaveInterval { - si = OCSPResponseCacheMinimumSaveInterval - } - pcfg.SaveInterval = si.Seconds() - default: - return nil, &configErr{tk, fmt.Sprintf(certidp.ErrParsingCacheOptFieldGeneric, mk)} - } - } - return pcfg, nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/opts.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/opts.go deleted file mode 100644 index e6da09c6..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/opts.go +++ /dev/null @@ -1,6019 +0,0 @@ -// Copyright 2012-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "context" - "crypto/tls" - "crypto/x509" - "errors" - "flag" - "fmt" - "math" - "net" - "net/url" - "os" - "path" - "path/filepath" - "regexp" - "runtime" - "strconv" - "strings" - "sync/atomic" - "time" - - "github.com/nats-io/jwt/v2" - "github.com/nats-io/nats-server/v2/conf" - "github.com/nats-io/nats-server/v2/server/certidp" - "github.com/nats-io/nats-server/v2/server/certstore" - "github.com/nats-io/nkeys" -) - -var allowUnknownTopLevelField = int32(0) - -// NoErrOnUnknownFields can be used to change the behavior the processing -// of a configuration file. By default, an error is reported if unknown -// fields are found. If `noError` is set to true, no error will be reported -// if top-level unknown fields are found. -func NoErrOnUnknownFields(noError bool) { - var val int32 - if noError { - val = int32(1) - } - atomic.StoreInt32(&allowUnknownTopLevelField, val) -} - -// PinnedCertSet is a set of lower case hex-encoded sha256 of DER encoded SubjectPublicKeyInfo -type PinnedCertSet map[string]struct{} - -// ClusterOpts are options for clusters. -// NOTE: This structure is no longer used for monitoring endpoints -// and json tags are deprecated and may be removed in the future. -type ClusterOpts struct { - Name string `json:"-"` - Host string `json:"addr,omitempty"` - Port int `json:"cluster_port,omitempty"` - Username string `json:"-"` - Password string `json:"-"` - AuthTimeout float64 `json:"auth_timeout,omitempty"` - Permissions *RoutePermissions `json:"-"` - TLSTimeout float64 `json:"-"` - TLSConfig *tls.Config `json:"-"` - TLSMap bool `json:"-"` - TLSCheckKnownURLs bool `json:"-"` - TLSPinnedCerts PinnedCertSet `json:"-"` - ListenStr string `json:"-"` - Advertise string `json:"-"` - NoAdvertise bool `json:"-"` - ConnectRetries int `json:"-"` - PoolSize int `json:"-"` - PinnedAccounts []string `json:"-"` - Compression CompressionOpts `json:"-"` - PingInterval time.Duration `json:"-"` - MaxPingsOut int `json:"-"` - - // Not exported (used in tests) - resolver netResolver - // Snapshot of configured TLS options. - tlsConfigOpts *TLSConfigOpts -} - -// CompressionOpts defines the compression mode and optional configuration. -type CompressionOpts struct { - Mode string - // If `Mode` is set to CompressionS2Auto, RTTThresholds provides the - // thresholds at which the compression level will go from - // CompressionS2Uncompressed to CompressionS2Fast, CompressionS2Better - // or CompressionS2Best. If a given level is not desired, specify 0 - // for this slot. For instance, the slice []{0, 10ms, 20ms} means that - // for any RTT up to 10ms included the compression level will be - // CompressionS2Fast, then from ]10ms..20ms], the level will be selected - // as CompressionS2Better. Anything above 20ms will result in picking - // the CompressionS2Best compression level. - RTTThresholds []time.Duration -} - -// GatewayOpts are options for gateways. -// NOTE: This structure is no longer used for monitoring endpoints -// and json tags are deprecated and may be removed in the future. -type GatewayOpts struct { - Name string `json:"name"` - Host string `json:"addr,omitempty"` - Port int `json:"port,omitempty"` - Username string `json:"-"` - Password string `json:"-"` - AuthTimeout float64 `json:"auth_timeout,omitempty"` - TLSConfig *tls.Config `json:"-"` - TLSTimeout float64 `json:"tls_timeout,omitempty"` - TLSMap bool `json:"-"` - TLSCheckKnownURLs bool `json:"-"` - TLSPinnedCerts PinnedCertSet `json:"-"` - Advertise string `json:"advertise,omitempty"` - ConnectRetries int `json:"connect_retries,omitempty"` - Gateways []*RemoteGatewayOpts `json:"gateways,omitempty"` - RejectUnknown bool `json:"reject_unknown,omitempty"` // config got renamed to reject_unknown_cluster - - // Not exported, for tests. - resolver netResolver - sendQSubsBufSize int - - // Snapshot of configured TLS options. - tlsConfigOpts *TLSConfigOpts -} - -// RemoteGatewayOpts are options for connecting to a remote gateway -// NOTE: This structure is no longer used for monitoring endpoints -// and json tags are deprecated and may be removed in the future. -type RemoteGatewayOpts struct { - Name string `json:"name"` - TLSConfig *tls.Config `json:"-"` - TLSTimeout float64 `json:"tls_timeout,omitempty"` - URLs []*url.URL `json:"urls,omitempty"` - tlsConfigOpts *TLSConfigOpts -} - -// LeafNodeOpts are options for a given server to accept leaf node connections and/or connect to a remote cluster. -type LeafNodeOpts struct { - Host string `json:"addr,omitempty"` - Port int `json:"port,omitempty"` - Username string `json:"-"` - Password string `json:"-"` - Nkey string `json:"-"` - Account string `json:"-"` - Users []*User `json:"-"` - AuthTimeout float64 `json:"auth_timeout,omitempty"` - TLSConfig *tls.Config `json:"-"` - TLSTimeout float64 `json:"tls_timeout,omitempty"` - TLSMap bool `json:"-"` - TLSPinnedCerts PinnedCertSet `json:"-"` - // When set to true, the server will perform the TLS handshake before - // sending the INFO protocol. For remote leafnodes that are not configured - // with a similar option, their connection will fail with some sort - // of timeout or EOF error since they are expecting to receive an - // INFO protocol first. - TLSHandshakeFirst bool `json:"-"` - // If TLSHandshakeFirst is true and this value is strictly positive, - // the server will wait for that amount of time for the TLS handshake - // to start before falling back to previous behavior of sending the - // INFO protocol first. It allows for a mix of newer remote leafnodes - // that can require a TLS handshake first, and older that can't. - TLSHandshakeFirstFallback time.Duration `json:"-"` - Advertise string `json:"-"` - NoAdvertise bool `json:"-"` - ReconnectInterval time.Duration `json:"-"` - - // Compression options - Compression CompressionOpts `json:"-"` - - // For solicited connections to other clusters/superclusters. - Remotes []*RemoteLeafOpts `json:"remotes,omitempty"` - - // This is the minimum version that is accepted for remote connections. - // Note that since the server version in the CONNECT protocol was added - // only starting at v2.8.0, any version below that will be rejected - // (since empty version string in CONNECT would fail the "version at - // least" test). - MinVersion string - - // Not exported, for tests. - resolver netResolver - dialTimeout time.Duration - connDelay time.Duration - - // Snapshot of configured TLS options. - tlsConfigOpts *TLSConfigOpts -} - -// SignatureHandler is used to sign a nonce from the server while -// authenticating with Nkeys. The callback should sign the nonce and -// return the JWT and the raw signature. -type SignatureHandler func([]byte) (string, []byte, error) - -// RemoteLeafOpts are options for connecting to a remote server as a leaf node. -type RemoteLeafOpts struct { - LocalAccount string `json:"local_account,omitempty"` - NoRandomize bool `json:"-"` - URLs []*url.URL `json:"urls,omitempty"` - Credentials string `json:"-"` - Nkey string `json:"-"` - SignatureCB SignatureHandler `json:"-"` - TLS bool `json:"-"` - TLSConfig *tls.Config `json:"-"` - TLSTimeout float64 `json:"tls_timeout,omitempty"` - TLSHandshakeFirst bool `json:"-"` - Hub bool `json:"hub,omitempty"` - DenyImports []string `json:"-"` - DenyExports []string `json:"-"` - - // FirstInfoTimeout is the amount of time the server will wait for the - // initial INFO protocol from the remote server before closing the - // connection. - FirstInfoTimeout time.Duration `json:"-"` - - // Compression options for this remote. Each remote could have a different - // setting and also be different from the LeafNode options. - Compression CompressionOpts `json:"-"` - - // When an URL has the "ws" (or "wss") scheme, then the server will initiate the - // connection as a websocket connection. By default, the websocket frames will be - // masked (as if this server was a websocket client to the remote server). The - // NoMasking option will change this behavior and will send umasked frames. - Websocket struct { - Compression bool `json:"-"` - NoMasking bool `json:"-"` - } - - tlsConfigOpts *TLSConfigOpts - - // If we are clustered and our local account has JetStream, if apps are accessing - // a stream or consumer leader through this LN and it gets dropped, the apps will - // not be able to work. This tells the system to migrate the leaders away from this server. - // This only changes leader for R>1 assets. - JetStreamClusterMigrate bool `json:"jetstream_cluster_migrate,omitempty"` - - // If JetStreamClusterMigrate is set to true, this is the time after which the leader - // will be migrated away from this server if still disconnected. - JetStreamClusterMigrateDelay time.Duration `json:"jetstream_cluster_migrate_delay,omitempty"` -} - -type JSLimitOpts struct { - MaxRequestBatch int `json:"max_request_batch,omitempty"` - MaxAckPending int `json:"max_ack_pending,omitempty"` - MaxHAAssets int `json:"max_ha_assets,omitempty"` - Duplicates time.Duration `json:"max_duplicate_window,omitempty"` -} - -type JSTpmOpts struct { - KeysFile string - KeyPassword string - SrkPassword string - Pcr int -} - -// AuthCallout option used to map external AuthN to NATS based AuthZ. -type AuthCallout struct { - // Must be a public account Nkey. - Issuer string - // Account to be used for sending requests. - Account string - // Users that will bypass auth_callout and be used for the auth service itself. - AuthUsers []string - // XKey is a public xkey for the authorization service. - // This will enable encryption for server requests and the authorization service responses. - XKey string - // AllowedAccounts that will be delegated to the auth service. - // If empty then all accounts will be delegated. - AllowedAccounts []string -} - -// Options block for nats-server. -// NOTE: This structure is no longer used for monitoring endpoints -// and json tags are deprecated and may be removed in the future. -type Options struct { - ConfigFile string `json:"-"` - ServerName string `json:"server_name"` - Host string `json:"addr"` - Port int `json:"port"` - DontListen bool `json:"dont_listen"` - ClientAdvertise string `json:"-"` - Trace bool `json:"-"` - Debug bool `json:"-"` - TraceVerbose bool `json:"-"` - NoLog bool `json:"-"` - NoSigs bool `json:"-"` - NoSublistCache bool `json:"-"` - NoHeaderSupport bool `json:"-"` - DisableShortFirstPing bool `json:"-"` - Logtime bool `json:"-"` - LogtimeUTC bool `json:"-"` - MaxConn int `json:"max_connections"` - MaxSubs int `json:"max_subscriptions,omitempty"` - MaxSubTokens uint8 `json:"-"` - Nkeys []*NkeyUser `json:"-"` - Users []*User `json:"-"` - Accounts []*Account `json:"-"` - NoAuthUser string `json:"-"` - SystemAccount string `json:"-"` - NoSystemAccount bool `json:"-"` - Username string `json:"-"` - Password string `json:"-"` - Authorization string `json:"-"` - AuthCallout *AuthCallout `json:"-"` - PingInterval time.Duration `json:"ping_interval"` - MaxPingsOut int `json:"ping_max"` - HTTPHost string `json:"http_host"` - HTTPPort int `json:"http_port"` - HTTPBasePath string `json:"http_base_path"` - HTTPSPort int `json:"https_port"` - AuthTimeout float64 `json:"auth_timeout"` - MaxControlLine int32 `json:"max_control_line"` - MaxPayload int32 `json:"max_payload"` - MaxPending int64 `json:"max_pending"` - NoFastProducerStall bool `json:"-"` - Cluster ClusterOpts `json:"cluster,omitempty"` - Gateway GatewayOpts `json:"gateway,omitempty"` - LeafNode LeafNodeOpts `json:"leaf,omitempty"` - JetStream bool `json:"jetstream"` - JetStreamStrict bool `json:"-"` - JetStreamMaxMemory int64 `json:"-"` - JetStreamMaxStore int64 `json:"-"` - JetStreamDomain string `json:"-"` - JetStreamExtHint string `json:"-"` - JetStreamKey string `json:"-"` - JetStreamOldKey string `json:"-"` - JetStreamCipher StoreCipher `json:"-"` - JetStreamUniqueTag string - JetStreamLimits JSLimitOpts - JetStreamTpm JSTpmOpts - JetStreamMaxCatchup int64 - JetStreamRequestQueueLimit int64 - StreamMaxBufferedMsgs int `json:"-"` - StreamMaxBufferedSize int64 `json:"-"` - StoreDir string `json:"-"` - SyncInterval time.Duration `json:"-"` - SyncAlways bool `json:"-"` - JsAccDefaultDomain map[string]string `json:"-"` // account to domain name mapping - Websocket WebsocketOpts `json:"-"` - MQTT MQTTOpts `json:"-"` - ProfPort int `json:"-"` - ProfBlockRate int `json:"-"` - PidFile string `json:"-"` - PortsFileDir string `json:"-"` - LogFile string `json:"-"` - LogSizeLimit int64 `json:"-"` - LogMaxFiles int64 `json:"-"` - Syslog bool `json:"-"` - RemoteSyslog string `json:"-"` - Routes []*url.URL `json:"-"` - RoutesStr string `json:"-"` - TLSTimeout float64 `json:"tls_timeout"` - TLS bool `json:"-"` - TLSVerify bool `json:"-"` - TLSMap bool `json:"-"` - TLSCert string `json:"-"` - TLSKey string `json:"-"` - TLSCaCert string `json:"-"` - TLSConfig *tls.Config `json:"-"` - TLSPinnedCerts PinnedCertSet `json:"-"` - TLSRateLimit int64 `json:"-"` - // When set to true, the server will perform the TLS handshake before - // sending the INFO protocol. For clients that are not configured - // with a similar option, their connection will fail with some sort - // of timeout or EOF error since they are expecting to receive an - // INFO protocol first. - TLSHandshakeFirst bool `json:"-"` - // If TLSHandshakeFirst is true and this value is strictly positive, - // the server will wait for that amount of time for the TLS handshake - // to start before falling back to previous behavior of sending the - // INFO protocol first. It allows for a mix of newer clients that can - // require a TLS handshake first, and older clients that can't. - TLSHandshakeFirstFallback time.Duration `json:"-"` - AllowNonTLS bool `json:"-"` - WriteDeadline time.Duration `json:"-"` - MaxClosedClients int `json:"-"` - LameDuckDuration time.Duration `json:"-"` - LameDuckGracePeriod time.Duration `json:"-"` - - // MaxTracedMsgLen is the maximum printable length for traced messages. - MaxTracedMsgLen int `json:"-"` - - // Operating a trusted NATS server - TrustedKeys []string `json:"-"` - TrustedOperators []*jwt.OperatorClaims `json:"-"` - AccountResolver AccountResolver `json:"-"` - AccountResolverTLSConfig *tls.Config `json:"-"` - - // AlwaysEnableNonce will always present a nonce to new connections - // typically used by custom Authentication implementations who embeds - // the server and so not presented as a configuration option - AlwaysEnableNonce bool - - CustomClientAuthentication Authentication `json:"-"` - CustomRouterAuthentication Authentication `json:"-"` - - // CheckConfig configuration file syntax test was successful and exit. - CheckConfig bool `json:"-"` - - // DisableJetStreamBanner will not print the ascii art on startup for JetStream enabled servers - DisableJetStreamBanner bool `json:"-"` - - // ConnectErrorReports specifies the number of failed attempts - // at which point server should report the failure of an initial - // connection to a route, gateway or leaf node. - // See DEFAULT_CONNECT_ERROR_REPORTS for default value. - ConnectErrorReports int - - // ReconnectErrorReports is similar to ConnectErrorReports except - // that this applies to reconnect events. - ReconnectErrorReports int - - // Tags describing the server. They will be included in varz - // and used as a filter criteria for some system requests. - Tags jwt.TagList `json:"-"` - - // OCSPConfig enables OCSP Stapling in the server. - OCSPConfig *OCSPConfig - tlsConfigOpts *TLSConfigOpts - - // private fields, used to know if bool options are explicitly - // defined in config and/or command line params. - inConfig map[string]bool - inCmdLine map[string]bool - - // private fields for operator mode - operatorJWT []string - resolverPreloads map[string]string - resolverPinnedAccounts map[string]struct{} - - // private fields, used for testing - gatewaysSolicitDelay time.Duration - overrideProto int - - // JetStream - maxMemSet bool - maxStoreSet bool - syncSet bool - - // OCSP Cache config enables next-gen cache for OCSP features - OCSPCacheConfig *OCSPResponseCacheConfig - - // Used to mark that we had a top level authorization block. - authBlockDefined bool - - // configDigest represents the state of configuration. - configDigest string -} - -// WebsocketOpts are options for websocket -type WebsocketOpts struct { - // The server will accept websocket client connections on this hostname/IP. - Host string - // The server will accept websocket client connections on this port. - Port int - // The host:port to advertise to websocket clients in the cluster. - Advertise string - - // If no user name is provided when a client connects, will default to the - // matching user from the global list of users in `Options.Users`. - NoAuthUser string - - // Name of the cookie, which if present in WebSocket upgrade headers, - // will be treated as JWT during CONNECT phase as long as - // "jwt" specified in the CONNECT options is missing or empty. - JWTCookie string - - // Name of the cookie, which if present in WebSocket upgrade headers, - // will be treated as Username during CONNECT phase as long as - // "user" specified in the CONNECT options is missing or empty. - UsernameCookie string - - // Name of the cookie, which if present in WebSocket upgrade headers, - // will be treated as Password during CONNECT phase as long as - // "pass" specified in the CONNECT options is missing or empty. - PasswordCookie string - - // Name of the cookie, which if present in WebSocket upgrade headers, - // will be treated as Token during CONNECT phase as long as - // "auth_token" specified in the CONNECT options is missing or empty. - // Note that when this is useful for passing a JWT to an cuth callout - // when the server uses delegated authentication ("operator mode") or - // when using delegated authentication, but the auth callout validates some - // other JWT or string. Note that this does map to an actual server-wide - // "auth_token", note that using it for that purpose is greatly discouraged. - TokenCookie string - - // Authentication section. If anything is configured in this section, - // it will override the authorization configuration of regular clients. - Username string - Password string - Token string - - // Timeout for the authentication process. - AuthTimeout float64 - - // By default the server will enforce the use of TLS. If no TLS configuration - // is provided, you need to explicitly set NoTLS to true to allow the server - // to start without TLS configuration. Note that if a TLS configuration is - // present, this boolean is ignored and the server will run the Websocket - // server with that TLS configuration. - // Running without TLS is less secure since Websocket clients that use bearer - // tokens will send them in clear. So this should not be used in production. - NoTLS bool - - // TLS configuration is required. - TLSConfig *tls.Config - // If true, map certificate values for authentication purposes. - TLSMap bool - - // When present, accepted client certificates (verify/verify_and_map) must be in this list - TLSPinnedCerts PinnedCertSet - - // If true, the Origin header must match the request's host. - SameOrigin bool - - // Only origins in this list will be accepted. If empty and - // SameOrigin is false, any origin is accepted. - AllowedOrigins []string - - // If set to true, the server will negotiate with clients - // if compression can be used. If this is false, no compression - // will be used (both in server and clients) since it has to - // be negotiated between both endpoints - Compression bool - - // Total time allowed for the server to read the client request - // and write the response back to the client. This include the - // time needed for the TLS Handshake. - HandshakeTimeout time.Duration - - // Headers to be added to the upgrade response. - // Useful for adding custom headers like Strict-Transport-Security. - Headers map[string]string - - // Snapshot of configured TLS options. - tlsConfigOpts *TLSConfigOpts -} - -// MQTTOpts are options for MQTT -type MQTTOpts struct { - // The server will accept MQTT client connections on this hostname/IP. - Host string - // The server will accept MQTT client connections on this port. - Port int - - // If no user name is provided when a client connects, will default to the - // matching user from the global list of users in `Options.Users`. - NoAuthUser string - - // Authentication section. If anything is configured in this section, - // it will override the authorization configuration of regular clients. - Username string - Password string - Token string - - // JetStream domain mqtt is supposed to pick up - JsDomain string - - // Number of replicas for MQTT streams. - // Negative or 0 value means that the server(s) will pick a replica - // number based on the known size of the cluster (but capped at 3). - // Note that if an account was already connected, the stream's replica - // count is not modified. Use the NATS CLI to update the count if desired. - StreamReplicas int - - // Number of replicas for MQTT consumers. - // Negative or 0 value means that there is no override and the consumer - // will have the same replica factor that the stream it belongs to. - // If a value is specified, it will require to be lower than the stream - // replicas count (lower than StreamReplicas if specified, but also lower - // than the automatic value determined by cluster size). - // Note that existing consumers are not modified. - // - // UPDATE: This is no longer used while messages stream has interest policy retention - // which requires consumer replica count to match the parent stream. - ConsumerReplicas int - - // Indicate if the consumers should be created with memory storage. - // Note that existing consumers are not modified. - ConsumerMemoryStorage bool - - // If specified will have the system auto-cleanup the consumers after being - // inactive for the specified amount of time. - ConsumerInactiveThreshold time.Duration - - // Timeout for the authentication process. - AuthTimeout float64 - - // TLS configuration is required. - TLSConfig *tls.Config - // If true, map certificate values for authentication purposes. - TLSMap bool - // Timeout for the TLS handshake - TLSTimeout float64 - // Set of allowable certificates - TLSPinnedCerts PinnedCertSet - - // AckWait is the amount of time after which a QoS 1 or 2 message sent to a - // client is redelivered as a DUPLICATE if the server has not received the - // PUBACK on the original Packet Identifier. The same value applies to - // PubRel redelivery. The value has to be positive. Zero will cause the - // server to use the default value (30 seconds). Note that changes to this - // option is applied only to new MQTT subscriptions (or sessions for - // PubRels). - AckWait time.Duration - - // MaxAckPending is the amount of QoS 1 and 2 messages (combined) the server - // can send to a subscription without receiving any PUBACK for those - // messages. The valid range is [0..65535]. - // - // The total of subscriptions' MaxAckPending on a given session cannot - // exceed 65535. Attempting to create a subscription that would bring the - // total above the limit would result in the server returning 0x80 in the - // SUBACK for this subscription. - // - // Due to how the NATS Server handles the MQTT "#" wildcard, each - // subscription ending with "#" will use 2 times the MaxAckPending value. - // Note that changes to this option is applied only to new subscriptions. - MaxAckPending uint16 - - // Snapshot of configured TLS options. - tlsConfigOpts *TLSConfigOpts - - // rejectQoS2Pub tells the MQTT client to not accept QoS2 PUBLISH, instead - // error and terminate the connection. - rejectQoS2Pub bool - - // downgradeQOS2Sub tells the MQTT client to downgrade QoS2 SUBSCRIBE - // requests to QoS1. - downgradeQoS2Sub bool -} - -type netResolver interface { - LookupHost(ctx context.Context, host string) ([]string, error) -} - -// Clone performs a deep copy of the Options struct, returning a new clone -// with all values copied. -func (o *Options) Clone() *Options { - if o == nil { - return nil - } - clone := &Options{} - *clone = *o - if o.Users != nil { - clone.Users = make([]*User, len(o.Users)) - for i, user := range o.Users { - clone.Users[i] = user.clone() - } - } - if o.Nkeys != nil { - clone.Nkeys = make([]*NkeyUser, len(o.Nkeys)) - for i, nkey := range o.Nkeys { - clone.Nkeys[i] = nkey.clone() - } - } - - if o.Routes != nil { - clone.Routes = deepCopyURLs(o.Routes) - } - if o.TLSConfig != nil { - clone.TLSConfig = o.TLSConfig.Clone() - } - if o.Cluster.TLSConfig != nil { - clone.Cluster.TLSConfig = o.Cluster.TLSConfig.Clone() - } - if o.Gateway.TLSConfig != nil { - clone.Gateway.TLSConfig = o.Gateway.TLSConfig.Clone() - } - if len(o.Gateway.Gateways) > 0 { - clone.Gateway.Gateways = make([]*RemoteGatewayOpts, len(o.Gateway.Gateways)) - for i, g := range o.Gateway.Gateways { - clone.Gateway.Gateways[i] = g.clone() - } - } - // FIXME(dlc) - clone leaf node stuff. - return clone -} - -func deepCopyURLs(urls []*url.URL) []*url.URL { - if urls == nil { - return nil - } - curls := make([]*url.URL, len(urls)) - for i, u := range urls { - cu := &url.URL{} - *cu = *u - curls[i] = cu - } - return curls -} - -// Configuration file authorization section. -type authorization struct { - // Singles - user string - pass string - token string - nkey string - acc string - // Multiple Nkeys/Users - nkeys []*NkeyUser - users []*User - timeout float64 - defaultPermissions *Permissions - // Auth Callouts - callout *AuthCallout -} - -// TLSConfigOpts holds the parsed tls config information, -// used with flag parsing -type TLSConfigOpts struct { - CertFile string - KeyFile string - CaFile string - Verify bool - Insecure bool - Map bool - TLSCheckKnownURLs bool - HandshakeFirst bool // Indicate that the TLS handshake should occur first, before sending the INFO protocol. - FallbackDelay time.Duration // Where supported, indicates how long to wait for the handshake before falling back to sending the INFO protocol first. - Timeout float64 - RateLimit int64 - Ciphers []uint16 - CurvePreferences []tls.CurveID - PinnedCerts PinnedCertSet - CertStore certstore.StoreType - CertMatchBy certstore.MatchByType - CertMatch string - CertMatchSkipInvalid bool - CaCertsMatch []string - OCSPPeerConfig *certidp.OCSPPeerConfig - Certificates []*TLSCertPairOpt - MinVersion uint16 -} - -// TLSCertPairOpt are the paths to a certificate and private key. -type TLSCertPairOpt struct { - CertFile string - KeyFile string -} - -// OCSPConfig represents the options of OCSP stapling options. -type OCSPConfig struct { - // Mode defines the policy for OCSP stapling. - Mode OCSPMode - - // OverrideURLs is the http URL endpoint used to get OCSP staples. - OverrideURLs []string -} - -var tlsUsage = ` -TLS configuration is specified in the tls section of a configuration file: - -e.g. - - tls { - cert_file: "./certs/server-cert.pem" - key_file: "./certs/server-key.pem" - ca_file: "./certs/ca.pem" - verify: true - verify_and_map: true - - cipher_suites: [ - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" - ] - curve_preferences: [ - "CurveP256", - "CurveP384", - "CurveP521" - ] - } - -Available cipher suites include: -` - -// ProcessConfigFile processes a configuration file. -// FIXME(dlc): A bit hacky -func ProcessConfigFile(configFile string) (*Options, error) { - opts := &Options{} - if err := opts.ProcessConfigFile(configFile); err != nil { - // If only warnings then continue and return the options. - if cerr, ok := err.(*processConfigErr); ok && len(cerr.Errors()) == 0 { - return opts, nil - } - - return nil, err - } - return opts, nil -} - -// token is an item parsed from the configuration. -type token interface { - Value() any - Line() int - IsUsedVariable() bool - SourceFile() string - Position() int -} - -// unwrapValue can be used to get the token and value from an item -// to be able to report the line number in case of an incorrect -// configuration. -// also stores the token in lastToken for use in convertPanicToError -func unwrapValue(v any, lastToken *token) (token, any) { - switch tk := v.(type) { - case token: - if lastToken != nil { - *lastToken = tk - } - return tk, tk.Value() - default: - return nil, v - } -} - -// use in defer to recover from panic and turn it into an error associated with last token -func convertPanicToErrorList(lastToken *token, errors *[]error) { - // only recover if an error can be stored - if errors == nil { - return - } else if err := recover(); err == nil { - return - } else if lastToken != nil && *lastToken != nil { - *errors = append(*errors, &configErr{*lastToken, fmt.Sprint(err)}) - } else { - *errors = append(*errors, fmt.Errorf("encountered panic without a token %v", err)) - } -} - -// use in defer to recover from panic and turn it into an error associated with last token -func convertPanicToError(lastToken *token, e *error) { - // only recover if an error can be stored - if e == nil || *e != nil { - return - } else if err := recover(); err == nil { - return - } else if lastToken != nil && *lastToken != nil { - *e = &configErr{*lastToken, fmt.Sprint(err)} - } else { - *e = fmt.Errorf("%v", err) - } -} - -// configureSystemAccount configures a system account -// if present in the configuration. -func configureSystemAccount(o *Options, m map[string]any) (retErr error) { - var lt token - defer convertPanicToError(<, &retErr) - configure := func(v any) error { - tk, v := unwrapValue(v, <) - sa, ok := v.(string) - if !ok { - return &configErr{tk, "system account name must be a string"} - } - o.SystemAccount = sa - return nil - } - - if v, ok := m["system_account"]; ok { - return configure(v) - } else if v, ok := m["system"]; ok { - return configure(v) - } - - return nil -} - -// ProcessConfigFile updates the Options structure with options -// present in the given configuration file. -// This version is convenient if one wants to set some default -// options and then override them with what is in the config file. -// For instance, this version allows you to do something such as: -// -// opts := &Options{Debug: true} -// opts.ProcessConfigFile(myConfigFile) -// -// If the config file contains "debug: false", after this call, -// opts.Debug would really be false. It would be impossible to -// achieve that with the non receiver ProcessConfigFile() version, -// since one would not know after the call if "debug" was not present -// or was present but set to false. -func (o *Options) ProcessConfigFile(configFile string) error { - o.ConfigFile = configFile - if configFile == _EMPTY_ { - return nil - } - m, digest, err := conf.ParseFileWithChecksDigest(configFile) - if err != nil { - return err - } - o.configDigest = digest - - return o.processConfigFile(configFile, m) -} - -// ProcessConfigString is the same as ProcessConfigFile, but expects the -// contents of the config file to be passed in rather than the file name. -func (o *Options) ProcessConfigString(data string) error { - m, err := conf.ParseWithChecks(data) - if err != nil { - return err - } - - return o.processConfigFile(_EMPTY_, m) -} - -// ConfigDigest returns the digest representing the configuration. -func (o *Options) ConfigDigest() string { - return o.configDigest -} - -func (o *Options) processConfigFile(configFile string, m map[string]any) error { - // Collect all errors and warnings and report them all together. - errors := make([]error, 0) - warnings := make([]error, 0) - if len(m) == 0 { - warnings = append(warnings, fmt.Errorf("%s: config has no values or is empty", configFile)) - } - - // First check whether a system account has been defined, - // as that is a condition for other features to be enabled. - if err := configureSystemAccount(o, m); err != nil { - errors = append(errors, err) - } - - for k, v := range m { - o.processConfigFileLine(k, v, &errors, &warnings) - } - - // Post-process: check auth callout allowed accounts against configured accounts. - if o.AuthCallout != nil { - accounts := make(map[string]struct{}) - for _, acc := range o.Accounts { - accounts[acc.Name] = struct{}{} - } - - for _, acc := range o.AuthCallout.AllowedAccounts { - if _, ok := accounts[acc]; !ok { - err := &configErr{nil, fmt.Sprintf("auth_callout allowed account %q not found in configured accounts", acc)} - errors = append(errors, err) - } - } - } - - if len(errors) > 0 || len(warnings) > 0 { - return &processConfigErr{ - errors: errors, - warnings: warnings, - } - } - - return nil -} - -func (o *Options) processConfigFileLine(k string, v any, errors *[]error, warnings *[]error) { - var lt token - defer convertPanicToErrorList(<, errors) - - tk, v := unwrapValue(v, <) - switch strings.ToLower(k) { - case "listen": - hp, err := parseListen(v) - if err != nil { - *errors = append(*errors, &configErr{tk, err.Error()}) - return - } - o.Host = hp.host - o.Port = hp.port - case "client_advertise": - o.ClientAdvertise = v.(string) - case "port": - o.Port = int(v.(int64)) - case "server_name": - sn := v.(string) - if strings.Contains(sn, " ") { - err := &configErr{tk, ErrServerNameHasSpaces.Error()} - *errors = append(*errors, err) - return - } - o.ServerName = sn - case "host", "net": - o.Host = v.(string) - case "debug": - o.Debug = v.(bool) - trackExplicitVal(&o.inConfig, "Debug", o.Debug) - case "trace": - o.Trace = v.(bool) - trackExplicitVal(&o.inConfig, "Trace", o.Trace) - case "trace_verbose": - o.TraceVerbose = v.(bool) - o.Trace = v.(bool) - trackExplicitVal(&o.inConfig, "TraceVerbose", o.TraceVerbose) - trackExplicitVal(&o.inConfig, "Trace", o.Trace) - case "logtime": - o.Logtime = v.(bool) - trackExplicitVal(&o.inConfig, "Logtime", o.Logtime) - case "logtime_utc": - o.LogtimeUTC = v.(bool) - trackExplicitVal(&o.inConfig, "LogtimeUTC", o.LogtimeUTC) - case "mappings", "maps": - gacc := NewAccount(globalAccountName) - o.Accounts = append(o.Accounts, gacc) - err := parseAccountMappings(tk, gacc, errors) - if err != nil { - *errors = append(*errors, err) - return - } - case "disable_sublist_cache", "no_sublist_cache": - o.NoSublistCache = v.(bool) - case "accounts": - err := parseAccounts(tk, o, errors, warnings) - if err != nil { - *errors = append(*errors, err) - return - } - case "authorization": - auth, err := parseAuthorization(tk, errors) - if err != nil { - *errors = append(*errors, err) - return - } - o.authBlockDefined = true - o.Username = auth.user - o.Password = auth.pass - o.Authorization = auth.token - o.AuthTimeout = auth.timeout - o.AuthCallout = auth.callout - - if (auth.user != _EMPTY_ || auth.pass != _EMPTY_) && auth.token != _EMPTY_ { - err := &configErr{tk, "Cannot have a user/pass and token"} - *errors = append(*errors, err) - return - } - // In case parseAccounts() was done first, we need to check for duplicates. - unames := setupUsersAndNKeysDuplicateCheckMap(o) - // Check for multiple users defined. - // Note: auth.users will be != nil as long as `users: []` is present - // in the authorization block, even if empty, and will also account for - // nkey users. We also check for users/nkeys that may have been already - // added in parseAccounts() (which means they will be in unames) - if auth.users != nil || len(unames) > 0 { - if auth.user != _EMPTY_ { - err := &configErr{tk, "Can not have a single user/pass and a users array"} - *errors = append(*errors, err) - return - } - if auth.token != _EMPTY_ { - err := &configErr{tk, "Can not have a token and a users array"} - *errors = append(*errors, err) - return - } - // Now check that if we have users, there is no duplicate, including - // users that may have been configured in parseAccounts(). - if len(auth.users) > 0 { - for _, u := range auth.users { - if _, ok := unames[u.Username]; ok { - err := &configErr{tk, fmt.Sprintf("Duplicate user %q detected", u.Username)} - *errors = append(*errors, err) - return - } - unames[u.Username] = struct{}{} - } - // Users may have been added from Accounts parsing, so do an append here - o.Users = append(o.Users, auth.users...) - } - } - // Check for nkeys - if len(auth.nkeys) > 0 { - for _, u := range auth.nkeys { - if _, ok := unames[u.Nkey]; ok { - err := &configErr{tk, fmt.Sprintf("Duplicate nkey %q detected", u.Nkey)} - *errors = append(*errors, err) - return - } - unames[u.Nkey] = struct{}{} - } - // NKeys may have been added from Accounts parsing, so do an append here - o.Nkeys = append(o.Nkeys, auth.nkeys...) - } - case "http": - hp, err := parseListen(v) - if err != nil { - err := &configErr{tk, err.Error()} - *errors = append(*errors, err) - return - } - o.HTTPHost = hp.host - o.HTTPPort = hp.port - case "https": - hp, err := parseListen(v) - if err != nil { - err := &configErr{tk, err.Error()} - *errors = append(*errors, err) - return - } - o.HTTPHost = hp.host - o.HTTPSPort = hp.port - case "http_port", "monitor_port": - o.HTTPPort = int(v.(int64)) - case "https_port": - o.HTTPSPort = int(v.(int64)) - case "http_base_path": - o.HTTPBasePath = v.(string) - case "cluster": - err := parseCluster(tk, o, errors, warnings) - if err != nil { - *errors = append(*errors, err) - return - } - case "gateway": - if err := parseGateway(tk, o, errors, warnings); err != nil { - *errors = append(*errors, err) - return - } - case "leaf", "leafnodes": - err := parseLeafNodes(tk, o, errors, warnings) - if err != nil { - *errors = append(*errors, err) - return - } - case "store_dir", "storedir": - // Check if JetStream configuration is also setting the storage directory. - if o.StoreDir != _EMPTY_ { - *errors = append(*errors, &configErr{tk, "Duplicate 'store_dir' configuration"}) - return - } - o.StoreDir = v.(string) - case "jetstream": - err := parseJetStream(tk, o, errors, warnings) - if err != nil { - *errors = append(*errors, err) - return - } - case "logfile", "log_file": - o.LogFile = v.(string) - case "logfile_size_limit", "log_size_limit": - o.LogSizeLimit = v.(int64) - case "logfile_max_num", "log_max_num": - o.LogMaxFiles = v.(int64) - case "syslog": - o.Syslog = v.(bool) - trackExplicitVal(&o.inConfig, "Syslog", o.Syslog) - case "remote_syslog": - o.RemoteSyslog = v.(string) - case "pidfile", "pid_file": - o.PidFile = v.(string) - case "ports_file_dir": - o.PortsFileDir = v.(string) - case "prof_port": - o.ProfPort = int(v.(int64)) - case "prof_block_rate": - o.ProfBlockRate = int(v.(int64)) - case "max_control_line": - if v.(int64) > 1<<31-1 { - err := &configErr{tk, fmt.Sprintf("%s value is too big", k)} - *errors = append(*errors, err) - return - } - o.MaxControlLine = int32(v.(int64)) - case "max_payload": - if v.(int64) > 1<<31-1 { - err := &configErr{tk, fmt.Sprintf("%s value is too big", k)} - *errors = append(*errors, err) - return - } - o.MaxPayload = int32(v.(int64)) - case "max_pending": - o.MaxPending = v.(int64) - case "max_connections", "max_conn": - o.MaxConn = int(v.(int64)) - case "max_traced_msg_len": - o.MaxTracedMsgLen = int(v.(int64)) - case "max_subscriptions", "max_subs": - o.MaxSubs = int(v.(int64)) - case "max_sub_tokens", "max_subscription_tokens": - if n := v.(int64); n > math.MaxUint8 { - err := &configErr{tk, fmt.Sprintf("%s value is too big", k)} - *errors = append(*errors, err) - return - } else if n <= 0 { - err := &configErr{tk, fmt.Sprintf("%s value can not be negative", k)} - *errors = append(*errors, err) - return - } else { - o.MaxSubTokens = uint8(n) - } - case "ping_interval": - o.PingInterval = parseDuration("ping_interval", tk, v, errors, warnings) - case "ping_max": - o.MaxPingsOut = int(v.(int64)) - case "tls": - tc, err := parseTLS(tk, true) - if err != nil { - *errors = append(*errors, err) - return - } - if o.TLSConfig, err = GenTLSConfig(tc); err != nil { - err := &configErr{tk, err.Error()} - *errors = append(*errors, err) - return - } - o.TLSTimeout = tc.Timeout - o.TLSMap = tc.Map - o.TLSPinnedCerts = tc.PinnedCerts - o.TLSRateLimit = tc.RateLimit - o.TLSHandshakeFirst = tc.HandshakeFirst - o.TLSHandshakeFirstFallback = tc.FallbackDelay - - // Need to keep track of path of the original TLS config - // and certs path for OCSP Stapling monitoring. - o.tlsConfigOpts = tc - case "ocsp": - switch vv := v.(type) { - case bool: - if vv { - // Default is Auto which honors Must Staple status request - // but does not shutdown the server in case it is revoked, - // letting the client choose whether to trust or not the server. - o.OCSPConfig = &OCSPConfig{Mode: OCSPModeAuto} - } else { - o.OCSPConfig = &OCSPConfig{Mode: OCSPModeNever} - } - case map[string]any: - ocsp := &OCSPConfig{Mode: OCSPModeAuto} - - for kk, kv := range vv { - _, v = unwrapValue(kv, &tk) - switch kk { - case "mode": - mode := v.(string) - switch { - case strings.EqualFold(mode, "always"): - ocsp.Mode = OCSPModeAlways - case strings.EqualFold(mode, "must"): - ocsp.Mode = OCSPModeMust - case strings.EqualFold(mode, "never"): - ocsp.Mode = OCSPModeNever - case strings.EqualFold(mode, "auto"): - ocsp.Mode = OCSPModeAuto - default: - *errors = append(*errors, &configErr{tk, fmt.Sprintf("error parsing ocsp config: unsupported ocsp mode %T", mode)}) - } - case "urls": - urls := v.([]string) - ocsp.OverrideURLs = urls - case "url": - url := v.(string) - ocsp.OverrideURLs = []string{url} - default: - *errors = append(*errors, &configErr{tk, fmt.Sprintf("error parsing ocsp config: unsupported field %T", kk)}) - return - } - } - o.OCSPConfig = ocsp - default: - *errors = append(*errors, &configErr{tk, fmt.Sprintf("error parsing ocsp config: unsupported type %T", v)}) - return - } - case "allow_non_tls": - o.AllowNonTLS = v.(bool) - case "write_deadline": - o.WriteDeadline = parseDuration("write_deadline", tk, v, errors, warnings) - case "lame_duck_duration": - dur, err := time.ParseDuration(v.(string)) - if err != nil { - err := &configErr{tk, fmt.Sprintf("error parsing lame_duck_duration: %v", err)} - *errors = append(*errors, err) - return - } - if dur < 30*time.Second { - err := &configErr{tk, fmt.Sprintf("invalid lame_duck_duration of %v, minimum is 30 seconds", dur)} - *errors = append(*errors, err) - return - } - o.LameDuckDuration = dur - case "lame_duck_grace_period": - dur, err := time.ParseDuration(v.(string)) - if err != nil { - err := &configErr{tk, fmt.Sprintf("error parsing lame_duck_grace_period: %v", err)} - *errors = append(*errors, err) - return - } - if dur < 0 { - err := &configErr{tk, "invalid lame_duck_grace_period, needs to be positive"} - *errors = append(*errors, err) - return - } - o.LameDuckGracePeriod = dur - case "operator", "operators", "roots", "root", "root_operators", "root_operator": - opFiles := []string{} - switch v := v.(type) { - case string: - opFiles = append(opFiles, v) - case []string: - opFiles = append(opFiles, v...) - case []any: - for _, t := range v { - if token, ok := t.(token); ok { - if v, ok := token.Value().(string); ok { - opFiles = append(opFiles, v) - } else { - err := &configErr{tk, fmt.Sprintf("error parsing operators: unsupported type %T where string is expected", token)} - *errors = append(*errors, err) - break - } - } else { - err := &configErr{tk, fmt.Sprintf("error parsing operators: unsupported type %T", t)} - *errors = append(*errors, err) - break - } - } - default: - err := &configErr{tk, fmt.Sprintf("error parsing operators: unsupported type %T", v)} - *errors = append(*errors, err) - } - // Assume for now these are file names, but they can also be the JWT itself inline. - o.TrustedOperators = make([]*jwt.OperatorClaims, 0, len(opFiles)) - for _, fname := range opFiles { - theJWT, opc, err := readOperatorJWT(fname) - if err != nil { - err := &configErr{tk, fmt.Sprintf("error parsing operator JWT: %v", err)} - *errors = append(*errors, err) - continue - } - o.operatorJWT = append(o.operatorJWT, theJWT) - o.TrustedOperators = append(o.TrustedOperators, opc) - } - if len(o.TrustedOperators) == 1 { - // In case "resolver" is defined as well, it takes precedence - if o.AccountResolver == nil { - if accUrl, err := parseURL(o.TrustedOperators[0].AccountServerURL, "account resolver"); err == nil { - // nsc automatically appends "/accounts" during nsc push - o.AccountResolver, _ = NewURLAccResolver(accUrl.String() + "/accounts") - } - } - // In case "system_account" is defined as well, it takes precedence - if o.SystemAccount == _EMPTY_ { - o.SystemAccount = o.TrustedOperators[0].SystemAccount - } - } - case "resolver", "account_resolver", "accounts_resolver": - switch v := v.(type) { - case string: - // "resolver" takes precedence over value obtained from "operator". - // Clear so that parsing errors are not silently ignored. - o.AccountResolver = nil - memResolverRe := regexp.MustCompile(`(?i)(MEM|MEMORY)\s*`) - resolverRe := regexp.MustCompile(`(?i)(?:URL){1}(?:\({1}\s*"?([^\s"]*)"?\s*\){1})?\s*`) - if memResolverRe.MatchString(v) { - o.AccountResolver = &MemAccResolver{} - } else if items := resolverRe.FindStringSubmatch(v); len(items) == 2 { - url := items[1] - _, err := parseURL(url, "account resolver") - if err != nil { - *errors = append(*errors, &configErr{tk, err.Error()}) - return - } - if ur, err := NewURLAccResolver(url); err != nil { - err := &configErr{tk, err.Error()} - *errors = append(*errors, err) - return - } else { - o.AccountResolver = ur - } - } - case map[string]any: - del := false - hdel := false - hdel_set := false - dir := _EMPTY_ - dirType := _EMPTY_ - limit := int64(0) - ttl := time.Duration(0) - sync := time.Duration(0) - opts := []DirResOption{} - var err error - if v, ok := v["dir"]; ok { - _, v := unwrapValue(v, <) - dir = v.(string) - } - if v, ok := v["type"]; ok { - _, v := unwrapValue(v, <) - dirType = v.(string) - } - if v, ok := v["allow_delete"]; ok { - _, v := unwrapValue(v, <) - del = v.(bool) - } - if v, ok := v["hard_delete"]; ok { - _, v := unwrapValue(v, <) - hdel_set = true - hdel = v.(bool) - } - if v, ok := v["limit"]; ok { - _, v := unwrapValue(v, <) - limit = v.(int64) - } - if v, ok := v["ttl"]; ok { - _, v := unwrapValue(v, <) - ttl, err = time.ParseDuration(v.(string)) - } - if v, ok := v["interval"]; err == nil && ok { - _, v := unwrapValue(v, <) - sync, err = time.ParseDuration(v.(string)) - } - if v, ok := v["timeout"]; err == nil && ok { - _, v := unwrapValue(v, <) - var to time.Duration - if to, err = time.ParseDuration(v.(string)); err == nil { - opts = append(opts, FetchTimeout(to)) - } - } - if err != nil { - *errors = append(*errors, &configErr{tk, err.Error()}) - return - } - - checkDir := func() { - if dir == _EMPTY_ { - *errors = append(*errors, &configErr{tk, "dir has no value and needs to point to a directory"}) - return - } - if info, _ := os.Stat(dir); info != nil && (!info.IsDir() || info.Mode().Perm()&(1<<(uint(7))) == 0) { - *errors = append(*errors, &configErr{tk, "dir needs to point to an accessible directory"}) - return - } - } - - var res AccountResolver - switch strings.ToUpper(dirType) { - case "CACHE": - checkDir() - if sync != 0 { - *errors = append(*errors, &configErr{tk, "CACHE does not accept sync"}) - } - if del { - *errors = append(*errors, &configErr{tk, "CACHE does not accept allow_delete"}) - } - if hdel_set { - *errors = append(*errors, &configErr{tk, "CACHE does not accept hard_delete"}) - } - res, err = NewCacheDirAccResolver(dir, limit, ttl, opts...) - case "FULL": - checkDir() - if ttl != 0 { - *errors = append(*errors, &configErr{tk, "FULL does not accept ttl"}) - } - if hdel_set && !del { - *errors = append(*errors, &configErr{tk, "hard_delete has no effect without delete"}) - } - delete := NoDelete - if del { - if hdel { - delete = HardDelete - } else { - delete = RenameDeleted - } - } - res, err = NewDirAccResolver(dir, limit, sync, delete, opts...) - case "MEM", "MEMORY": - res = &MemAccResolver{} - } - if err != nil { - *errors = append(*errors, &configErr{tk, err.Error()}) - return - } - o.AccountResolver = res - default: - err := &configErr{tk, fmt.Sprintf("error parsing operator resolver, wrong type %T", v)} - *errors = append(*errors, err) - return - } - if o.AccountResolver == nil { - err := &configErr{tk, "error parsing account resolver, should be MEM or " + - " URL(\"url\") or a map containing dir and type state=[FULL|CACHE])"} - *errors = append(*errors, err) - } - case "resolver_tls": - tc, err := parseTLS(tk, true) - if err != nil { - *errors = append(*errors, err) - return - } - tlsConfig, err := GenTLSConfig(tc) - if err != nil { - err := &configErr{tk, err.Error()} - *errors = append(*errors, err) - return - } - o.AccountResolverTLSConfig = tlsConfig - // GenTLSConfig loads the CA file into ClientCAs, but since this will - // be used as a client connection, we need to set RootCAs. - o.AccountResolverTLSConfig.RootCAs = tlsConfig.ClientCAs - case "resolver_preload": - mp, ok := v.(map[string]any) - if !ok { - err := &configErr{tk, "preload should be a map of account_public_key:account_jwt"} - *errors = append(*errors, err) - return - } - o.resolverPreloads = make(map[string]string) - for key, val := range mp { - tk, val = unwrapValue(val, <) - if jwtstr, ok := val.(string); !ok { - *errors = append(*errors, &configErr{tk, "preload map value should be a string JWT"}) - continue - } else { - // Make sure this is a valid account JWT, that is a config error. - // We will warn of expirations, etc later. - if _, err := jwt.DecodeAccountClaims(jwtstr); err != nil { - err := &configErr{tk, "invalid account JWT"} - *errors = append(*errors, err) - continue - } - o.resolverPreloads[key] = jwtstr - } - } - case "resolver_pinned_accounts": - switch v := v.(type) { - case string: - o.resolverPinnedAccounts = map[string]struct{}{v: {}} - case []string: - o.resolverPinnedAccounts = make(map[string]struct{}) - for _, mv := range v { - o.resolverPinnedAccounts[mv] = struct{}{} - } - case []any: - o.resolverPinnedAccounts = make(map[string]struct{}) - for _, mv := range v { - tk, mv = unwrapValue(mv, <) - if key, ok := mv.(string); ok { - o.resolverPinnedAccounts[key] = struct{}{} - } else { - err := &configErr{tk, - fmt.Sprintf("error parsing resolver_pinned_accounts: unsupported type in array %T", mv)} - *errors = append(*errors, err) - continue - } - } - default: - err := &configErr{tk, fmt.Sprintf("error parsing resolver_pinned_accounts: unsupported type %T", v)} - *errors = append(*errors, err) - return - } - case "no_auth_user": - o.NoAuthUser = v.(string) - case "system_account", "system": - // Already processed at the beginning so we just skip them - // to not treat them as unknown values. - return - case "no_system_account", "no_system", "no_sys_acc": - o.NoSystemAccount = v.(bool) - case "no_header_support": - o.NoHeaderSupport = v.(bool) - case "trusted", "trusted_keys": - switch v := v.(type) { - case string: - o.TrustedKeys = []string{v} - case []string: - o.TrustedKeys = v - case []any: - keys := make([]string, 0, len(v)) - for _, mv := range v { - tk, mv = unwrapValue(mv, <) - if key, ok := mv.(string); ok { - keys = append(keys, key) - } else { - err := &configErr{tk, fmt.Sprintf("error parsing trusted: unsupported type in array %T", mv)} - *errors = append(*errors, err) - continue - } - } - o.TrustedKeys = keys - default: - err := &configErr{tk, fmt.Sprintf("error parsing trusted: unsupported type %T", v)} - *errors = append(*errors, err) - } - // Do a quick sanity check on keys - for _, key := range o.TrustedKeys { - if !nkeys.IsValidPublicOperatorKey(key) { - err := &configErr{tk, fmt.Sprintf("trust key %q required to be a valid public operator nkey", key)} - *errors = append(*errors, err) - } - } - case "connect_error_reports": - o.ConnectErrorReports = int(v.(int64)) - case "reconnect_error_reports": - o.ReconnectErrorReports = int(v.(int64)) - case "websocket", "ws": - if err := parseWebsocket(tk, o, errors); err != nil { - *errors = append(*errors, err) - return - } - case "mqtt": - if err := parseMQTT(tk, o, errors, warnings); err != nil { - *errors = append(*errors, err) - return - } - case "server_tags": - var err error - switch v := v.(type) { - case string: - o.Tags.Add(v) - case []string: - o.Tags.Add(v...) - case []any: - for _, t := range v { - if token, ok := t.(token); ok { - if ts, ok := token.Value().(string); ok { - o.Tags.Add(ts) - continue - } else { - err = &configErr{tk, fmt.Sprintf("error parsing tags: unsupported type %T where string is expected", token)} - } - } else { - err = &configErr{tk, fmt.Sprintf("error parsing tags: unsupported type %T", t)} - } - break - } - default: - err = &configErr{tk, fmt.Sprintf("error parsing tags: unsupported type %T", v)} - } - if err != nil { - *errors = append(*errors, err) - return - } - case "default_js_domain": - vv, ok := v.(map[string]any) - if !ok { - *errors = append(*errors, &configErr{tk, fmt.Sprintf("error default_js_domain config: unsupported type %T", v)}) - return - } - m := make(map[string]string) - for kk, kv := range vv { - _, v = unwrapValue(kv, &tk) - m[kk] = v.(string) - } - o.JsAccDefaultDomain = m - case "ocsp_cache": - var err error - switch vv := v.(type) { - case bool: - pc := NewOCSPResponseCacheConfig() - if vv { - // Set enabled - pc.Type = LOCAL - o.OCSPCacheConfig = pc - } else { - // Set disabled (none cache) - pc.Type = NONE - o.OCSPCacheConfig = pc - } - case map[string]any: - pc, err := parseOCSPResponseCache(v) - if err != nil { - *errors = append(*errors, err) - return - } - o.OCSPCacheConfig = pc - default: - err = &configErr{tk, fmt.Sprintf("error parsing tags: unsupported type %T", v)} - } - if err != nil { - *errors = append(*errors, err) - return - } - case "no_fast_producer_stall": - o.NoFastProducerStall = v.(bool) - case "max_closed_clients": - o.MaxClosedClients = int(v.(int64)) - default: - if au := atomic.LoadInt32(&allowUnknownTopLevelField); au == 0 && !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: k, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - } - } -} - -func setupUsersAndNKeysDuplicateCheckMap(o *Options) map[string]struct{} { - unames := make(map[string]struct{}, len(o.Users)+len(o.Nkeys)) - for _, u := range o.Users { - unames[u.Username] = struct{}{} - } - for _, u := range o.Nkeys { - unames[u.Nkey] = struct{}{} - } - return unames -} - -func parseDuration(field string, tk token, v any, errors *[]error, warnings *[]error) time.Duration { - if wd, ok := v.(string); ok { - if dur, err := time.ParseDuration(wd); err != nil { - err := &configErr{tk, fmt.Sprintf("error parsing %s: %v", field, err)} - *errors = append(*errors, err) - return 0 - } else { - return dur - } - } else { - // Backward compatible with old type, assume this is the - // number of seconds. - err := &configWarningErr{ - field: field, - configErr: configErr{ - token: tk, - reason: field + " should be converted to a duration", - }, - } - *warnings = append(*warnings, err) - return time.Duration(v.(int64)) * time.Second - } -} - -func trackExplicitVal(pm *map[string]bool, name string, val bool) { - m := *pm - if m == nil { - m = make(map[string]bool) - *pm = m - } - m[name] = val -} - -// hostPort is simple struct to hold parsed listen/addr strings. -type hostPort struct { - host string - port int -} - -// parseListen will parse listen option which is replacing host/net and port -func parseListen(v any) (*hostPort, error) { - hp := &hostPort{} - switch vv := v.(type) { - // Only a port - case int64: - hp.port = int(vv) - case string: - host, port, err := net.SplitHostPort(vv) - if err != nil { - return nil, fmt.Errorf("could not parse address string %q", vv) - } - hp.port, err = strconv.Atoi(port) - if err != nil { - return nil, fmt.Errorf("could not parse port %q", port) - } - hp.host = host - default: - return nil, fmt.Errorf("expected port or host:port, got %T", vv) - } - return hp, nil -} - -// parseCluster will parse the cluster config. -func parseCluster(v any, opts *Options, errors *[]error, warnings *[]error) error { - var lt token - defer convertPanicToErrorList(<, errors) - - tk, v := unwrapValue(v, <) - cm, ok := v.(map[string]any) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected map to define cluster, got %T", v)} - } - - for mk, mv := range cm { - // Again, unwrap token value if line check is required. - tk, mv = unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "name": - cn := mv.(string) - if strings.Contains(cn, " ") { - err := &configErr{tk, ErrClusterNameHasSpaces.Error()} - *errors = append(*errors, err) - continue - } - opts.Cluster.Name = cn - case "listen": - hp, err := parseListen(mv) - if err != nil { - err := &configErr{tk, err.Error()} - *errors = append(*errors, err) - continue - } - opts.Cluster.Host = hp.host - opts.Cluster.Port = hp.port - case "port": - opts.Cluster.Port = int(mv.(int64)) - case "host", "net": - opts.Cluster.Host = mv.(string) - case "authorization": - auth, err := parseAuthorization(tk, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - if auth.users != nil { - err := &configErr{tk, "Cluster authorization does not allow multiple users"} - *errors = append(*errors, err) - continue - } - if auth.token != _EMPTY_ { - err := &configErr{tk, "Cluster authorization does not support tokens"} - *errors = append(*errors, err) - continue - } - if auth.callout != nil { - err := &configErr{tk, "Cluster authorization does not support callouts"} - *errors = append(*errors, err) - continue - } - - opts.Cluster.Username = auth.user - opts.Cluster.Password = auth.pass - opts.Cluster.AuthTimeout = auth.timeout - - if auth.defaultPermissions != nil { - err := &configWarningErr{ - field: mk, - configErr: configErr{ - token: tk, - reason: `setting "permissions" within cluster authorization block is deprecated`, - }, - } - *warnings = append(*warnings, err) - - // Do not set permissions if they were specified in top-level cluster block. - if opts.Cluster.Permissions == nil { - setClusterPermissions(&opts.Cluster, auth.defaultPermissions) - } - } - case "routes": - ra := mv.([]any) - routes, errs := parseURLs(ra, "route", warnings) - if errs != nil { - *errors = append(*errors, errs...) - continue - } - opts.Routes = routes - case "tls": - config, tlsopts, err := getTLSConfig(tk) - if err != nil { - *errors = append(*errors, err) - continue - } - opts.Cluster.TLSConfig = config - opts.Cluster.TLSTimeout = tlsopts.Timeout - opts.Cluster.TLSMap = tlsopts.Map - opts.Cluster.TLSPinnedCerts = tlsopts.PinnedCerts - opts.Cluster.TLSCheckKnownURLs = tlsopts.TLSCheckKnownURLs - opts.Cluster.tlsConfigOpts = tlsopts - case "cluster_advertise", "advertise": - opts.Cluster.Advertise = mv.(string) - case "no_advertise": - opts.Cluster.NoAdvertise = mv.(bool) - trackExplicitVal(&opts.inConfig, "Cluster.NoAdvertise", opts.Cluster.NoAdvertise) - case "connect_retries": - opts.Cluster.ConnectRetries = int(mv.(int64)) - case "permissions": - perms, err := parseUserPermissions(mv, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - // Dynamic response permissions do not make sense here. - if perms.Response != nil { - err := &configErr{tk, "Cluster permissions do not support dynamic responses"} - *errors = append(*errors, err) - continue - } - // This will possibly override permissions that were define in auth block - setClusterPermissions(&opts.Cluster, perms) - case "pool_size": - opts.Cluster.PoolSize = int(mv.(int64)) - case "accounts": - opts.Cluster.PinnedAccounts, _ = parseStringArray("accounts", tk, <, mv, errors) - case "compression": - if err := parseCompression(&opts.Cluster.Compression, CompressionS2Fast, tk, mk, mv); err != nil { - *errors = append(*errors, err) - continue - } - case "ping_interval": - opts.Cluster.PingInterval = parseDuration("ping_interval", tk, mv, errors, warnings) - if opts.Cluster.PingInterval > routeMaxPingInterval { - *warnings = append(*warnings, &configErr{tk, fmt.Sprintf("Cluster 'ping_interval' will reset to %v which is the max for routes", routeMaxPingInterval)}) - } - case "ping_max": - opts.Cluster.MaxPingsOut = int(mv.(int64)) - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: mk, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - continue - } - } - } - return nil -} - -// The parameter `chosenModeForOn` indicates which compression mode to use -// when the user selects "on" (or enabled, true, etc..). This is because -// we may have different defaults depending on where the compression is used. -func parseCompression(c *CompressionOpts, chosenModeForOn string, tk token, mk string, mv any) (retErr error) { - var lt token - defer convertPanicToError(<, &retErr) - - switch mv := mv.(type) { - case string: - // Do not validate here, it will be done in NewServer. - c.Mode = mv - case bool: - if mv { - c.Mode = chosenModeForOn - } else { - c.Mode = CompressionOff - } - case map[string]any: - for mk, mv := range mv { - tk, mv = unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "mode": - c.Mode = mv.(string) - case "rtt_thresholds", "thresholds", "rtts", "rtt": - for _, iv := range mv.([]any) { - _, mv := unwrapValue(iv, <) - dur, err := time.ParseDuration(mv.(string)) - if err != nil { - return &configErr{tk, err.Error()} - } - c.RTTThresholds = append(c.RTTThresholds, dur) - } - default: - if !tk.IsUsedVariable() { - return &configErr{tk, fmt.Sprintf("unknown field %q", mk)} - } - } - } - default: - return &configErr{tk, fmt.Sprintf("field %q should be a boolean or a structure, got %T", mk, mv)} - } - return nil -} - -func parseURLs(a []any, typ string, warnings *[]error) (urls []*url.URL, errors []error) { - urls = make([]*url.URL, 0, len(a)) - var lt token - defer convertPanicToErrorList(<, &errors) - - dd := make(map[string]bool) - - for _, u := range a { - tk, u := unwrapValue(u, <) - sURL := u.(string) - if dd[sURL] { - err := &configWarningErr{ - field: sURL, - configErr: configErr{ - token: tk, - reason: fmt.Sprintf("Duplicate %s entry detected", typ), - }, - } - *warnings = append(*warnings, err) - continue - } - dd[sURL] = true - url, err := parseURL(sURL, typ) - if err != nil { - err := &configErr{tk, err.Error()} - errors = append(errors, err) - continue - } - urls = append(urls, url) - } - return urls, errors -} - -func parseURL(u string, typ string) (*url.URL, error) { - urlStr := strings.TrimSpace(u) - url, err := url.Parse(urlStr) - if err != nil { - // Security note: if it's not well-formed but still reached us, then we're going to log as-is which might include password information here. - // If the URL parses, we don't log the credentials ever, but if it doesn't even parse we don't have a sane way to redact. - return nil, fmt.Errorf("error parsing %s url [%q]", typ, urlStr) - } - return url, nil -} - -func parseGateway(v any, o *Options, errors *[]error, warnings *[]error) error { - var lt token - defer convertPanicToErrorList(<, errors) - - tk, v := unwrapValue(v, <) - gm, ok := v.(map[string]any) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected gateway to be a map, got %T", v)} - } - for mk, mv := range gm { - // Again, unwrap token value if line check is required. - tk, mv = unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "name": - gn := mv.(string) - if strings.Contains(gn, " ") { - err := &configErr{tk, ErrGatewayNameHasSpaces.Error()} - *errors = append(*errors, err) - continue - } - o.Gateway.Name = gn - case "listen": - hp, err := parseListen(mv) - if err != nil { - err := &configErr{tk, err.Error()} - *errors = append(*errors, err) - continue - } - o.Gateway.Host = hp.host - o.Gateway.Port = hp.port - case "port": - o.Gateway.Port = int(mv.(int64)) - case "host", "net": - o.Gateway.Host = mv.(string) - case "authorization": - auth, err := parseAuthorization(tk, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - if auth.users != nil { - *errors = append(*errors, &configErr{tk, "Gateway authorization does not allow multiple users"}) - continue - } - if auth.token != _EMPTY_ { - err := &configErr{tk, "Gateway authorization does not support tokens"} - *errors = append(*errors, err) - continue - } - if auth.callout != nil { - err := &configErr{tk, "Gateway authorization does not support callouts"} - *errors = append(*errors, err) - continue - } - - o.Gateway.Username = auth.user - o.Gateway.Password = auth.pass - o.Gateway.AuthTimeout = auth.timeout - case "tls": - config, tlsopts, err := getTLSConfig(tk) - if err != nil { - *errors = append(*errors, err) - continue - } - o.Gateway.TLSConfig = config - o.Gateway.TLSTimeout = tlsopts.Timeout - o.Gateway.TLSMap = tlsopts.Map - o.Gateway.TLSCheckKnownURLs = tlsopts.TLSCheckKnownURLs - o.Gateway.TLSPinnedCerts = tlsopts.PinnedCerts - o.Gateway.tlsConfigOpts = tlsopts - case "advertise": - o.Gateway.Advertise = mv.(string) - case "connect_retries": - o.Gateway.ConnectRetries = int(mv.(int64)) - case "gateways": - gateways, err := parseGateways(mv, errors, warnings) - if err != nil { - return err - } - o.Gateway.Gateways = gateways - case "reject_unknown", "reject_unknown_cluster": - o.Gateway.RejectUnknown = mv.(bool) - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: mk, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - continue - } - } - } - return nil -} - -var dynamicJSAccountLimits = JetStreamAccountLimits{-1, -1, -1, -1, -1, -1, -1, false} -var defaultJSAccountTiers = map[string]JetStreamAccountLimits{_EMPTY_: dynamicJSAccountLimits} - -// Parses jetstream account limits for an account. Simple setup with boolen is allowed, and we will -// use dynamic account limits. -func parseJetStreamForAccount(v any, acc *Account, errors *[]error) error { - var lt token - - tk, v := unwrapValue(v, <) - - // Value here can be bool, or string "enabled" or a map. - switch vv := v.(type) { - case bool: - if vv { - acc.jsLimits = defaultJSAccountTiers - } - case string: - switch strings.ToLower(vv) { - case "enabled", "enable": - acc.jsLimits = defaultJSAccountTiers - case "disabled", "disable": - acc.jsLimits = nil - default: - return &configErr{tk, fmt.Sprintf("Expected 'enabled' or 'disabled' for string value, got '%s'", vv)} - } - case map[string]any: - jsLimits := JetStreamAccountLimits{-1, -1, -1, -1, -1, -1, -1, false} - for mk, mv := range vv { - tk, mv = unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "max_memory", "max_mem", "mem", "memory": - vv, ok := mv.(int64) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected a parseable size for %q, got %v", mk, mv)} - } - jsLimits.MaxMemory = vv - case "max_store", "max_file", "max_disk", "store", "disk": - vv, ok := mv.(int64) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected a parseable size for %q, got %v", mk, mv)} - } - jsLimits.MaxStore = vv - case "max_streams", "streams": - vv, ok := mv.(int64) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected a parseable size for %q, got %v", mk, mv)} - } - jsLimits.MaxStreams = int(vv) - case "max_consumers", "consumers": - vv, ok := mv.(int64) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected a parseable size for %q, got %v", mk, mv)} - } - jsLimits.MaxConsumers = int(vv) - case "max_bytes_required", "max_stream_bytes", "max_bytes": - vv, ok := mv.(bool) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected a parseable bool for %q, got %v", mk, mv)} - } - jsLimits.MaxBytesRequired = vv - case "mem_max_stream_bytes", "memory_max_stream_bytes": - vv, ok := mv.(int64) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected a parseable size for %q, got %v", mk, mv)} - } - jsLimits.MemoryMaxStreamBytes = vv - case "disk_max_stream_bytes", "store_max_stream_bytes": - vv, ok := mv.(int64) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected a parseable size for %q, got %v", mk, mv)} - } - jsLimits.StoreMaxStreamBytes = vv - case "max_ack_pending": - vv, ok := mv.(int64) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected a parseable size for %q, got %v", mk, mv)} - } - jsLimits.MaxAckPending = int(vv) - case "cluster_traffic": - vv, ok := mv.(string) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected either 'system' or 'account' string value for %q, got %v", mk, mv)} - } - switch vv { - case "system", _EMPTY_: - acc.js.nrgAccount = _EMPTY_ - case "owner": - acc.js.nrgAccount = acc.Name - default: - return &configErr{tk, fmt.Sprintf("Expected 'system' or 'owner' string value for %q, got %v", mk, mv)} - } - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: mk, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - continue - } - } - } - acc.jsLimits = map[string]JetStreamAccountLimits{_EMPTY_: jsLimits} - default: - return &configErr{tk, fmt.Sprintf("Expected map, bool or string to define JetStream, got %T", v)} - } - return nil -} - -// takes in a storage size as either an int or a string and returns an int64 value based on the input. -func getStorageSize(v any) (int64, error) { - _, ok := v.(int64) - if ok { - return v.(int64), nil - } - - s, ok := v.(string) - if !ok { - return 0, fmt.Errorf("must be int64 or string") - } - - if s == _EMPTY_ { - return 0, nil - } - - suffix := s[len(s)-1:] - prefix := s[:len(s)-1] - num, err := strconv.ParseInt(prefix, 10, 64) - if err != nil { - return 0, err - } - - suffixMap := map[string]int64{"K": 10, "M": 20, "G": 30, "T": 40} - - mult, ok := suffixMap[suffix] - if !ok { - return 0, fmt.Errorf("sizes defined as strings must end in K, M, G, T") - } - num *= 1 << mult - - return num, nil -} - -// Parse enablement of jetstream for a server. -func parseJetStreamLimits(v any, opts *Options, errors *[]error) error { - var lt token - tk, v := unwrapValue(v, <) - - lim := JSLimitOpts{} - - vv, ok := v.(map[string]any) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected a map to define JetStreamLimits, got %T", v)} - } - for mk, mv := range vv { - tk, mv = unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "max_ack_pending": - lim.MaxAckPending = int(mv.(int64)) - case "max_ha_assets": - lim.MaxHAAssets = int(mv.(int64)) - case "max_request_batch": - lim.MaxRequestBatch = int(mv.(int64)) - case "duplicate_window": - var err error - lim.Duplicates, err = time.ParseDuration(mv.(string)) - if err != nil { - *errors = append(*errors, err) - } - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: mk, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - continue - } - } - } - opts.JetStreamLimits = lim - return nil -} - -// Parse the JetStream TPM options. -func parseJetStreamTPM(v interface{}, opts *Options, errors *[]error) error { - var lt token - tk, v := unwrapValue(v, <) - - tpm := JSTpmOpts{} - - vv, ok := v.(map[string]interface{}) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected a map to define JetStreamLimits, got %T", v)} - } - for mk, mv := range vv { - tk, mv = unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "keys_file": - tpm.KeysFile = mv.(string) - case "encryption_password": - tpm.KeyPassword = mv.(string) - case "srk_password": - tpm.SrkPassword = mv.(string) - case "pcr": - tpm.Pcr = int(mv.(int64)) - case "cipher": - if err := setJetStreamEkCipher(opts, mv, tk); err != nil { - return err - } - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: mk, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - continue - } - } - } - opts.JetStreamTpm = tpm - return nil -} - -func setJetStreamEkCipher(opts *Options, mv interface{}, tk token) error { - switch strings.ToLower(mv.(string)) { - case "chacha", "chachapoly": - opts.JetStreamCipher = ChaCha - case "aes": - opts.JetStreamCipher = AES - default: - return &configErr{tk, fmt.Sprintf("Unknown cipher type: %q", mv)} - } - return nil -} - -// Parse enablement of jetstream for a server. -func parseJetStream(v any, opts *Options, errors *[]error, warnings *[]error) error { - var lt token - - tk, v := unwrapValue(v, <) - - // Value here can be bool, or string "enabled" or a map. - switch vv := v.(type) { - case bool: - opts.JetStream = v.(bool) - case string: - switch strings.ToLower(vv) { - case "enabled", "enable": - opts.JetStream = true - case "disabled", "disable": - opts.JetStream = false - default: - return &configErr{tk, fmt.Sprintf("Expected 'enabled' or 'disabled' for string value, got '%s'", vv)} - } - case map[string]any: - doEnable := true - for mk, mv := range vv { - tk, mv = unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "strict": - if v, ok := mv.(bool); ok { - opts.JetStreamStrict = v - } else { - return &configErr{tk, fmt.Sprintf("Expected 'true' or 'false' for bool value, got '%s'", mv)} - } - case "store", "store_dir", "storedir": - // StoreDir can be set at the top level as well so have to prevent ambiguous declarations. - if opts.StoreDir != _EMPTY_ { - return &configErr{tk, "Duplicate 'store_dir' configuration"} - } - opts.StoreDir = mv.(string) - case "sync", "sync_interval": - if v, ok := mv.(string); ok && strings.ToLower(v) == "always" { - opts.SyncInterval = defaultSyncInterval - opts.SyncAlways = true - } else { - opts.SyncInterval = parseDuration(mk, tk, mv, errors, warnings) - } - opts.syncSet = true - case "max_memory_store", "max_mem_store", "max_mem": - s, err := getStorageSize(mv) - if err != nil { - return &configErr{tk, fmt.Sprintf("max_mem_store %s", err)} - } - opts.JetStreamMaxMemory = s - opts.maxMemSet = true - case "max_file_store", "max_file": - s, err := getStorageSize(mv) - if err != nil { - return &configErr{tk, fmt.Sprintf("max_file_store %s", err)} - } - opts.JetStreamMaxStore = s - opts.maxStoreSet = true - case "domain": - opts.JetStreamDomain = mv.(string) - case "enable", "enabled": - doEnable = mv.(bool) - case "key", "ek", "encryption_key": - opts.JetStreamKey = mv.(string) - case "prev_key", "prev_ek", "prev_encryption_key": - opts.JetStreamOldKey = mv.(string) - case "cipher": - if err := setJetStreamEkCipher(opts, mv, tk); err != nil { - return err - } - case "extension_hint": - opts.JetStreamExtHint = mv.(string) - case "limits": - if err := parseJetStreamLimits(tk, opts, errors); err != nil { - return err - } - case "tpm": - if err := parseJetStreamTPM(tk, opts, errors); err != nil { - return err - } - case "unique_tag": - opts.JetStreamUniqueTag = strings.ToLower(strings.TrimSpace(mv.(string))) - case "max_outstanding_catchup": - s, err := getStorageSize(mv) - if err != nil { - return &configErr{tk, fmt.Sprintf("%s %s", strings.ToLower(mk), err)} - } - opts.JetStreamMaxCatchup = s - case "max_buffered_size": - s, err := getStorageSize(mv) - if err != nil { - return &configErr{tk, fmt.Sprintf("%s %s", strings.ToLower(mk), err)} - } - opts.StreamMaxBufferedSize = s - case "max_buffered_msgs": - mlen, ok := mv.(int64) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected a parseable size for %q, got %v", mk, mv)} - } - opts.StreamMaxBufferedMsgs = int(mlen) - case "request_queue_limit": - lim, ok := mv.(int64) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected a parseable size for %q, got %v", mk, mv)} - } - opts.JetStreamRequestQueueLimit = lim - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: mk, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - continue - } - } - } - opts.JetStream = doEnable - default: - return &configErr{tk, fmt.Sprintf("Expected map, bool or string to define JetStream, got %T", v)} - } - - return nil -} - -// parseLeafNodes will parse the leaf node config. -func parseLeafNodes(v any, opts *Options, errors *[]error, warnings *[]error) error { - var lt token - defer convertPanicToErrorList(<, errors) - - tk, v := unwrapValue(v, <) - cm, ok := v.(map[string]any) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected map to define a leafnode, got %T", v)} - } - - for mk, mv := range cm { - // Again, unwrap token value if line check is required. - tk, mv = unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "listen": - hp, err := parseListen(mv) - if err != nil { - err := &configErr{tk, err.Error()} - *errors = append(*errors, err) - continue - } - opts.LeafNode.Host = hp.host - opts.LeafNode.Port = hp.port - case "port": - opts.LeafNode.Port = int(mv.(int64)) - case "host", "net": - opts.LeafNode.Host = mv.(string) - case "authorization": - auth, err := parseLeafAuthorization(tk, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - opts.LeafNode.Username = auth.user - opts.LeafNode.Password = auth.pass - opts.LeafNode.AuthTimeout = auth.timeout - opts.LeafNode.Account = auth.acc - opts.LeafNode.Users = auth.users - opts.LeafNode.Nkey = auth.nkey - // Validate user info config for leafnode authorization - if err := validateLeafNodeAuthOptions(opts); err != nil { - *errors = append(*errors, &configErr{tk, err.Error()}) - continue - } - case "remotes": - // Parse the remote options here. - remotes, err := parseRemoteLeafNodes(tk, errors, warnings) - if err != nil { - *errors = append(*errors, err) - continue - } - opts.LeafNode.Remotes = remotes - case "reconnect", "reconnect_delay", "reconnect_interval": - opts.LeafNode.ReconnectInterval = parseDuration("reconnect", tk, mv, errors, warnings) - case "tls": - tc, err := parseTLS(tk, true) - if err != nil { - *errors = append(*errors, err) - continue - } - if opts.LeafNode.TLSConfig, err = GenTLSConfig(tc); err != nil { - err := &configErr{tk, err.Error()} - *errors = append(*errors, err) - continue - } - opts.LeafNode.TLSTimeout = tc.Timeout - opts.LeafNode.TLSMap = tc.Map - opts.LeafNode.TLSPinnedCerts = tc.PinnedCerts - opts.LeafNode.TLSHandshakeFirst = tc.HandshakeFirst - opts.LeafNode.TLSHandshakeFirstFallback = tc.FallbackDelay - opts.LeafNode.tlsConfigOpts = tc - case "leafnode_advertise", "advertise": - opts.LeafNode.Advertise = mv.(string) - case "no_advertise": - opts.LeafNode.NoAdvertise = mv.(bool) - trackExplicitVal(&opts.inConfig, "LeafNode.NoAdvertise", opts.LeafNode.NoAdvertise) - case "min_version", "minimum_version": - version := mv.(string) - if err := checkLeafMinVersionConfig(version); err != nil { - err = &configErr{tk, err.Error()} - *errors = append(*errors, err) - continue - } - opts.LeafNode.MinVersion = version - case "compression": - if err := parseCompression(&opts.LeafNode.Compression, CompressionS2Auto, tk, mk, mv); err != nil { - *errors = append(*errors, err) - continue - } - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: mk, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - continue - } - } - } - return nil -} - -// This is the authorization parser adapter for the leafnode's -// authorization config. -func parseLeafAuthorization(v any, errors *[]error) (*authorization, error) { - var ( - am map[string]any - tk token - lt token - auth = &authorization{} - ) - defer convertPanicToErrorList(<, errors) - - _, v = unwrapValue(v, <) - am = v.(map[string]any) - for mk, mv := range am { - tk, mv = unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "user", "username": - auth.user = mv.(string) - case "pass", "password": - auth.pass = mv.(string) - case "nkey": - nk := mv.(string) - if !nkeys.IsValidPublicUserKey(nk) { - *errors = append(*errors, &configErr{tk, "Not a valid public nkey for leafnode authorization"}) - } - auth.nkey = nk - case "timeout": - at := float64(1) - switch mv := mv.(type) { - case int64: - at = float64(mv) - case float64: - at = mv - } - auth.timeout = at - case "users": - users, err := parseLeafUsers(tk, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - auth.users = users - case "account": - auth.acc = mv.(string) - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: mk, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - } - continue - } - } - return auth, nil -} - -// This is a trimmed down version of parseUsers that is adapted -// for the users possibly defined in the authorization{} section -// of leafnodes {}. -func parseLeafUsers(mv any, errors *[]error) ([]*User, error) { - var ( - tk token - lt token - users = []*User{} - ) - defer convertPanicToErrorList(<, errors) - - tk, mv = unwrapValue(mv, <) - // Make sure we have an array - uv, ok := mv.([]any) - if !ok { - return nil, &configErr{tk, fmt.Sprintf("Expected users field to be an array, got %v", mv)} - } - for _, u := range uv { - tk, u = unwrapValue(u, <) - // Check its a map/struct - um, ok := u.(map[string]any) - if !ok { - err := &configErr{tk, fmt.Sprintf("Expected user entry to be a map/struct, got %v", u)} - *errors = append(*errors, err) - continue - } - user := &User{} - for k, v := range um { - tk, v = unwrapValue(v, <) - switch strings.ToLower(k) { - case "user", "username": - user.Username = v.(string) - case "pass", "password": - user.Password = v.(string) - case "account": - // We really want to save just the account name here, but - // the User object is *Account. So we create an account object - // but it won't be registered anywhere. The server will just - // use opts.LeafNode.Users[].Account.Name. Alternatively - // we need to create internal objects to store u/p and account - // name and have a server structure to hold that. - user.Account = NewAccount(v.(string)) - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: k, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - continue - } - } - } - users = append(users, user) - } - return users, nil -} - -func parseRemoteLeafNodes(v any, errors *[]error, warnings *[]error) ([]*RemoteLeafOpts, error) { - var lt token - defer convertPanicToErrorList(<, errors) - tk, v := unwrapValue(v, <) - ra, ok := v.([]any) - if !ok { - return nil, &configErr{tk, fmt.Sprintf("Expected remotes field to be an array, got %T", v)} - } - remotes := make([]*RemoteLeafOpts, 0, len(ra)) - for _, r := range ra { - tk, r = unwrapValue(r, <) - // Check its a map/struct - rm, ok := r.(map[string]any) - if !ok { - *errors = append(*errors, &configErr{tk, fmt.Sprintf("Expected remote leafnode entry to be a map/struct, got %v", r)}) - continue - } - remote := &RemoteLeafOpts{} - for k, v := range rm { - tk, v = unwrapValue(v, <) - switch strings.ToLower(k) { - case "no_randomize", "dont_randomize": - remote.NoRandomize = v.(bool) - case "url", "urls": - switch v := v.(type) { - case []any, []string: - urls, errs := parseURLs(v.([]any), "leafnode", warnings) - if errs != nil { - *errors = append(*errors, errs...) - continue - } - remote.URLs = urls - case string: - url, err := parseURL(v, "leafnode") - if err != nil { - *errors = append(*errors, &configErr{tk, err.Error()}) - continue - } - remote.URLs = append(remote.URLs, url) - default: - *errors = append(*errors, &configErr{tk, fmt.Sprintf("Expected remote leafnode url to be an array or string, got %v", v)}) - continue - } - case "account", "local": - remote.LocalAccount = v.(string) - case "creds", "credentials": - p, err := expandPath(v.(string)) - if err != nil { - *errors = append(*errors, &configErr{tk, err.Error()}) - continue - } - // Can't have both creds and nkey - if remote.Nkey != _EMPTY_ { - *errors = append(*errors, &configErr{tk, "Remote leafnode can not have both creds and nkey defined"}) - continue - } - remote.Credentials = p - case "nkey", "seed": - nk := v.(string) - if pb, _, err := nkeys.DecodeSeed([]byte(nk)); err != nil || pb != nkeys.PrefixByteUser { - err := &configErr{tk, fmt.Sprintf("Remote leafnode nkey is not a valid seed: %q", v)} - *errors = append(*errors, err) - continue - } - if remote.Credentials != _EMPTY_ { - *errors = append(*errors, &configErr{tk, "Remote leafnode can not have both creds and nkey defined"}) - continue - } - remote.Nkey = nk - case "tls": - tc, err := parseTLS(tk, true) - if err != nil { - *errors = append(*errors, err) - continue - } - if remote.TLSConfig, err = GenTLSConfig(tc); err != nil { - *errors = append(*errors, &configErr{tk, err.Error()}) - continue - } - // If ca_file is defined, GenTLSConfig() sets TLSConfig.ClientCAs. - // Set RootCAs since this tls.Config is used when soliciting - // a connection (therefore behaves as a client). - remote.TLSConfig.RootCAs = remote.TLSConfig.ClientCAs - if tc.Timeout > 0 { - remote.TLSTimeout = tc.Timeout - } else { - remote.TLSTimeout = float64(DEFAULT_LEAF_TLS_TIMEOUT) / float64(time.Second) - } - remote.TLSHandshakeFirst = tc.HandshakeFirst - remote.tlsConfigOpts = tc - case "hub": - remote.Hub = v.(bool) - case "deny_imports", "deny_import": - subjects, err := parsePermSubjects(tk, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - remote.DenyImports = subjects - case "deny_exports", "deny_export": - subjects, err := parsePermSubjects(tk, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - remote.DenyExports = subjects - case "ws_compress", "ws_compression", "websocket_compress", "websocket_compression": - remote.Websocket.Compression = v.(bool) - case "ws_no_masking", "websocket_no_masking": - remote.Websocket.NoMasking = v.(bool) - case "jetstream_cluster_migrate", "js_cluster_migrate": - var lt token - - tk, v := unwrapValue(v, <) - switch vv := v.(type) { - case bool: - remote.JetStreamClusterMigrate = vv - case map[string]any: - remote.JetStreamClusterMigrate = true - migrateConfig, ok := v.(map[string]any) - if !ok { - continue - } - val, ok := migrateConfig["leader_migrate_delay"] - tk, delay := unwrapValue(val, &tk) - if ok { - remote.JetStreamClusterMigrateDelay = parseDuration("leader_migrate_delay", tk, delay, errors, warnings) - } - default: - *errors = append(*errors, &configErr{tk, fmt.Sprintf("Expected boolean or map for jetstream_cluster_migrate, got %T", v)}) - } - case "compression": - if err := parseCompression(&remote.Compression, CompressionS2Auto, tk, k, v); err != nil { - *errors = append(*errors, err) - continue - } - case "first_info_timeout": - remote.FirstInfoTimeout = parseDuration(k, tk, v, errors, warnings) - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: k, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - continue - } - } - } - remotes = append(remotes, remote) - } - return remotes, nil -} - -// Parse TLS and returns a TLSConfig and TLSTimeout. -// Used by cluster and gateway parsing. -func getTLSConfig(tk token) (*tls.Config, *TLSConfigOpts, error) { - tc, err := parseTLS(tk, false) - if err != nil { - return nil, nil, err - } - config, err := GenTLSConfig(tc) - if err != nil { - err := &configErr{tk, err.Error()} - return nil, nil, err - } - // For clusters/gateways, we will force strict verification. We also act - // as both client and server, so will mirror the rootCA to the - // clientCA pool. - config.ClientAuth = tls.RequireAndVerifyClientCert - config.RootCAs = config.ClientCAs - return config, tc, nil -} - -func parseGateways(v any, errors *[]error, warnings *[]error) ([]*RemoteGatewayOpts, error) { - var lt token - defer convertPanicToErrorList(<, errors) - - tk, v := unwrapValue(v, <) - // Make sure we have an array - ga, ok := v.([]any) - if !ok { - return nil, &configErr{tk, fmt.Sprintf("Expected gateways field to be an array, got %T", v)} - } - gateways := []*RemoteGatewayOpts{} - for _, g := range ga { - tk, g = unwrapValue(g, <) - // Check its a map/struct - gm, ok := g.(map[string]any) - if !ok { - *errors = append(*errors, &configErr{tk, fmt.Sprintf("Expected gateway entry to be a map/struct, got %v", g)}) - continue - } - gateway := &RemoteGatewayOpts{} - for k, v := range gm { - tk, v = unwrapValue(v, <) - switch strings.ToLower(k) { - case "name": - gateway.Name = v.(string) - case "tls": - tls, tlsopts, err := getTLSConfig(tk) - if err != nil { - *errors = append(*errors, err) - continue - } - gateway.TLSConfig = tls - gateway.TLSTimeout = tlsopts.Timeout - gateway.tlsConfigOpts = tlsopts - case "url": - url, err := parseURL(v.(string), "gateway") - if err != nil { - *errors = append(*errors, &configErr{tk, err.Error()}) - continue - } - gateway.URLs = append(gateway.URLs, url) - case "urls": - urls, errs := parseURLs(v.([]any), "gateway", warnings) - if errs != nil { - *errors = append(*errors, errs...) - continue - } - gateway.URLs = urls - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: k, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - continue - } - } - } - gateways = append(gateways, gateway) - } - return gateways, nil -} - -// Sets cluster's permissions based on given pub/sub permissions, -// doing the appropriate translation. -func setClusterPermissions(opts *ClusterOpts, perms *Permissions) { - // Import is whether or not we will send a SUB for interest to the other side. - // Export is whether or not we will accept a SUB from the remote for a given subject. - // Both only effect interest registration. - // The parsing sets Import into Publish and Export into Subscribe, convert - // accordingly. - opts.Permissions = &RoutePermissions{ - Import: perms.Publish, - Export: perms.Subscribe, - } -} - -// Temp structures to hold account import and export defintions since they need -// to be processed after being parsed. -type export struct { - acc *Account - sub string - accs []string - rt ServiceRespType - lat *serviceLatency - rthr time.Duration - tPos uint - atrc bool // allow_trace -} - -type importStream struct { - acc *Account - an string - sub string - to string - pre string - atrc bool // allow_trace -} - -type importService struct { - acc *Account - an string - sub string - to string - share bool -} - -// Checks if an account name is reserved. -func isReservedAccount(name string) bool { - return name == globalAccountName -} - -func parseAccountMapDest(v any, tk token, errors *[]error) (*MapDest, *configErr) { - // These should be maps. - mv, ok := v.(map[string]any) - if !ok { - err := &configErr{tk, "Expected an entry for the mapping destination"} - *errors = append(*errors, err) - return nil, err - } - - mdest := &MapDest{} - var lt token - var sw bool - - for k, v := range mv { - tk, dmv := unwrapValue(v, <) - switch strings.ToLower(k) { - case "dest", "destination": - mdest.Subject = dmv.(string) - case "weight": - switch vv := dmv.(type) { - case string: - ws := vv - ws = strings.TrimSuffix(ws, "%") - weight, err := strconv.Atoi(ws) - if err != nil { - err := &configErr{tk, fmt.Sprintf("Invalid weight %q for mapping destination", ws)} - *errors = append(*errors, err) - return nil, err - } - if weight > 100 || weight < 0 { - err := &configErr{tk, fmt.Sprintf("Invalid weight %d for mapping destination", weight)} - *errors = append(*errors, err) - return nil, err - } - mdest.Weight = uint8(weight) - sw = true - case int64: - weight := vv - if weight > 100 || weight < 0 { - err := &configErr{tk, fmt.Sprintf("Invalid weight %d for mapping destination", weight)} - *errors = append(*errors, err) - return nil, err - } - mdest.Weight = uint8(weight) - sw = true - default: - err := &configErr{tk, fmt.Sprintf("Unknown entry type for weight of %v\n", vv)} - *errors = append(*errors, err) - return nil, err - } - case "cluster": - mdest.Cluster = dmv.(string) - default: - err := &configErr{tk, fmt.Sprintf("Unknown field %q for mapping destination", k)} - *errors = append(*errors, err) - return nil, err - } - } - - if !sw { - err := &configErr{tk, fmt.Sprintf("Missing weight for mapping destination %q", mdest.Subject)} - *errors = append(*errors, err) - return nil, err - } - - return mdest, nil -} - -// parseAccountMappings is called to parse account mappings. -func parseAccountMappings(v any, acc *Account, errors *[]error) error { - var lt token - defer convertPanicToErrorList(<, errors) - - tk, v := unwrapValue(v, <) - am := v.(map[string]any) - for subj, mv := range am { - if !IsValidSubject(subj) { - err := &configErr{tk, fmt.Sprintf("Subject %q is not a valid subject", subj)} - *errors = append(*errors, err) - continue - } - tk, v := unwrapValue(mv, <) - - switch vv := v.(type) { - case string: - if err := acc.AddMapping(subj, v.(string)); err != nil { - err := &configErr{tk, fmt.Sprintf("Error adding mapping for %q to %q : %v", subj, v.(string), err)} - *errors = append(*errors, err) - continue - } - case []any: - var mappings []*MapDest - for _, mv := range v.([]any) { - tk, amv := unwrapValue(mv, <) - mdest, err := parseAccountMapDest(amv, tk, errors) - if err != nil { - continue - } - mappings = append(mappings, mdest) - } - - // Now add them in.. - if err := acc.AddWeightedMappings(subj, mappings...); err != nil { - err := &configErr{tk, fmt.Sprintf("Error adding mapping for %q : %v", subj, err)} - *errors = append(*errors, err) - continue - } - case any: - tk, amv := unwrapValue(mv, <) - mdest, err := parseAccountMapDest(amv, tk, errors) - if err != nil { - continue - } - // Now add it in.. - if err := acc.AddWeightedMappings(subj, mdest); err != nil { - err := &configErr{tk, fmt.Sprintf("Error adding mapping for %q : %v", subj, err)} - *errors = append(*errors, err) - continue - } - default: - err := &configErr{tk, fmt.Sprintf("Unknown type %T for mapping destination", vv)} - *errors = append(*errors, err) - continue - } - } - - return nil -} - -// parseAccountLimits is called to parse account limits in a server config. -func parseAccountLimits(mv any, acc *Account, errors *[]error) error { - var lt token - defer convertPanicToErrorList(<, errors) - - tk, v := unwrapValue(mv, <) - am, ok := v.(map[string]any) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected account limits to be a map/struct, got %+v", v)} - } - - for k, v := range am { - tk, mv = unwrapValue(v, <) - switch strings.ToLower(k) { - case "max_connections", "max_conn": - acc.mconns = int32(mv.(int64)) - case "max_subscriptions", "max_subs": - acc.msubs = int32(mv.(int64)) - case "max_payload", "max_pay": - acc.mpay = int32(mv.(int64)) - case "max_leafnodes", "max_leafs": - acc.mleafs = int32(mv.(int64)) - default: - if !tk.IsUsedVariable() { - err := &configErr{tk, fmt.Sprintf("Unknown field %q parsing account limits", k)} - *errors = append(*errors, err) - } - } - } - - return nil -} - -func parseAccountMsgTrace(mv any, topKey string, acc *Account) error { - processDest := func(tk token, k string, v any) error { - td, ok := v.(string) - if !ok { - return &configErr{tk, fmt.Sprintf("Field %q should be a string, got %T", k, v)} - } - if !IsValidPublishSubject(td) { - return &configErr{tk, fmt.Sprintf("Trace destination %q is not valid", td)} - } - acc.traceDest = td - return nil - } - processSampling := func(tk token, n int) error { - if n <= 0 || n > 100 { - return &configErr{tk, fmt.Sprintf("Ttrace destination sampling value %d is invalid, needs to be [1..100]", n)} - } - acc.traceDestSampling = n - return nil - } - - var lt token - tk, v := unwrapValue(mv, <) - switch vv := v.(type) { - case string: - return processDest(tk, topKey, v) - case map[string]any: - for k, v := range vv { - tk, v := unwrapValue(v, <) - switch strings.ToLower(k) { - case "dest": - if err := processDest(tk, k, v); err != nil { - return err - } - case "sampling": - switch vv := v.(type) { - case int64: - if err := processSampling(tk, int(vv)); err != nil { - return err - } - case string: - s := strings.TrimSuffix(vv, "%") - n, err := strconv.Atoi(s) - if err != nil { - return &configErr{tk, fmt.Sprintf("Invalid trace destination sampling value %q", vv)} - } - if err := processSampling(tk, n); err != nil { - return err - } - default: - return &configErr{tk, fmt.Sprintf("Trace destination sampling field %q should be an integer or a percentage, got %T", k, v)} - } - default: - if !tk.IsUsedVariable() { - return &configErr{tk, fmt.Sprintf("Unknown field %q parsing account message trace map/struct %q", k, topKey)} - } - } - } - default: - return &configErr{tk, fmt.Sprintf("Expected account message trace %q to be a string or a map/struct, got %T", topKey, v)} - } - return nil -} - -// parseAccounts will parse the different accounts syntax. -func parseAccounts(v any, opts *Options, errors *[]error, warnings *[]error) error { - var ( - importStreams []*importStream - importServices []*importService - exportStreams []*export - exportServices []*export - lt token - ) - defer convertPanicToErrorList(<, errors) - - tk, v := unwrapValue(v, <) - switch vv := v.(type) { - // Simple array of account names. - case []any, []string: - m := make(map[string]struct{}, len(v.([]any))) - for _, n := range v.([]any) { - tk, name := unwrapValue(n, <) - ns := name.(string) - // Check for reserved names. - if isReservedAccount(ns) { - err := &configErr{tk, fmt.Sprintf("%q is a Reserved Account", ns)} - *errors = append(*errors, err) - continue - } - if _, ok := m[ns]; ok { - err := &configErr{tk, fmt.Sprintf("Duplicate Account Entry: %s", ns)} - *errors = append(*errors, err) - continue - } - opts.Accounts = append(opts.Accounts, NewAccount(ns)) - m[ns] = struct{}{} - } - // More common map entry - case map[string]any: - // Track users across accounts, must be unique across - // accounts and nkeys vs users. - // We also want to check for users that may have been added in - // parseAuthorization{} if that happened first. - uorn := setupUsersAndNKeysDuplicateCheckMap(opts) - - for aname, mv := range vv { - tk, amv := unwrapValue(mv, <) - - // Skip referenced config vars within the account block. - if tk.IsUsedVariable() { - continue - } - - // These should be maps. - mv, ok := amv.(map[string]any) - if !ok { - err := &configErr{tk, "Expected map entries for accounts"} - *errors = append(*errors, err) - continue - } - if isReservedAccount(aname) { - err := &configErr{tk, fmt.Sprintf("%q is a Reserved Account", aname)} - *errors = append(*errors, err) - continue - } - var ( - users []*User - nkeyUsr []*NkeyUser - usersTk token - ) - acc := NewAccount(aname) - opts.Accounts = append(opts.Accounts, acc) - - for k, v := range mv { - tk, mv := unwrapValue(v, <) - switch strings.ToLower(k) { - case "nkey": - nk, ok := mv.(string) - if !ok || !nkeys.IsValidPublicAccountKey(nk) { - err := &configErr{tk, fmt.Sprintf("Not a valid public nkey for an account: %q", mv)} - *errors = append(*errors, err) - continue - } - acc.Nkey = nk - case "imports": - streams, services, err := parseAccountImports(tk, acc, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - importStreams = append(importStreams, streams...) - importServices = append(importServices, services...) - case "exports": - streams, services, err := parseAccountExports(tk, acc, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - exportStreams = append(exportStreams, streams...) - exportServices = append(exportServices, services...) - case "jetstream": - err := parseJetStreamForAccount(mv, acc, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - case "users": - var err error - usersTk = tk - nkeyUsr, users, err = parseUsers(mv, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - case "default_permissions": - permissions, err := parseUserPermissions(tk, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - acc.defaultPerms = permissions - case "mappings", "maps": - err := parseAccountMappings(tk, acc, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - case "limits": - err := parseAccountLimits(tk, acc, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - case "msg_trace", "trace_dest": - if err := parseAccountMsgTrace(tk, k, acc); err != nil { - *errors = append(*errors, err) - continue - } - // If trace destination is set but no sampling, set it to 100%. - if acc.traceDest != _EMPTY_ && acc.traceDestSampling == 0 { - acc.traceDestSampling = 100 - } else if acc.traceDestSampling > 0 && acc.traceDest == _EMPTY_ { - // If no trace destination is provided, no trace would be - // triggered, so if the user set a sampling value expecting - // something to happen, want and set the value to 0 for good - // measure. - *warnings = append(*warnings, - &configErr{tk, "Trace destination sampling ignored since no destination was set"}) - acc.traceDestSampling = 0 - } - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: k, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - } - } - } - // Report error if there is an authorization{} block - // with u/p or token and any user defined in accounts{} - if len(nkeyUsr) > 0 || len(users) > 0 { - if opts.Username != _EMPTY_ { - err := &configErr{usersTk, "Can not have a single user/pass and accounts"} - *errors = append(*errors, err) - continue - } - if opts.Authorization != _EMPTY_ { - err := &configErr{usersTk, "Can not have a token and accounts"} - *errors = append(*errors, err) - continue - } - } - applyDefaultPermissions(users, nkeyUsr, acc.defaultPerms) - for _, u := range nkeyUsr { - if _, ok := uorn[u.Nkey]; ok { - err := &configErr{usersTk, fmt.Sprintf("Duplicate nkey %q detected", u.Nkey)} - *errors = append(*errors, err) - continue - } - uorn[u.Nkey] = struct{}{} - u.Account = acc - } - opts.Nkeys = append(opts.Nkeys, nkeyUsr...) - for _, u := range users { - if _, ok := uorn[u.Username]; ok { - err := &configErr{usersTk, fmt.Sprintf("Duplicate user %q detected", u.Username)} - *errors = append(*errors, err) - continue - } - uorn[u.Username] = struct{}{} - u.Account = acc - } - opts.Users = append(opts.Users, users...) - } - } - lt = tk - // Bail already if there are previous errors. - if len(*errors) > 0 { - return nil - } - - // Parse Imports and Exports here after all accounts defined. - // Do exports first since they need to be defined for imports to succeed - // since we do permissions checks. - - // Create a lookup map for accounts lookups. - am := make(map[string]*Account, len(opts.Accounts)) - for _, a := range opts.Accounts { - am[a.Name] = a - } - // Do stream exports - for _, stream := range exportStreams { - // Make array of accounts if applicable. - var accounts []*Account - for _, an := range stream.accs { - ta := am[an] - if ta == nil { - msg := fmt.Sprintf("%q account not defined for stream export", an) - *errors = append(*errors, &configErr{tk, msg}) - continue - } - accounts = append(accounts, ta) - } - if err := stream.acc.addStreamExportWithAccountPos(stream.sub, accounts, stream.tPos); err != nil { - msg := fmt.Sprintf("Error adding stream export %q: %v", stream.sub, err) - *errors = append(*errors, &configErr{tk, msg}) - continue - } - } - for _, service := range exportServices { - // Make array of accounts if applicable. - var accounts []*Account - for _, an := range service.accs { - ta := am[an] - if ta == nil { - msg := fmt.Sprintf("%q account not defined for service export", an) - *errors = append(*errors, &configErr{tk, msg}) - continue - } - accounts = append(accounts, ta) - } - if err := service.acc.addServiceExportWithResponseAndAccountPos(service.sub, service.rt, accounts, service.tPos); err != nil { - msg := fmt.Sprintf("Error adding service export %q: %v", service.sub, err) - *errors = append(*errors, &configErr{tk, msg}) - continue - } - - if service.rthr != 0 { - // Response threshold was set in options. - if err := service.acc.SetServiceExportResponseThreshold(service.sub, service.rthr); err != nil { - msg := fmt.Sprintf("Error adding service export response threshold for %q: %v", service.sub, err) - *errors = append(*errors, &configErr{tk, msg}) - continue - } - } - - if service.lat != nil { - // System accounts are on be default so just make sure we have not opted out.. - if opts.NoSystemAccount { - msg := fmt.Sprintf("Error adding service latency sampling for %q: %v", service.sub, ErrNoSysAccount.Error()) - *errors = append(*errors, &configErr{tk, msg}) - continue - } - - if err := service.acc.TrackServiceExportWithSampling(service.sub, service.lat.subject, int(service.lat.sampling)); err != nil { - msg := fmt.Sprintf("Error adding service latency sampling for %q on subject %q: %v", service.sub, service.lat.subject, err) - *errors = append(*errors, &configErr{tk, msg}) - continue - } - } - - if service.atrc { - if err := service.acc.SetServiceExportAllowTrace(service.sub, true); err != nil { - msg := fmt.Sprintf("Error adding allow_trace for %q: %v", service.sub, err) - *errors = append(*errors, &configErr{tk, msg}) - continue - } - } - } - for _, stream := range importStreams { - ta := am[stream.an] - if ta == nil { - msg := fmt.Sprintf("%q account not defined for stream import", stream.an) - *errors = append(*errors, &configErr{tk, msg}) - continue - } - if stream.pre != _EMPTY_ { - if err := stream.acc.addStreamImportWithClaim(ta, stream.sub, stream.pre, stream.atrc, nil); err != nil { - msg := fmt.Sprintf("Error adding stream import %q: %v", stream.sub, err) - *errors = append(*errors, &configErr{tk, msg}) - continue - } - } else { - if err := stream.acc.addMappedStreamImportWithClaim(ta, stream.sub, stream.to, stream.atrc, nil); err != nil { - msg := fmt.Sprintf("Error adding stream import %q: %v", stream.sub, err) - *errors = append(*errors, &configErr{tk, msg}) - continue - } - } - } - for _, service := range importServices { - ta := am[service.an] - if ta == nil { - msg := fmt.Sprintf("%q account not defined for service import", service.an) - *errors = append(*errors, &configErr{tk, msg}) - continue - } - if service.to == _EMPTY_ { - service.to = service.sub - } - if err := service.acc.AddServiceImport(ta, service.to, service.sub); err != nil { - msg := fmt.Sprintf("Error adding service import %q: %v", service.sub, err) - *errors = append(*errors, &configErr{tk, msg}) - continue - } - if err := service.acc.SetServiceImportSharing(ta, service.sub, service.share); err != nil { - msg := fmt.Sprintf("Error setting service import sharing %q: %v", service.sub, err) - *errors = append(*errors, &configErr{tk, msg}) - continue - } - } - - return nil -} - -// Parse the account exports -func parseAccountExports(v any, acc *Account, errors *[]error) ([]*export, []*export, error) { - var lt token - defer convertPanicToErrorList(<, errors) - - // This should be an array of objects/maps. - tk, v := unwrapValue(v, <) - ims, ok := v.([]any) - if !ok { - return nil, nil, &configErr{tk, fmt.Sprintf("Exports should be an array, got %T", v)} - } - - var services []*export - var streams []*export - - for _, v := range ims { - // Should have stream or service - stream, service, err := parseExportStreamOrService(v, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - if service != nil { - service.acc = acc - services = append(services, service) - } - if stream != nil { - stream.acc = acc - streams = append(streams, stream) - } - } - return streams, services, nil -} - -// Parse the account imports -func parseAccountImports(v any, acc *Account, errors *[]error) ([]*importStream, []*importService, error) { - var lt token - defer convertPanicToErrorList(<, errors) - - // This should be an array of objects/maps. - tk, v := unwrapValue(v, <) - ims, ok := v.([]any) - if !ok { - return nil, nil, &configErr{tk, fmt.Sprintf("Imports should be an array, got %T", v)} - } - - var services []*importService - var streams []*importStream - svcSubjects := map[string]*importService{} - - for _, v := range ims { - // Should have stream or service - stream, service, err := parseImportStreamOrService(v, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - if service != nil { - if dup := svcSubjects[service.to]; dup != nil { - tk, _ := unwrapValue(v, <) - err := &configErr{tk, - fmt.Sprintf("Duplicate service import subject %q, previously used in import for account %q, subject %q", - service.to, dup.an, dup.sub)} - *errors = append(*errors, err) - continue - } - svcSubjects[service.to] = service - service.acc = acc - services = append(services, service) - } - if stream != nil { - stream.acc = acc - streams = append(streams, stream) - } - } - return streams, services, nil -} - -// Helper to parse an embedded account description for imported services or streams. -func parseAccount(v map[string]any, errors *[]error) (string, string, error) { - var lt token - defer convertPanicToErrorList(<, errors) - - var accountName, subject string - for mk, mv := range v { - tk, mv := unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "account": - accountName = mv.(string) - case "subject": - subject = mv.(string) - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: mk, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - } - } - } - return accountName, subject, nil -} - -// Parse an export stream or service. -// e.g. -// {stream: "public.>"} # No accounts means public. -// {stream: "synadia.private.>", accounts: [cncf, natsio]} -// {service: "pub.request"} # No accounts means public. -// {service: "pub.special.request", accounts: [nats.io]} -func parseExportStreamOrService(v any, errors *[]error) (*export, *export, error) { - var ( - curStream *export - curService *export - accounts []string - rt ServiceRespType - rtSeen bool - rtToken token - lat *serviceLatency - threshSeen bool - thresh time.Duration - latToken token - lt token - accTokPos uint - atrc bool - atrcSeen bool - atrcToken token - ) - defer convertPanicToErrorList(<, errors) - - tk, v := unwrapValue(v, <) - vv, ok := v.(map[string]any) - if !ok { - return nil, nil, &configErr{tk, fmt.Sprintf("Export Items should be a map with type entry, got %T", v)} - } - for mk, mv := range vv { - tk, mv := unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "stream": - if curService != nil { - err := &configErr{tk, fmt.Sprintf("Detected stream %q but already saw a service", mv)} - *errors = append(*errors, err) - continue - } - if rtToken != nil { - err := &configErr{rtToken, "Detected response directive on non-service"} - *errors = append(*errors, err) - continue - } - if latToken != nil { - err := &configErr{latToken, "Detected latency directive on non-service"} - *errors = append(*errors, err) - continue - } - if atrcToken != nil { - err := &configErr{atrcToken, "Detected allow_trace directive on non-service"} - *errors = append(*errors, err) - continue - } - mvs, ok := mv.(string) - if !ok { - err := &configErr{tk, fmt.Sprintf("Expected stream name to be string, got %T", mv)} - *errors = append(*errors, err) - continue - } - curStream = &export{sub: mvs} - if accounts != nil { - curStream.accs = accounts - } - case "service": - if curStream != nil { - err := &configErr{tk, fmt.Sprintf("Detected service %q but already saw a stream", mv)} - *errors = append(*errors, err) - continue - } - mvs, ok := mv.(string) - if !ok { - err := &configErr{tk, fmt.Sprintf("Expected service name to be string, got %T", mv)} - *errors = append(*errors, err) - continue - } - curService = &export{sub: mvs} - if accounts != nil { - curService.accs = accounts - } - if rtSeen { - curService.rt = rt - } - if lat != nil { - curService.lat = lat - } - if threshSeen { - curService.rthr = thresh - } - if atrcSeen { - curService.atrc = atrc - } - case "response", "response_type": - if rtSeen { - err := &configErr{tk, "Duplicate response type definition"} - *errors = append(*errors, err) - continue - } - rtSeen = true - rtToken = tk - mvs, ok := mv.(string) - if !ok { - err := &configErr{tk, fmt.Sprintf("Expected response type to be string, got %T", mv)} - *errors = append(*errors, err) - continue - } - switch strings.ToLower(mvs) { - case "single", "singleton": - rt = Singleton - case "stream": - rt = Streamed - case "chunk", "chunked": - rt = Chunked - default: - err := &configErr{tk, fmt.Sprintf("Unknown response type: %q", mvs)} - *errors = append(*errors, err) - continue - } - if curService != nil { - curService.rt = rt - } - if curStream != nil { - err := &configErr{tk, "Detected response directive on non-service"} - *errors = append(*errors, err) - } - case "threshold", "response_threshold", "response_max_time", "response_time": - if threshSeen { - err := &configErr{tk, "Duplicate response threshold detected"} - *errors = append(*errors, err) - continue - } - threshSeen = true - mvs, ok := mv.(string) - if !ok { - err := &configErr{tk, fmt.Sprintf("Expected response threshold to be a parseable time duration, got %T", mv)} - *errors = append(*errors, err) - continue - } - var err error - thresh, err = time.ParseDuration(mvs) - if err != nil { - err := &configErr{tk, fmt.Sprintf("Expected response threshold to be a parseable time duration, got %q", mvs)} - *errors = append(*errors, err) - continue - } - if curService != nil { - curService.rthr = thresh - } - if curStream != nil { - err := &configErr{tk, "Detected response directive on non-service"} - *errors = append(*errors, err) - } - case "accounts": - for _, iv := range mv.([]any) { - _, mv := unwrapValue(iv, <) - accounts = append(accounts, mv.(string)) - } - if curStream != nil { - curStream.accs = accounts - } else if curService != nil { - curService.accs = accounts - } - case "latency": - latToken = tk - var err error - lat, err = parseServiceLatency(tk, mv) - if err != nil { - *errors = append(*errors, err) - continue - } - if curStream != nil { - err = &configErr{tk, "Detected latency directive on non-service"} - *errors = append(*errors, err) - continue - } - if curService != nil { - curService.lat = lat - } - case "account_token_position": - accTokPos = uint(mv.(int64)) - case "allow_trace": - atrcSeen = true - atrcToken = tk - atrc = mv.(bool) - if curStream != nil { - *errors = append(*errors, - &configErr{tk, "Detected allow_trace directive on non-service"}) - continue - } - if curService != nil { - curService.atrc = atrc - } - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: mk, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - } - } - } - if curStream != nil { - curStream.tPos = accTokPos - } - if curService != nil { - curService.tPos = accTokPos - } - return curStream, curService, nil -} - -// parseServiceLatency returns a latency config block. -func parseServiceLatency(root token, v any) (l *serviceLatency, retErr error) { - var lt token - defer convertPanicToError(<, &retErr) - - if subject, ok := v.(string); ok { - return &serviceLatency{ - subject: subject, - sampling: DEFAULT_SERVICE_LATENCY_SAMPLING, - }, nil - } - - latency, ok := v.(map[string]any) - if !ok { - return nil, &configErr{token: root, - reason: fmt.Sprintf("Expected latency entry to be a map/struct or string, got %T", v)} - } - - sl := serviceLatency{ - sampling: DEFAULT_SERVICE_LATENCY_SAMPLING, - } - - // Read sampling value. - if v, ok := latency["sampling"]; ok { - tk, v := unwrapValue(v, <) - header := false - var sample int64 - switch vv := v.(type) { - case int64: - // Sample is an int, like 50. - sample = vv - case string: - // Sample is a string, like "50%". - if strings.ToLower(strings.TrimSpace(vv)) == "headers" { - header = true - sample = 0 - break - } - s := strings.TrimSuffix(vv, "%") - n, err := strconv.Atoi(s) - if err != nil { - return nil, &configErr{token: tk, - reason: fmt.Sprintf("Failed to parse latency sample: %v", err)} - } - sample = int64(n) - default: - return nil, &configErr{token: tk, - reason: fmt.Sprintf("Expected latency sample to be a string or map/struct, got %T", v)} - } - if !header { - if sample < 1 || sample > 100 { - return nil, &configErr{token: tk, - reason: ErrBadSampling.Error()} - } - } - - sl.sampling = int8(sample) - } - - // Read subject value. - v, ok = latency["subject"] - if !ok { - return nil, &configErr{token: root, - reason: "Latency subject required, but missing"} - } - - tk, v := unwrapValue(v, <) - subject, ok := v.(string) - if !ok { - return nil, &configErr{token: tk, - reason: fmt.Sprintf("Expected latency subject to be a string, got %T", subject)} - } - sl.subject = subject - - return &sl, nil -} - -// Parse an import stream or service. -// e.g. -// {stream: {account: "synadia", subject:"public.synadia"}, prefix: "imports.synadia"} -// {stream: {account: "synadia", subject:"synadia.private.*"}} -// {service: {account: "synadia", subject: "pub.special.request"}, to: "synadia.request"} -func parseImportStreamOrService(v any, errors *[]error) (*importStream, *importService, error) { - var ( - curStream *importStream - curService *importService - pre, to string - share bool - lt token - atrc bool - atrcSeen bool - atrcToken token - ) - defer convertPanicToErrorList(<, errors) - - tk, mv := unwrapValue(v, <) - vv, ok := mv.(map[string]any) - if !ok { - return nil, nil, &configErr{tk, fmt.Sprintf("Import Items should be a map with type entry, got %T", mv)} - } - for mk, mv := range vv { - tk, mv := unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "stream": - if curService != nil { - err := &configErr{tk, "Detected stream but already saw a service"} - *errors = append(*errors, err) - continue - } - ac, ok := mv.(map[string]any) - if !ok { - err := &configErr{tk, fmt.Sprintf("Stream entry should be an account map, got %T", mv)} - *errors = append(*errors, err) - continue - } - // Make sure this is a map with account and subject - accountName, subject, err := parseAccount(ac, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - if accountName == _EMPTY_ || subject == _EMPTY_ { - err := &configErr{tk, "Expect an account name and a subject"} - *errors = append(*errors, err) - continue - } - curStream = &importStream{an: accountName, sub: subject} - if to != _EMPTY_ { - curStream.to = to - } - if pre != _EMPTY_ { - curStream.pre = pre - } - if atrcSeen { - curStream.atrc = atrc - } - case "service": - if curStream != nil { - err := &configErr{tk, "Detected service but already saw a stream"} - *errors = append(*errors, err) - continue - } - if atrcToken != nil { - err := &configErr{atrcToken, "Detected allow_trace directive on a non-stream"} - *errors = append(*errors, err) - continue - } - ac, ok := mv.(map[string]any) - if !ok { - err := &configErr{tk, fmt.Sprintf("Service entry should be an account map, got %T", mv)} - *errors = append(*errors, err) - continue - } - // Make sure this is a map with account and subject - accountName, subject, err := parseAccount(ac, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - if accountName == _EMPTY_ || subject == _EMPTY_ { - err := &configErr{tk, "Expect an account name and a subject"} - *errors = append(*errors, err) - continue - } - curService = &importService{an: accountName, sub: subject} - if to != _EMPTY_ { - curService.to = to - } else { - curService.to = subject - } - curService.share = share - case "prefix": - pre = mv.(string) - if curStream != nil { - curStream.pre = pre - } - case "to": - to = mv.(string) - if curService != nil { - curService.to = to - } - if curStream != nil { - curStream.to = to - if curStream.pre != _EMPTY_ { - err := &configErr{tk, "Stream import can not have a 'prefix' and a 'to' property"} - *errors = append(*errors, err) - continue - } - } - case "share": - share = mv.(bool) - if curService != nil { - curService.share = share - } - case "allow_trace": - if curService != nil { - err := &configErr{tk, "Detected allow_trace directive on a non-stream"} - *errors = append(*errors, err) - continue - } - atrcSeen = true - atrc = mv.(bool) - atrcToken = tk - if curStream != nil { - curStream.atrc = atrc - } - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: mk, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - } - } - - } - return curStream, curService, nil -} - -// Apply permission defaults to users/nkeyuser that don't have their own. -func applyDefaultPermissions(users []*User, nkeys []*NkeyUser, defaultP *Permissions) { - if defaultP == nil { - return - } - for _, user := range users { - if user.Permissions == nil { - user.Permissions = defaultP - } - } - for _, user := range nkeys { - if user.Permissions == nil { - user.Permissions = defaultP - } - } -} - -// Helper function to parse Authorization configs. -func parseAuthorization(v any, errors *[]error) (*authorization, error) { - var ( - am map[string]any - tk token - lt token - auth = &authorization{} - ) - defer convertPanicToErrorList(<, errors) - - _, v = unwrapValue(v, <) - am = v.(map[string]any) - for mk, mv := range am { - tk, mv = unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "user", "username": - auth.user = mv.(string) - case "pass", "password": - auth.pass = mv.(string) - case "token": - auth.token = mv.(string) - case "timeout": - at := float64(1) - switch mv := mv.(type) { - case int64: - at = float64(mv) - case float64: - at = mv - } - auth.timeout = at - case "users": - nkeys, users, err := parseUsers(tk, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - auth.users = users - auth.nkeys = nkeys - case "default_permission", "default_permissions", "permissions": - permissions, err := parseUserPermissions(tk, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - auth.defaultPermissions = permissions - case "auth_callout", "auth_hook": - ac, err := parseAuthCallout(tk, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - auth.callout = ac - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: mk, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - } - continue - } - - applyDefaultPermissions(auth.users, auth.nkeys, auth.defaultPermissions) - } - return auth, nil -} - -// Helper function to parse multiple users array with optional permissions. -func parseUsers(mv any, errors *[]error) ([]*NkeyUser, []*User, error) { - var ( - tk token - lt token - keys []*NkeyUser - users = []*User{} - ) - defer convertPanicToErrorList(<, errors) - tk, mv = unwrapValue(mv, <) - - // Make sure we have an array - uv, ok := mv.([]any) - if !ok { - return nil, nil, &configErr{tk, fmt.Sprintf("Expected users field to be an array, got %v", mv)} - } - for _, u := range uv { - tk, u = unwrapValue(u, <) - - // Check its a map/struct - um, ok := u.(map[string]any) - if !ok { - err := &configErr{tk, fmt.Sprintf("Expected user entry to be a map/struct, got %v", u)} - *errors = append(*errors, err) - continue - } - - var ( - user = &User{} - nkey = &NkeyUser{} - perms *Permissions - err error - ) - for k, v := range um { - // Also needs to unwrap first - tk, v = unwrapValue(v, <) - - switch strings.ToLower(k) { - case "nkey": - nkey.Nkey = v.(string) - case "user", "username": - user.Username = v.(string) - case "pass", "password": - user.Password = v.(string) - case "permission", "permissions", "authorization": - perms, err = parseUserPermissions(tk, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - case "allowed_connection_types", "connection_types", "clients": - cts := parseAllowedConnectionTypes(tk, <, v, errors) - nkey.AllowedConnectionTypes = cts - user.AllowedConnectionTypes = cts - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: k, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - continue - } - } - } - // Place perms if we have them. - if perms != nil { - // nkey takes precedent. - if nkey.Nkey != _EMPTY_ { - nkey.Permissions = perms - } else { - user.Permissions = perms - } - } - - // Check to make sure we have at least an nkey or username defined. - if nkey.Nkey == _EMPTY_ && user.Username == _EMPTY_ { - return nil, nil, &configErr{tk, "User entry requires a user"} - } else if nkey.Nkey != _EMPTY_ { - // Make sure the nkey a proper public nkey for a user.. - if !nkeys.IsValidPublicUserKey(nkey.Nkey) { - return nil, nil, &configErr{tk, "Not a valid public nkey for a user"} - } - // If we have user or password defined here that is an error. - if user.Username != _EMPTY_ || user.Password != _EMPTY_ { - return nil, nil, &configErr{tk, "Nkey users do not take usernames or passwords"} - } - keys = append(keys, nkey) - } else { - users = append(users, user) - } - } - return keys, users, nil -} - -func parseAllowedConnectionTypes(tk token, lt *token, mv any, errors *[]error) map[string]struct{} { - cts, err := parseStringArray("allowed connection types", tk, lt, mv, errors) - // If error, it has already been added to the `errors` array, simply return - if err != nil { - return nil - } - m, err := convertAllowedConnectionTypes(cts) - if err != nil { - *errors = append(*errors, &configErr{tk, err.Error()}) - } - return m -} - -// Helper function to parse auth callouts. -func parseAuthCallout(mv any, errors *[]error) (*AuthCallout, error) { - var ( - tk token - lt token - ac = &AuthCallout{} - ) - defer convertPanicToErrorList(<, errors) - - tk, mv = unwrapValue(mv, <) - pm, ok := mv.(map[string]any) - if !ok { - return nil, &configErr{tk, fmt.Sprintf("Expected authorization callout to be a map/struct, got %+v", mv)} - } - for k, v := range pm { - tk, mv = unwrapValue(v, <) - - switch strings.ToLower(k) { - case "issuer": - ac.Issuer = mv.(string) - if !nkeys.IsValidPublicAccountKey(ac.Issuer) { - return nil, &configErr{tk, fmt.Sprintf("Expected callout user to be a valid public account nkey, got %q", ac.Issuer)} - } - case "account", "acc": - ac.Account = mv.(string) - case "auth_users", "users": - aua, ok := mv.([]any) - if !ok { - return nil, &configErr{tk, fmt.Sprintf("Expected auth_users field to be an array, got %T", v)} - } - for _, uv := range aua { - _, uv = unwrapValue(uv, <) - ac.AuthUsers = append(ac.AuthUsers, uv.(string)) - } - case "xkey", "key": - ac.XKey = mv.(string) - if !nkeys.IsValidPublicCurveKey(ac.XKey) { - return nil, &configErr{tk, fmt.Sprintf("Expected callout xkey to be a valid public xkey, got %q", ac.XKey)} - } - case "allowed_accounts": - aua, ok := mv.([]any) - if !ok { - return nil, &configErr{tk, fmt.Sprintf("Expected allowed accounts field to be an array, got %T", v)} - } - for _, uv := range aua { - _, uv = unwrapValue(uv, <) - ac.AllowedAccounts = append(ac.AllowedAccounts, uv.(string)) - } - default: - if !tk.IsUsedVariable() { - err := &configErr{tk, fmt.Sprintf("Unknown field %q parsing authorization callout", k)} - *errors = append(*errors, err) - } - } - } - // Make sure we have all defined. All fields are required. - // If no account specified, selet $G. - if ac.Account == _EMPTY_ { - ac.Account = globalAccountName - } - if ac.Issuer == _EMPTY_ { - return nil, &configErr{tk, "Authorization callouts require an issuer to be specified"} - } - if len(ac.AuthUsers) == 0 { - return nil, &configErr{tk, "Authorization callouts require authorized users to be specified"} - } - return ac, nil -} - -// Helper function to parse user/account permissions -func parseUserPermissions(mv any, errors *[]error) (*Permissions, error) { - var ( - tk token - lt token - p = &Permissions{} - ) - defer convertPanicToErrorList(<, errors) - - tk, mv = unwrapValue(mv, <) - pm, ok := mv.(map[string]any) - if !ok { - return nil, &configErr{tk, fmt.Sprintf("Expected permissions to be a map/struct, got %+v", mv)} - } - for k, v := range pm { - tk, mv = unwrapValue(v, <) - - switch strings.ToLower(k) { - // For routes: - // Import is Publish - // Export is Subscribe - case "pub", "publish", "import": - perms, err := parseVariablePermissions(mv, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - p.Publish = perms - case "sub", "subscribe", "export": - perms, err := parseVariablePermissions(mv, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - p.Subscribe = perms - case "publish_allow_responses", "allow_responses": - rp := &ResponsePermission{ - MaxMsgs: DEFAULT_ALLOW_RESPONSE_MAX_MSGS, - Expires: DEFAULT_ALLOW_RESPONSE_EXPIRATION, - } - // Try boolean first - responses, ok := mv.(bool) - if ok { - if responses { - p.Response = rp - } - } else { - p.Response = parseAllowResponses(v, errors) - } - if p.Response != nil { - if p.Publish == nil { - p.Publish = &SubjectPermission{} - } - if p.Publish.Allow == nil { - // We turn off the blanket allow statement. - p.Publish.Allow = []string{} - } - } - default: - if !tk.IsUsedVariable() { - err := &configErr{tk, fmt.Sprintf("Unknown field %q parsing permissions", k)} - *errors = append(*errors, err) - } - } - } - return p, nil -} - -// Top level parser for authorization configurations. -func parseVariablePermissions(v any, errors *[]error) (*SubjectPermission, error) { - switch vv := v.(type) { - case map[string]any: - // New style with allow and/or deny properties. - return parseSubjectPermission(vv, errors) - default: - // Old style - return parseOldPermissionStyle(v, errors) - } -} - -// Helper function to parse subject singletons and/or arrays -func parsePermSubjects(v any, errors *[]error) ([]string, error) { - var lt token - defer convertPanicToErrorList(<, errors) - - tk, v := unwrapValue(v, <) - - var subjects []string - switch vv := v.(type) { - case string: - subjects = append(subjects, vv) - case []string: - subjects = vv - case []any: - for _, i := range vv { - tk, i := unwrapValue(i, <) - - subject, ok := i.(string) - if !ok { - return nil, &configErr{tk, "Subject in permissions array cannot be cast to string"} - } - subjects = append(subjects, subject) - } - default: - return nil, &configErr{tk, fmt.Sprintf("Expected subject permissions to be a subject, or array of subjects, got %T", v)} - } - if err := checkPermSubjectArray(subjects); err != nil { - return nil, &configErr{tk, err.Error()} - } - return subjects, nil -} - -// Helper function to parse a ResponsePermission. -func parseAllowResponses(v any, errors *[]error) *ResponsePermission { - var lt token - defer convertPanicToErrorList(<, errors) - - tk, v := unwrapValue(v, <) - // Check if this is a map. - pm, ok := v.(map[string]any) - if !ok { - err := &configErr{tk, "error parsing response permissions, expected a boolean or a map"} - *errors = append(*errors, err) - return nil - } - - rp := &ResponsePermission{ - MaxMsgs: DEFAULT_ALLOW_RESPONSE_MAX_MSGS, - Expires: DEFAULT_ALLOW_RESPONSE_EXPIRATION, - } - - for k, v := range pm { - tk, v = unwrapValue(v, <) - switch strings.ToLower(k) { - case "max", "max_msgs", "max_messages", "max_responses": - max := int(v.(int64)) - // Negative values are accepted (mean infinite), and 0 - // means default value (set above). - if max != 0 { - rp.MaxMsgs = max - } - case "expires", "expiration", "ttl": - wd, ok := v.(string) - if ok { - ttl, err := time.ParseDuration(wd) - if err != nil { - err := &configErr{tk, fmt.Sprintf("error parsing expires: %v", err)} - *errors = append(*errors, err) - return nil - } - // Negative values are accepted (mean infinite), and 0 - // means default value (set above). - if ttl != 0 { - rp.Expires = ttl - } - } else { - err := &configErr{tk, "error parsing expires, not a duration string"} - *errors = append(*errors, err) - return nil - } - default: - if !tk.IsUsedVariable() { - err := &configErr{tk, fmt.Sprintf("Unknown field %q parsing permissions", k)} - *errors = append(*errors, err) - } - } - } - return rp -} - -// Helper function to parse old style authorization configs. -func parseOldPermissionStyle(v any, errors *[]error) (*SubjectPermission, error) { - subjects, err := parsePermSubjects(v, errors) - if err != nil { - return nil, err - } - return &SubjectPermission{Allow: subjects}, nil -} - -// Helper function to parse new style authorization into a SubjectPermission with Allow and Deny. -func parseSubjectPermission(v any, errors *[]error) (*SubjectPermission, error) { - var lt token - defer convertPanicToErrorList(<, errors) - - m := v.(map[string]any) - if len(m) == 0 { - return nil, nil - } - p := &SubjectPermission{} - for k, v := range m { - tk, _ := unwrapValue(v, <) - switch strings.ToLower(k) { - case "allow": - subjects, err := parsePermSubjects(tk, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - p.Allow = subjects - case "deny": - subjects, err := parsePermSubjects(tk, errors) - if err != nil { - *errors = append(*errors, err) - continue - } - p.Deny = subjects - default: - if !tk.IsUsedVariable() { - err := &configErr{tk, fmt.Sprintf("Unknown field name %q parsing subject permissions, only 'allow' or 'deny' are permitted", k)} - *errors = append(*errors, err) - } - } - } - return p, nil -} - -// Helper function to validate permissions subjects. -func checkPermSubjectArray(sa []string) error { - for _, s := range sa { - if !IsValidSubject(s) { - // Check here if this is a queue group qualified subject. - elements := strings.Fields(s) - if len(elements) != 2 { - return fmt.Errorf("subject %q is not a valid subject", s) - } else if !IsValidSubject(elements[0]) { - return fmt.Errorf("subject %q is not a valid subject", elements[0]) - } - } - } - return nil -} - -// PrintTLSHelpAndDie prints TLS usage and exits. -func PrintTLSHelpAndDie() { - fmt.Printf("%s", tlsUsage) - for k := range cipherMap { - fmt.Printf(" %s\n", k) - } - fmt.Printf("\nAvailable curve preferences include:\n") - for k := range curvePreferenceMap { - fmt.Printf(" %s\n", k) - } - if runtime.GOOS == "windows" { - fmt.Printf("%s\n", certstore.Usage) - } - fmt.Printf("%s", certidp.OCSPPeerUsage) - fmt.Printf("%s", OCSPResponseCacheUsage) - os.Exit(0) -} - -func parseCipher(cipherName string) (uint16, error) { - cipher, exists := cipherMap[cipherName] - if !exists { - return 0, fmt.Errorf("unrecognized cipher %s", cipherName) - } - - return cipher, nil -} - -func parseCurvePreferences(curveName string) (tls.CurveID, error) { - curve, exists := curvePreferenceMap[curveName] - if !exists { - return 0, fmt.Errorf("unrecognized curve preference %s", curveName) - } - return curve, nil -} - -func parseTLSVersion(v any) (uint16, error) { - var tlsVersionNumber uint16 - switch v := v.(type) { - case string: - n, err := tlsVersionFromString(v) - if err != nil { - return 0, err - } - tlsVersionNumber = n - default: - return 0, fmt.Errorf("'min_version' wrong type: %v", v) - } - if tlsVersionNumber < tls.VersionTLS12 { - return 0, fmt.Errorf("unsupported TLS version: %s", tls.VersionName(tlsVersionNumber)) - } - return tlsVersionNumber, nil -} - -// Helper function to parse TLS configs. -func parseTLS(v any, isClientCtx bool) (t *TLSConfigOpts, retErr error) { - var ( - tlsm map[string]any - tc = TLSConfigOpts{} - lt token - ) - defer convertPanicToError(<, &retErr) - - tk, v := unwrapValue(v, <) - tlsm = v.(map[string]any) - for mk, mv := range tlsm { - tk, mv := unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "cert_file": - certFile, ok := mv.(string) - if !ok { - return nil, &configErr{tk, "error parsing tls config, expected 'cert_file' to be filename"} - } - tc.CertFile = certFile - case "key_file": - keyFile, ok := mv.(string) - if !ok { - return nil, &configErr{tk, "error parsing tls config, expected 'key_file' to be filename"} - } - tc.KeyFile = keyFile - case "ca_file": - caFile, ok := mv.(string) - if !ok { - return nil, &configErr{tk, "error parsing tls config, expected 'ca_file' to be filename"} - } - tc.CaFile = caFile - case "insecure": - insecure, ok := mv.(bool) - if !ok { - return nil, &configErr{tk, "error parsing tls config, expected 'insecure' to be a boolean"} - } - tc.Insecure = insecure - case "verify": - verify, ok := mv.(bool) - if !ok { - return nil, &configErr{tk, "error parsing tls config, expected 'verify' to be a boolean"} - } - tc.Verify = verify - case "verify_and_map": - verify, ok := mv.(bool) - if !ok { - return nil, &configErr{tk, "error parsing tls config, expected 'verify_and_map' to be a boolean"} - } - if verify { - tc.Verify = verify - } - tc.Map = verify - case "verify_cert_and_check_known_urls": - verify, ok := mv.(bool) - if !ok { - return nil, &configErr{tk, "error parsing tls config, expected 'verify_cert_and_check_known_urls' to be a boolean"} - } - if verify && isClientCtx { - return nil, &configErr{tk, "verify_cert_and_check_known_urls not supported in this context"} - } - if verify { - tc.Verify = verify - } - tc.TLSCheckKnownURLs = verify - case "cipher_suites": - ra := mv.([]any) - if len(ra) == 0 { - return nil, &configErr{tk, "error parsing tls config, 'cipher_suites' cannot be empty"} - } - tc.Ciphers = make([]uint16, 0, len(ra)) - for _, r := range ra { - tk, r := unwrapValue(r, <) - cipher, err := parseCipher(r.(string)) - if err != nil { - return nil, &configErr{tk, err.Error()} - } - tc.Ciphers = append(tc.Ciphers, cipher) - } - case "curve_preferences": - ra := mv.([]any) - if len(ra) == 0 { - return nil, &configErr{tk, "error parsing tls config, 'curve_preferences' cannot be empty"} - } - tc.CurvePreferences = make([]tls.CurveID, 0, len(ra)) - for _, r := range ra { - tk, r := unwrapValue(r, <) - cps, err := parseCurvePreferences(r.(string)) - if err != nil { - return nil, &configErr{tk, err.Error()} - } - tc.CurvePreferences = append(tc.CurvePreferences, cps) - } - case "timeout": - at := float64(0) - switch mv := mv.(type) { - case int64: - at = float64(mv) - case float64: - at = mv - case string: - d, err := time.ParseDuration(mv) - if err != nil { - return nil, &configErr{tk, fmt.Sprintf("error parsing tls config, 'timeout' %s", err)} - } - at = d.Seconds() - default: - return nil, &configErr{tk, "error parsing tls config, 'timeout' wrong type"} - } - tc.Timeout = at - case "connection_rate_limit": - at := int64(0) - switch mv := mv.(type) { - case int64: - at = mv - default: - return nil, &configErr{tk, "error parsing tls config, 'connection_rate_limit' wrong type"} - } - tc.RateLimit = at - case "pinned_certs": - ra, ok := mv.([]any) - if !ok { - return nil, &configErr{tk, "error parsing tls config, expected 'pinned_certs' to be a list of hex-encoded sha256 of DER encoded SubjectPublicKeyInfo"} - } - if len(ra) != 0 { - wl := PinnedCertSet{} - re := regexp.MustCompile("^[A-Fa-f0-9]{64}$") - for _, r := range ra { - tk, r := unwrapValue(r, <) - entry := strings.ToLower(r.(string)) - if !re.MatchString(entry) { - return nil, &configErr{tk, fmt.Sprintf("error parsing tls config, 'pinned_certs' key %s does not look like hex-encoded sha256 of DER encoded SubjectPublicKeyInfo", entry)} - } - wl[entry] = struct{}{} - } - tc.PinnedCerts = wl - } - case "cert_store": - certStore, ok := mv.(string) - if !ok || certStore == _EMPTY_ { - return nil, &configErr{tk, certstore.ErrBadCertStoreField.Error()} - } - certStoreType, err := certstore.ParseCertStore(certStore) - if err != nil { - return nil, &configErr{tk, err.Error()} - } - tc.CertStore = certStoreType - case "cert_match_by": - certMatchBy, ok := mv.(string) - if !ok || certMatchBy == _EMPTY_ { - return nil, &configErr{tk, certstore.ErrBadCertMatchByField.Error()} - } - certMatchByType, err := certstore.ParseCertMatchBy(certMatchBy) - if err != nil { - return nil, &configErr{tk, err.Error()} - } - tc.CertMatchBy = certMatchByType - case "cert_match": - certMatch, ok := mv.(string) - if !ok || certMatch == _EMPTY_ { - return nil, &configErr{tk, certstore.ErrBadCertMatchField.Error()} - } - tc.CertMatch = certMatch - case "ca_certs_match": - rv := []string{} - switch mv := mv.(type) { - case string: - rv = append(rv, mv) - case []string: - rv = append(rv, mv...) - case []any: - for _, t := range mv { - if token, ok := t.(token); ok { - if ts, ok := token.Value().(string); ok { - rv = append(rv, ts) - continue - } else { - return nil, &configErr{tk, fmt.Sprintf("error parsing ca_cert_match: unsupported type %T where string is expected", token)} - } - } else { - return nil, &configErr{tk, fmt.Sprintf("error parsing ca_cert_match: unsupported type %T", t)} - } - } - } - tc.CaCertsMatch = rv - case "handshake_first", "first", "immediate": - switch mv := mv.(type) { - case bool: - tc.HandshakeFirst = mv - case string: - switch strings.ToLower(mv) { - case "true", "on": - tc.HandshakeFirst = true - case "false", "off": - tc.HandshakeFirst = false - case "auto", "auto_fallback": - tc.HandshakeFirst = true - tc.FallbackDelay = DEFAULT_TLS_HANDSHAKE_FIRST_FALLBACK_DELAY - default: - // Check to see if this is a duration. - if dur, err := time.ParseDuration(mv); err == nil { - tc.HandshakeFirst = true - tc.FallbackDelay = dur - break - } - return nil, &configErr{tk, fmt.Sprintf("field %q's value %q is invalid", mk, mv)} - } - default: - return nil, &configErr{tk, fmt.Sprintf("field %q should be a boolean or a string, got %T", mk, mv)} - } - case "cert_match_skip_invalid": - certMatchSkipInvalid, ok := mv.(bool) - if !ok { - return nil, &configErr{tk, certstore.ErrBadCertMatchSkipInvalidField.Error()} - } - tc.CertMatchSkipInvalid = certMatchSkipInvalid - case "ocsp_peer": - switch vv := mv.(type) { - case bool: - pc := certidp.NewOCSPPeerConfig() - if vv { - // Set enabled - pc.Verify = true - tc.OCSPPeerConfig = pc - } else { - // Set disabled - pc.Verify = false - tc.OCSPPeerConfig = pc - } - case map[string]any: - pc, err := parseOCSPPeer(mv) - if err != nil { - return nil, &configErr{tk, err.Error()} - } - tc.OCSPPeerConfig = pc - default: - return nil, &configErr{tk, fmt.Sprintf("error parsing ocsp peer config: unsupported type %T", v)} - } - case "certs", "certificates": - certs, ok := mv.([]any) - if !ok { - return nil, &configErr{tk, fmt.Sprintf("error parsing certificates config: unsupported type %T", v)} - } - tc.Certificates = make([]*TLSCertPairOpt, len(certs)) - for i, v := range certs { - tk, vv := unwrapValue(v, <) - pair, ok := vv.(map[string]any) - if !ok { - return nil, &configErr{tk, fmt.Sprintf("error parsing certificates config: unsupported type %T", vv)} - } - certPair := &TLSCertPairOpt{} - for k, v := range pair { - tk, vv = unwrapValue(v, <) - file, ok := vv.(string) - if !ok { - return nil, &configErr{tk, fmt.Sprintf("error parsing certificates config: unsupported type %T", vv)} - } - switch k { - case "cert_file": - certPair.CertFile = file - case "key_file": - certPair.KeyFile = file - default: - return nil, &configErr{tk, fmt.Sprintf("error parsing tls certs config, unknown field %q", k)} - } - } - if certPair.CertFile == _EMPTY_ || certPair.KeyFile == _EMPTY_ { - return nil, &configErr{tk, "error parsing certificates config: both 'cert_file' and 'cert_key' options are required"} - } - tc.Certificates[i] = certPair - } - case "min_version": - minVersion, err := parseTLSVersion(mv) - if err != nil { - return nil, &configErr{tk, fmt.Sprintf("error parsing tls config: %v", err)} - } - tc.MinVersion = minVersion - default: - return nil, &configErr{tk, fmt.Sprintf("error parsing tls config, unknown field %q", mk)} - } - } - if len(tc.Certificates) > 0 && tc.CertFile != _EMPTY_ { - return nil, &configErr{tk, "error parsing tls config, cannot combine 'cert_file' option with 'certs' option"} - } - - // If cipher suites were not specified then use the defaults - if tc.Ciphers == nil { - tc.Ciphers = defaultCipherSuites() - } - - // If curve preferences were not specified, then use the defaults - if tc.CurvePreferences == nil { - tc.CurvePreferences = defaultCurvePreferences() - } - - return &tc, nil -} - -func parseSimpleAuth(v any, errors *[]error) *authorization { - var ( - am map[string]any - tk token - lt token - auth = &authorization{} - ) - defer convertPanicToErrorList(<, errors) - - _, v = unwrapValue(v, <) - am = v.(map[string]any) - for mk, mv := range am { - tk, mv = unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "user", "username": - auth.user = mv.(string) - case "pass", "password": - auth.pass = mv.(string) - case "token": - auth.token = mv.(string) - case "timeout": - at := float64(1) - switch mv := mv.(type) { - case int64: - at = float64(mv) - case float64: - at = mv - } - auth.timeout = at - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: mk, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - } - continue - } - } - return auth -} - -func parseStringArray(fieldName string, tk token, lt *token, mv any, errors *[]error) ([]string, error) { - switch mv := mv.(type) { - case string: - return []string{mv}, nil - case []any: - strs := make([]string, 0, len(mv)) - for _, val := range mv { - tk, val = unwrapValue(val, lt) - if str, ok := val.(string); ok { - strs = append(strs, str) - } else { - err := &configErr{tk, fmt.Sprintf("error parsing %s: unsupported type in array %T", fieldName, val)} - *errors = append(*errors, err) - continue - } - } - return strs, nil - default: - err := &configErr{tk, fmt.Sprintf("error parsing %s: unsupported type %T", fieldName, mv)} - *errors = append(*errors, err) - return nil, err - } -} - -func parseWebsocket(v any, o *Options, errors *[]error) error { - var lt token - defer convertPanicToErrorList(<, errors) - - tk, v := unwrapValue(v, <) - gm, ok := v.(map[string]any) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected websocket to be a map, got %T", v)} - } - for mk, mv := range gm { - // Again, unwrap token value if line check is required. - tk, mv = unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "listen": - hp, err := parseListen(mv) - if err != nil { - err := &configErr{tk, err.Error()} - *errors = append(*errors, err) - continue - } - o.Websocket.Host = hp.host - o.Websocket.Port = hp.port - case "port": - o.Websocket.Port = int(mv.(int64)) - case "host", "net": - o.Websocket.Host = mv.(string) - case "advertise": - o.Websocket.Advertise = mv.(string) - case "no_tls": - o.Websocket.NoTLS = mv.(bool) - case "tls": - tc, err := parseTLS(tk, true) - if err != nil { - *errors = append(*errors, err) - continue - } - if o.Websocket.TLSConfig, err = GenTLSConfig(tc); err != nil { - err := &configErr{tk, err.Error()} - *errors = append(*errors, err) - continue - } - o.Websocket.TLSMap = tc.Map - o.Websocket.TLSPinnedCerts = tc.PinnedCerts - o.Websocket.tlsConfigOpts = tc - case "same_origin": - o.Websocket.SameOrigin = mv.(bool) - case "allowed_origins", "allowed_origin", "allow_origins", "allow_origin", "origins", "origin": - o.Websocket.AllowedOrigins, _ = parseStringArray("allowed origins", tk, <, mv, errors) - case "handshake_timeout": - ht := time.Duration(0) - switch mv := mv.(type) { - case int64: - ht = time.Duration(mv) * time.Second - case string: - var err error - ht, err = time.ParseDuration(mv) - if err != nil { - err := &configErr{tk, err.Error()} - *errors = append(*errors, err) - continue - } - default: - err := &configErr{tk, fmt.Sprintf("error parsing handshake timeout: unsupported type %T", mv)} - *errors = append(*errors, err) - } - o.Websocket.HandshakeTimeout = ht - case "compress", "compression": - o.Websocket.Compression = mv.(bool) - case "authorization", "authentication": - auth := parseSimpleAuth(tk, errors) - o.Websocket.Username = auth.user - o.Websocket.Password = auth.pass - o.Websocket.Token = auth.token - o.Websocket.AuthTimeout = auth.timeout - case "jwt_cookie": - o.Websocket.JWTCookie = mv.(string) - case "user_cookie": - o.Websocket.UsernameCookie = mv.(string) - case "pass_cookie": - o.Websocket.PasswordCookie = mv.(string) - case "token_cookie": - o.Websocket.TokenCookie = mv.(string) - case "no_auth_user": - o.Websocket.NoAuthUser = mv.(string) - case "headers": - m, ok := mv.(map[string]any) - if !ok { - err := &configErr{tk, fmt.Sprintf("error parsing headers: unsupported type %T", mv)} - *errors = append(*errors, err) - continue - } - o.Websocket.Headers = make(map[string]string) - for key, val := range m { - tk, val = unwrapValue(val, <) - if headerValue, ok := val.(string); !ok { - *errors = append(*errors, &configErr{tk, fmt.Sprintf("error parsing header key %s: unsupported type %T", key, val)}) - continue - } else { - o.Websocket.Headers[key] = headerValue - } - } - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: mk, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - continue - } - } - } - return nil -} - -func parseMQTT(v any, o *Options, errors *[]error, warnings *[]error) error { - var lt token - defer convertPanicToErrorList(<, errors) - - tk, v := unwrapValue(v, <) - gm, ok := v.(map[string]any) - if !ok { - return &configErr{tk, fmt.Sprintf("Expected mqtt to be a map, got %T", v)} - } - for mk, mv := range gm { - // Again, unwrap token value if line check is required. - tk, mv = unwrapValue(mv, <) - switch strings.ToLower(mk) { - case "listen": - hp, err := parseListen(mv) - if err != nil { - err := &configErr{tk, err.Error()} - *errors = append(*errors, err) - continue - } - o.MQTT.Host = hp.host - o.MQTT.Port = hp.port - case "port": - o.MQTT.Port = int(mv.(int64)) - case "host", "net": - o.MQTT.Host = mv.(string) - case "tls": - tc, err := parseTLS(tk, true) - if err != nil { - *errors = append(*errors, err) - continue - } - if o.MQTT.TLSConfig, err = GenTLSConfig(tc); err != nil { - err := &configErr{tk, err.Error()} - *errors = append(*errors, err) - continue - } - o.MQTT.TLSTimeout = tc.Timeout - o.MQTT.TLSMap = tc.Map - o.MQTT.TLSPinnedCerts = tc.PinnedCerts - o.MQTT.tlsConfigOpts = tc - case "authorization", "authentication": - auth := parseSimpleAuth(tk, errors) - o.MQTT.Username = auth.user - o.MQTT.Password = auth.pass - o.MQTT.Token = auth.token - o.MQTT.AuthTimeout = auth.timeout - case "no_auth_user": - o.MQTT.NoAuthUser = mv.(string) - case "ack_wait", "ackwait": - o.MQTT.AckWait = parseDuration("ack_wait", tk, mv, errors, warnings) - case "max_ack_pending", "max_pending", "max_inflight": - tmp := int(mv.(int64)) - if tmp < 0 || tmp > 0xFFFF { - err := &configErr{tk, fmt.Sprintf("invalid value %v, should in [0..%d] range", tmp, 0xFFFF)} - *errors = append(*errors, err) - } else { - o.MQTT.MaxAckPending = uint16(tmp) - } - case "js_domain": - o.MQTT.JsDomain = mv.(string) - case "stream_replicas": - o.MQTT.StreamReplicas = int(mv.(int64)) - case "consumer_replicas": - err := &configWarningErr{ - field: mk, - configErr: configErr{ - token: tk, - reason: `consumer replicas setting ignored in this server version`, - }, - } - *warnings = append(*warnings, err) - case "consumer_memory_storage": - o.MQTT.ConsumerMemoryStorage = mv.(bool) - case "consumer_inactive_threshold", "consumer_auto_cleanup": - o.MQTT.ConsumerInactiveThreshold = parseDuration("consumer_inactive_threshold", tk, mv, errors, warnings) - - case "reject_qos2_publish": - o.MQTT.rejectQoS2Pub = mv.(bool) - case "downgrade_qos2_subscribe": - o.MQTT.downgradeQoS2Sub = mv.(bool) - - default: - if !tk.IsUsedVariable() { - err := &unknownConfigFieldErr{ - field: mk, - configErr: configErr{ - token: tk, - }, - } - *errors = append(*errors, err) - continue - } - } - } - return nil -} - -// GenTLSConfig loads TLS related configuration parameters. -func GenTLSConfig(tc *TLSConfigOpts) (*tls.Config, error) { - // Create the tls.Config from our options before including the certs. - // It will determine the cipher suites that we prefer. - // FIXME(dlc) change if ARM based. - config := tls.Config{ - MinVersion: tls.VersionTLS12, - CipherSuites: tc.Ciphers, - PreferServerCipherSuites: true, - CurvePreferences: tc.CurvePreferences, - InsecureSkipVerify: tc.Insecure, - } - - switch { - case tc.CertFile != _EMPTY_ && tc.CertStore != certstore.STOREEMPTY: - return nil, certstore.ErrConflictCertFileAndStore - case tc.CertFile != _EMPTY_ && tc.KeyFile == _EMPTY_: - return nil, fmt.Errorf("missing 'key_file' in TLS configuration") - case tc.CertFile == _EMPTY_ && tc.KeyFile != _EMPTY_: - return nil, fmt.Errorf("missing 'cert_file' in TLS configuration") - case tc.CertFile != _EMPTY_ && tc.KeyFile != _EMPTY_: - // Now load in cert and private key - cert, err := tls.LoadX509KeyPair(tc.CertFile, tc.KeyFile) - if err != nil { - return nil, fmt.Errorf("error parsing X509 certificate/key pair: %v", err) - } - cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) - if err != nil { - return nil, fmt.Errorf("error parsing certificate: %v", err) - } - config.Certificates = []tls.Certificate{cert} - case tc.CertStore != certstore.STOREEMPTY: - err := certstore.TLSConfig(tc.CertStore, tc.CertMatchBy, tc.CertMatch, tc.CaCertsMatch, tc.CertMatchSkipInvalid, &config) - if err != nil { - return nil, err - } - case tc.Certificates != nil: - // Multiple certificate support. - config.Certificates = make([]tls.Certificate, len(tc.Certificates)) - for i, certPair := range tc.Certificates { - cert, err := tls.LoadX509KeyPair(certPair.CertFile, certPair.KeyFile) - if err != nil { - return nil, fmt.Errorf("error parsing X509 certificate/key pair %d/%d: %v", i+1, len(tc.Certificates), err) - } - cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0]) - if err != nil { - return nil, fmt.Errorf("error parsing certificate %d/%d: %v", i+1, len(tc.Certificates), err) - } - config.Certificates[i] = cert - } - } - - // Require client certificates as needed - if tc.Verify { - config.ClientAuth = tls.RequireAndVerifyClientCert - } - // Add in CAs if applicable. - if tc.CaFile != _EMPTY_ { - rootPEM, err := os.ReadFile(tc.CaFile) - if err != nil || rootPEM == nil { - return nil, err - } - pool := x509.NewCertPool() - ok := pool.AppendCertsFromPEM(rootPEM) - if !ok { - return nil, fmt.Errorf("failed to parse root ca certificate") - } - config.ClientCAs = pool - } - // Allow setting TLS minimum version. - if tc.MinVersion > 0 { - if tc.MinVersion < tls.VersionTLS12 { - return nil, fmt.Errorf("unsupported minimum TLS version: %s", tls.VersionName(tc.MinVersion)) - } - config.MinVersion = tc.MinVersion - } - - return &config, nil -} - -// MergeOptions will merge two options giving preference to the flagOpts -// if the item is present. -func MergeOptions(fileOpts, flagOpts *Options) *Options { - if fileOpts == nil { - return flagOpts - } - if flagOpts == nil { - return fileOpts - } - // Merge the two, flagOpts override - opts := *fileOpts - - if flagOpts.Port != 0 { - opts.Port = flagOpts.Port - } - if flagOpts.Host != _EMPTY_ { - opts.Host = flagOpts.Host - } - if flagOpts.DontListen { - opts.DontListen = flagOpts.DontListen - } - if flagOpts.ClientAdvertise != _EMPTY_ { - opts.ClientAdvertise = flagOpts.ClientAdvertise - } - if flagOpts.Username != _EMPTY_ { - opts.Username = flagOpts.Username - } - if flagOpts.Password != _EMPTY_ { - opts.Password = flagOpts.Password - } - if flagOpts.Authorization != _EMPTY_ { - opts.Authorization = flagOpts.Authorization - } - if flagOpts.HTTPPort != 0 { - opts.HTTPPort = flagOpts.HTTPPort - } - if flagOpts.HTTPBasePath != _EMPTY_ { - opts.HTTPBasePath = flagOpts.HTTPBasePath - } - if flagOpts.Debug { - opts.Debug = true - } - if flagOpts.Trace { - opts.Trace = true - } - if flagOpts.Logtime { - opts.Logtime = true - } - if flagOpts.LogFile != _EMPTY_ { - opts.LogFile = flagOpts.LogFile - } - if flagOpts.PidFile != _EMPTY_ { - opts.PidFile = flagOpts.PidFile - } - if flagOpts.PortsFileDir != _EMPTY_ { - opts.PortsFileDir = flagOpts.PortsFileDir - } - if flagOpts.ProfPort != 0 { - opts.ProfPort = flagOpts.ProfPort - } - if flagOpts.Cluster.ListenStr != _EMPTY_ { - opts.Cluster.ListenStr = flagOpts.Cluster.ListenStr - } - if flagOpts.Cluster.NoAdvertise { - opts.Cluster.NoAdvertise = true - } - if flagOpts.Cluster.ConnectRetries != 0 { - opts.Cluster.ConnectRetries = flagOpts.Cluster.ConnectRetries - } - if flagOpts.Cluster.Advertise != _EMPTY_ { - opts.Cluster.Advertise = flagOpts.Cluster.Advertise - } - if flagOpts.RoutesStr != _EMPTY_ { - mergeRoutes(&opts, flagOpts) - } - if flagOpts.JetStream { - opts.JetStream = flagOpts.JetStream - } - if flagOpts.StoreDir != _EMPTY_ { - opts.StoreDir = flagOpts.StoreDir - } - return &opts -} - -// RoutesFromStr parses route URLs from a string -func RoutesFromStr(routesStr string) []*url.URL { - routes := strings.Split(routesStr, ",") - if len(routes) == 0 { - return nil - } - routeUrls := []*url.URL{} - for _, r := range routes { - r = strings.TrimSpace(r) - u, _ := url.Parse(r) - routeUrls = append(routeUrls, u) - } - return routeUrls -} - -// This will merge the flag routes and override anything that was present. -func mergeRoutes(opts, flagOpts *Options) { - routeUrls := RoutesFromStr(flagOpts.RoutesStr) - if routeUrls == nil { - return - } - opts.Routes = routeUrls - opts.RoutesStr = flagOpts.RoutesStr -} - -func setBaselineOptions(opts *Options) { - // Setup non-standard Go defaults - if opts.Host == _EMPTY_ { - opts.Host = DEFAULT_HOST - } - if opts.HTTPHost == _EMPTY_ { - // Default to same bind from server if left undefined - opts.HTTPHost = opts.Host - } - if opts.Port == 0 { - opts.Port = DEFAULT_PORT - } else if opts.Port == RANDOM_PORT { - // Choose randomly inside of net.Listen - opts.Port = 0 - } - if opts.MaxConn == 0 { - opts.MaxConn = DEFAULT_MAX_CONNECTIONS - } - if opts.PingInterval == 0 { - opts.PingInterval = DEFAULT_PING_INTERVAL - } - if opts.MaxPingsOut == 0 { - opts.MaxPingsOut = DEFAULT_PING_MAX_OUT - } - if opts.TLSTimeout == 0 { - opts.TLSTimeout = float64(TLS_TIMEOUT) / float64(time.Second) - } - if opts.AuthTimeout == 0 { - opts.AuthTimeout = getDefaultAuthTimeout(opts.TLSConfig, opts.TLSTimeout) - } - if opts.Cluster.Port != 0 || opts.Cluster.ListenStr != _EMPTY_ { - if opts.Cluster.Host == _EMPTY_ { - opts.Cluster.Host = DEFAULT_HOST - } - if opts.Cluster.TLSTimeout == 0 { - opts.Cluster.TLSTimeout = float64(TLS_TIMEOUT) / float64(time.Second) - } - if opts.Cluster.AuthTimeout == 0 { - opts.Cluster.AuthTimeout = getDefaultAuthTimeout(opts.Cluster.TLSConfig, opts.Cluster.TLSTimeout) - } - if opts.Cluster.PoolSize == 0 { - opts.Cluster.PoolSize = DEFAULT_ROUTE_POOL_SIZE - } - // Unless pooling/accounts are disabled (by PoolSize being set to -1), - // check for Cluster.Accounts. Add the system account if not present and - // unless we have a configuration that disabled it. - if opts.Cluster.PoolSize > 0 { - sysAccName := opts.SystemAccount - if sysAccName == _EMPTY_ && !opts.NoSystemAccount { - sysAccName = DEFAULT_SYSTEM_ACCOUNT - } - if sysAccName != _EMPTY_ { - var found bool - for _, acc := range opts.Cluster.PinnedAccounts { - if acc == sysAccName { - found = true - break - } - } - if !found { - opts.Cluster.PinnedAccounts = append(opts.Cluster.PinnedAccounts, sysAccName) - } - } - } - // Default to compression "accept", which means that compression is not - // initiated, but if the remote selects compression, this server will - // use the same. - if c := &opts.Cluster.Compression; c.Mode == _EMPTY_ { - if testDefaultClusterCompression != _EMPTY_ { - c.Mode = testDefaultClusterCompression - } else { - c.Mode = CompressionAccept - } - } - } - if opts.LeafNode.Port != 0 { - if opts.LeafNode.Host == _EMPTY_ { - opts.LeafNode.Host = DEFAULT_HOST - } - if opts.LeafNode.TLSTimeout == 0 { - opts.LeafNode.TLSTimeout = float64(TLS_TIMEOUT) / float64(time.Second) - } - if opts.LeafNode.AuthTimeout == 0 { - opts.LeafNode.AuthTimeout = getDefaultAuthTimeout(opts.LeafNode.TLSConfig, opts.LeafNode.TLSTimeout) - } - // Default to compression "s2_auto". - if c := &opts.LeafNode.Compression; c.Mode == _EMPTY_ { - if testDefaultLeafNodeCompression != _EMPTY_ { - c.Mode = testDefaultLeafNodeCompression - } else { - c.Mode = CompressionS2Auto - } - } - } - // Set baseline connect port for remotes. - for _, r := range opts.LeafNode.Remotes { - if r != nil { - for _, u := range r.URLs { - if u.Port() == _EMPTY_ { - u.Host = net.JoinHostPort(u.Host, strconv.Itoa(DEFAULT_LEAFNODE_PORT)) - } - } - // Default to compression "s2_auto". - if c := &r.Compression; c.Mode == _EMPTY_ { - if testDefaultLeafNodeCompression != _EMPTY_ { - c.Mode = testDefaultLeafNodeCompression - } else { - c.Mode = CompressionS2Auto - } - } - // Set default first info timeout value if not set. - if r.FirstInfoTimeout <= 0 { - r.FirstInfoTimeout = DEFAULT_LEAFNODE_INFO_WAIT - } - } - } - - // Set this regardless of opts.LeafNode.Port - if opts.LeafNode.ReconnectInterval == 0 { - opts.LeafNode.ReconnectInterval = DEFAULT_LEAF_NODE_RECONNECT - } - - if opts.MaxControlLine == 0 { - opts.MaxControlLine = MAX_CONTROL_LINE_SIZE - } - if opts.MaxPayload == 0 { - opts.MaxPayload = MAX_PAYLOAD_SIZE - } - if opts.MaxPending == 0 { - opts.MaxPending = MAX_PENDING_SIZE - } - if opts.WriteDeadline == time.Duration(0) { - opts.WriteDeadline = DEFAULT_FLUSH_DEADLINE - } - if opts.MaxClosedClients == 0 { - opts.MaxClosedClients = DEFAULT_MAX_CLOSED_CLIENTS - } - if opts.LameDuckDuration == 0 { - opts.LameDuckDuration = DEFAULT_LAME_DUCK_DURATION - } - if opts.LameDuckGracePeriod == 0 { - opts.LameDuckGracePeriod = DEFAULT_LAME_DUCK_GRACE_PERIOD - } - if opts.Gateway.Port != 0 { - if opts.Gateway.Host == _EMPTY_ { - opts.Gateway.Host = DEFAULT_HOST - } - if opts.Gateway.TLSTimeout == 0 { - opts.Gateway.TLSTimeout = float64(TLS_TIMEOUT) / float64(time.Second) - } - if opts.Gateway.AuthTimeout == 0 { - opts.Gateway.AuthTimeout = getDefaultAuthTimeout(opts.Gateway.TLSConfig, opts.Gateway.TLSTimeout) - } - } - if opts.ConnectErrorReports == 0 { - opts.ConnectErrorReports = DEFAULT_CONNECT_ERROR_REPORTS - } - if opts.ReconnectErrorReports == 0 { - opts.ReconnectErrorReports = DEFAULT_RECONNECT_ERROR_REPORTS - } - if opts.Websocket.Port != 0 { - if opts.Websocket.Host == _EMPTY_ { - opts.Websocket.Host = DEFAULT_HOST - } - } - if opts.MQTT.Port != 0 { - if opts.MQTT.Host == _EMPTY_ { - opts.MQTT.Host = DEFAULT_HOST - } - if opts.MQTT.TLSTimeout == 0 { - opts.MQTT.TLSTimeout = float64(TLS_TIMEOUT) / float64(time.Second) - } - } - // JetStream - if opts.JetStreamMaxMemory == 0 && !opts.maxMemSet { - opts.JetStreamMaxMemory = -1 - } - if opts.JetStreamMaxStore == 0 && !opts.maxStoreSet { - opts.JetStreamMaxStore = -1 - } - if opts.SyncInterval == 0 && !opts.syncSet { - opts.SyncInterval = defaultSyncInterval - } - if opts.JetStreamRequestQueueLimit <= 0 { - opts.JetStreamRequestQueueLimit = JSDefaultRequestQueueLimit - } -} - -func getDefaultAuthTimeout(tls *tls.Config, tlsTimeout float64) float64 { - var authTimeout float64 - if tls != nil { - authTimeout = tlsTimeout + 1.0 - } else { - authTimeout = float64(AUTH_TIMEOUT / time.Second) - } - return authTimeout -} - -// ConfigureOptions accepts a flag set and augments it with NATS Server -// specific flags. On success, an options structure is returned configured -// based on the selected flags and/or configuration file. -// The command line options take precedence to the ones in the configuration file. -func ConfigureOptions(fs *flag.FlagSet, args []string, printVersion, printHelp, printTLSHelp func()) (*Options, error) { - opts := &Options{} - var ( - showVersion bool - showHelp bool - showTLSHelp bool - signal string - configFile string - dbgAndTrace bool - trcAndVerboseTrc bool - dbgAndTrcAndVerboseTrc bool - err error - ) - - fs.BoolVar(&showHelp, "h", false, "Show this message.") - fs.BoolVar(&showHelp, "help", false, "Show this message.") - fs.IntVar(&opts.Port, "port", 0, "Port to listen on.") - fs.IntVar(&opts.Port, "p", 0, "Port to listen on.") - fs.StringVar(&opts.ServerName, "n", _EMPTY_, "Server name.") - fs.StringVar(&opts.ServerName, "name", _EMPTY_, "Server name.") - fs.StringVar(&opts.ServerName, "server_name", _EMPTY_, "Server name.") - fs.StringVar(&opts.Host, "addr", _EMPTY_, "Network host to listen on.") - fs.StringVar(&opts.Host, "a", _EMPTY_, "Network host to listen on.") - fs.StringVar(&opts.Host, "net", _EMPTY_, "Network host to listen on.") - fs.StringVar(&opts.ClientAdvertise, "client_advertise", _EMPTY_, "Client URL to advertise to other servers.") - fs.BoolVar(&opts.Debug, "D", false, "Enable Debug logging.") - fs.BoolVar(&opts.Debug, "debug", false, "Enable Debug logging.") - fs.BoolVar(&opts.Trace, "V", false, "Enable Trace logging.") - fs.BoolVar(&trcAndVerboseTrc, "VV", false, "Enable Verbose Trace logging. (Traces system account as well)") - fs.BoolVar(&opts.Trace, "trace", false, "Enable Trace logging.") - fs.BoolVar(&dbgAndTrace, "DV", false, "Enable Debug and Trace logging.") - fs.BoolVar(&dbgAndTrcAndVerboseTrc, "DVV", false, "Enable Debug and Verbose Trace logging. (Traces system account as well)") - fs.BoolVar(&opts.Logtime, "T", true, "Timestamp log entries.") - fs.BoolVar(&opts.Logtime, "logtime", true, "Timestamp log entries.") - fs.BoolVar(&opts.LogtimeUTC, "logtime_utc", false, "Timestamps in UTC instead of local timezone.") - fs.StringVar(&opts.Username, "user", _EMPTY_, "Username required for connection.") - fs.StringVar(&opts.Password, "pass", _EMPTY_, "Password required for connection.") - fs.StringVar(&opts.Authorization, "auth", _EMPTY_, "Authorization token required for connection.") - fs.IntVar(&opts.HTTPPort, "m", 0, "HTTP Port for /varz, /connz endpoints.") - fs.IntVar(&opts.HTTPPort, "http_port", 0, "HTTP Port for /varz, /connz endpoints.") - fs.IntVar(&opts.HTTPSPort, "ms", 0, "HTTPS Port for /varz, /connz endpoints.") - fs.IntVar(&opts.HTTPSPort, "https_port", 0, "HTTPS Port for /varz, /connz endpoints.") - fs.StringVar(&configFile, "c", _EMPTY_, "Configuration file.") - fs.StringVar(&configFile, "config", _EMPTY_, "Configuration file.") - fs.BoolVar(&opts.CheckConfig, "t", false, "Check configuration and exit.") - fs.StringVar(&signal, "sl", "", "Send signal to nats-server process (ldm, stop, quit, term, reopen, reload).") - fs.StringVar(&signal, "signal", "", "Send signal to nats-server process (ldm, stop, quit, term, reopen, reload).") - fs.StringVar(&opts.PidFile, "P", "", "File to store process pid.") - fs.StringVar(&opts.PidFile, "pid", "", "File to store process pid.") - fs.StringVar(&opts.PortsFileDir, "ports_file_dir", "", "Creates a ports file in the specified directory (_.ports).") - fs.StringVar(&opts.LogFile, "l", "", "File to store logging output.") - fs.StringVar(&opts.LogFile, "log", "", "File to store logging output.") - fs.Int64Var(&opts.LogSizeLimit, "log_size_limit", 0, "Logfile size limit being auto-rotated") - fs.BoolVar(&opts.Syslog, "s", false, "Enable syslog as log method.") - fs.BoolVar(&opts.Syslog, "syslog", false, "Enable syslog as log method.") - fs.StringVar(&opts.RemoteSyslog, "r", _EMPTY_, "Syslog server addr (udp://127.0.0.1:514).") - fs.StringVar(&opts.RemoteSyslog, "remote_syslog", _EMPTY_, "Syslog server addr (udp://127.0.0.1:514).") - fs.BoolVar(&showVersion, "version", false, "Print version information.") - fs.BoolVar(&showVersion, "v", false, "Print version information.") - fs.IntVar(&opts.ProfPort, "profile", 0, "Profiling HTTP port.") - fs.StringVar(&opts.RoutesStr, "routes", _EMPTY_, "Routes to actively solicit a connection.") - fs.StringVar(&opts.Cluster.ListenStr, "cluster", _EMPTY_, "Cluster url from which members can solicit routes.") - fs.StringVar(&opts.Cluster.ListenStr, "cluster_listen", _EMPTY_, "Cluster url from which members can solicit routes.") - fs.StringVar(&opts.Cluster.Advertise, "cluster_advertise", _EMPTY_, "Cluster URL to advertise to other servers.") - fs.BoolVar(&opts.Cluster.NoAdvertise, "no_advertise", false, "Advertise known cluster IPs to clients.") - fs.IntVar(&opts.Cluster.ConnectRetries, "connect_retries", 0, "For implicit routes, number of connect retries.") - fs.StringVar(&opts.Cluster.Name, "cluster_name", _EMPTY_, "Cluster Name, if not set one will be dynamically generated.") - fs.BoolVar(&showTLSHelp, "help_tls", false, "TLS help.") - fs.BoolVar(&opts.TLS, "tls", false, "Enable TLS.") - fs.BoolVar(&opts.TLSVerify, "tlsverify", false, "Enable TLS with client verification.") - fs.StringVar(&opts.TLSCert, "tlscert", _EMPTY_, "Server certificate file.") - fs.StringVar(&opts.TLSKey, "tlskey", _EMPTY_, "Private key for server certificate.") - fs.StringVar(&opts.TLSCaCert, "tlscacert", _EMPTY_, "Client certificate CA for verification.") - fs.IntVar(&opts.MaxTracedMsgLen, "max_traced_msg_len", 0, "Maximum printable length for traced messages. 0 for unlimited.") - fs.BoolVar(&opts.JetStream, "js", false, "Enable JetStream.") - fs.BoolVar(&opts.JetStream, "jetstream", false, "Enable JetStream.") - fs.StringVar(&opts.StoreDir, "sd", _EMPTY_, "Storage directory.") - fs.StringVar(&opts.StoreDir, "store_dir", _EMPTY_, "Storage directory.") - - // The flags definition above set "default" values to some of the options. - // Calling Parse() here will override the default options with any value - // specified from the command line. This is ok. We will then update the - // options with the content of the configuration file (if present), and then, - // call Parse() again to override the default+config with command line values. - // Calling Parse() before processing config file is necessary since configFile - // itself is a command line argument, and also Parse() is required in order - // to know if user wants simply to show "help" or "version", etc... - if err := fs.Parse(args); err != nil { - return nil, err - } - - if showVersion { - printVersion() - return nil, nil - } - - if showHelp { - printHelp() - return nil, nil - } - - if showTLSHelp { - printTLSHelp() - return nil, nil - } - - // Process args looking for non-flag options, - // 'version' and 'help' only for now - showVersion, showHelp, err = ProcessCommandLineArgs(fs) - if err != nil { - return nil, err - } else if showVersion { - printVersion() - return nil, nil - } else if showHelp { - printHelp() - return nil, nil - } - - // Snapshot flag options. - FlagSnapshot = opts.Clone() - - // Keep track of the boolean flags that were explicitly set with their value. - fs.Visit(func(f *flag.Flag) { - switch f.Name { - case "DVV": - trackExplicitVal(&FlagSnapshot.inCmdLine, "Debug", dbgAndTrcAndVerboseTrc) - trackExplicitVal(&FlagSnapshot.inCmdLine, "Trace", dbgAndTrcAndVerboseTrc) - trackExplicitVal(&FlagSnapshot.inCmdLine, "TraceVerbose", dbgAndTrcAndVerboseTrc) - case "DV": - trackExplicitVal(&FlagSnapshot.inCmdLine, "Debug", dbgAndTrace) - trackExplicitVal(&FlagSnapshot.inCmdLine, "Trace", dbgAndTrace) - case "D": - fallthrough - case "debug": - trackExplicitVal(&FlagSnapshot.inCmdLine, "Debug", FlagSnapshot.Debug) - case "VV": - trackExplicitVal(&FlagSnapshot.inCmdLine, "Trace", trcAndVerboseTrc) - trackExplicitVal(&FlagSnapshot.inCmdLine, "TraceVerbose", trcAndVerboseTrc) - case "V": - fallthrough - case "trace": - trackExplicitVal(&FlagSnapshot.inCmdLine, "Trace", FlagSnapshot.Trace) - case "T": - fallthrough - case "logtime": - trackExplicitVal(&FlagSnapshot.inCmdLine, "Logtime", FlagSnapshot.Logtime) - case "s": - fallthrough - case "syslog": - trackExplicitVal(&FlagSnapshot.inCmdLine, "Syslog", FlagSnapshot.Syslog) - case "no_advertise": - trackExplicitVal(&FlagSnapshot.inCmdLine, "Cluster.NoAdvertise", FlagSnapshot.Cluster.NoAdvertise) - case "js": - trackExplicitVal(&FlagSnapshot.inCmdLine, "JetStream", FlagSnapshot.JetStream) - } - }) - - // Process signal control. - if signal != _EMPTY_ { - if err := processSignal(signal); err != nil { - return nil, err - } - } - - // Parse config if given - if configFile != _EMPTY_ { - // This will update the options with values from the config file. - err := opts.ProcessConfigFile(configFile) - if err != nil { - if opts.CheckConfig { - return nil, err - } - if cerr, ok := err.(*processConfigErr); !ok || len(cerr.Errors()) != 0 { - return nil, err - } - // If we get here we only have warnings and can still continue - fmt.Fprint(os.Stderr, err) - } else if opts.CheckConfig { - // Report configuration file syntax test was successful and exit. - return opts, nil - } - - // Call this again to override config file options with options from command line. - // Note: We don't need to check error here since if there was an error, it would - // have been caught the first time this function was called (after setting up the - // flags). - fs.Parse(args) - } else if opts.CheckConfig { - return nil, fmt.Errorf("must specify [-c, --config] option to check configuration file syntax") - } - - // Special handling of some flags - var ( - flagErr error - tlsDisabled bool - tlsOverride bool - ) - fs.Visit(func(f *flag.Flag) { - // short-circuit if an error was encountered - if flagErr != nil { - return - } - if strings.HasPrefix(f.Name, "tls") { - if f.Name == "tls" { - if !opts.TLS { - // User has specified "-tls=false", we need to disable TLS - opts.TLSConfig = nil - tlsDisabled = true - tlsOverride = false - return - } - tlsOverride = true - } else if !tlsDisabled { - tlsOverride = true - } - } else { - switch f.Name { - case "VV": - opts.Trace, opts.TraceVerbose = trcAndVerboseTrc, trcAndVerboseTrc - case "DVV": - opts.Trace, opts.Debug, opts.TraceVerbose = dbgAndTrcAndVerboseTrc, dbgAndTrcAndVerboseTrc, dbgAndTrcAndVerboseTrc - case "DV": - // Check value to support -DV=false - opts.Trace, opts.Debug = dbgAndTrace, dbgAndTrace - case "cluster", "cluster_listen": - // Override cluster config if explicitly set via flags. - flagErr = overrideCluster(opts) - case "routes": - // Keep in mind that the flag has updated opts.RoutesStr at this point. - if opts.RoutesStr == _EMPTY_ { - // Set routes array to nil since routes string is empty - opts.Routes = nil - return - } - routeUrls := RoutesFromStr(opts.RoutesStr) - opts.Routes = routeUrls - } - } - }) - if flagErr != nil { - return nil, flagErr - } - - // This will be true if some of the `-tls` params have been set and - // `-tls=false` has not been set. - if tlsOverride { - if err := overrideTLS(opts); err != nil { - return nil, err - } - } - - // If we don't have cluster defined in the configuration - // file and no cluster listen string override, but we do - // have a routes override, we need to report misconfiguration. - if opts.RoutesStr != _EMPTY_ && opts.Cluster.ListenStr == _EMPTY_ && opts.Cluster.Host == _EMPTY_ && opts.Cluster.Port == 0 { - return nil, errors.New("solicited routes require cluster capabilities, e.g. --cluster") - } - - return opts, nil -} - -func normalizeBasePath(p string) string { - if len(p) == 0 { - return "/" - } - // add leading slash - if p[0] != '/' { - p = "/" + p - } - return path.Clean(p) -} - -// overrideTLS is called when at least "-tls=true" has been set. -func overrideTLS(opts *Options) error { - if opts.TLSCert == _EMPTY_ { - return errors.New("TLS Server certificate must be present and valid") - } - if opts.TLSKey == _EMPTY_ { - return errors.New("TLS Server private key must be present and valid") - } - - tc := TLSConfigOpts{} - tc.CertFile = opts.TLSCert - tc.KeyFile = opts.TLSKey - tc.CaFile = opts.TLSCaCert - tc.Verify = opts.TLSVerify - tc.Ciphers = defaultCipherSuites() - - var err error - opts.TLSConfig, err = GenTLSConfig(&tc) - return err -} - -// overrideCluster updates Options.Cluster if that flag "cluster" (or "cluster_listen") -// has explicitly be set in the command line. If it is set to empty string, it will -// clear the Cluster options. -func overrideCluster(opts *Options) error { - if opts.Cluster.ListenStr == _EMPTY_ { - // This one is enough to disable clustering. - opts.Cluster.Port = 0 - return nil - } - // -1 will fail url.Parse, so if we have -1, change it to - // 0, and then after parse, replace the port with -1 so we get - // automatic port allocation - wantsRandom := false - if strings.HasSuffix(opts.Cluster.ListenStr, ":-1") { - wantsRandom = true - cls := fmt.Sprintf("%s:0", opts.Cluster.ListenStr[0:len(opts.Cluster.ListenStr)-3]) - opts.Cluster.ListenStr = cls - } - clusterURL, err := url.Parse(opts.Cluster.ListenStr) - if err != nil { - return err - } - h, p, err := net.SplitHostPort(clusterURL.Host) - if err != nil { - return err - } - if wantsRandom { - p = "-1" - } - opts.Cluster.Host = h - _, err = fmt.Sscan(p, &opts.Cluster.Port) - if err != nil { - return err - } - - if clusterURL.User != nil { - pass, hasPassword := clusterURL.User.Password() - if !hasPassword { - return errors.New("expected cluster password to be set") - } - opts.Cluster.Password = pass - - user := clusterURL.User.Username() - opts.Cluster.Username = user - } else { - // Since we override from flag and there is no user/pwd, make - // sure we clear what we may have gotten from config file. - opts.Cluster.Username = _EMPTY_ - opts.Cluster.Password = _EMPTY_ - } - - return nil -} - -func processSignal(signal string) error { - var ( - pid string - commandAndPid = strings.Split(signal, "=") - ) - if l := len(commandAndPid); l == 2 { - pid = maybeReadPidFile(commandAndPid[1]) - } else if l > 2 { - return fmt.Errorf("invalid signal parameters: %v", commandAndPid[2:]) - } - if err := ProcessSignal(Command(commandAndPid[0]), pid); err != nil { - return err - } - os.Exit(0) - return nil -} - -// maybeReadPidFile returns a PID or Windows service name obtained via the following method: -// 1. Try to open a file with path "pidStr" (absolute or relative). -// 2. If such a file exists and can be read, return its contents. -// 3. Otherwise, return the original "pidStr" string. -func maybeReadPidFile(pidStr string) string { - if b, err := os.ReadFile(pidStr); err == nil { - return string(b) - } - return pidStr -} - -func homeDir() (string, error) { - if runtime.GOOS == "windows" { - homeDrive, homePath := os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH") - userProfile := os.Getenv("USERPROFILE") - - home := filepath.Join(homeDrive, homePath) - if homeDrive == _EMPTY_ || homePath == _EMPTY_ { - if userProfile == _EMPTY_ { - return _EMPTY_, errors.New("nats: failed to get home dir, require %HOMEDRIVE% and %HOMEPATH% or %USERPROFILE%") - } - home = userProfile - } - - return home, nil - } - - home := os.Getenv("HOME") - if home == _EMPTY_ { - return _EMPTY_, errors.New("failed to get home dir, require $HOME") - } - return home, nil -} - -func expandPath(p string) (string, error) { - p = os.ExpandEnv(p) - - if !strings.HasPrefix(p, "~") { - return p, nil - } - - home, err := homeDir() - if err != nil { - return _EMPTY_, err - } - - return filepath.Join(home, p[1:]), nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/parser.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/parser.go deleted file mode 100644 index ed3eaa15..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/parser.go +++ /dev/null @@ -1,1309 +0,0 @@ -// Copyright 2012-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bufio" - "bytes" - "fmt" - "net/http" - "net/textproto" -) - -type parserState int -type parseState struct { - state parserState - op byte - as int - drop int - pa pubArg - argBuf []byte - msgBuf []byte - header http.Header // access via getHeader - scratch [MAX_CONTROL_LINE_SIZE]byte -} - -type pubArg struct { - arg []byte - pacache []byte - origin []byte - account []byte - subject []byte - deliver []byte - mapped []byte - reply []byte - szb []byte - hdb []byte - queues [][]byte - size int - hdr int - psi []*serviceImport - trace *msgTrace - delivered bool // Only used for service imports -} - -// Parser constants -const ( - OP_START parserState = iota - OP_PLUS - OP_PLUS_O - OP_PLUS_OK - OP_MINUS - OP_MINUS_E - OP_MINUS_ER - OP_MINUS_ERR - OP_MINUS_ERR_SPC - MINUS_ERR_ARG - OP_C - OP_CO - OP_CON - OP_CONN - OP_CONNE - OP_CONNEC - OP_CONNECT - CONNECT_ARG - OP_H - OP_HP - OP_HPU - OP_HPUB - OP_HPUB_SPC - HPUB_ARG - OP_HM - OP_HMS - OP_HMSG - OP_HMSG_SPC - HMSG_ARG - OP_P - OP_PU - OP_PUB - OP_PUB_SPC - PUB_ARG - OP_PI - OP_PIN - OP_PING - OP_PO - OP_PON - OP_PONG - MSG_PAYLOAD - MSG_END_R - MSG_END_N - OP_S - OP_SU - OP_SUB - OP_SUB_SPC - SUB_ARG - OP_A - OP_ASUB - OP_ASUB_SPC - ASUB_ARG - OP_AUSUB - OP_AUSUB_SPC - AUSUB_ARG - OP_L - OP_LS - OP_R - OP_RS - OP_U - OP_UN - OP_UNS - OP_UNSU - OP_UNSUB - OP_UNSUB_SPC - UNSUB_ARG - OP_M - OP_MS - OP_MSG - OP_MSG_SPC - MSG_ARG - OP_I - OP_IN - OP_INF - OP_INFO - INFO_ARG -) - -func (c *client) parse(buf []byte) error { - // Branch out to mqtt clients. c.mqtt is immutable, but should it become - // an issue (say data race detection), we could branch outside in readLoop - if c.isMqtt() { - return c.mqttParse(buf) - } - var i int - var b byte - var lmsg bool - - // Snapshots - c.mu.Lock() - // Snapshot and then reset when we receive a - // proper CONNECT if needed. - authSet := c.awaitingAuth() - // Snapshot max control line as well. - s, mcl, trace := c.srv, c.mcl, c.trace - c.mu.Unlock() - - // Move to loop instead of range syntax to allow jumping of i - for i = 0; i < len(buf); i++ { - b = buf[i] - - switch c.state { - case OP_START: - c.op = b - if b != 'C' && b != 'c' { - if authSet { - if s == nil { - goto authErr - } - var ok bool - // Check here for NoAuthUser. If this is set allow non CONNECT protos as our first. - // E.g. telnet proto demos. - if noAuthUser := s.getOpts().NoAuthUser; noAuthUser != _EMPTY_ { - s.mu.Lock() - user, exists := s.users[noAuthUser] - s.mu.Unlock() - if exists { - c.RegisterUser(user) - c.mu.Lock() - c.clearAuthTimer() - c.flags.set(connectReceived) - c.mu.Unlock() - authSet, ok = false, true - } - } - if !ok { - goto authErr - } - } - // If the connection is a gateway connection, make sure that - // if this is an inbound, it starts with a CONNECT. - if c.kind == GATEWAY && !c.gw.outbound && !c.gw.connected { - // Use auth violation since no CONNECT was sent. - // It could be a parseErr too. - goto authErr - } - } - switch b { - case 'P', 'p': - c.state = OP_P - case 'H', 'h': - c.state = OP_H - case 'S', 's': - c.state = OP_S - case 'U', 'u': - c.state = OP_U - case 'R', 'r': - if c.kind == CLIENT { - goto parseErr - } else { - c.state = OP_R - } - case 'L', 'l': - if c.kind != LEAF && c.kind != ROUTER { - goto parseErr - } else { - c.state = OP_L - } - case 'A', 'a': - if c.kind == CLIENT { - goto parseErr - } else { - c.state = OP_A - } - case 'C', 'c': - c.state = OP_C - case 'I', 'i': - c.state = OP_I - case '+': - c.state = OP_PLUS - case '-': - c.state = OP_MINUS - default: - goto parseErr - } - case OP_H: - switch b { - case 'P', 'p': - c.state = OP_HP - case 'M', 'm': - c.state = OP_HM - default: - goto parseErr - } - case OP_HP: - switch b { - case 'U', 'u': - c.state = OP_HPU - default: - goto parseErr - } - case OP_HPU: - switch b { - case 'B', 'b': - c.state = OP_HPUB - default: - goto parseErr - } - case OP_HPUB: - switch b { - case ' ', '\t': - c.state = OP_HPUB_SPC - default: - goto parseErr - } - case OP_HPUB_SPC: - switch b { - case ' ', '\t': - continue - default: - c.pa.hdr = 0 - c.state = HPUB_ARG - c.as = i - } - case HPUB_ARG: - switch b { - case '\r': - c.drop = 1 - case '\n': - var arg []byte - if c.argBuf != nil { - arg = c.argBuf - c.argBuf = nil - } else { - arg = buf[c.as : i-c.drop] - } - if err := c.overMaxControlLineLimit(arg, mcl); err != nil { - return err - } - if trace { - c.traceInOp("HPUB", arg) - } - var remaining []byte - if i < len(buf) { - remaining = buf[i+1:] - } - if err := c.processHeaderPub(arg, remaining); err != nil { - return err - } - - c.drop, c.as, c.state = 0, i+1, MSG_PAYLOAD - // If we don't have a saved buffer then jump ahead with - // the index. If this overruns what is left we fall out - // and process split buffer. - if c.msgBuf == nil { - i = c.as + c.pa.size - LEN_CR_LF - } - default: - if c.argBuf != nil { - c.argBuf = append(c.argBuf, b) - } - } - case OP_HM: - switch b { - case 'S', 's': - c.state = OP_HMS - default: - goto parseErr - } - case OP_HMS: - switch b { - case 'G', 'g': - c.state = OP_HMSG - default: - goto parseErr - } - case OP_HMSG: - switch b { - case ' ', '\t': - c.state = OP_HMSG_SPC - default: - goto parseErr - } - case OP_HMSG_SPC: - switch b { - case ' ', '\t': - continue - default: - c.pa.hdr = 0 - c.state = HMSG_ARG - c.as = i - } - case HMSG_ARG: - switch b { - case '\r': - c.drop = 1 - case '\n': - var arg []byte - if c.argBuf != nil { - arg = c.argBuf - c.argBuf = nil - } else { - arg = buf[c.as : i-c.drop] - } - if err := c.overMaxControlLineLimit(arg, mcl); err != nil { - return err - } - var err error - if c.kind == ROUTER || c.kind == GATEWAY { - if trace { - c.traceInOp("HMSG", arg) - } - err = c.processRoutedHeaderMsgArgs(arg) - } else if c.kind == LEAF { - if trace { - c.traceInOp("HMSG", arg) - } - err = c.processLeafHeaderMsgArgs(arg) - } - if err != nil { - return err - } - c.drop, c.as, c.state = 0, i+1, MSG_PAYLOAD - - // jump ahead with the index. If this overruns - // what is left we fall out and process split - // buffer. - i = c.as + c.pa.size - LEN_CR_LF - default: - if c.argBuf != nil { - c.argBuf = append(c.argBuf, b) - } - } - case OP_P: - switch b { - case 'U', 'u': - c.state = OP_PU - case 'I', 'i': - c.state = OP_PI - case 'O', 'o': - c.state = OP_PO - default: - goto parseErr - } - case OP_PU: - switch b { - case 'B', 'b': - c.state = OP_PUB - default: - goto parseErr - } - case OP_PUB: - switch b { - case ' ', '\t': - c.state = OP_PUB_SPC - default: - goto parseErr - } - case OP_PUB_SPC: - switch b { - case ' ', '\t': - continue - default: - c.pa.hdr = -1 - c.state = PUB_ARG - c.as = i - } - case PUB_ARG: - switch b { - case '\r': - c.drop = 1 - case '\n': - var arg []byte - if c.argBuf != nil { - arg = c.argBuf - c.argBuf = nil - } else { - arg = buf[c.as : i-c.drop] - } - if err := c.overMaxControlLineLimit(arg, mcl); err != nil { - return err - } - if trace { - c.traceInOp("PUB", arg) - } - if err := c.processPub(arg); err != nil { - return err - } - - c.drop, c.as, c.state = 0, i+1, MSG_PAYLOAD - // If we don't have a saved buffer then jump ahead with - // the index. If this overruns what is left we fall out - // and process split buffer. - if c.msgBuf == nil { - i = c.as + c.pa.size - LEN_CR_LF - } - default: - if c.argBuf != nil { - c.argBuf = append(c.argBuf, b) - } - } - case MSG_PAYLOAD: - if c.msgBuf != nil { - // copy as much as we can to the buffer and skip ahead. - toCopy := c.pa.size - len(c.msgBuf) - avail := len(buf) - i - if avail < toCopy { - toCopy = avail - } - if toCopy > 0 { - start := len(c.msgBuf) - // This is needed for copy to work. - c.msgBuf = c.msgBuf[:start+toCopy] - copy(c.msgBuf[start:], buf[i:i+toCopy]) - // Update our index - i = (i + toCopy) - 1 - } else { - // Fall back to append if needed. - c.msgBuf = append(c.msgBuf, b) - } - if len(c.msgBuf) >= c.pa.size { - c.state = MSG_END_R - } - } else if i-c.as+1 >= c.pa.size { - c.state = MSG_END_R - } - case MSG_END_R: - if b != '\r' { - goto parseErr - } - if c.msgBuf != nil { - c.msgBuf = append(c.msgBuf, b) - } - c.state = MSG_END_N - case MSG_END_N: - if b != '\n' { - goto parseErr - } - if c.msgBuf != nil { - c.msgBuf = append(c.msgBuf, b) - } else { - c.msgBuf = buf[c.as : i+1] - } - - var mt *msgTrace - if c.pa.hdr > 0 { - mt = c.initMsgTrace() - } - // Check for mappings. - if (c.kind == CLIENT || c.kind == LEAF) && c.in.flags.isSet(hasMappings) { - changed := c.selectMappedSubject() - if changed { - if trace { - c.traceInOp("MAPPING", []byte(fmt.Sprintf("%s -> %s", c.pa.mapped, c.pa.subject))) - } - // c.pa.subject is the subject the original is now mapped to. - mt.addSubjectMappingEvent(c.pa.subject) - } - } - if trace { - c.traceMsg(c.msgBuf) - } - - c.processInboundMsg(c.msgBuf) - - mt.sendEvent() - c.argBuf, c.msgBuf, c.header = nil, nil, nil - c.drop, c.as, c.state = 0, i+1, OP_START - // Drop all pub args - c.pa.arg, c.pa.pacache, c.pa.origin, c.pa.account, c.pa.subject, c.pa.mapped = nil, nil, nil, nil, nil, nil - c.pa.reply, c.pa.hdr, c.pa.size, c.pa.szb, c.pa.hdb, c.pa.queues = nil, -1, 0, nil, nil, nil - c.pa.trace = nil - c.pa.delivered = false - lmsg = false - case OP_A: - switch b { - case '+': - c.state = OP_ASUB - case '-', 'u': - c.state = OP_AUSUB - default: - goto parseErr - } - case OP_ASUB: - switch b { - case ' ', '\t': - c.state = OP_ASUB_SPC - default: - goto parseErr - } - case OP_ASUB_SPC: - switch b { - case ' ', '\t': - continue - default: - c.state = ASUB_ARG - c.as = i - } - case ASUB_ARG: - switch b { - case '\r': - c.drop = 1 - case '\n': - var arg []byte - if c.argBuf != nil { - arg = c.argBuf - c.argBuf = nil - } else { - arg = buf[c.as : i-c.drop] - } - if err := c.overMaxControlLineLimit(arg, mcl); err != nil { - return err - } - if trace { - c.traceInOp("A+", arg) - } - if err := c.processAccountSub(arg); err != nil { - return err - } - c.drop, c.as, c.state = 0, i+1, OP_START - default: - if c.argBuf != nil { - c.argBuf = append(c.argBuf, b) - } - } - case OP_AUSUB: - switch b { - case ' ', '\t': - c.state = OP_AUSUB_SPC - default: - goto parseErr - } - case OP_AUSUB_SPC: - switch b { - case ' ', '\t': - continue - default: - c.state = AUSUB_ARG - c.as = i - } - case AUSUB_ARG: - switch b { - case '\r': - c.drop = 1 - case '\n': - var arg []byte - if c.argBuf != nil { - arg = c.argBuf - c.argBuf = nil - } else { - arg = buf[c.as : i-c.drop] - } - if err := c.overMaxControlLineLimit(arg, mcl); err != nil { - return err - } - if trace { - c.traceInOp("A-", arg) - } - c.processAccountUnsub(arg) - c.drop, c.as, c.state = 0, i+1, OP_START - default: - if c.argBuf != nil { - c.argBuf = append(c.argBuf, b) - } - } - case OP_S: - switch b { - case 'U', 'u': - c.state = OP_SU - default: - goto parseErr - } - case OP_SU: - switch b { - case 'B', 'b': - c.state = OP_SUB - default: - goto parseErr - } - case OP_SUB: - switch b { - case ' ', '\t': - c.state = OP_SUB_SPC - default: - goto parseErr - } - case OP_SUB_SPC: - switch b { - case ' ', '\t': - continue - default: - c.state = SUB_ARG - c.as = i - } - case SUB_ARG: - switch b { - case '\r': - c.drop = 1 - case '\n': - var arg []byte - if c.argBuf != nil { - arg = c.argBuf - c.argBuf = nil - } else { - arg = buf[c.as : i-c.drop] - } - if err := c.overMaxControlLineLimit(arg, mcl); err != nil { - return err - } - var err error - - switch c.kind { - case CLIENT: - if trace { - c.traceInOp("SUB", arg) - } - err = c.parseSub(arg, false) - case ROUTER: - switch c.op { - case 'R', 'r': - if trace { - c.traceInOp("RS+", arg) - } - err = c.processRemoteSub(arg, false) - case 'L', 'l': - if trace { - c.traceInOp("LS+", arg) - } - err = c.processRemoteSub(arg, true) - } - case GATEWAY: - if trace { - c.traceInOp("RS+", arg) - } - err = c.processGatewayRSub(arg) - case LEAF: - if trace { - c.traceInOp("LS+", arg) - } - err = c.processLeafSub(arg) - } - if err != nil { - return err - } - c.drop, c.as, c.state = 0, i+1, OP_START - default: - if c.argBuf != nil { - c.argBuf = append(c.argBuf, b) - } - } - case OP_L: - switch b { - case 'S', 's': - c.state = OP_LS - case 'M', 'm': - c.state = OP_M - default: - goto parseErr - } - case OP_LS: - switch b { - case '+': - c.state = OP_SUB - case '-': - c.state = OP_UNSUB - default: - goto parseErr - } - case OP_R: - switch b { - case 'S', 's': - c.state = OP_RS - case 'M', 'm': - c.state = OP_M - default: - goto parseErr - } - case OP_RS: - switch b { - case '+': - c.state = OP_SUB - case '-': - c.state = OP_UNSUB - default: - goto parseErr - } - case OP_U: - switch b { - case 'N', 'n': - c.state = OP_UN - default: - goto parseErr - } - case OP_UN: - switch b { - case 'S', 's': - c.state = OP_UNS - default: - goto parseErr - } - case OP_UNS: - switch b { - case 'U', 'u': - c.state = OP_UNSU - default: - goto parseErr - } - case OP_UNSU: - switch b { - case 'B', 'b': - c.state = OP_UNSUB - default: - goto parseErr - } - case OP_UNSUB: - switch b { - case ' ', '\t': - c.state = OP_UNSUB_SPC - default: - goto parseErr - } - case OP_UNSUB_SPC: - switch b { - case ' ', '\t': - continue - default: - c.state = UNSUB_ARG - c.as = i - } - case UNSUB_ARG: - switch b { - case '\r': - c.drop = 1 - case '\n': - var arg []byte - if c.argBuf != nil { - arg = c.argBuf - c.argBuf = nil - } else { - arg = buf[c.as : i-c.drop] - } - if err := c.overMaxControlLineLimit(arg, mcl); err != nil { - return err - } - var err error - - switch c.kind { - case CLIENT: - if trace { - c.traceInOp("UNSUB", arg) - } - err = c.processUnsub(arg) - case ROUTER: - if trace && c.srv != nil { - switch c.op { - case 'R', 'r': - c.traceInOp("RS-", arg) - case 'L', 'l': - c.traceInOp("LS-", arg) - } - } - leafUnsub := c.op == 'L' || c.op == 'l' - err = c.processRemoteUnsub(arg, leafUnsub) - case GATEWAY: - if trace { - c.traceInOp("RS-", arg) - } - err = c.processGatewayRUnsub(arg) - case LEAF: - if trace { - c.traceInOp("LS-", arg) - } - err = c.processLeafUnsub(arg) - } - if err != nil { - return err - } - c.drop, c.as, c.state = 0, i+1, OP_START - default: - if c.argBuf != nil { - c.argBuf = append(c.argBuf, b) - } - } - case OP_PI: - switch b { - case 'N', 'n': - c.state = OP_PIN - default: - goto parseErr - } - case OP_PIN: - switch b { - case 'G', 'g': - c.state = OP_PING - default: - goto parseErr - } - case OP_PING: - switch b { - case '\n': - if trace { - c.traceInOp("PING", nil) - } - c.processPing() - c.drop, c.state = 0, OP_START - } - case OP_PO: - switch b { - case 'N', 'n': - c.state = OP_PON - default: - goto parseErr - } - case OP_PON: - switch b { - case 'G', 'g': - c.state = OP_PONG - default: - goto parseErr - } - case OP_PONG: - switch b { - case '\n': - if trace { - c.traceInOp("PONG", nil) - } - c.processPong() - c.drop, c.state = 0, OP_START - } - case OP_C: - switch b { - case 'O', 'o': - c.state = OP_CO - default: - goto parseErr - } - case OP_CO: - switch b { - case 'N', 'n': - c.state = OP_CON - default: - goto parseErr - } - case OP_CON: - switch b { - case 'N', 'n': - c.state = OP_CONN - default: - goto parseErr - } - case OP_CONN: - switch b { - case 'E', 'e': - c.state = OP_CONNE - default: - goto parseErr - } - case OP_CONNE: - switch b { - case 'C', 'c': - c.state = OP_CONNEC - default: - goto parseErr - } - case OP_CONNEC: - switch b { - case 'T', 't': - c.state = OP_CONNECT - default: - goto parseErr - } - case OP_CONNECT: - switch b { - case ' ', '\t': - continue - default: - c.state = CONNECT_ARG - c.as = i - } - case CONNECT_ARG: - switch b { - case '\r': - c.drop = 1 - case '\n': - var arg []byte - if c.argBuf != nil { - arg = c.argBuf - c.argBuf = nil - } else { - arg = buf[c.as : i-c.drop] - } - if err := c.overMaxControlLineLimit(arg, mcl); err != nil { - return err - } - if trace { - c.traceInOp("CONNECT", removePassFromTrace(arg)) - } - if err := c.processConnect(arg); err != nil { - return err - } - c.drop, c.state = 0, OP_START - // Reset notion on authSet - c.mu.Lock() - authSet = c.awaitingAuth() - c.mu.Unlock() - default: - if c.argBuf != nil { - c.argBuf = append(c.argBuf, b) - } - } - case OP_M: - switch b { - case 'S', 's': - c.state = OP_MS - default: - goto parseErr - } - case OP_MS: - switch b { - case 'G', 'g': - c.state = OP_MSG - default: - goto parseErr - } - case OP_MSG: - switch b { - case ' ', '\t': - c.state = OP_MSG_SPC - default: - goto parseErr - } - case OP_MSG_SPC: - switch b { - case ' ', '\t': - continue - default: - c.pa.hdr = -1 - c.state = MSG_ARG - c.as = i - } - case MSG_ARG: - switch b { - case '\r': - c.drop = 1 - case '\n': - var arg []byte - if c.argBuf != nil { - arg = c.argBuf - c.argBuf = nil - } else { - arg = buf[c.as : i-c.drop] - } - if err := c.overMaxControlLineLimit(arg, mcl); err != nil { - return err - } - var err error - if c.kind == ROUTER || c.kind == GATEWAY { - switch c.op { - case 'R', 'r': - if trace { - c.traceInOp("RMSG", arg) - } - err = c.processRoutedMsgArgs(arg) - case 'L', 'l': - if trace { - c.traceInOp("LMSG", arg) - } - lmsg = true - err = c.processRoutedOriginClusterMsgArgs(arg) - } - } else if c.kind == LEAF { - if trace { - c.traceInOp("LMSG", arg) - } - err = c.processLeafMsgArgs(arg) - } - if err != nil { - return err - } - c.drop, c.as, c.state = 0, i+1, MSG_PAYLOAD - - // jump ahead with the index. If this overruns - // what is left we fall out and process split - // buffer. - i = c.as + c.pa.size - LEN_CR_LF - default: - if c.argBuf != nil { - c.argBuf = append(c.argBuf, b) - } - } - case OP_I: - switch b { - case 'N', 'n': - c.state = OP_IN - default: - goto parseErr - } - case OP_IN: - switch b { - case 'F', 'f': - c.state = OP_INF - default: - goto parseErr - } - case OP_INF: - switch b { - case 'O', 'o': - c.state = OP_INFO - default: - goto parseErr - } - case OP_INFO: - switch b { - case ' ', '\t': - continue - default: - c.state = INFO_ARG - c.as = i - } - case INFO_ARG: - switch b { - case '\r': - c.drop = 1 - case '\n': - var arg []byte - if c.argBuf != nil { - arg = c.argBuf - c.argBuf = nil - } else { - arg = buf[c.as : i-c.drop] - } - if err := c.overMaxControlLineLimit(arg, mcl); err != nil { - return err - } - if err := c.processInfo(arg); err != nil { - return err - } - c.drop, c.as, c.state = 0, i+1, OP_START - default: - if c.argBuf != nil { - c.argBuf = append(c.argBuf, b) - } - } - case OP_PLUS: - switch b { - case 'O', 'o': - c.state = OP_PLUS_O - default: - goto parseErr - } - case OP_PLUS_O: - switch b { - case 'K', 'k': - c.state = OP_PLUS_OK - default: - goto parseErr - } - case OP_PLUS_OK: - switch b { - case '\n': - c.drop, c.state = 0, OP_START - } - case OP_MINUS: - switch b { - case 'E', 'e': - c.state = OP_MINUS_E - default: - goto parseErr - } - case OP_MINUS_E: - switch b { - case 'R', 'r': - c.state = OP_MINUS_ER - default: - goto parseErr - } - case OP_MINUS_ER: - switch b { - case 'R', 'r': - c.state = OP_MINUS_ERR - default: - goto parseErr - } - case OP_MINUS_ERR: - switch b { - case ' ', '\t': - c.state = OP_MINUS_ERR_SPC - default: - goto parseErr - } - case OP_MINUS_ERR_SPC: - switch b { - case ' ', '\t': - continue - default: - c.state = MINUS_ERR_ARG - c.as = i - } - case MINUS_ERR_ARG: - switch b { - case '\r': - c.drop = 1 - case '\n': - var arg []byte - if c.argBuf != nil { - arg = c.argBuf - c.argBuf = nil - } else { - arg = buf[c.as : i-c.drop] - } - if err := c.overMaxControlLineLimit(arg, mcl); err != nil { - return err - } - c.processErr(string(arg)) - c.drop, c.as, c.state = 0, i+1, OP_START - default: - if c.argBuf != nil { - c.argBuf = append(c.argBuf, b) - } - } - default: - goto parseErr - } - } - - // Check for split buffer scenarios for any ARG state. - if c.state == SUB_ARG || c.state == UNSUB_ARG || - c.state == PUB_ARG || c.state == HPUB_ARG || - c.state == ASUB_ARG || c.state == AUSUB_ARG || - c.state == MSG_ARG || c.state == HMSG_ARG || - c.state == MINUS_ERR_ARG || c.state == CONNECT_ARG || c.state == INFO_ARG { - - // Setup a holder buffer to deal with split buffer scenario. - if c.argBuf == nil { - c.argBuf = c.scratch[:0] - c.argBuf = append(c.argBuf, buf[c.as:i-c.drop]...) - } - // Check for violations of control line length here. Note that this is not - // exact at all but the performance hit is too great to be precise, and - // catching here should prevent memory exhaustion attacks. - if err := c.overMaxControlLineLimit(c.argBuf, mcl); err != nil { - return err - } - } - - // Check for split msg - if (c.state == MSG_PAYLOAD || c.state == MSG_END_R || c.state == MSG_END_N) && c.msgBuf == nil { - // We need to clone the pubArg if it is still referencing the - // read buffer and we are not able to process the msg. - - if c.argBuf == nil { - // Works also for MSG_ARG, when message comes from ROUTE or GATEWAY. - if err := c.clonePubArg(lmsg); err != nil { - goto parseErr - } - } - - // If we will overflow the scratch buffer, just create a - // new buffer to hold the split message. - if c.pa.size > cap(c.scratch)-len(c.argBuf) { - lrem := len(buf[c.as:]) - // Consider it a protocol error when the remaining payload - // is larger than the reported size for PUB. It can happen - // when processing incomplete messages from rogue clients. - if lrem > c.pa.size+LEN_CR_LF { - goto parseErr - } - c.msgBuf = make([]byte, lrem, c.pa.size+LEN_CR_LF) - copy(c.msgBuf, buf[c.as:]) - } else { - c.msgBuf = c.scratch[len(c.argBuf):len(c.argBuf)] - c.msgBuf = append(c.msgBuf, (buf[c.as:])...) - } - } - - return nil - -authErr: - c.authViolation() - return ErrAuthentication - -parseErr: - c.sendErr("Unknown Protocol Operation") - snip := protoSnippet(i, PROTO_SNIPPET_SIZE, buf) - err := fmt.Errorf("%s parser ERROR, state=%d, i=%d: proto='%s...'", c.kindString(), c.state, i, snip) - return err -} - -func protoSnippet(start, max int, buf []byte) string { - stop := start + max - bufSize := len(buf) - if start >= bufSize { - return `""` - } - if stop > bufSize { - stop = bufSize - 1 - } - return fmt.Sprintf("%q", buf[start:stop]) -} - -// Check if the length of buffer `arg` is over the max control line limit `mcl`. -// If so, an error is sent to the client and the connection is closed. -// The error ErrMaxControlLine is returned. -func (c *client) overMaxControlLineLimit(arg []byte, mcl int32) error { - if c.kind != CLIENT { - return nil - } - if len(arg) > int(mcl) { - err := NewErrorCtx(ErrMaxControlLine, "State %d, max_control_line %d, Buffer len %d (snip: %s...)", - c.state, int(mcl), len(c.argBuf), protoSnippet(0, MAX_CONTROL_LINE_SNIPPET_SIZE, arg)) - c.sendErr(err.Error()) - c.closeConnection(MaxControlLineExceeded) - return err - } - return nil -} - -// clonePubArg is used when the split buffer scenario has the pubArg in the existing read buffer, but -// we need to hold onto it into the next read. -func (c *client) clonePubArg(lmsg bool) error { - // Just copy and re-process original arg buffer. - c.argBuf = c.scratch[:0] - c.argBuf = append(c.argBuf, c.pa.arg...) - - switch c.kind { - case ROUTER, GATEWAY: - if lmsg { - return c.processRoutedOriginClusterMsgArgs(c.argBuf) - } - if c.pa.hdr < 0 { - return c.processRoutedMsgArgs(c.argBuf) - } else { - return c.processRoutedHeaderMsgArgs(c.argBuf) - } - case LEAF: - if c.pa.hdr < 0 { - return c.processLeafMsgArgs(c.argBuf) - } else { - return c.processLeafHeaderMsgArgs(c.argBuf) - } - default: - if c.pa.hdr < 0 { - return c.processPub(c.argBuf) - } else { - return c.processHeaderPub(c.argBuf, nil) - } - } -} - -func (ps *parseState) getHeader() http.Header { - if ps.header == nil { - if hdr := ps.pa.hdr; hdr > 0 { - reader := bufio.NewReader(bytes.NewReader(ps.msgBuf[0:hdr])) - tp := textproto.NewReader(reader) - tp.ReadLine() // skip over first line, contains version - if mimeHeader, err := tp.ReadMIMEHeader(); err == nil { - ps.header = http.Header(mimeHeader) - } - } - } - return ps.header -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/proto.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/proto.go deleted file mode 100644 index 9843fff2..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/proto.go +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright 2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Inspired by https://github.com/protocolbuffers/protobuf-go/blob/master/encoding/protowire/wire.go - -package server - -import ( - "errors" - "fmt" - "math" -) - -var errProtoInsufficient = errors.New("insufficient data to read a value") -var errProtoOverflow = errors.New("too much data for a value") -var errProtoInvalidFieldNumber = errors.New("invalid field number") - -func protoScanField(b []byte) (num, typ, size int, err error) { - num, typ, sizeTag, err := protoScanTag(b) - if err != nil { - return 0, 0, 0, err - } - b = b[sizeTag:] - - sizeValue, err := protoScanFieldValue(typ, b) - if err != nil { - return 0, 0, 0, err - } - return num, typ, sizeTag + sizeValue, nil -} - -func protoScanTag(b []byte) (num, typ, size int, err error) { - tagint, size, err := protoScanVarint(b) - if err != nil { - return 0, 0, 0, err - } - - // NOTE: MessageSet allows for larger field numbers than normal. - if (tagint >> 3) > uint64(math.MaxInt32) { - return 0, 0, 0, errProtoInvalidFieldNumber - } - num = int(tagint >> 3) - if num < 1 { - return 0, 0, 0, errProtoInvalidFieldNumber - } - typ = int(tagint & 7) - - return num, typ, size, nil -} - -func protoScanFieldValue(typ int, b []byte) (size int, err error) { - switch typ { - case 0: - _, size, err = protoScanVarint(b) - case 5: // fixed32 - size = 4 - case 1: // fixed64 - size = 8 - case 2: // length-delimited - size, err = protoScanBytes(b) - default: - return 0, fmt.Errorf("unsupported type: %d", typ) - } - return size, err -} - -func protoScanVarint(b []byte) (v uint64, size int, err error) { - var y uint64 - if len(b) <= 0 { - return 0, 0, errProtoInsufficient - } - v = uint64(b[0]) - if v < 0x80 { - return v, 1, nil - } - v -= 0x80 - - if len(b) <= 1 { - return 0, 0, errProtoInsufficient - } - y = uint64(b[1]) - v += y << 7 - if y < 0x80 { - return v, 2, nil - } - v -= 0x80 << 7 - - if len(b) <= 2 { - return 0, 0, errProtoInsufficient - } - y = uint64(b[2]) - v += y << 14 - if y < 0x80 { - return v, 3, nil - } - v -= 0x80 << 14 - - if len(b) <= 3 { - return 0, 0, errProtoInsufficient - } - y = uint64(b[3]) - v += y << 21 - if y < 0x80 { - return v, 4, nil - } - v -= 0x80 << 21 - - if len(b) <= 4 { - return 0, 0, errProtoInsufficient - } - y = uint64(b[4]) - v += y << 28 - if y < 0x80 { - return v, 5, nil - } - v -= 0x80 << 28 - - if len(b) <= 5 { - return 0, 0, errProtoInsufficient - } - y = uint64(b[5]) - v += y << 35 - if y < 0x80 { - return v, 6, nil - } - v -= 0x80 << 35 - - if len(b) <= 6 { - return 0, 0, errProtoInsufficient - } - y = uint64(b[6]) - v += y << 42 - if y < 0x80 { - return v, 7, nil - } - v -= 0x80 << 42 - - if len(b) <= 7 { - return 0, 0, errProtoInsufficient - } - y = uint64(b[7]) - v += y << 49 - if y < 0x80 { - return v, 8, nil - } - v -= 0x80 << 49 - - if len(b) <= 8 { - return 0, 0, errProtoInsufficient - } - y = uint64(b[8]) - v += y << 56 - if y < 0x80 { - return v, 9, nil - } - v -= 0x80 << 56 - - if len(b) <= 9 { - return 0, 0, errProtoInsufficient - } - y = uint64(b[9]) - v += y << 63 - if y < 2 { - return v, 10, nil - } - return 0, 0, errProtoOverflow -} - -func protoScanBytes(b []byte) (size int, err error) { - l, lenSize, err := protoScanVarint(b) - if err != nil { - return 0, err - } - if l > uint64(len(b[lenSize:])) { - return 0, errProtoInsufficient - } - return lenSize + int(l), nil -} - -func protoEncodeVarint(v uint64) []byte { - b := make([]byte, 0, 10) - switch { - case v < 1<<7: - b = append(b, byte(v)) - case v < 1<<14: - b = append(b, - byte((v>>0)&0x7f|0x80), - byte(v>>7)) - case v < 1<<21: - b = append(b, - byte((v>>0)&0x7f|0x80), - byte((v>>7)&0x7f|0x80), - byte(v>>14)) - case v < 1<<28: - b = append(b, - byte((v>>0)&0x7f|0x80), - byte((v>>7)&0x7f|0x80), - byte((v>>14)&0x7f|0x80), - byte(v>>21)) - case v < 1<<35: - b = append(b, - byte((v>>0)&0x7f|0x80), - byte((v>>7)&0x7f|0x80), - byte((v>>14)&0x7f|0x80), - byte((v>>21)&0x7f|0x80), - byte(v>>28)) - case v < 1<<42: - b = append(b, - byte((v>>0)&0x7f|0x80), - byte((v>>7)&0x7f|0x80), - byte((v>>14)&0x7f|0x80), - byte((v>>21)&0x7f|0x80), - byte((v>>28)&0x7f|0x80), - byte(v>>35)) - case v < 1<<49: - b = append(b, - byte((v>>0)&0x7f|0x80), - byte((v>>7)&0x7f|0x80), - byte((v>>14)&0x7f|0x80), - byte((v>>21)&0x7f|0x80), - byte((v>>28)&0x7f|0x80), - byte((v>>35)&0x7f|0x80), - byte(v>>42)) - case v < 1<<56: - b = append(b, - byte((v>>0)&0x7f|0x80), - byte((v>>7)&0x7f|0x80), - byte((v>>14)&0x7f|0x80), - byte((v>>21)&0x7f|0x80), - byte((v>>28)&0x7f|0x80), - byte((v>>35)&0x7f|0x80), - byte((v>>42)&0x7f|0x80), - byte(v>>49)) - case v < 1<<63: - b = append(b, - byte((v>>0)&0x7f|0x80), - byte((v>>7)&0x7f|0x80), - byte((v>>14)&0x7f|0x80), - byte((v>>21)&0x7f|0x80), - byte((v>>28)&0x7f|0x80), - byte((v>>35)&0x7f|0x80), - byte((v>>42)&0x7f|0x80), - byte((v>>49)&0x7f|0x80), - byte(v>>56)) - default: - b = append(b, - byte((v>>0)&0x7f|0x80), - byte((v>>7)&0x7f|0x80), - byte((v>>14)&0x7f|0x80), - byte((v>>21)&0x7f|0x80), - byte((v>>28)&0x7f|0x80), - byte((v>>35)&0x7f|0x80), - byte((v>>42)&0x7f|0x80), - byte((v>>49)&0x7f|0x80), - byte((v>>56)&0x7f|0x80), - 1) - } - return b -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/freebsd.txt b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/freebsd.txt deleted file mode 100644 index 877068cc..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/freebsd.txt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Compile and run this as a C program to get the kinfo_proc offsets - * for your architecture. - * While FreeBSD works hard at binary-compatibility within an ABI, various - * we can't say for sure that these are right for _all_ use on a hardware - * platform. The LP64 ifdef affects the offsets considerably. - * - * We use these offsets in hardware-specific files for FreeBSD, to avoid a cgo - * compilation-time dependency, allowing us to cross-compile for FreeBSD from - * other hardware platforms, letting us distribute binaries for FreeBSD. - */ - -#include -#include -#include -#include -#include - -#define SHOW_OFFSET(FIELD) printf(" KIP_OFF_%s = %zu\n", #FIELD, offsetof(struct kinfo_proc, ki_ ## FIELD)) - -int main(int argc, char *argv[]) { - /* Uncomment these if you want some extra debugging aids: - SHOW_OFFSET(pid); - SHOW_OFFSET(ppid); - SHOW_OFFSET(uid); - */ - SHOW_OFFSET(size); - SHOW_OFFSET(rssize); - SHOW_OFFSET(pctcpu); -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_darwin.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_darwin.go deleted file mode 100644 index 7fd26b15..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_darwin.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2015-2021 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pse - -// On macs after some studying it seems that typical tools like ps and activity monitor report MaxRss and not -// current RSS. I wrote some C code to pull the real RSS and although it does not go down very often, when it does -// that is not reflected in the typical tooling one might compare us to, so we can skip cgo and just use rusage imo. -// We also do not use virtual memory in the upper layers at all, so ok to skip since rusage does not report vss. - -import ( - "math" - "sync" - "syscall" - "time" -) - -type lastUsage struct { - sync.Mutex - last time.Time - cpu time.Duration - rss int64 - pcpu float64 -} - -// To hold the last usage and call time. -var lu lastUsage - -func init() { - updateUsage() - periodic() -} - -// Get our usage. -func getUsage() (now time.Time, cpu time.Duration, rss int64) { - var ru syscall.Rusage - syscall.Getrusage(syscall.RUSAGE_SELF, &ru) - now = time.Now() - cpu = time.Duration(ru.Utime.Sec)*time.Second + time.Duration(ru.Utime.Usec)*time.Microsecond - cpu += time.Duration(ru.Stime.Sec)*time.Second + time.Duration(ru.Stime.Usec)*time.Microsecond - return now, cpu, ru.Maxrss -} - -// Update last usage. -// We need to have a prior sample to compute pcpu. -func updateUsage() (pcpu float64, rss int64) { - lu.Lock() - defer lu.Unlock() - - now, cpu, rss := getUsage() - // Don't skew pcpu by sampling too close to last sample. - if elapsed := now.Sub(lu.last); elapsed < 500*time.Millisecond { - // Always update rss. - lu.rss = rss - } else { - tcpu := float64(cpu - lu.cpu) - lu.last, lu.cpu, lu.rss = now, cpu, rss - // Want to make this one decimal place and not count on upper layers. - // Cores already taken into account via cpu time measurements. - lu.pcpu = math.Round(tcpu/float64(elapsed)*1000) / 10 - } - return lu.pcpu, lu.rss -} - -// Sampling function to keep pcpu relevant. -func periodic() { - updateUsage() - time.AfterFunc(time.Second, periodic) -} - -// ProcUsage returns CPU and memory usage. -// Note upper layers do not use virtual memory size, so ok that it is not filled in here. -func ProcUsage(pcpu *float64, rss, vss *int64) error { - *pcpu, *rss = updateUsage() - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_dragonfly.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_dragonfly.go deleted file mode 100644 index 55a917ab..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_dragonfly.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2015-2023 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Copied from pse_openbsd.go - -package pse - -import ( - "fmt" - "os" - "os/exec" -) - -// ProcUsage returns CPU usage -func ProcUsage(pcpu *float64, rss, vss *int64) error { - pidStr := fmt.Sprintf("%d", os.Getpid()) - out, err := exec.Command("ps", "o", "pcpu=,rss=,vsz=", "-p", pidStr).Output() - if err != nil { - *rss, *vss = -1, -1 - return fmt.Errorf("ps call failed:%v", err) - } - fmt.Sscanf(string(out), "%f %d %d", pcpu, rss, vss) - *rss *= 1024 // 1k blocks, want bytes. - *vss *= 1024 // 1k blocks, want bytes. - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_freebsd.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_freebsd.go deleted file mode 100644 index 21b5445c..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_freebsd.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2015-2021 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build !amd64 - -package pse - -/* -#include -#include -#include -#include -#include - -long pagetok(long size) -{ - int pageshift, pagesize; - - pagesize = getpagesize(); - pageshift = 0; - - while (pagesize > 1) { - pageshift++; - pagesize >>= 1; - } - - return (size << pageshift); -} - -int getusage(double *pcpu, unsigned int *rss, unsigned int *vss) -{ - int mib[4], ret; - size_t len; - struct kinfo_proc kp; - - len = 4; - sysctlnametomib("kern.proc.pid", mib, &len); - - mib[3] = getpid(); - len = sizeof(kp); - - ret = sysctl(mib, 4, &kp, &len, NULL, 0); - if (ret != 0) { - return (errno); - } - - *rss = pagetok(kp.ki_rssize); - *vss = kp.ki_size; - *pcpu = (double)kp.ki_pctcpu / FSCALE; - - return 0; -} - -*/ -import "C" - -import ( - "syscall" -) - -// This is a placeholder for now. -func ProcUsage(pcpu *float64, rss, vss *int64) error { - var r, v C.uint - var c C.double - - if ret := C.getusage(&c, &r, &v); ret != 0 { - return syscall.Errno(ret) - } - - *pcpu = float64(c) - *rss = int64(r) - *vss = int64(v) - - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_freebsd_amd64.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_freebsd_amd64.go deleted file mode 100644 index 2d21e5c2..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_freebsd_amd64.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2015-2020 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// This is the amd64-specific FreeBSD implementation, with hard-coded offset -// constants derived by running freebsd.txt; having this implementation allows -// us to compile without CGO, which lets us cross-compile for FreeBSD from our -// CI system and so supply binaries for FreeBSD amd64. -// -// To generate for other architectures: -// 1. Update pse_freebsd.go, change the build exclusion to exclude your arch -// 2. Copy this file to be built for your arch -// 3. Update `nativeEndian` below -// 4. Link `freebsd.txt` to have a .c filename and compile and run, then -// paste the outputs into the const section below. - -package pse - -import ( - "encoding/binary" - "syscall" - - "golang.org/x/sys/unix" -) - -// On FreeBSD, to get proc information we read it out of the kernel using a -// binary sysctl. The endianness of integers is thus explicitly "host", rather -// than little or big endian. -var nativeEndian = binary.LittleEndian - -const ( - KIP_OFF_size = 256 - KIP_OFF_rssize = 264 - KIP_OFF_pctcpu = 308 -) - -var pageshift int - -func init() { - // To get the physical page size, the C library checks two places: - // process ELF auxiliary info, AT_PAGESZ - // as a fallback, the hw.pagesize sysctl - // In looking closely, I found that the Go runtime support is handling - // this for us, and exposing that as syscall.Getpagesize, having checked - // both in the same ways, at process start, so a call to that should return - // a memory value without even a syscall bounce. - pagesize := syscall.Getpagesize() - pageshift = 0 - for pagesize > 1 { - pageshift += 1 - pagesize >>= 1 - } -} - -func ProcUsage(pcpu *float64, rss, vss *int64) error { - rawdata, err := unix.SysctlRaw("kern.proc.pid", unix.Getpid()) - if err != nil { - return err - } - - r_vss_bytes := nativeEndian.Uint32(rawdata[KIP_OFF_size:]) - r_rss_pages := nativeEndian.Uint32(rawdata[KIP_OFF_rssize:]) - rss_bytes := r_rss_pages << pageshift - - // In C: fixpt_t ki_pctcpu - // Doc: %cpu for process during ki_swtime - // fixpt_t is __uint32_t - // usr.bin/top uses pctdouble to convert to a double (float64) - // define pctdouble(p) ((double)(p) / FIXED_PCTCPU) - // FIXED_PCTCPU is _usually_ FSCALE (some architectures are special) - // has: - // #define FSHIFT 11 /* bits to right of fixed binary point */ - // #define FSCALE (1< 0 { - atomic.StoreInt64(&ipcpu, (total*1000/ticks)/seconds) - } - - time.AfterFunc(1*time.Second, periodic) -} - -// ProcUsage returns CPU usage -func ProcUsage(pcpu *float64, rss, vss *int64) error { - contents, err := os.ReadFile(procStatFile) - if err != nil { - return err - } - fields := bytes.Fields(contents) - - // Memory - *rss = (parseInt64(fields[rssPos])) << 12 - *vss = parseInt64(fields[vssPos]) - - // PCPU - // We track this with periodic sampling, so just load and go. - *pcpu = float64(atomic.LoadInt64(&ipcpu)) / 10.0 - - return nil -} - -// Ascii numbers 0-9 -const ( - asciiZero = 48 - asciiNine = 57 -) - -// parseInt64 expects decimal positive numbers. We -// return -1 to signal error -func parseInt64(d []byte) (n int64) { - if len(d) == 0 { - return -1 - } - for _, dec := range d { - if dec < asciiZero || dec > asciiNine { - return -1 - } - n = n*10 + (int64(dec) - asciiZero) - } - return n -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_netbsd.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_netbsd.go deleted file mode 100644 index d8393f67..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_netbsd.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2022 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Copied from pse_openbsd.go - -package pse - -import ( - "fmt" - "os" - "os/exec" -) - -// ProcUsage returns CPU usage -func ProcUsage(pcpu *float64, rss, vss *int64) error { - pidStr := fmt.Sprintf("%d", os.Getpid()) - out, err := exec.Command("ps", "o", "pcpu=,rss=,vsz=", "-p", pidStr).Output() - if err != nil { - *rss, *vss = -1, -1 - return fmt.Errorf("ps call failed:%v", err) - } - fmt.Sscanf(string(out), "%f %d %d", pcpu, rss, vss) - *rss *= 1024 // 1k blocks, want bytes. - *vss *= 1024 // 1k blocks, want bytes. - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_openbsd.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_openbsd.go deleted file mode 100644 index 260f1a7c..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_openbsd.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2015-2018 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Copied from pse_darwin.go - -package pse - -import ( - "fmt" - "os" - "os/exec" -) - -// ProcUsage returns CPU usage -func ProcUsage(pcpu *float64, rss, vss *int64) error { - pidStr := fmt.Sprintf("%d", os.Getpid()) - out, err := exec.Command("ps", "o", "pcpu=,rss=,vsz=", "-p", pidStr).Output() - if err != nil { - *rss, *vss = -1, -1 - return fmt.Errorf("ps call failed:%v", err) - } - fmt.Sscanf(string(out), "%f %d %d", pcpu, rss, vss) - *rss *= 1024 // 1k blocks, want bytes. - *vss *= 1024 // 1k blocks, want bytes. - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_rumprun.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_rumprun.go deleted file mode 100644 index d16e6ea9..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_rumprun.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2015-2021 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build rumprun - -package pse - -// This is a placeholder for now. -func ProcUsage(pcpu *float64, rss, vss *int64) error { - *pcpu = 0.0 - *rss = 0 - *vss = 0 - - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_solaris.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_solaris.go deleted file mode 100644 index 8e40d2ed..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_solaris.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2015-2018 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pse - -// This is a placeholder for now. -func ProcUsage(pcpu *float64, rss, vss *int64) error { - *pcpu = 0.0 - *rss = 0 - *vss = 0 - - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_wasm.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_wasm.go deleted file mode 100644 index e3db060c..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_wasm.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2022 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build wasm - -package pse - -// This is a placeholder for now. -func ProcUsage(pcpu *float64, rss, vss *int64) error { - *pcpu = 0.0 - *rss = 0 - *vss = 0 - - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_windows.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_windows.go deleted file mode 100644 index 09f84a0c..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_windows.go +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright 2015-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build windows - -package pse - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "sync" - "syscall" - "time" - "unsafe" - - "golang.org/x/sys/windows" -) - -var ( - pdh = windows.NewLazySystemDLL("pdh.dll") - winPdhOpenQuery = pdh.NewProc("PdhOpenQuery") - winPdhAddCounter = pdh.NewProc("PdhAddCounterW") - winPdhCollectQueryData = pdh.NewProc("PdhCollectQueryData") - winPdhGetFormattedCounterValue = pdh.NewProc("PdhGetFormattedCounterValue") - winPdhGetFormattedCounterArray = pdh.NewProc("PdhGetFormattedCounterArrayW") -) - -func init() { - if err := pdh.Load(); err != nil { - panic(err) - } - for _, p := range []*windows.LazyProc{ - winPdhOpenQuery, winPdhAddCounter, winPdhCollectQueryData, - winPdhGetFormattedCounterValue, winPdhGetFormattedCounterArray, - } { - if err := p.Find(); err != nil { - panic(err) - } - } -} - -// global performance counter query handle and counters -var ( - pcHandle PDH_HQUERY - pidCounter, cpuCounter, rssCounter, vssCounter PDH_HCOUNTER - prevCPU float64 - prevRss int64 - prevVss int64 - lastSampleTime time.Time - processPid int - pcQueryLock sync.Mutex - initialSample = true -) - -// maxQuerySize is the number of values to return from a query. -// It represents the maximum # of servers that can be queried -// simultaneously running on a machine. -const maxQuerySize = 512 - -// Keep static memory around to reuse; this works best for passing -// into the pdh API. -var counterResults [maxQuerySize]PDH_FMT_COUNTERVALUE_ITEM_DOUBLE - -// PDH Types -type ( - PDH_HQUERY syscall.Handle - PDH_HCOUNTER syscall.Handle -) - -// PDH constants used here -const ( - PDH_FMT_DOUBLE = 0x00000200 - PDH_INVALID_DATA = 0xC0000BC6 - PDH_MORE_DATA = 0x800007D2 -) - -// PDH_FMT_COUNTERVALUE_DOUBLE - double value -type PDH_FMT_COUNTERVALUE_DOUBLE struct { - CStatus uint32 - DoubleValue float64 -} - -// PDH_FMT_COUNTERVALUE_ITEM_DOUBLE is an array -// element of a double value -type PDH_FMT_COUNTERVALUE_ITEM_DOUBLE struct { - SzName *uint16 // pointer to a string - FmtValue PDH_FMT_COUNTERVALUE_DOUBLE -} - -func pdhAddCounter(hQuery PDH_HQUERY, szFullCounterPath string, dwUserData uintptr, phCounter *PDH_HCOUNTER) error { - ptxt, _ := syscall.UTF16PtrFromString(szFullCounterPath) - r0, _, _ := winPdhAddCounter.Call( - uintptr(hQuery), - uintptr(unsafe.Pointer(ptxt)), - dwUserData, - uintptr(unsafe.Pointer(phCounter))) - - if r0 != 0 { - return fmt.Errorf("pdhAddCounter failed. %d", r0) - } - return nil -} - -func pdhOpenQuery(datasrc *uint16, userdata uint32, query *PDH_HQUERY) error { - r0, _, _ := syscall.Syscall(winPdhOpenQuery.Addr(), 3, 0, uintptr(userdata), uintptr(unsafe.Pointer(query))) - if r0 != 0 { - return fmt.Errorf("pdhOpenQuery failed - %d", r0) - } - return nil -} - -func pdhCollectQueryData(hQuery PDH_HQUERY) error { - r0, _, _ := winPdhCollectQueryData.Call(uintptr(hQuery)) - if r0 != 0 { - return fmt.Errorf("pdhCollectQueryData failed - %d", r0) - } - return nil -} - -// pdhGetFormattedCounterArrayDouble returns the value of return code -// rather than error, to easily check return codes -func pdhGetFormattedCounterArrayDouble(hCounter PDH_HCOUNTER, lpdwBufferSize *uint32, lpdwBufferCount *uint32, itemBuffer *PDH_FMT_COUNTERVALUE_ITEM_DOUBLE) uint32 { - ret, _, _ := winPdhGetFormattedCounterArray.Call( - uintptr(hCounter), - uintptr(PDH_FMT_DOUBLE), - uintptr(unsafe.Pointer(lpdwBufferSize)), - uintptr(unsafe.Pointer(lpdwBufferCount)), - uintptr(unsafe.Pointer(itemBuffer))) - - return uint32(ret) -} - -func getCounterArrayData(counter PDH_HCOUNTER) ([]float64, error) { - var bufSize uint32 - var bufCount uint32 - - // Retrieving array data requires two calls, the first which - // requires an addressable empty buffer, and sets size fields. - // The second call returns the data. - initialBuf := make([]PDH_FMT_COUNTERVALUE_ITEM_DOUBLE, 1) - ret := pdhGetFormattedCounterArrayDouble(counter, &bufSize, &bufCount, &initialBuf[0]) - if ret == PDH_MORE_DATA { - // we'll likely never get here, but be safe. - if bufCount > maxQuerySize { - bufCount = maxQuerySize - } - ret = pdhGetFormattedCounterArrayDouble(counter, &bufSize, &bufCount, &counterResults[0]) - if ret == 0 { - rv := make([]float64, bufCount) - for i := 0; i < int(bufCount); i++ { - rv[i] = counterResults[i].FmtValue.DoubleValue - } - return rv, nil - } - } - if ret != 0 { - return nil, fmt.Errorf("getCounterArrayData failed - %d", ret) - } - - return nil, nil -} - -// getProcessImageName returns the name of the process image, as expected by -// the performance counter API. -func getProcessImageName() (name string) { - name = filepath.Base(os.Args[0]) - name = strings.TrimSuffix(name, ".exe") - return -} - -// initialize our counters -func initCounters() (err error) { - - processPid = os.Getpid() - // require an addressible nil pointer - var source uint16 - if err := pdhOpenQuery(&source, 0, &pcHandle); err != nil { - return err - } - - // setup the performance counters, search for all server instances - name := fmt.Sprintf("%s*", getProcessImageName()) - pidQuery := fmt.Sprintf("\\Process(%s)\\ID Process", name) - cpuQuery := fmt.Sprintf("\\Process(%s)\\%% Processor Time", name) - rssQuery := fmt.Sprintf("\\Process(%s)\\Working Set - Private", name) - vssQuery := fmt.Sprintf("\\Process(%s)\\Virtual Bytes", name) - - if err = pdhAddCounter(pcHandle, pidQuery, 0, &pidCounter); err != nil { - return err - } - if err = pdhAddCounter(pcHandle, cpuQuery, 0, &cpuCounter); err != nil { - return err - } - if err = pdhAddCounter(pcHandle, rssQuery, 0, &rssCounter); err != nil { - return err - } - if err = pdhAddCounter(pcHandle, vssQuery, 0, &vssCounter); err != nil { - return err - } - - // prime the counters by collecting once, and sleep to get somewhat - // useful information the first request. Counters for the CPU require - // at least two collect calls. - if err = pdhCollectQueryData(pcHandle); err != nil { - return err - } - time.Sleep(50) - - return nil -} - -// ProcUsage returns process CPU and memory statistics -func ProcUsage(pcpu *float64, rss, vss *int64) error { - var err error - - // For simplicity, protect the entire call. - // Most simultaneous requests will immediately return - // with cached values. - pcQueryLock.Lock() - defer pcQueryLock.Unlock() - - // First time through, initialize counters. - if initialSample { - if err = initCounters(); err != nil { - return err - } - initialSample = false - } else if time.Since(lastSampleTime) < (2 * time.Second) { - // only refresh every two seconds as to minimize impact - // on the server. - *pcpu = prevCPU - *rss = prevRss - *vss = prevVss - return nil - } - - // always save the sample time, even on errors. - defer func() { - lastSampleTime = time.Now() - }() - - // refresh the performance counter data - if err = pdhCollectQueryData(pcHandle); err != nil { - return err - } - - // retrieve the data - var pidAry, cpuAry, rssAry, vssAry []float64 - if pidAry, err = getCounterArrayData(pidCounter); err != nil { - return err - } - if cpuAry, err = getCounterArrayData(cpuCounter); err != nil { - return err - } - if rssAry, err = getCounterArrayData(rssCounter); err != nil { - return err - } - if vssAry, err = getCounterArrayData(vssCounter); err != nil { - return err - } - // find the index of the entry for this process - idx := int(-1) - for i := range pidAry { - if int(pidAry[i]) == processPid { - idx = i - break - } - } - // no pid found... - if idx < 0 { - return fmt.Errorf("could not find pid in performance counter results") - } - // assign values from the performance counters - *pcpu = cpuAry[idx] - *rss = int64(rssAry[idx]) - *vss = int64(vssAry[idx]) - - // save off cache values - prevCPU = *pcpu - prevRss = *rss - prevVss = *vss - - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_zos.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_zos.go deleted file mode 100644 index df469f4e..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/pse/pse_zos.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2023 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build zos - -package pse - -// This is a placeholder for now. -func ProcUsage(pcpu *float64, rss, vss *int64) error { - *pcpu = 0.0 - *rss = 0 - *vss = 0 - - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/raft.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/raft.go deleted file mode 100644 index dd2f345b..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/raft.go +++ /dev/null @@ -1,4369 +0,0 @@ -// Copyright 2020-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "crypto/sha256" - "encoding/binary" - "errors" - "fmt" - "hash" - "math" - "math/rand" - "net" - "os" - "path/filepath" - "runtime" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/nats-io/nats-server/v2/internal/fastrand" - - "github.com/minio/highwayhash" -) - -type RaftNode interface { - Propose(entry []byte) error - ProposeMulti(entries []*Entry) error - ForwardProposal(entry []byte) error - InstallSnapshot(snap []byte) error - SendSnapshot(snap []byte) error - NeedSnapshot() bool - Applied(index uint64) (entries uint64, bytes uint64) - State() RaftState - Size() (entries, bytes uint64) - Progress() (index, commit, applied uint64) - Leader() bool - Quorum() bool - Current() bool - Healthy() bool - Term() uint64 - Leaderless() bool - GroupLeader() string - HadPreviousLeader() bool - StepDown(preferred ...string) error - SetObserver(isObserver bool) - IsObserver() bool - Campaign() error - ID() string - Group() string - Peers() []*Peer - ProposeKnownPeers(knownPeers []string) - UpdateKnownPeers(knownPeers []string) - ProposeAddPeer(peer string) error - ProposeRemovePeer(peer string) error - AdjustClusterSize(csz int) error - AdjustBootClusterSize(csz int) error - ClusterSize() int - ApplyQ() *ipQueue[*CommittedEntry] - PauseApply() error - ResumeApply() - LeadChangeC() <-chan bool - QuitC() <-chan struct{} - Created() time.Time - Stop() - WaitForStop() - Delete() - RecreateInternalSubs() error - IsSystemAccount() bool -} - -type WAL interface { - Type() StorageType - StoreMsg(subj string, hdr, msg []byte, ttl int64) (uint64, int64, error) - LoadMsg(index uint64, sm *StoreMsg) (*StoreMsg, error) - RemoveMsg(index uint64) (bool, error) - Compact(index uint64) (uint64, error) - Purge() (uint64, error) - PurgeEx(subject string, seq, keep uint64, noMarkers bool) (uint64, error) - Truncate(seq uint64) error - State() StreamState - FastState(*StreamState) - Stop() error - Delete() error -} - -type Peer struct { - ID string - Current bool - Last time.Time - Lag uint64 -} - -type RaftState uint8 - -// Allowable states for a NATS Consensus Group. -const ( - Follower RaftState = iota - Leader - Candidate - Closed -) - -func (state RaftState) String() string { - switch state { - case Follower: - return "FOLLOWER" - case Candidate: - return "CANDIDATE" - case Leader: - return "LEADER" - case Closed: - return "CLOSED" - } - return "UNKNOWN" -} - -type raft struct { - sync.RWMutex - - created time.Time // Time that the group was created - accName string // Account name of the asset this raft group is for - acc *Account // Account that NRG traffic will be sent/received in - group string // Raft group - sd string // Store directory - id string // Node ID - wg sync.WaitGroup // Wait for running goroutines to exit on shutdown - - wal WAL // WAL store (filestore or memstore) - wtype StorageType // WAL type, e.g. FileStorage or MemoryStorage - track bool // - werr error // Last write error - - state atomic.Int32 // RaftState - leaderState atomic.Bool // Is in (complete) leader state. - hh hash.Hash64 // Highwayhash, used for snapshots - snapfile string // Snapshot filename - - csz int // Cluster size - qn int // Number of nodes needed to establish quorum - peers map[string]*lps // Other peers in the Raft group - - removed map[string]struct{} // Peers that were removed from the group - acks map[uint64]map[string]struct{} // Append entry responses/acks, map of entry index -> peer ID - pae map[uint64]*appendEntry // Pending append entries - - elect *time.Timer // Election timer, normally accessed via electTimer - etlr time.Time // Election timer last reset time, for unit tests only - active time.Time // Last activity time, i.e. for heartbeats - llqrt time.Time // Last quorum lost time - lsut time.Time // Last scale-up time - - term uint64 // The current vote term - pterm uint64 // Previous term from the last snapshot - pindex uint64 // Previous index from the last snapshot - commit uint64 // Index of the most recent commit - applied uint64 // Index of the most recently applied commit - - aflr uint64 // Index when to signal initial messages have been applied after becoming leader. 0 means signaling is disabled. - - leader string // The ID of the leader - vote string // Our current vote state - lxfer bool // Are we doing a leadership transfer? - - hcbehind bool // Were we falling behind at the last health check? (see: isCurrent) - - s *Server // Reference to top-level server - c *client // Internal client for subscriptions - js *jetStream // JetStream, if running, to see if we are out of resources - - dflag bool // Debug flag - hasleader atomic.Bool // Is there a group leader right now? - pleader atomic.Bool // Has the group ever had a leader? - isSysAcc atomic.Bool // Are we utilizing the system account? - - observer bool // The node is observing, i.e. not participating in voting - - extSt extensionState // Extension state - - psubj string // Proposals subject - rpsubj string // Remove peers subject - vsubj string // Vote requests subject - vreply string // Vote responses subject - asubj string // Append entries subject - areply string // Append entries responses subject - - sq *sendq // Send queue for outbound RPC messages - aesub *subscription // Subscription for handleAppendEntry callbacks - - wtv []byte // Term and vote to be written - wps []byte // Peer state to be written - - catchup *catchupState // For when we need to catch up as a follower. - progress map[string]*ipQueue[uint64] // For leader or server catching up a follower. - - paused bool // Whether or not applies are paused - hcommit uint64 // The commit at the time that applies were paused - pobserver bool // Whether we were an observer at the time that applies were paused - - prop *ipQueue[*proposedEntry] // Proposals - entry *ipQueue[*appendEntry] // Append entries - resp *ipQueue[*appendEntryResponse] // Append entries responses - apply *ipQueue[*CommittedEntry] // Apply queue (committed entries to be passed to upper layer) - reqs *ipQueue[*voteRequest] // Vote requests - votes *ipQueue[*voteResponse] // Vote responses - leadc chan bool // Leader changes - quit chan struct{} // Raft group shutdown -} - -type proposedEntry struct { - *Entry - reply string // Optional, to respond once proposal handled -} - -// cacthupState structure that holds our subscription, and catchup term and index -// as well as starting term and index and how many updates we have seen. -type catchupState struct { - sub *subscription // Subscription that catchup messages will arrive on - cterm uint64 // Catchup term - cindex uint64 // Catchup index - pterm uint64 // Starting term - pindex uint64 // Starting index - active time.Time // Last time we received a message for this catchup -} - -// lps holds peer state of last time and last index replicated. -type lps struct { - ts int64 // Last timestamp - li uint64 // Last index replicated - kp bool // Known peer -} - -const ( - minElectionTimeoutDefault = 4 * time.Second - maxElectionTimeoutDefault = 9 * time.Second - minCampaignTimeoutDefault = 100 * time.Millisecond - maxCampaignTimeoutDefault = 8 * minCampaignTimeoutDefault - hbIntervalDefault = 1 * time.Second - lostQuorumIntervalDefault = hbIntervalDefault * 10 // 10 seconds - lostQuorumCheckIntervalDefault = hbIntervalDefault * 10 // 10 seconds - observerModeIntervalDefault = 48 * time.Hour -) - -var ( - minElectionTimeout = minElectionTimeoutDefault - maxElectionTimeout = maxElectionTimeoutDefault - minCampaignTimeout = minCampaignTimeoutDefault - maxCampaignTimeout = maxCampaignTimeoutDefault - hbInterval = hbIntervalDefault - lostQuorumInterval = lostQuorumIntervalDefault - lostQuorumCheck = lostQuorumCheckIntervalDefault - observerModeInterval = observerModeIntervalDefault -) - -type RaftConfig struct { - Name string - Store string - Log WAL - Track bool - Observer bool -} - -var ( - errNotLeader = errors.New("raft: not leader") - errAlreadyLeader = errors.New("raft: already leader") - errNilCfg = errors.New("raft: no config given") - errCorruptPeers = errors.New("raft: corrupt peer state") - errEntryLoadFailed = errors.New("raft: could not load entry from WAL") - errEntryStoreFailed = errors.New("raft: could not store entry to WAL") - errNodeClosed = errors.New("raft: node is closed") - errBadSnapName = errors.New("raft: snapshot name could not be parsed") - errNoSnapAvailable = errors.New("raft: no snapshot available") - errCatchupsRunning = errors.New("raft: snapshot can not be installed while catchups running") - errSnapshotCorrupt = errors.New("raft: snapshot corrupt") - errTooManyPrefs = errors.New("raft: stepdown requires at most one preferred new leader") - errNoPeerState = errors.New("raft: no peerstate") - errAdjustBootCluster = errors.New("raft: can not adjust boot peer size on established group") - errLeaderLen = fmt.Errorf("raft: leader should be exactly %d bytes", idLen) - errTooManyEntries = errors.New("raft: append entry can contain a max of 64k entries") - errBadAppendEntry = errors.New("raft: append entry corrupt") - errNoInternalClient = errors.New("raft: no internal client") -) - -// This will bootstrap a raftNode by writing its config into the store directory. -func (s *Server) bootstrapRaftNode(cfg *RaftConfig, knownPeers []string, allPeersKnown bool) error { - if cfg == nil { - return errNilCfg - } - // Check validity of peers if presented. - for _, p := range knownPeers { - if len(p) != idLen { - return fmt.Errorf("raft: illegal peer: %q", p) - } - } - expected := len(knownPeers) - // We need to adjust this is all peers are not known. - if !allPeersKnown { - s.Debugf("Determining expected peer size for JetStream meta group") - if expected < 2 { - expected = 2 - } - opts := s.getOpts() - nrs := len(opts.Routes) - - cn := s.ClusterName() - ngwps := 0 - for _, gw := range opts.Gateway.Gateways { - // Ignore our own cluster if specified. - if gw.Name == cn { - continue - } - for _, u := range gw.URLs { - host := u.Hostname() - // If this is an IP just add one. - if net.ParseIP(host) != nil { - ngwps++ - } else { - addrs, _ := net.LookupHost(host) - ngwps += len(addrs) - } - } - } - - if expected < nrs+ngwps { - expected = nrs + ngwps - s.Debugf("Adjusting expected peer set size to %d with %d known", expected, len(knownPeers)) - } - } - - // Check the store directory. If we have a memory based WAL we need to make sure the directory is setup. - if stat, err := os.Stat(cfg.Store); os.IsNotExist(err) { - if err := os.MkdirAll(cfg.Store, defaultDirPerms); err != nil { - return fmt.Errorf("raft: could not create storage directory - %v", err) - } - } else if stat == nil || !stat.IsDir() { - return fmt.Errorf("raft: storage directory is not a directory") - } - tmpfile, err := os.CreateTemp(cfg.Store, "_test_") - if err != nil { - return fmt.Errorf("raft: storage directory is not writable") - } - tmpfile.Close() - os.Remove(tmpfile.Name()) - - return writePeerState(cfg.Store, &peerState{knownPeers, expected, extUndetermined}) -} - -// initRaftNode will initialize the raft node, to be used by startRaftNode or when testing to not run the Go routine. -func (s *Server) initRaftNode(accName string, cfg *RaftConfig, labels pprofLabels) (*raft, error) { - if cfg == nil { - return nil, errNilCfg - } - s.mu.RLock() - if s.sys == nil { - s.mu.RUnlock() - return nil, ErrNoSysAccount - } - hash := s.sys.shash - s.mu.RUnlock() - - // Do this here to process error quicker. - ps, err := readPeerState(cfg.Store) - if err != nil { - return nil, err - } - if ps == nil { - return nil, errNoPeerState - } - - qpfx := fmt.Sprintf("[ACC:%s] RAFT '%s' ", accName, cfg.Name) - n := &raft{ - created: time.Now(), - id: hash[:idLen], - group: cfg.Name, - sd: cfg.Store, - wal: cfg.Log, - wtype: cfg.Log.Type(), - track: cfg.Track, - csz: ps.clusterSize, - qn: ps.clusterSize/2 + 1, - peers: make(map[string]*lps), - acks: make(map[uint64]map[string]struct{}), - pae: make(map[uint64]*appendEntry), - s: s, - js: s.getJetStream(), - quit: make(chan struct{}), - reqs: newIPQueue[*voteRequest](s, qpfx+"vreq"), - votes: newIPQueue[*voteResponse](s, qpfx+"vresp"), - prop: newIPQueue[*proposedEntry](s, qpfx+"entry"), - entry: newIPQueue[*appendEntry](s, qpfx+"appendEntry"), - resp: newIPQueue[*appendEntryResponse](s, qpfx+"appendEntryResponse"), - apply: newIPQueue[*CommittedEntry](s, qpfx+"committedEntry"), - accName: accName, - leadc: make(chan bool, 32), - observer: cfg.Observer, - extSt: ps.domainExt, - } - - // Setup our internal subscriptions for proposals, votes and append entries. - // If we fail to do this for some reason then this is fatal — we cannot - // continue setting up or the Raft node may be partially/totally isolated. - if err := n.RecreateInternalSubs(); err != nil { - n.shutdown() - return nil, err - } - - if atomic.LoadInt32(&s.logging.debug) > 0 { - n.dflag = true - } - - // Set up the highwayhash for the snapshots. - key := sha256.Sum256([]byte(n.group)) - n.hh, _ = highwayhash.New64(key[:]) - - // If we have a term and vote file (tav.idx on the filesystem) then read in - // what we think the term and vote was. It's possible these are out of date - // so a catch-up may be required. - if term, vote, err := n.readTermVote(); err == nil && term > 0 { - n.term = term - n.vote = vote - } - - // Can't recover snapshots if memory based since wal will be reset. - // We will inherit from the current leader. - if _, ok := n.wal.(*memStore); ok { - _ = os.RemoveAll(filepath.Join(n.sd, snapshotsDir)) - } else { - // See if we have any snapshots and if so load and process on startup. - n.setupLastSnapshot() - } - - // Make sure that the snapshots directory exists. - if err := os.MkdirAll(filepath.Join(n.sd, snapshotsDir), defaultDirPerms); err != nil { - return nil, fmt.Errorf("could not create snapshots directory - %v", err) - } - - truncateAndErr := func(index uint64) { - if err := n.wal.Truncate(index); err != nil { - n.setWriteErr(err) - } - } - - // Retrieve the stream state from the WAL. If there are pending append - // entries that were committed but not applied before we last shut down, - // we will try to replay them and process them here. - var state StreamState - n.wal.FastState(&state) - if state.Msgs > 0 { - n.debug("Replaying state of %d entries", state.Msgs) - if first, err := n.loadFirstEntry(); err == nil { - n.pterm, n.pindex = first.pterm, first.pindex - if first.commit > 0 && first.commit > n.commit { - n.commit = first.commit - } - } - - // This process will queue up entries on our applied queue but prior to the upper - // state machine running. So we will monitor how much we have queued and if we - // reach a limit will pause the apply queue and resume inside of run() go routine. - const maxQsz = 32 * 1024 * 1024 // 32MB max - - // It looks like there are entries we have committed but not applied - // yet. Replay them. - for index, qsz := state.FirstSeq, 0; index <= state.LastSeq; index++ { - ae, err := n.loadEntry(index) - if err != nil { - n.warn("Could not load %d from WAL [%+v]: %v", index, state, err) - truncateAndErr(index) - break - } - if ae.pindex != index-1 { - n.warn("Corrupt WAL, will truncate") - truncateAndErr(index) - break - } - n.processAppendEntry(ae, nil) - // Check how much we have queued up so far to determine if we should pause. - for _, e := range ae.entries { - qsz += len(e.Data) - if qsz > maxQsz && !n.paused { - n.PauseApply() - } - } - } - } - - // Make sure to track ourselves. - n.peers[n.id] = &lps{time.Now().UnixNano(), 0, true} - - // Track known peers - for _, peer := range ps.knownPeers { - if peer != n.id { - // Set these to 0 to start but mark as known peer. - n.peers[peer] = &lps{0, 0, true} - } - } - - n.debug("Started") - - // Check if we need to start in observer mode due to lame duck status. - // This will stop us from taking on the leader role when we're about to - // shutdown anyway. - if s.isLameDuckMode() { - n.debug("Will start in observer mode due to lame duck status") - n.SetObserver(true) - } - - // Set the election timer and lost quorum timers to now, so that we - // won't accidentally trigger either state without knowing the real state - // of the other nodes. - n.Lock() - n.resetElectionTimeout() - n.llqrt = time.Now() - n.Unlock() - - // Register the Raft group. - labels["group"] = n.group - s.registerRaftNode(n.group, n) - - return n, nil -} - -// startRaftNode will start the raft node. -func (s *Server) startRaftNode(accName string, cfg *RaftConfig, labels pprofLabels) (RaftNode, error) { - n, err := s.initRaftNode(accName, cfg, labels) - if err != nil { - return nil, err - } - - // Start the run goroutine for the Raft state machine. - n.wg.Add(1) - s.startGoRoutine(n.run, labels) - - return n, nil -} - -// Returns whether peers within this group claim to support -// moving NRG traffic into the asset account. -// Lock must be held. -func (n *raft) checkAccountNRGStatus() bool { - if !n.s.accountNRGAllowed.Load() { - return false - } - enabled := true - for pn := range n.peers { - if si, ok := n.s.nodeToInfo.Load(pn); ok && si != nil { - enabled = enabled && si.(nodeInfo).accountNRG - } - } - return enabled -} - -// Whether we are using the system account or not. -func (n *raft) IsSystemAccount() bool { - return n.isSysAcc.Load() -} - -func (n *raft) RecreateInternalSubs() error { - n.Lock() - defer n.Unlock() - return n.recreateInternalSubsLocked() -} - -func (n *raft) recreateInternalSubsLocked() error { - // Sanity check for system account, as it can disappear when - // the system is shutting down. - if n.s == nil { - return fmt.Errorf("server not found") - } - n.s.mu.RLock() - sys := n.s.sys - n.s.mu.RUnlock() - if sys == nil { - return fmt.Errorf("system account not found") - } - - // Default is the system account. - nrgAcc := sys.account - n.isSysAcc.Store(true) - - // Is account NRG enabled in this account and do all group - // peers claim to also support account NRG? - if n.checkAccountNRGStatus() { - // Check whether the account that the asset belongs to - // has volunteered a different NRG account. - target := nrgAcc.Name - if a, _ := n.s.lookupAccount(n.accName); a != nil { - a.mu.RLock() - if a.js != nil { - target = a.js.nrgAccount - } - a.mu.RUnlock() - } - - // If the target account exists, then we'll use that. - if target != _EMPTY_ { - if a, _ := n.s.lookupAccount(target); a != nil { - nrgAcc = a - if a != sys.account { - n.isSysAcc.Store(false) - } - } - } - } - if n.aesub != nil && n.acc == nrgAcc { - // Subscriptions already exist and the account NRG state - // hasn't changed. - return nil - } - - // Need to cancel any in-progress catch-ups, otherwise the - // inboxes are about to be pulled out from underneath it in - // the next step... - n.cancelCatchup() - - // If we have an existing client then tear down any existing - // subscriptions and close the internal client. - if c := n.c; c != nil { - c.mu.Lock() - subs := make([]*subscription, 0, len(c.subs)) - for _, sub := range c.subs { - subs = append(subs, sub) - } - c.mu.Unlock() - for _, sub := range subs { - n.unsubscribe(sub) - } - c.closeConnection(InternalClient) - } - - if n.acc != nrgAcc { - n.debug("Subscribing in '%s'", nrgAcc.GetName()) - } - - c := n.s.createInternalSystemClient() - c.registerWithAccount(nrgAcc) - if nrgAcc.sq == nil { - nrgAcc.sq = n.s.newSendQ(nrgAcc) - } - n.c = c - n.sq = nrgAcc.sq - n.acc = nrgAcc - - // Recreate any internal subscriptions for voting, append - // entries etc in the new account. - return n.createInternalSubs() -} - -// outOfResources checks to see if we are out of resources. -func (n *raft) outOfResources() bool { - js := n.js - if !n.track || js == nil { - return false - } - return js.limitsExceeded(n.wtype) -} - -// Maps node names back to server names. -func (s *Server) serverNameForNode(node string) string { - if si, ok := s.nodeToInfo.Load(node); ok && si != nil { - return si.(nodeInfo).name - } - return _EMPTY_ -} - -// Maps node names back to cluster names. -func (s *Server) clusterNameForNode(node string) string { - if si, ok := s.nodeToInfo.Load(node); ok && si != nil { - return si.(nodeInfo).cluster - } - return _EMPTY_ -} - -// Registers the Raft node with the server, as it will track all of the Raft -// nodes. -func (s *Server) registerRaftNode(group string, n RaftNode) { - s.rnMu.Lock() - defer s.rnMu.Unlock() - if s.raftNodes == nil { - s.raftNodes = make(map[string]RaftNode) - } - s.raftNodes[group] = n -} - -// Unregisters the Raft node from the server, i.e. at shutdown. -func (s *Server) unregisterRaftNode(group string) { - s.rnMu.Lock() - defer s.rnMu.Unlock() - if s.raftNodes != nil { - delete(s.raftNodes, group) - } -} - -// Returns how many Raft nodes are running in this server instance. -func (s *Server) numRaftNodes() int { - s.rnMu.RLock() - defer s.rnMu.RUnlock() - return len(s.raftNodes) -} - -// Finds the Raft node for a given Raft group, if any. If there is no Raft node -// running for this group then it can return nil. -func (s *Server) lookupRaftNode(group string) RaftNode { - s.rnMu.RLock() - defer s.rnMu.RUnlock() - var n RaftNode - if s.raftNodes != nil { - n = s.raftNodes[group] - } - return n -} - -// Reloads the debug state for all running Raft nodes. This is necessary when -// the configuration has been reloaded and the debug log level has changed. -func (s *Server) reloadDebugRaftNodes(debug bool) { - if s == nil { - return - } - s.rnMu.RLock() - for _, ni := range s.raftNodes { - n := ni.(*raft) - n.Lock() - n.dflag = debug - n.Unlock() - } - s.rnMu.RUnlock() -} - -// Requests that all Raft nodes on this server step down and place them into -// observer mode. This is called when the server is shutting down. -func (s *Server) stepdownRaftNodes() { - if s == nil { - return - } - s.rnMu.RLock() - if len(s.raftNodes) == 0 { - s.rnMu.RUnlock() - return - } - s.Debugf("Stepping down all leader raft nodes") - nodes := make([]RaftNode, 0, len(s.raftNodes)) - for _, n := range s.raftNodes { - nodes = append(nodes, n) - } - s.rnMu.RUnlock() - - for _, node := range nodes { - node.StepDown() - node.SetObserver(true) - } -} - -// Shuts down all Raft nodes on this server. This is called either when the -// server is either entering lame duck mode, shutting down or when JetStream -// has been disabled. -func (s *Server) shutdownRaftNodes() { - if s == nil { - return - } - s.rnMu.RLock() - if len(s.raftNodes) == 0 { - s.rnMu.RUnlock() - return - } - nodes := make([]RaftNode, 0, len(s.raftNodes)) - s.Debugf("Shutting down all raft nodes") - for _, n := range s.raftNodes { - nodes = append(nodes, n) - } - s.rnMu.RUnlock() - - for _, node := range nodes { - node.Stop() - } -} - -// Used in lameduck mode to move off the leaders. -// We also put all nodes in observer mode so new leaders -// can not be placed on this server. -func (s *Server) transferRaftLeaders() bool { - if s == nil { - return false - } - s.rnMu.RLock() - if len(s.raftNodes) == 0 { - s.rnMu.RUnlock() - return false - } - nodes := make([]RaftNode, 0, len(s.raftNodes)) - for _, n := range s.raftNodes { - nodes = append(nodes, n) - } - s.rnMu.RUnlock() - - var didTransfer bool - for _, node := range nodes { - if err := node.StepDown(); err == nil { - didTransfer = true - } - node.SetObserver(true) - } - return didTransfer -} - -// Formal API - -// Propose will propose a new entry to the group. -// This should only be called on the leader. -func (n *raft) Propose(data []byte) error { - if state := n.State(); state != Leader { - n.debug("Proposal ignored, not leader (state: %v)", state) - return errNotLeader - } - n.Lock() - defer n.Unlock() - - // Error if we had a previous write error. - if werr := n.werr; werr != nil { - return werr - } - n.prop.push(newProposedEntry(newEntry(EntryNormal, data), _EMPTY_)) - return nil -} - -// ProposeDirect will propose multiple entries at once. -// This should only be called on the leader. -func (n *raft) ProposeMulti(entries []*Entry) error { - if state := n.State(); state != Leader { - n.debug("Direct proposal ignored, not leader (state: %v)", state) - return errNotLeader - } - n.Lock() - defer n.Unlock() - - // Error if we had a previous write error. - if werr := n.werr; werr != nil { - return werr - } - for _, e := range entries { - n.prop.push(newProposedEntry(e, _EMPTY_)) - } - return nil -} - -// ForwardProposal will forward the proposal to the leader if known. -// If we are the leader this is the same as calling propose. -func (n *raft) ForwardProposal(entry []byte) error { - if n.State() == Leader { - return n.Propose(entry) - } - - // TODO: Currently we do not set a reply subject, even though we are - // now capable of responding. Do this once enough time has passed, - // i.e. maybe in 2.12. - n.sendRPC(n.psubj, _EMPTY_, entry) - return nil -} - -// ProposeAddPeer is called to add a peer to the group. -func (n *raft) ProposeAddPeer(peer string) error { - if n.State() != Leader { - return errNotLeader - } - n.RLock() - // Error if we had a previous write error. - if werr := n.werr; werr != nil { - n.RUnlock() - return werr - } - prop := n.prop - n.RUnlock() - - prop.push(newProposedEntry(newEntry(EntryAddPeer, []byte(peer)), _EMPTY_)) - return nil -} - -// As a leader if we are proposing to remove a peer assume its already gone. -func (n *raft) doRemovePeerAsLeader(peer string) { - n.Lock() - if n.removed == nil { - n.removed = map[string]struct{}{} - } - n.removed[peer] = struct{}{} - if _, ok := n.peers[peer]; ok { - delete(n.peers, peer) - // We should decrease our cluster size since we are tracking this peer and the peer is most likely already gone. - n.adjustClusterSizeAndQuorum() - } - n.Unlock() -} - -// ProposeRemovePeer is called to remove a peer from the group. -func (n *raft) ProposeRemovePeer(peer string) error { - n.RLock() - prop, subj := n.prop, n.rpsubj - isLeader := n.State() == Leader - werr := n.werr - n.RUnlock() - - // Error if we had a previous write error. - if werr != nil { - return werr - } - - // If we are the leader then we are responsible for processing the - // peer remove and then notifying the rest of the group that the - // peer was removed. - if isLeader { - prop.push(newProposedEntry(newEntry(EntryRemovePeer, []byte(peer)), _EMPTY_)) - n.doRemovePeerAsLeader(peer) - return nil - } - - // Otherwise we need to forward the proposal to the leader. - n.sendRPC(subj, _EMPTY_, []byte(peer)) - return nil -} - -// ClusterSize reports back the total cluster size. -// This effects quorum etc. -func (n *raft) ClusterSize() int { - n.Lock() - defer n.Unlock() - return n.csz -} - -// AdjustBootClusterSize can be called to adjust the boot cluster size. -// Will error if called on a group with a leader or a previous leader. -// This can be helpful in mixed mode. -func (n *raft) AdjustBootClusterSize(csz int) error { - n.Lock() - defer n.Unlock() - - if n.leader != noLeader || n.pleader.Load() { - return errAdjustBootCluster - } - // Same floor as bootstrap. - if csz < 2 { - csz = 2 - } - // Adjust the cluster size and the number of nodes needed to establish - // a quorum. - n.csz = csz - n.qn = n.csz/2 + 1 - - return nil -} - -// AdjustClusterSize will change the cluster set size. -// Must be the leader. -func (n *raft) AdjustClusterSize(csz int) error { - if n.State() != Leader { - return errNotLeader - } - n.Lock() - // Same floor as bootstrap. - if csz < 2 { - csz = 2 - } - - // Adjust the cluster size and the number of nodes needed to establish - // a quorum. - n.csz = csz - n.qn = n.csz/2 + 1 - n.Unlock() - - n.sendPeerState() - return nil -} - -// PauseApply will allow us to pause processing of append entries onto our -// external apply queue. In effect this means that the upper layer will no longer -// receive any new entries from the Raft group. -func (n *raft) PauseApply() error { - if n.State() == Leader { - return errAlreadyLeader - } - - n.Lock() - defer n.Unlock() - - // If we are currently a candidate make sure we step down. - if n.State() == Candidate { - n.stepdownLocked(noLeader) - } - - n.debug("Pausing our apply channel") - n.paused = true - n.hcommit = n.commit - // Also prevent us from trying to become a leader while paused and catching up. - n.pobserver, n.observer = n.observer, true - n.resetElect(observerModeInterval) - - return nil -} - -// ResumeApply will resume sending applies to the external apply queue. This -// means that we will start sending new entries to the upper layer. -func (n *raft) ResumeApply() { - n.Lock() - defer n.Unlock() - - if !n.paused { - return - } - - n.debug("Resuming our apply channel") - - // Reset before we start. - n.resetElectionTimeout() - - // Run catchup.. - if n.hcommit > n.commit { - n.debug("Resuming %d replays", n.hcommit+1-n.commit) - for index := n.commit + 1; index <= n.hcommit; index++ { - if err := n.applyCommit(index); err != nil { - n.warn("Got error on apply commit during replay: %v", err) - break - } - // We want to unlock here to allow the upper layers to call Applied() without blocking. - n.Unlock() - // Give hint to let other Go routines run. - // Might not be necessary but seems to make it more fine grained interleaving. - runtime.Gosched() - // Simply re-acquire - n.Lock() - // Need to check if we got closed. - if n.State() == Closed { - return - } - } - } - - // Clear our observer and paused state after we apply. - n.observer, n.pobserver = n.pobserver, false - n.paused = false - n.hcommit = 0 - - // If we had been selected to be the next leader campaign here now that we have resumed. - if n.lxfer { - n.xferCampaign() - } else { - n.resetElectionTimeout() - } -} - -// Applied is a callback that must be called by the upper layer when it -// has successfully applied the committed entries that it received from the -// apply queue. It will return the number of entries and an estimation of the -// byte size that could be removed with a snapshot/compact. -func (n *raft) Applied(index uint64) (entries uint64, bytes uint64) { - n.Lock() - defer n.Unlock() - - // Ignore if not applicable. This can happen during a reset. - if index > n.commit { - return 0, 0 - } - - // Ignore if already applied. - if index > n.applied { - n.applied = index - } - - // If it was set, and we reached the minimum applied index, reset and send signal to upper layer. - if n.aflr > 0 && index >= n.aflr { - n.aflr = 0 - // Quick sanity-check to confirm we're still leader. - // In which case we must signal, since switchToLeader would not have done so already. - if n.State() == Leader { - n.leaderState.Store(true) - n.updateLeadChange(true) - } - } - - // Calculate the number of entries and estimate the byte size that - // we can now remove with a compaction/snapshot. - var state StreamState - n.wal.FastState(&state) - if n.applied > state.FirstSeq { - entries = n.applied - state.FirstSeq - } - if state.Msgs > 0 { - bytes = entries * state.Bytes / state.Msgs - } - return entries, bytes -} - -// For capturing data needed by snapshot. -type snapshot struct { - lastTerm uint64 - lastIndex uint64 - peerstate []byte - data []byte -} - -const minSnapshotLen = 28 - -// Encodes a snapshot into a buffer for storage. -// Lock should be held. -func (n *raft) encodeSnapshot(snap *snapshot) []byte { - if snap == nil { - return nil - } - var le = binary.LittleEndian - buf := make([]byte, minSnapshotLen+len(snap.peerstate)+len(snap.data)) - le.PutUint64(buf[0:], snap.lastTerm) - le.PutUint64(buf[8:], snap.lastIndex) - // Peer state - le.PutUint32(buf[16:], uint32(len(snap.peerstate))) - wi := 20 - copy(buf[wi:], snap.peerstate) - wi += len(snap.peerstate) - // data itself. - copy(buf[wi:], snap.data) - wi += len(snap.data) - - // Now do the hash for the end. - n.hh.Reset() - n.hh.Write(buf[:wi]) - checksum := n.hh.Sum(nil) - copy(buf[wi:], checksum) - wi += len(checksum) - return buf[:wi] -} - -// SendSnapshot will send the latest snapshot as a normal AE. -// Should only be used when the upper layers know this is most recent. -// Used when restoring streams, moving a stream from R1 to R>1, etc. -func (n *raft) SendSnapshot(data []byte) error { - n.sendAppendEntry([]*Entry{{EntrySnapshot, data}}) - return nil -} - -// Used to install a snapshot for the given term and applied index. This will release -// all of the log entries up to and including index. This should not be called with -// entries that have been applied to the FSM but have not been applied to the raft state. -func (n *raft) InstallSnapshot(data []byte) error { - if n.State() == Closed { - return errNodeClosed - } - - n.Lock() - defer n.Unlock() - - // If a write error has occurred already then stop here. - if werr := n.werr; werr != nil { - return werr - } - - // Check that a catchup isn't already taking place. If it is then we won't - // allow installing snapshots until it is done. - if len(n.progress) > 0 || n.paused { - return errCatchupsRunning - } - - if n.applied == 0 { - n.debug("Not snapshotting as there are no applied entries") - return errNoSnapAvailable - } - - term := n.pterm - if ae, _ := n.loadEntry(n.applied); ae != nil { - term = ae.term - } - - n.debug("Installing snapshot of %d bytes", len(data)) - - return n.installSnapshot(&snapshot{ - lastTerm: term, - lastIndex: n.applied, - peerstate: encodePeerState(&peerState{n.peerNames(), n.csz, n.extSt}), - data: data, - }) -} - -// Install the snapshot. -// Lock should be held. -func (n *raft) installSnapshot(snap *snapshot) error { - snapDir := filepath.Join(n.sd, snapshotsDir) - sn := fmt.Sprintf(snapFileT, snap.lastTerm, snap.lastIndex) - sfile := filepath.Join(snapDir, sn) - - if err := writeFileWithSync(sfile, n.encodeSnapshot(snap), defaultFilePerms); err != nil { - // We could set write err here, but if this is a temporary situation, too many open files etc. - // we want to retry and snapshots are not fatal. - return err - } - - // Delete our previous snapshot file if it exists. - if n.snapfile != _EMPTY_ && n.snapfile != sfile { - os.Remove(n.snapfile) - } - // Remember our latest snapshot file. - n.snapfile = sfile - if _, err := n.wal.Compact(snap.lastIndex + 1); err != nil { - n.setWriteErrLocked(err) - return err - } - - return nil -} - -// NeedSnapshot returns true if it is necessary to try to install a snapshot, i.e. -// after we have finished recovering/replaying at startup, on a regular interval or -// as a part of cleaning up when shutting down. -func (n *raft) NeedSnapshot() bool { - n.RLock() - defer n.RUnlock() - return n.snapfile == _EMPTY_ && n.applied > 1 -} - -const ( - snapshotsDir = "snapshots" - snapFileT = "snap.%d.%d" -) - -// termAndIndexFromSnapfile tries to load the snapshot file and returns the term -// and index from that snapshot. -func termAndIndexFromSnapFile(sn string) (term, index uint64, err error) { - if sn == _EMPTY_ { - return 0, 0, errBadSnapName - } - fn := filepath.Base(sn) - if n, err := fmt.Sscanf(fn, snapFileT, &term, &index); err != nil || n != 2 { - return 0, 0, errBadSnapName - } - return term, index, nil -} - -// setupLastSnapshot is called at startup to try and recover the last snapshot from -// the disk if possible. We will try to recover the term, index and commit/applied -// indices and then notify the upper layer what we found. Compacts the WAL if needed. -func (n *raft) setupLastSnapshot() { - snapDir := filepath.Join(n.sd, snapshotsDir) - psnaps, err := os.ReadDir(snapDir) - if err != nil { - return - } - - var lterm, lindex uint64 - var latest string - for _, sf := range psnaps { - sfile := filepath.Join(snapDir, sf.Name()) - var term, index uint64 - term, index, err := termAndIndexFromSnapFile(sf.Name()) - if err == nil { - if term > lterm { - lterm, lindex = term, index - latest = sfile - } else if term == lterm && index > lindex { - lindex = index - latest = sfile - } - } else { - // Clean this up, can't parse the name. - // TODO(dlc) - We could read in and check actual contents. - n.debug("Removing snapshot, can't parse name: %q", sf.Name()) - os.Remove(sfile) - } - } - - // Now cleanup any old entries - for _, sf := range psnaps { - sfile := filepath.Join(snapDir, sf.Name()) - if sfile != latest { - n.debug("Removing old snapshot: %q", sfile) - os.Remove(sfile) - } - } - - if latest == _EMPTY_ { - return - } - - // Set latest snapshot we have. - n.Lock() - defer n.Unlock() - - n.snapfile = latest - snap, err := n.loadLastSnapshot() - if err != nil { - // We failed to recover the last snapshot for some reason, so we will - // assume it has been corrupted and will try to delete it. - if n.snapfile != _EMPTY_ { - os.Remove(n.snapfile) - n.snapfile = _EMPTY_ - } - return - } - - // We successfully recovered the last snapshot from the disk. - // Recover state from the snapshot and then notify the upper layer. - // Compact the WAL when we're done if needed. - n.pindex = snap.lastIndex - n.pterm = snap.lastTerm - n.commit = snap.lastIndex - n.applied = snap.lastIndex - n.apply.push(newCommittedEntry(n.commit, []*Entry{{EntrySnapshot, snap.data}})) - if _, err := n.wal.Compact(snap.lastIndex + 1); err != nil { - n.setWriteErrLocked(err) - } -} - -// loadLastSnapshot will load and return our last snapshot. -// Lock should be held. -func (n *raft) loadLastSnapshot() (*snapshot, error) { - if n.snapfile == _EMPTY_ { - return nil, errNoSnapAvailable - } - - <-dios - buf, err := os.ReadFile(n.snapfile) - dios <- struct{}{} - - if err != nil { - n.warn("Error reading snapshot: %v", err) - os.Remove(n.snapfile) - n.snapfile = _EMPTY_ - return nil, err - } - if len(buf) < minSnapshotLen { - n.warn("Snapshot corrupt, too short") - os.Remove(n.snapfile) - n.snapfile = _EMPTY_ - return nil, errSnapshotCorrupt - } - - // Check to make sure hash is consistent. - hoff := len(buf) - 8 - lchk := buf[hoff:] - n.hh.Reset() - n.hh.Write(buf[:hoff]) - if !bytes.Equal(lchk[:], n.hh.Sum(nil)) { - n.warn("Snapshot corrupt, checksums did not match") - os.Remove(n.snapfile) - n.snapfile = _EMPTY_ - return nil, errSnapshotCorrupt - } - - var le = binary.LittleEndian - lps := le.Uint32(buf[16:]) - snap := &snapshot{ - lastTerm: le.Uint64(buf[0:]), - lastIndex: le.Uint64(buf[8:]), - peerstate: buf[20 : 20+lps], - data: buf[20+lps : hoff], - } - - // We had a bug in 2.9.12 that would allow snapshots on last index of 0. - // Detect that here and return err. - if snap.lastIndex == 0 { - n.warn("Snapshot with last index 0 is invalid, cleaning up") - os.Remove(n.snapfile) - n.snapfile = _EMPTY_ - return nil, errSnapshotCorrupt - } - - return snap, nil -} - -// Leader returns if we are the leader for our group. -// We use an atomic here now vs acquiring the read lock. -func (n *raft) Leader() bool { - if n == nil { - return false - } - return n.leaderState.Load() -} - -// stepdown immediately steps down the Raft node to the -// follower state. This will take the lock itself. -func (n *raft) stepdown(newLeader string) { - n.Lock() - defer n.Unlock() - n.stepdownLocked(newLeader) -} - -// stepdownLocked immediately steps down the Raft node to the -// follower state. This requires the lock is already held. -func (n *raft) stepdownLocked(newLeader string) { - n.debug("Stepping down") - n.switchToFollowerLocked(newLeader) -} - -// isCatchingUp returns true if a catchup is currently taking place. -func (n *raft) isCatchingUp() bool { - n.RLock() - defer n.RUnlock() - return n.catchup != nil -} - -// isCurrent is called from the healthchecks and returns true if we believe -// that the upper layer is current with the Raft layer, i.e. that it has applied -// all of the commits that we have given it. -// Optionally we can also check whether or not we're making forward progress if we -// aren't current, in which case this function may block for up to ~10ms to find out. -// Lock should be held. -func (n *raft) isCurrent(includeForwardProgress bool) bool { - // Check if we are closed. - if n.State() == Closed { - n.debug("Not current, node is closed") - return false - } - - // Check whether we've made progress on any state, 0 is invalid so not healthy. - if n.commit == 0 { - n.debug("Not current, no commits") - return false - } - - // If we were previously logging about falling behind, also log when the problem - // was cleared. - clearBehindState := func() { - if n.hcbehind { - n.warn("Health check OK, no longer falling behind") - n.hcbehind = false - } - } - - // Make sure we are the leader or we know we have heard from the leader recently. - if n.State() == Leader { - clearBehindState() - return true - } - - // Check to see that we have heard from the current leader lately. - if n.leader != noLeader && n.leader != n.id && n.catchup == nil { - okInterval := int64(hbInterval) * 2 - ts := time.Now().UnixNano() - if ps := n.peers[n.leader]; ps == nil || ps.ts == 0 && (ts-ps.ts) > okInterval { - n.debug("Not current, no recent leader contact") - return false - } - } - if cs := n.catchup; cs != nil { - // We're actively catching up, can't mark current even if commit==applied. - n.debug("Not current, still catching up pindex=%d, cindex=%d", n.pindex, cs.cindex) - return false - } - - if n.paused && n.hcommit > n.commit { - // We're currently paused, waiting to be resumed to apply pending commits. - n.debug("Not current, waiting to resume applies commit=%d, hcommit=%d", n.commit, n.hcommit) - return false - } - - if n.commit == n.applied { - // At this point if we are current, we can return saying so. - clearBehindState() - return true - } else if !includeForwardProgress { - // Otherwise, if we aren't allowed to include forward progress - // (i.e. we are checking "current" instead of "healthy") then - // give up now. - return false - } - - // Otherwise, wait for a short period of time and see if we are making any - // forward progress. - if startDelta := n.commit - n.applied; startDelta > 0 { - for i := 0; i < 10; i++ { // 10ms, in 1ms increments - n.Unlock() - time.Sleep(time.Millisecond) - n.Lock() - if n.commit-n.applied < startDelta { - // The gap is getting smaller, so we're making forward progress. - clearBehindState() - return true - } - } - } - - n.hcbehind = true - n.warn("Falling behind in health check, commit %d != applied %d", n.commit, n.applied) - return false -} - -// Current returns if we are the leader for our group or an up to date follower. -func (n *raft) Current() bool { - if n == nil { - return false - } - n.Lock() - defer n.Unlock() - return n.isCurrent(false) -} - -// Healthy returns if we are the leader for our group and nearly up-to-date. -func (n *raft) Healthy() bool { - if n == nil { - return false - } - n.Lock() - defer n.Unlock() - return n.isCurrent(true) -} - -// HadPreviousLeader indicates if this group ever had a leader. -func (n *raft) HadPreviousLeader() bool { - return n.pleader.Load() -} - -// GroupLeader returns the current leader of the group. -func (n *raft) GroupLeader() string { - if n == nil { - return noLeader - } - n.RLock() - defer n.RUnlock() - return n.leader -} - -// Leaderless is a lockless way of finding out if the group has a -// leader or not. Use instead of GroupLeader in hot paths. -func (n *raft) Leaderless() bool { - if n == nil { - return true - } - // Negated because we want the default state of hasLeader to be - // false until the first setLeader() call. - return !n.hasleader.Load() -} - -// Guess the best next leader. Stepdown will check more thoroughly. -// Lock should be held. -func (n *raft) selectNextLeader() string { - nextLeader, hli := noLeader, uint64(0) - for peer, ps := range n.peers { - if peer == n.id || ps.li <= hli { - continue - } - hli = ps.li - nextLeader = peer - } - return nextLeader -} - -// StepDown will have a leader stepdown and optionally do a leader transfer. -func (n *raft) StepDown(preferred ...string) error { - if n.State() != Leader { - return errNotLeader - } - - n.Lock() - if len(preferred) > 1 { - n.Unlock() - return errTooManyPrefs - } - - n.debug("Being asked to stepdown") - - // See if we have up to date followers. - maybeLeader := noLeader - if len(preferred) > 0 { - if preferred[0] != _EMPTY_ { - maybeLeader = preferred[0] - } else { - preferred = nil - } - } - - // Can't pick ourselves. - if maybeLeader == n.id { - maybeLeader = noLeader - preferred = nil - } - - nowts := time.Now().UnixNano() - - // If we have a preferred check it first. - if maybeLeader != noLeader { - var isHealthy bool - if ps, ok := n.peers[maybeLeader]; ok { - si, ok := n.s.nodeToInfo.Load(maybeLeader) - isHealthy = ok && !si.(nodeInfo).offline && (nowts-ps.ts) < int64(hbInterval*3) - } - if !isHealthy { - maybeLeader = noLeader - } - } - - // If we do not have a preferred at this point pick the first healthy one. - // Make sure not ourselves. - if maybeLeader == noLeader { - for peer, ps := range n.peers { - if peer == n.id { - continue - } - si, ok := n.s.nodeToInfo.Load(peer) - isHealthy := ok && !si.(nodeInfo).offline && (nowts-ps.ts) < int64(hbInterval*3) - if isHealthy { - maybeLeader = peer - break - } - } - } - - // Clear our vote state. - n.vote = noVote - n.writeTermVote() - - n.Unlock() - - if len(preferred) > 0 && maybeLeader == noLeader { - n.debug("Can not transfer to preferred peer %q", preferred[0]) - } - - // If we have a new leader selected, transfer over to them. - // Send the append entry directly rather than via the proposals queue, - // as we will switch to follower state immediately and will blow away - // the contents of the proposal queue in the process. - if maybeLeader != noLeader { - n.debug("Selected %q for new leader, stepping down due to leadership transfer", maybeLeader) - ae := newEntry(EntryLeaderTransfer, []byte(maybeLeader)) - n.sendAppendEntry([]*Entry{ae}) - } - - // Force us to stepdown here. - n.stepdown(noLeader) - - return nil -} - -// Campaign will have our node start a leadership vote. -func (n *raft) Campaign() error { - n.Lock() - defer n.Unlock() - return n.campaign() -} - -func randCampaignTimeout() time.Duration { - delta := rand.Int63n(int64(maxCampaignTimeout - minCampaignTimeout)) - return (minCampaignTimeout + time.Duration(delta)) -} - -// Campaign will have our node start a leadership vote. -// Lock should be held. -func (n *raft) campaign() error { - n.debug("Starting campaign") - if n.State() == Leader { - return errAlreadyLeader - } - n.resetElect(randCampaignTimeout()) - return nil -} - -// xferCampaign will have our node start an immediate leadership vote. -// Lock should be held. -func (n *raft) xferCampaign() error { - n.debug("Starting transfer campaign") - if n.State() == Leader { - n.lxfer = false - return errAlreadyLeader - } - n.resetElect(10 * time.Millisecond) - return nil -} - -// State returns the current state for this node. -// Upper layers should not check State to check if we're Leader, use n.Leader() instead. -func (n *raft) State() RaftState { - return RaftState(n.state.Load()) -} - -// Progress returns the current index, commit and applied values. -func (n *raft) Progress() (index, commit, applied uint64) { - n.RLock() - defer n.RUnlock() - return n.pindex, n.commit, n.applied -} - -// Size returns number of entries and total bytes for our WAL. -func (n *raft) Size() (uint64, uint64) { - n.RLock() - var state StreamState - n.wal.FastState(&state) - n.RUnlock() - return state.Msgs, state.Bytes -} - -func (n *raft) ID() string { - if n == nil { - return _EMPTY_ - } - // Lock not needed as n.id is never changed after creation. - return n.id -} - -func (n *raft) Group() string { - // Lock not needed as n.group is never changed after creation. - return n.group -} - -func (n *raft) Peers() []*Peer { - n.RLock() - defer n.RUnlock() - - var peers []*Peer - for id, ps := range n.peers { - var lag uint64 - if n.commit > ps.li { - lag = n.commit - ps.li - } - p := &Peer{ - ID: id, - Current: id == n.leader || ps.li >= n.applied, - Last: time.Unix(0, ps.ts), - Lag: lag, - } - peers = append(peers, p) - } - return peers -} - -// Update and propose our known set of peers. -func (n *raft) ProposeKnownPeers(knownPeers []string) { - // If we are the leader update and send this update out. - if n.State() != Leader { - return - } - n.UpdateKnownPeers(knownPeers) - n.sendPeerState() -} - -// Update our known set of peers. -func (n *raft) UpdateKnownPeers(knownPeers []string) { - n.Lock() - // Process like peer state update. - ps := &peerState{knownPeers, len(knownPeers), n.extSt} - n.processPeerState(ps) - n.Unlock() -} - -// ApplyQ returns the apply queue that new commits will be sent to for the -// upper layer to apply. -func (n *raft) ApplyQ() *ipQueue[*CommittedEntry] { return n.apply } - -// LeadChangeC returns the leader change channel, notifying when the Raft -// leader role has moved. -func (n *raft) LeadChangeC() <-chan bool { return n.leadc } - -// QuitC returns the quit channel, notifying when the Raft group has shut down. -func (n *raft) QuitC() <-chan struct{} { return n.quit } - -func (n *raft) Created() time.Time { - // Lock not needed as n.created is never changed after creation. - return n.created -} - -func (n *raft) Stop() { - n.shutdown() -} - -func (n *raft) WaitForStop() { - if n.state.Load() == int32(Closed) { - n.wg.Wait() - } -} - -func (n *raft) Delete() { - n.shutdown() - n.wg.Wait() - - n.Lock() - defer n.Unlock() - - if wal := n.wal; wal != nil { - wal.Delete() - } - os.RemoveAll(n.sd) - n.debug("Deleted") -} - -func (n *raft) shutdown() { - // First call to Stop or Delete should close the quit chan - // to notify the runAs goroutines to stop what they're doing. - if n.state.Swap(int32(Closed)) != int32(Closed) { - n.leaderState.Store(false) - close(n.quit) - } -} - -const ( - raftAllSubj = "$NRG.>" - raftVoteSubj = "$NRG.V.%s" - raftAppendSubj = "$NRG.AE.%s" - raftPropSubj = "$NRG.P.%s" - raftRemovePeerSubj = "$NRG.RP.%s" - raftReply = "$NRG.R.%s" - raftCatchupReply = "$NRG.CR.%s" -) - -// Lock should be held (due to use of random generator) -func (n *raft) newCatchupInbox() string { - var b [replySuffixLen]byte - rn := fastrand.Uint64() - for i, l := 0, rn; i < len(b); i++ { - b[i] = digits[l%base] - l /= base - } - return fmt.Sprintf(raftCatchupReply, b[:]) -} - -func (n *raft) newInbox() string { - var b [replySuffixLen]byte - rn := fastrand.Uint64() - for i, l := 0, rn; i < len(b); i++ { - b[i] = digits[l%base] - l /= base - } - return fmt.Sprintf(raftReply, b[:]) -} - -// Our internal subscribe. -// Lock should be held. -func (n *raft) subscribe(subject string, cb msgHandler) (*subscription, error) { - if n.c == nil { - return nil, errNoInternalClient - } - return n.s.systemSubscribe(subject, _EMPTY_, false, n.c, cb) -} - -// Lock should be held. -func (n *raft) unsubscribe(sub *subscription) { - if n.c != nil && sub != nil { - n.c.processUnsub(sub.sid) - } -} - -// Lock should be held. -func (n *raft) createInternalSubs() error { - n.vsubj, n.vreply = fmt.Sprintf(raftVoteSubj, n.group), n.newInbox() - n.asubj, n.areply = fmt.Sprintf(raftAppendSubj, n.group), n.newInbox() - n.psubj = fmt.Sprintf(raftPropSubj, n.group) - n.rpsubj = fmt.Sprintf(raftRemovePeerSubj, n.group) - - // Votes - if _, err := n.subscribe(n.vreply, n.handleVoteResponse); err != nil { - return err - } - if _, err := n.subscribe(n.vsubj, n.handleVoteRequest); err != nil { - return err - } - // AppendEntry - if _, err := n.subscribe(n.areply, n.handleAppendEntryResponse); err != nil { - return err - } - if sub, err := n.subscribe(n.asubj, n.handleAppendEntry); err != nil { - return err - } else { - n.aesub = sub - } - - return nil -} - -func randElectionTimeout() time.Duration { - delta := rand.Int63n(int64(maxElectionTimeout - minElectionTimeout)) - return (minElectionTimeout + time.Duration(delta)) -} - -// Lock should be held. -func (n *raft) resetElectionTimeout() { - n.resetElect(randElectionTimeout()) -} - -func (n *raft) resetElectionTimeoutWithLock() { - n.resetElectWithLock(randElectionTimeout()) -} - -// Lock should be held. -func (n *raft) resetElect(et time.Duration) { - n.etlr = time.Now() - if n.elect == nil { - n.elect = time.NewTimer(et) - } else { - if !n.elect.Stop() { - select { - case <-n.elect.C: - default: - } - } - n.elect.Reset(et) - } -} - -func (n *raft) resetElectWithLock(et time.Duration) { - n.Lock() - n.resetElect(et) - n.Unlock() -} - -// run is the top-level runner for the Raft state machine. Depending on the -// state of the node (leader, follower, candidate, observer), this will call -// through to other functions. It is expected that this function will run for -// the entire life of the Raft node once started. -func (n *raft) run() { - s := n.s - defer s.grWG.Done() - defer n.wg.Done() - - // We want to wait for some routing to be enabled, so we will wait for - // at least a route, leaf or gateway connection to be established before - // starting the run loop. - for gw := s.gateway; ; { - s.mu.RLock() - ready, gwEnabled := s.numRemotes()+len(s.leafs) > 0, gw.enabled - s.mu.RUnlock() - if !ready && gwEnabled { - gw.RLock() - ready = len(gw.out)+len(gw.in) > 0 - gw.RUnlock() - } - if !ready { - select { - case <-s.quitCh: - return - case <-time.After(100 * time.Millisecond): - s.RateLimitWarnf("Waiting for routing to be established...") - } - } else { - break - } - } - - // We may have paused adding entries to apply queue, resume here. - // No-op if not paused. - n.ResumeApply() - - // Send nil entry to signal the upper layers we are done doing replay/restore. - n.apply.push(nil) - -runner: - for s.isRunning() { - switch n.State() { - case Follower: - n.runAsFollower() - case Candidate: - n.runAsCandidate() - case Leader: - n.runAsLeader() - case Closed: - break runner - } - } - - // If we've reached this point then we're shutting down, either because - // the server is stopping or because the Raft group is closing/closed. - n.Lock() - defer n.Unlock() - - if c := n.c; c != nil { - var subs []*subscription - c.mu.Lock() - for _, sub := range c.subs { - subs = append(subs, sub) - } - c.mu.Unlock() - for _, sub := range subs { - n.unsubscribe(sub) - } - c.closeConnection(InternalClient) - n.c = nil - } - - // Unregistering ipQueues do not prevent them from push/pop - // just will remove them from the central monitoring map - queues := []interface { - unregister() - drain() int - }{n.reqs, n.votes, n.prop, n.entry, n.resp, n.apply} - for _, q := range queues { - q.drain() - q.unregister() - } - - n.s.unregisterRaftNode(n.group) - - if wal := n.wal; wal != nil { - wal.Stop() - } - - n.debug("Shutdown") -} - -func (n *raft) debug(format string, args ...any) { - if n.dflag { - nf := fmt.Sprintf("RAFT [%s - %s] %s", n.id, n.group, format) - n.s.Debugf(nf, args...) - } -} - -func (n *raft) warn(format string, args ...any) { - nf := fmt.Sprintf("RAFT [%s - %s] %s", n.id, n.group, format) - n.s.RateLimitWarnf(nf, args...) -} - -func (n *raft) error(format string, args ...any) { - nf := fmt.Sprintf("RAFT [%s - %s] %s", n.id, n.group, format) - n.s.Errorf(nf, args...) -} - -func (n *raft) electTimer() *time.Timer { - n.RLock() - defer n.RUnlock() - return n.elect -} - -func (n *raft) IsObserver() bool { - n.RLock() - defer n.RUnlock() - return n.observer -} - -// Sets the state to observer only. -func (n *raft) SetObserver(isObserver bool) { - n.setObserver(isObserver, extUndetermined) -} - -func (n *raft) setObserver(isObserver bool, extSt extensionState) { - n.Lock() - defer n.Unlock() - - if n.paused { - // Applies are paused so we're already in observer state. - // Resuming the applies will set the state back to whatever - // is in "pobserver", so update that instead. - n.pobserver = isObserver - return - } - - wasObserver := n.observer - n.observer = isObserver - n.extSt = extSt - - // If we're leaving observer state then reset the election timer or - // we might end up waiting for up to the observerModeInterval. - if wasObserver && !isObserver { - n.resetElect(randCampaignTimeout()) - } -} - -// processAppendEntries is called by the Raft state machine when there are -// new append entries to be committed and sent to the upper state machine. -func (n *raft) processAppendEntries() { - canProcess := true - if n.isClosed() { - n.debug("AppendEntry not processing inbound, closed") - canProcess = false - } - if n.outOfResources() { - n.debug("AppendEntry not processing inbound, no resources") - canProcess = false - } - // Always pop the entries, but check if we can process them. If we can't - // then the entries are effectively dropped. - aes := n.entry.pop() - if canProcess { - for _, ae := range aes { - n.processAppendEntry(ae, ae.sub) - } - } - n.entry.recycle(&aes) -} - -// runAsFollower is called by run and will block for as long as the node is -// running in the follower state. -func (n *raft) runAsFollower() { - for n.State() == Follower { - elect := n.electTimer() - - select { - case <-n.entry.ch: - // New append entries have arrived over the network. - n.processAppendEntries() - case <-n.s.quitCh: - // The server is shutting down. - return - case <-n.quit: - // The Raft node is shutting down. - return - case <-elect.C: - // The election timer has fired so we think it's time to call an election. - // If we are out of resources we just want to stay in this state for the moment. - if n.outOfResources() { - n.resetElectionTimeoutWithLock() - n.debug("Not switching to candidate, no resources") - } else if n.IsObserver() { - n.resetElectWithLock(observerModeInterval) - n.debug("Not switching to candidate, observer only") - } else if n.isCatchingUp() { - n.debug("Not switching to candidate, catching up") - // Check to see if our catchup has stalled. - n.Lock() - if n.catchupStalled() { - n.cancelCatchup() - } - n.resetElectionTimeout() - n.Unlock() - } else { - n.switchToCandidate() - return - } - case <-n.votes.ch: - // We're receiving votes from the network, probably because we have only - // just stepped down and they were already in flight. Ignore them. - n.debug("Ignoring old vote response, we have stepped down") - n.votes.popOne() - case <-n.resp.ch: - // Ignore append entry responses received from before the state change. - n.resp.drain() - case <-n.prop.ch: - // Ignore proposals received from before the state change. - n.prop.drain() - case <-n.reqs.ch: - // We've just received a vote request from the network. - // Because of drain() it is possible that we get nil from popOne(). - if voteReq, ok := n.reqs.popOne(); ok { - n.processVoteRequest(voteReq) - } - } - } -} - -// Pool for CommittedEntry re-use. -var cePool = sync.Pool{ - New: func() any { - return &CommittedEntry{} - }, -} - -// CommittedEntry is handed back to the user to apply a commit to their upper layer. -type CommittedEntry struct { - Index uint64 - Entries []*Entry -} - -// Create a new CommittedEntry. When the returned entry is no longer needed, it -// should be returned to the pool by calling ReturnToPool. -func newCommittedEntry(index uint64, entries []*Entry) *CommittedEntry { - ce := cePool.Get().(*CommittedEntry) - ce.Index, ce.Entries = index, entries - return ce -} - -// ReturnToPool returns the CommittedEntry to the pool, after which point it is -// no longer safe to reuse. -func (ce *CommittedEntry) ReturnToPool() { - if ce == nil { - return - } - if len(ce.Entries) > 0 { - for _, e := range ce.Entries { - entryPool.Put(e) - } - } - ce.Index, ce.Entries = 0, nil - cePool.Put(ce) -} - -// Pool for Entry re-use. -var entryPool = sync.Pool{ - New: func() any { - return &Entry{} - }, -} - -// Helper to create new entries. When the returned entry is no longer needed, it -// should be returned to the entryPool pool. -func newEntry(t EntryType, data []byte) *Entry { - entry := entryPool.Get().(*Entry) - entry.Type, entry.Data = t, data - return entry -} - -// Pool for appendEntry re-use. -var aePool = sync.Pool{ - New: func() any { - return &appendEntry{} - }, -} - -// appendEntry is the main struct that is used to sync raft peers. -type appendEntry struct { - leader string // The leader that this append entry came from. - term uint64 // The current term, as the leader understands it. - commit uint64 // The commit index, as the leader understands it. - pterm uint64 // The previous term, for checking consistency. - pindex uint64 // The previous commit index, for checking consistency. - entries []*Entry // Entries to process. - // Below fields are for internal use only: - reply string // Reply subject to respond to once committed. - sub *subscription // The subscription that the append entry came in on. - buf []byte -} - -// Create a new appendEntry. -func newAppendEntry(leader string, term, commit, pterm, pindex uint64, entries []*Entry) *appendEntry { - ae := aePool.Get().(*appendEntry) - ae.leader, ae.term, ae.commit, ae.pterm, ae.pindex, ae.entries = leader, term, commit, pterm, pindex, entries - ae.reply, ae.sub, ae.buf = _EMPTY_, nil, nil - return ae -} - -// Will return this append entry, and its interior entries to their respective pools. -func (ae *appendEntry) returnToPool() { - ae.entries, ae.buf, ae.sub, ae.reply = nil, nil, nil, _EMPTY_ - aePool.Put(ae) -} - -// Pool for proposedEntry re-use. -var pePool = sync.Pool{ - New: func() any { - return &proposedEntry{} - }, -} - -// Create a new proposedEntry. -func newProposedEntry(entry *Entry, reply string) *proposedEntry { - pe := pePool.Get().(*proposedEntry) - pe.Entry, pe.reply = entry, reply - return pe -} - -// Will return this proosed entry. -func (pe *proposedEntry) returnToPool() { - pe.Entry, pe.reply = nil, _EMPTY_ - pePool.Put(pe) -} - -type EntryType uint8 - -const ( - EntryNormal EntryType = iota - EntryOldSnapshot - EntryPeerState - EntryAddPeer - EntryRemovePeer - EntryLeaderTransfer - EntrySnapshot -) - -func (t EntryType) String() string { - switch t { - case EntryNormal: - return "Normal" - case EntryOldSnapshot: - return "OldSnapshot" - case EntryPeerState: - return "PeerState" - case EntryAddPeer: - return "AddPeer" - case EntryRemovePeer: - return "RemovePeer" - case EntryLeaderTransfer: - return "LeaderTransfer" - case EntrySnapshot: - return "Snapshot" - } - return fmt.Sprintf("Unknown [%d]", uint8(t)) -} - -type Entry struct { - Type EntryType - Data []byte -} - -func (ae *appendEntry) String() string { - return fmt.Sprintf("&{leader:%s term:%d commit:%d pterm:%d pindex:%d entries: %d}", - ae.leader, ae.term, ae.commit, ae.pterm, ae.pindex, len(ae.entries)) -} - -const appendEntryBaseLen = idLen + 4*8 + 2 - -func (ae *appendEntry) encode(b []byte) ([]byte, error) { - if ll := len(ae.leader); ll != idLen && ll != 0 { - return nil, errLeaderLen - } - if len(ae.entries) > math.MaxUint16 { - return nil, errTooManyEntries - } - - var elen int - for _, e := range ae.entries { - elen += len(e.Data) + 1 + 4 // 1 is type, 4 is for size. - } - tlen := appendEntryBaseLen + elen + 1 - - var buf []byte - if cap(b) >= tlen { - buf = b[:tlen] - } else { - buf = make([]byte, tlen) - } - - var le = binary.LittleEndian - copy(buf[:idLen], ae.leader) - le.PutUint64(buf[8:], ae.term) - le.PutUint64(buf[16:], ae.commit) - le.PutUint64(buf[24:], ae.pterm) - le.PutUint64(buf[32:], ae.pindex) - le.PutUint16(buf[40:], uint16(len(ae.entries))) - wi := 42 - for _, e := range ae.entries { - le.PutUint32(buf[wi:], uint32(len(e.Data)+1)) - wi += 4 - buf[wi] = byte(e.Type) - wi++ - copy(buf[wi:], e.Data) - wi += len(e.Data) - } - return buf[:wi], nil -} - -// This can not be used post the wire level callback since we do not copy. -func (n *raft) decodeAppendEntry(msg []byte, sub *subscription, reply string) (*appendEntry, error) { - if len(msg) < appendEntryBaseLen { - return nil, errBadAppendEntry - } - - var le = binary.LittleEndian - - ae := newAppendEntry(string(msg[:idLen]), le.Uint64(msg[8:]), le.Uint64(msg[16:]), le.Uint64(msg[24:]), le.Uint64(msg[32:]), nil) - ae.reply, ae.sub = reply, sub - - // Decode Entries. - ne, ri := int(le.Uint16(msg[40:])), 42 - for i, max := 0, len(msg); i < ne; i++ { - if ri >= max-1 { - return nil, errBadAppendEntry - } - le := int(le.Uint32(msg[ri:])) - ri += 4 - if le <= 0 || ri+le > max { - return nil, errBadAppendEntry - } - entry := newEntry(EntryType(msg[ri]), msg[ri+1:ri+le]) - ae.entries = append(ae.entries, entry) - ri += le - } - ae.buf = msg - return ae, nil -} - -// Pool for appendEntryResponse re-use. -var arPool = sync.Pool{ - New: func() any { - return &appendEntryResponse{} - }, -} - -// We want to make sure this does not change from system changing length of syshash. -const idLen = 8 -const appendEntryResponseLen = 24 + 1 - -// appendEntryResponse is our response to a received appendEntry. -type appendEntryResponse struct { - term uint64 - index uint64 - peer string - reply string // internal usage. - success bool -} - -// Create a new appendEntryResponse. -func newAppendEntryResponse(term, index uint64, peer string, success bool) *appendEntryResponse { - ar := arPool.Get().(*appendEntryResponse) - ar.term, ar.index, ar.peer, ar.success = term, index, peer, success - // Always empty out. - ar.reply = _EMPTY_ - return ar -} - -func (ar *appendEntryResponse) encode(b []byte) []byte { - var buf []byte - if cap(b) >= appendEntryResponseLen { - buf = b[:appendEntryResponseLen] - } else { - buf = make([]byte, appendEntryResponseLen) - } - var le = binary.LittleEndian - le.PutUint64(buf[0:], ar.term) - le.PutUint64(buf[8:], ar.index) - copy(buf[16:16+idLen], ar.peer) - if ar.success { - buf[24] = 1 - } else { - buf[24] = 0 - } - return buf[:appendEntryResponseLen] -} - -// Track all peers we may have ever seen to use an string interns for appendEntryResponse decoding. -var peers sync.Map - -func (n *raft) decodeAppendEntryResponse(msg []byte) *appendEntryResponse { - if len(msg) != appendEntryResponseLen { - return nil - } - var le = binary.LittleEndian - ar := arPool.Get().(*appendEntryResponse) - ar.term = le.Uint64(msg[0:]) - ar.index = le.Uint64(msg[8:]) - - peer, ok := peers.Load(string(msg[16 : 16+idLen])) - if !ok { - // We missed so store inline here. - peer = string(msg[16 : 16+idLen]) - peers.Store(peer, peer) - } - ar.peer = peer.(string) - ar.success = msg[24] == 1 - return ar -} - -// Called when a remove peer proposal has been forwarded -func (n *raft) handleForwardedRemovePeerProposal(sub *subscription, c *client, _ *Account, _, reply string, msg []byte) { - n.debug("Received forwarded remove peer proposal: %q", msg) - - if n.State() != Leader { - n.debug("Ignoring forwarded peer removal proposal, not leader") - return - } - if len(msg) != idLen { - n.warn("Received invalid peer name for remove proposal: %q", msg) - return - } - - n.RLock() - prop, werr := n.prop, n.werr - n.RUnlock() - - // Ignore if we have had a write error previous. - if werr != nil { - return - } - - // Need to copy since this is underlying client/route buffer. - peer := copyBytes(msg) - prop.push(newProposedEntry(newEntry(EntryRemovePeer, peer), reply)) -} - -// Called when a peer has forwarded a proposal. -func (n *raft) handleForwardedProposal(sub *subscription, c *client, _ *Account, _, reply string, msg []byte) { - if n.State() != Leader { - n.debug("Ignoring forwarded proposal, not leader") - return - } - // Need to copy since this is underlying client/route buffer. - msg = copyBytes(msg) - - n.RLock() - prop, werr := n.prop, n.werr - n.RUnlock() - - // Ignore if we have had a write error previous. - if werr != nil { - return - } - - prop.push(newProposedEntry(newEntry(EntryNormal, msg), reply)) -} - -func (n *raft) runAsLeader() { - if n.State() == Closed { - return - } - - n.Lock() - psubj, rpsubj := n.psubj, n.rpsubj - - // For forwarded proposals, both normal and remove peer proposals. - fsub, err := n.subscribe(psubj, n.handleForwardedProposal) - if err != nil { - n.warn("Error subscribing to forwarded proposals: %v", err) - n.stepdownLocked(noLeader) - n.Unlock() - return - } - rpsub, err := n.subscribe(rpsubj, n.handleForwardedRemovePeerProposal) - if err != nil { - n.warn("Error subscribing to forwarded remove peer proposals: %v", err) - n.unsubscribe(fsub) - n.stepdownLocked(noLeader) - n.Unlock() - return - } - n.Unlock() - - // Cleanup our subscription when we leave. - defer func() { - n.Lock() - n.unsubscribe(fsub) - n.unsubscribe(rpsub) - n.Unlock() - }() - - // To send out our initial peer state. - n.sendPeerState() - - hb := time.NewTicker(hbInterval) - defer hb.Stop() - - lq := time.NewTicker(lostQuorumCheck) - defer lq.Stop() - - for n.State() == Leader { - select { - case <-n.s.quitCh: - return - case <-n.quit: - return - case <-n.resp.ch: - ars := n.resp.pop() - for _, ar := range ars { - n.processAppendEntryResponse(ar) - } - n.resp.recycle(&ars) - case <-n.prop.ch: - const maxBatch = 256 * 1024 - const maxEntries = 512 - var entries []*Entry - - es, sz := n.prop.pop(), 0 - for _, b := range es { - if b.Type == EntryRemovePeer { - n.doRemovePeerAsLeader(string(b.Data)) - } - entries = append(entries, b.Entry) - // Increment size. - sz += len(b.Data) + 1 - // If below thresholds go ahead and send. - if sz < maxBatch && len(entries) < maxEntries { - continue - } - n.sendAppendEntry(entries) - // Reset our sz and entries. - // We need to re-create `entries` because there is a reference - // to it in the node's pae map. - sz, entries = 0, nil - } - if len(entries) > 0 { - n.sendAppendEntry(entries) - } - // Respond to any proposals waiting for a confirmation. - for _, pe := range es { - if pe.reply != _EMPTY_ { - n.sendReply(pe.reply, nil) - } - pe.returnToPool() - } - n.prop.recycle(&es) - - case <-hb.C: - if n.notActive() { - n.sendHeartbeat() - } - case <-lq.C: - if n.lostQuorum() { - n.stepdown(noLeader) - return - } - case <-n.votes.ch: - // Because of drain() it is possible that we get nil from popOne(). - vresp, ok := n.votes.popOne() - if !ok { - continue - } - if vresp.term > n.Term() { - n.stepdown(noLeader) - return - } - n.trackPeer(vresp.peer) - case <-n.reqs.ch: - // Because of drain() it is possible that we get nil from popOne(). - if voteReq, ok := n.reqs.popOne(); ok { - n.processVoteRequest(voteReq) - } - case <-n.entry.ch: - n.processAppendEntries() - } - } -} - -// Quorum reports the quorum status. Will be called on former leaders. -func (n *raft) Quorum() bool { - n.RLock() - defer n.RUnlock() - - now, nc := time.Now().UnixNano(), 0 - for id, peer := range n.peers { - if id == n.id || time.Duration(now-peer.ts) < lostQuorumInterval { - nc++ - if nc >= n.qn { - return true - } - } - } - return false -} - -func (n *raft) lostQuorum() bool { - n.RLock() - defer n.RUnlock() - return n.lostQuorumLocked() -} - -func (n *raft) lostQuorumLocked() bool { - // In order to avoid false positives that can happen in heavily loaded systems - // make sure nothing is queued up that we have not processed yet. - // Also make sure we let any scale up actions settle before deciding. - if n.resp.len() != 0 || (!n.lsut.IsZero() && time.Since(n.lsut) < lostQuorumInterval) { - return false - } - - now, nc := time.Now().UnixNano(), 0 - for id, peer := range n.peers { - if id == n.id || time.Duration(now-peer.ts) < lostQuorumInterval { - nc++ - if nc >= n.qn { - return false - } - } - } - return true -} - -// Check for being not active in terms of sending entries. -// Used in determining if we need to send a heartbeat. -func (n *raft) notActive() bool { - n.RLock() - defer n.RUnlock() - return time.Since(n.active) > hbInterval -} - -// Return our current term. -func (n *raft) Term() uint64 { - n.RLock() - defer n.RUnlock() - return n.term -} - -// Lock should be held. -func (n *raft) loadFirstEntry() (ae *appendEntry, err error) { - var state StreamState - n.wal.FastState(&state) - return n.loadEntry(state.FirstSeq) -} - -func (n *raft) runCatchup(ar *appendEntryResponse, indexUpdatesQ *ipQueue[uint64]) { - n.RLock() - s, reply := n.s, n.areply - peer, subj, last := ar.peer, ar.reply, n.pindex - n.RUnlock() - - defer s.grWG.Done() - defer arPool.Put(ar) - - defer func() { - n.Lock() - delete(n.progress, peer) - if len(n.progress) == 0 { - n.progress = nil - } - // Check if this is a new peer and if so go ahead and propose adding them. - _, exists := n.peers[peer] - n.Unlock() - if !exists { - n.debug("Catchup done for %q, will add into peers", peer) - n.ProposeAddPeer(peer) - } - indexUpdatesQ.unregister() - }() - - n.debug("Running catchup for %q", peer) - - const maxOutstanding = 2 * 1024 * 1024 // 2MB for now. - next, total, om := uint64(0), 0, make(map[uint64]int) - - sendNext := func() bool { - for total <= maxOutstanding { - next++ - if next > last { - return true - } - ae, err := n.loadEntry(next) - if err != nil { - if err != ErrStoreEOF { - n.warn("Got an error loading %d index: %v", next, err) - } - return true - } - // Update our tracking total. - om[next] = len(ae.buf) - total += len(ae.buf) - n.sendRPC(subj, reply, ae.buf) - } - return false - } - - const activityInterval = 2 * time.Second - timeout := time.NewTimer(activityInterval) - defer timeout.Stop() - - stepCheck := time.NewTicker(100 * time.Millisecond) - defer stepCheck.Stop() - - // Run as long as we are leader and still not caught up. - for n.State() == Leader { - select { - case <-n.s.quitCh: - return - case <-n.quit: - return - case <-stepCheck.C: - if n.State() != Leader { - n.debug("Catching up canceled, no longer leader") - return - } - case <-timeout.C: - n.debug("Catching up for %q stalled", peer) - return - case <-indexUpdatesQ.ch: - if index, ok := indexUpdatesQ.popOne(); ok { - // Update our activity timer. - timeout.Reset(activityInterval) - // Update outstanding total. - total -= om[index] - delete(om, index) - if next == 0 { - next = index - } - // Check if we are done. - if index > last || sendNext() { - n.debug("Finished catching up") - return - } - } - } - } -} - -// Lock should be held. -func (n *raft) sendSnapshotToFollower(subject string) (uint64, error) { - snap, err := n.loadLastSnapshot() - if err != nil { - // We need to stepdown here when this happens. - n.stepdownLocked(noLeader) - // We need to reset our state here as well. - n.resetWAL() - return 0, err - } - // Go ahead and send the snapshot and peerstate here as first append entry to the catchup follower. - ae := n.buildAppendEntry([]*Entry{{EntrySnapshot, snap.data}, {EntryPeerState, snap.peerstate}}) - ae.pterm, ae.pindex = snap.lastTerm, snap.lastIndex - var state StreamState - n.wal.FastState(&state) - - fpIndex := state.FirstSeq - 1 - if snap.lastIndex < fpIndex && state.FirstSeq != 0 { - snap.lastIndex = fpIndex - ae.pindex = fpIndex - } - - encoding, err := ae.encode(nil) - if err != nil { - return 0, err - } - n.sendRPC(subject, n.areply, encoding) - return snap.lastIndex, nil -} - -func (n *raft) catchupFollower(ar *appendEntryResponse) { - n.debug("Being asked to catch up follower: %q", ar.peer) - n.Lock() - if n.progress == nil { - n.progress = make(map[string]*ipQueue[uint64]) - } else if q, ok := n.progress[ar.peer]; ok { - n.debug("Will cancel existing entry for catching up %q", ar.peer) - delete(n.progress, ar.peer) - q.push(n.pindex) - } - - // Check to make sure we have this entry. - start := ar.index + 1 - var state StreamState - n.wal.FastState(&state) - - if start < state.FirstSeq || (state.Msgs == 0 && start <= state.LastSeq) { - n.debug("Need to send snapshot to follower") - if lastIndex, err := n.sendSnapshotToFollower(ar.reply); err != nil { - n.error("Error sending snapshot to follower [%s]: %v", ar.peer, err) - n.Unlock() - arPool.Put(ar) - return - } else { - start = lastIndex + 1 - // If no other entries, we can just return here. - if state.Msgs == 0 || start > state.LastSeq { - n.debug("Finished catching up") - n.Unlock() - arPool.Put(ar) - return - } - n.debug("Snapshot sent, reset first catchup entry to %d", lastIndex) - } - } - - ae, err := n.loadEntry(start) - if err != nil { - n.warn("Request from follower for entry at index [%d] errored for state %+v - %v", start, state, err) - if err == ErrStoreEOF { - // If we are here we are seeing a request for an item beyond our state, meaning we should stepdown. - n.stepdownLocked(noLeader) - n.Unlock() - arPool.Put(ar) - return - } - ae, err = n.loadFirstEntry() - } - if err != nil || ae == nil { - n.warn("Could not find a starting entry for catchup request: %v", err) - // If we are here we are seeing a request for an item we do not have, meaning we should stepdown. - // This is possible on a reset of our WAL but the other side has a snapshot already. - // If we do not stepdown this can cycle. - n.stepdownLocked(noLeader) - n.Unlock() - arPool.Put(ar) - return - } - if ae.pindex != ar.index || ae.pterm != ar.term { - n.debug("Our first entry [%d:%d] does not match request from follower [%d:%d]", ae.pterm, ae.pindex, ar.term, ar.index) - } - // Create a queue for delivering updates from responses. - indexUpdates := newIPQueue[uint64](n.s, fmt.Sprintf("[ACC:%s] RAFT '%s' indexUpdates", n.accName, n.group)) - indexUpdates.push(ae.pindex) - n.progress[ar.peer] = indexUpdates - n.Unlock() - - n.wg.Add(1) - n.s.startGoRoutine(func() { - defer n.wg.Done() - n.runCatchup(ar, indexUpdates) - }) -} - -func (n *raft) loadEntry(index uint64) (*appendEntry, error) { - var smp StoreMsg - sm, err := n.wal.LoadMsg(index, &smp) - if err != nil { - return nil, err - } - return n.decodeAppendEntry(sm.msg, nil, _EMPTY_) -} - -// applyCommit will update our commit index and apply the entry to the apply queue. -// lock should be held. -func (n *raft) applyCommit(index uint64) error { - if n.State() == Closed { - return errNodeClosed - } - if index <= n.commit { - n.debug("Ignoring apply commit for %d, already processed", index) - return nil - } - - if n.State() == Leader { - delete(n.acks, index) - } - - ae := n.pae[index] - if ae == nil { - var state StreamState - n.wal.FastState(&state) - if index < state.FirstSeq { - return nil - } - var err error - if ae, err = n.loadEntry(index); err != nil { - if err != ErrStoreClosed && err != ErrStoreEOF { - n.warn("Got an error loading %d index: %v - will reset", index, err) - if n.State() == Leader { - n.stepdownLocked(n.selectNextLeader()) - } - // Reset and cancel any catchup. - n.resetWAL() - n.cancelCatchup() - } - return errEntryLoadFailed - } - } else { - defer delete(n.pae, index) - } - - n.commit = index - ae.buf = nil - - var committed []*Entry - for _, e := range ae.entries { - switch e.Type { - case EntryNormal: - committed = append(committed, e) - case EntryOldSnapshot: - // For old snapshots in our WAL. - committed = append(committed, newEntry(EntrySnapshot, e.Data)) - case EntrySnapshot: - committed = append(committed, e) - case EntryPeerState: - if n.State() != Leader { - if ps, err := decodePeerState(e.Data); err == nil { - n.processPeerState(ps) - } - } - case EntryAddPeer: - newPeer := string(e.Data) - n.debug("Added peer %q", newPeer) - - // Store our peer in our global peer map for all peers. - peers.LoadOrStore(newPeer, newPeer) - - // If we were on the removed list reverse that here. - if n.removed != nil { - delete(n.removed, newPeer) - } - - if lp, ok := n.peers[newPeer]; !ok { - // We are not tracking this one automatically so we need to bump cluster size. - n.peers[newPeer] = &lps{time.Now().UnixNano(), 0, true} - } else { - // Mark as added. - lp.kp = true - } - // Adjust cluster size and quorum if needed. - n.adjustClusterSizeAndQuorum() - // Write out our new state. - n.writePeerState(&peerState{n.peerNames(), n.csz, n.extSt}) - // We pass these up as well. - committed = append(committed, e) - - case EntryRemovePeer: - peer := string(e.Data) - n.debug("Removing peer %q", peer) - - // Make sure we have our removed map. - if n.removed == nil { - n.removed = make(map[string]struct{}) - } - n.removed[peer] = struct{}{} - - if _, ok := n.peers[peer]; ok { - delete(n.peers, peer) - // We should decrease our cluster size since we are tracking this peer. - n.adjustClusterSizeAndQuorum() - // Write out our new state. - n.writePeerState(&peerState{n.peerNames(), n.csz, n.extSt}) - } - - // If this is us and we are the leader we should attempt to stepdown. - if peer == n.id && n.State() == Leader { - n.stepdownLocked(n.selectNextLeader()) - } - - // Remove from string intern map. - peers.Delete(peer) - - // We pass these up as well. - committed = append(committed, e) - } - } - // Pass to the upper layers if we have normal entries. It is - // entirely possible that 'committed' might be an empty slice here, - // which will happen if we've processed updates inline (like peer - // states). In which case the upper layer will just call down with - // Applied() with no further action. - n.apply.push(newCommittedEntry(index, committed)) - // Place back in the pool. - ae.returnToPool() - return nil -} - -// Used to track a success response and apply entries. -func (n *raft) trackResponse(ar *appendEntryResponse) { - if n.State() == Closed { - return - } - - n.Lock() - - // Update peer's last index. - if ps := n.peers[ar.peer]; ps != nil && ar.index > ps.li { - ps.li = ar.index - } - - // If we are tracking this peer as a catchup follower, update that here. - if indexUpdateQ := n.progress[ar.peer]; indexUpdateQ != nil { - indexUpdateQ.push(ar.index) - } - - // Ignore items already committed. - if ar.index <= n.commit { - n.Unlock() - return - } - - // See if we have items to apply. - var sendHB bool - - results := n.acks[ar.index] - if results == nil { - results = make(map[string]struct{}) - n.acks[ar.index] = results - } - results[ar.peer] = struct{}{} - - // We don't count ourselves to account for leader changes, so add 1. - if nr := len(results); nr+1 >= n.qn { - // We have a quorum. - for index := n.commit + 1; index <= ar.index; index++ { - if err := n.applyCommit(index); err != nil && err != errNodeClosed { - n.error("Got an error applying commit for %d: %v", index, err) - break - } - } - sendHB = n.prop.len() == 0 - } - n.Unlock() - - if sendHB { - n.sendHeartbeat() - } -} - -// Used to adjust cluster size and peer count based on added official peers. -// lock should be held. -func (n *raft) adjustClusterSizeAndQuorum() { - pcsz, ncsz := n.csz, 0 - for _, peer := range n.peers { - if peer.kp { - ncsz++ - } - } - n.csz = ncsz - n.qn = n.csz/2 + 1 - - if ncsz > pcsz { - n.debug("Expanding our clustersize: %d -> %d", pcsz, ncsz) - n.lsut = time.Now() - } else if ncsz < pcsz { - n.debug("Decreasing our clustersize: %d -> %d", pcsz, ncsz) - if n.State() == Leader { - go n.sendHeartbeat() - } - } - if ncsz != pcsz { - n.recreateInternalSubsLocked() - } -} - -// Track interactions with this peer. -func (n *raft) trackPeer(peer string) error { - n.Lock() - var needPeerAdd, isRemoved bool - if n.removed != nil { - _, isRemoved = n.removed[peer] - } - if n.State() == Leader { - if lp, ok := n.peers[peer]; !ok || !lp.kp { - // Check if this peer had been removed previously. - needPeerAdd = !isRemoved - } - } - if ps := n.peers[peer]; ps != nil { - ps.ts = time.Now().UnixNano() - } else if !isRemoved { - n.peers[peer] = &lps{time.Now().UnixNano(), 0, false} - } - n.Unlock() - - if needPeerAdd { - n.ProposeAddPeer(peer) - } - return nil -} - -func (n *raft) runAsCandidate() { - n.Lock() - // Drain old responses. - n.votes.drain() - n.Unlock() - - // Send out our request for votes. - n.requestVote() - - // We vote for ourselves. - votes := map[string]struct{}{ - n.ID(): {}, - } - - for n.State() == Candidate { - elect := n.electTimer() - select { - case <-n.entry.ch: - n.processAppendEntries() - case <-n.resp.ch: - // Ignore append entry responses received from before the state change. - n.resp.drain() - case <-n.prop.ch: - // Ignore proposals received from before the state change. - n.prop.drain() - case <-n.s.quitCh: - return - case <-n.quit: - return - case <-elect.C: - n.switchToCandidate() - return - case <-n.votes.ch: - // Because of drain() it is possible that we get nil from popOne(). - vresp, ok := n.votes.popOne() - if !ok { - continue - } - n.RLock() - nterm := n.term - n.RUnlock() - - if vresp.granted && nterm == vresp.term { - // only track peers that would be our followers - n.trackPeer(vresp.peer) - votes[vresp.peer] = struct{}{} - if n.wonElection(len(votes)) { - // Become LEADER if we have won and gotten a quorum with everyone we should hear from. - n.switchToLeader() - return - } - } else if vresp.term > nterm { - // if we observe a bigger term, we should start over again or risk forming a quorum fully knowing - // someone with a better term exists. This is even the right thing to do if won == true. - n.Lock() - n.debug("Stepping down from candidate, detected higher term: %d vs %d", vresp.term, n.term) - n.term = vresp.term - n.vote = noVote - n.writeTermVote() - n.lxfer = false - n.stepdownLocked(noLeader) - n.Unlock() - } - case <-n.reqs.ch: - // Because of drain() it is possible that we get nil from popOne(). - if voteReq, ok := n.reqs.popOne(); ok { - n.processVoteRequest(voteReq) - } - } - } -} - -// handleAppendEntry handles an append entry from the wire. This function -// is an internal callback from the "asubj" append entry subscription. -func (n *raft) handleAppendEntry(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) { - msg = copyBytes(msg) - if ae, err := n.decodeAppendEntry(msg, sub, reply); err == nil { - // Push to the new entry channel. From here one of the worker - // goroutines (runAsLeader, runAsFollower, runAsCandidate) will - // pick it up. - n.entry.push(ae) - } else { - n.warn("AppendEntry failed to be placed on internal channel: corrupt entry") - } -} - -// cancelCatchup will stop an in-flight catchup by unsubscribing from the -// catchup subscription. -// Lock should be held. -func (n *raft) cancelCatchup() { - n.debug("Canceling catchup subscription since we are now up to date") - - if n.catchup != nil && n.catchup.sub != nil { - n.unsubscribe(n.catchup.sub) - } - n.catchup = nil -} - -// catchupStalled will try to determine if we are stalled. This is called -// on a new entry from our leader. -// Lock should be held. -func (n *raft) catchupStalled() bool { - if n.catchup == nil { - return false - } - if n.catchup.pindex == n.pindex { - return time.Since(n.catchup.active) > 2*time.Second - } - n.catchup.pindex = n.pindex - n.catchup.active = time.Now() - return false -} - -// createCatchup will create the state needed to track a catchup as it -// runs. It then creates a unique inbox for this catchup and subscribes -// to it. The remote side will stream entries to that subject. -// Lock should be held. -func (n *raft) createCatchup(ae *appendEntry) string { - // Cleanup any old ones. - if n.catchup != nil && n.catchup.sub != nil { - n.unsubscribe(n.catchup.sub) - } - // Snapshot term and index. - n.catchup = &catchupState{ - cterm: ae.pterm, - cindex: ae.pindex, - pterm: n.pterm, - pindex: n.pindex, - active: time.Now(), - } - inbox := n.newCatchupInbox() - sub, _ := n.subscribe(inbox, n.handleAppendEntry) - n.catchup.sub = sub - - return inbox -} - -// Truncate our WAL and reset. -// Lock should be held. -func (n *raft) truncateWAL(term, index uint64) { - n.debug("Truncating and repairing WAL to Term %d Index %d", term, index) - - if term == 0 && index == 0 { - n.warn("Resetting WAL state") - } - - defer func() { - // Check to see if we invalidated any snapshots that might have held state - // from the entries we are truncating. - if snap, _ := n.loadLastSnapshot(); snap != nil && snap.lastIndex > index { - os.Remove(n.snapfile) - n.snapfile = _EMPTY_ - } - // Make sure to reset commit and applied if above - if n.commit > n.pindex { - n.commit = n.pindex - } - if n.applied > n.commit { - n.applied = n.commit - } - }() - - if err := n.wal.Truncate(index); err != nil { - // If we get an invalid sequence, reset our wal all together. - // We will not have holes, so this means we do not have this message stored anymore. - if err == ErrInvalidSequence { - n.debug("Resetting WAL") - n.wal.Truncate(0) - // If our index is non-zero use PurgeEx to set us to the correct next index. - if index > 0 { - n.wal.PurgeEx(fwcs, index+1, 0, true) - } - } else { - n.warn("Error truncating WAL: %v", err) - n.setWriteErrLocked(err) - return - } - } - // Set after we know we have truncated properly. - n.pterm, n.pindex = term, index -} - -// Reset our WAL. This is equivalent to truncating all data from the log. -// Lock should be held. -func (n *raft) resetWAL() { - n.truncateWAL(0, 0) -} - -// Lock should be held -func (n *raft) updateLeader(newLeader string) { - n.leader = newLeader - n.hasleader.Store(newLeader != _EMPTY_) - if !n.pleader.Load() && newLeader != noLeader { - n.pleader.Store(true) - } -} - -// processAppendEntry will process an appendEntry. This is called either -// during recovery or from processAppendEntries when there are new entries -// to be committed. -func (n *raft) processAppendEntry(ae *appendEntry, sub *subscription) { - n.Lock() - // Don't reset here if we have been asked to assume leader position. - if !n.lxfer { - n.resetElectionTimeout() - } - - // Just return if closed or we had previous write error. - if n.State() == Closed || n.werr != nil { - n.Unlock() - return - } - - // Scratch buffer for responses. - var scratch [appendEntryResponseLen]byte - arbuf := scratch[:] - - // Are we receiving from another leader. - if n.State() == Leader { - // If we are the same we should step down to break the tie. - if ae.term >= n.term { - // If the append entry term is newer than the current term, erase our - // vote. - if ae.term > n.term { - n.term = ae.term - n.vote = noVote - n.writeTermVote() - } - n.debug("Received append entry from another leader, stepping down to %q", ae.leader) - n.stepdownLocked(ae.leader) - } else { - // Let them know we are the leader. - ar := newAppendEntryResponse(n.term, n.pindex, n.id, false) - n.debug("AppendEntry ignoring old term from another leader") - n.sendRPC(ae.reply, _EMPTY_, ar.encode(arbuf)) - arPool.Put(ar) - } - // Always return here from processing. - n.Unlock() - return - } - - // If we received an append entry as a candidate then it would appear that - // another node has taken on the leader role already, so we should convert - // to a follower of that node instead. - if n.State() == Candidate { - // If we have a leader in the current term or higher, we should stepdown, - // write the term and vote if the term of the request is higher. - if ae.term >= n.term { - // If the append entry term is newer than the current term, erase our - // vote. - if ae.term > n.term { - n.term = ae.term - n.vote = noVote - n.writeTermVote() - } - n.debug("Received append entry in candidate state from %q, converting to follower", ae.leader) - n.stepdownLocked(ae.leader) - } - } - - // Catching up state. - catchingUp := n.catchup != nil - // Is this a new entry? New entries will be delivered on the append entry - // sub, rather than a catch-up sub. - isNew := sub != nil && sub == n.aesub - - // Track leader directly - if isNew && ae.leader != noLeader { - if ps := n.peers[ae.leader]; ps != nil { - ps.ts = time.Now().UnixNano() - } else { - n.peers[ae.leader] = &lps{time.Now().UnixNano(), 0, true} - } - } - - // If we are catching up ignore old catchup subs. - // This could happen when we stall or cancel a catchup. - if !isNew && catchingUp && sub != n.catchup.sub { - n.Unlock() - n.debug("AppendEntry ignoring old entry from previous catchup") - return - } - - // Check state if we are catching up. - if catchingUp { - if cs := n.catchup; cs != nil && n.pterm >= cs.cterm && n.pindex >= cs.cindex { - // If we are here we are good, so if we have a catchup pending we can cancel. - n.cancelCatchup() - // Reset our notion of catching up. - catchingUp = false - } else if isNew { - var ar *appendEntryResponse - var inbox string - // Check to see if we are stalled. If so recreate our catchup state and resend response. - if n.catchupStalled() { - n.debug("Catchup may be stalled, will request again") - inbox = n.createCatchup(ae) - ar = newAppendEntryResponse(n.pterm, n.pindex, n.id, false) - } - n.Unlock() - if ar != nil { - n.sendRPC(ae.reply, inbox, ar.encode(arbuf)) - arPool.Put(ar) - } - // Ignore new while catching up or replaying. - return - } - } - - // If this term is greater than ours. - if ae.term > n.term { - n.term = ae.term - n.vote = noVote - if isNew { - n.writeTermVote() - } - if n.State() != Follower { - n.debug("Term higher than ours and we are not a follower: %v, stepping down to %q", n.State(), ae.leader) - n.stepdownLocked(ae.leader) - } - } else if ae.term < n.term && !catchingUp && isNew { - n.debug("Rejected AppendEntry from a leader (%s) with term %d which is less than ours", ae.leader, ae.term) - ar := newAppendEntryResponse(n.term, n.pindex, n.id, false) - n.Unlock() - n.sendRPC(ae.reply, _EMPTY_, ar.encode(arbuf)) - arPool.Put(ar) - return - } - - if isNew && n.leader != ae.leader && n.State() == Follower { - n.debug("AppendEntry updating leader to %q", ae.leader) - n.updateLeader(ae.leader) - n.writeTermVote() - n.resetElectionTimeout() - n.updateLeadChange(false) - } - - if ae.pterm != n.pterm || ae.pindex != n.pindex { - // Check if this is a lower or equal index than what we were expecting. - if ae.pindex <= n.pindex { - n.debug("AppendEntry detected pindex less than/equal to ours: %d:%d vs %d:%d", ae.pterm, ae.pindex, n.pterm, n.pindex) - var ar *appendEntryResponse - var success bool - - if ae.pindex < n.commit { - // If we have already committed this entry, just mark success. - success = true - } else if eae, _ := n.loadEntry(ae.pindex); eae == nil { - // If terms are equal, and we are not catching up, we have simply already processed this message. - // So we will ACK back to the leader. This can happen on server restarts based on timings of snapshots. - if ae.pterm == n.pterm && !catchingUp { - success = true - } else if ae.pindex == n.pindex { - // Check if only our terms do not match here. - // Make sure pterms match and we take on the leader's. - // This prevents constant spinning. - n.truncateWAL(ae.pterm, ae.pindex) - } else { - n.resetWAL() - } - } else if eae.term == ae.pterm { - // If terms match we can delete all entries past this one, and then continue storing the current entry. - n.truncateWAL(ae.pterm, ae.pindex) - // Only continue if truncation was successful, and we ended up such that we can safely continue. - if ae.pterm == n.pterm && ae.pindex == n.pindex { - goto CONTINUE - } - } else { - // If terms mismatched, delete that entry and all others past it. - // Make sure to cancel any catchups in progress. - // Truncate will reset our pterm and pindex. Only do so if we have an entry. - n.truncateWAL(eae.pterm, eae.pindex) - } - // Cancel regardless if unsuccessful. - if !success { - n.cancelCatchup() - } - - // Create response. - ar = newAppendEntryResponse(ae.pterm, ae.pindex, n.id, success) - n.Unlock() - n.sendRPC(ae.reply, _EMPTY_, ar.encode(arbuf)) - arPool.Put(ar) - return - } - - // Check if we are catching up. If we are here we know the leader did not have all of the entries - // so make sure this is a snapshot entry. If it is not start the catchup process again since it - // means we may have missed additional messages. - if catchingUp { - // This means we already entered into a catchup state but what the leader sent us did not match what we expected. - // Snapshots and peerstate will always be together when a leader is catching us up in this fashion. - if len(ae.entries) != 2 || ae.entries[0].Type != EntrySnapshot || ae.entries[1].Type != EntryPeerState { - n.warn("Expected first catchup entry to be a snapshot and peerstate, will retry") - n.cancelCatchup() - n.Unlock() - return - } - - if ps, err := decodePeerState(ae.entries[1].Data); err == nil { - n.processPeerState(ps) - // Also need to copy from client's buffer. - ae.entries[0].Data = copyBytes(ae.entries[0].Data) - } else { - n.warn("Could not parse snapshot peerstate correctly") - n.cancelCatchup() - n.Unlock() - return - } - - // Inherit state from appendEntry with the leader's snapshot. - n.pindex = ae.pindex - n.pterm = ae.pterm - n.commit = ae.pindex - - if _, err := n.wal.Compact(n.pindex + 1); err != nil { - n.setWriteErrLocked(err) - n.Unlock() - return - } - - snap := &snapshot{ - lastTerm: n.pterm, - lastIndex: n.pindex, - peerstate: encodePeerState(&peerState{n.peerNames(), n.csz, n.extSt}), - data: ae.entries[0].Data, - } - // Install the leader's snapshot as our own. - if err := n.installSnapshot(snap); err != nil { - n.setWriteErrLocked(err) - n.Unlock() - return - } - - // Now send snapshot to upper levels. Only send the snapshot, not the peerstate entry. - n.apply.push(newCommittedEntry(n.commit, ae.entries[:1])) - n.Unlock() - return - } - - // Setup our state for catching up. - n.debug("AppendEntry did not match %d %d with %d %d", ae.pterm, ae.pindex, n.pterm, n.pindex) - inbox := n.createCatchup(ae) - ar := newAppendEntryResponse(n.pterm, n.pindex, n.id, false) - n.Unlock() - n.sendRPC(ae.reply, inbox, ar.encode(arbuf)) - arPool.Put(ar) - return - } - -CONTINUE: - // Save to our WAL if we have entries. - if ae.shouldStore() { - // Only store if an original which will have sub != nil - if sub != nil { - if err := n.storeToWAL(ae); err != nil { - if err != ErrStoreClosed { - n.warn("Error storing entry to WAL: %v", err) - } - n.Unlock() - return - } - // Save in memory for faster processing during applyCommit. - // Only save so many however to avoid memory bloat. - if l := len(n.pae); l <= paeDropThreshold { - n.pae[n.pindex], l = ae, l+1 - if l > paeWarnThreshold && l%paeWarnModulo == 0 { - n.warn("%d append entries pending", len(n.pae)) - } - } else { - // Invalidate cache entry at this index, we might have - // stored it previously with a different value. - delete(n.pae, n.pindex) - if l%paeWarnModulo == 0 { - n.debug("Not saving to append entries pending") - } - } - } else { - // This is a replay on startup so just take the appendEntry version. - n.pterm = ae.term - n.pindex = ae.pindex + 1 - } - } - - // Check to see if we have any related entries to process here. - for _, e := range ae.entries { - switch e.Type { - case EntryLeaderTransfer: - // Only process these if they are new, so no replays or catchups. - if isNew { - maybeLeader := string(e.Data) - // This is us. We need to check if we can become the leader. - if maybeLeader == n.id { - // If not an observer and not paused we are good to go. - if !n.observer && !n.paused { - n.lxfer = true - n.xferCampaign() - } else if n.paused && !n.pobserver { - // Here we can become a leader but need to wait for resume of the apply queue. - n.lxfer = true - } - } else if n.vote != noVote { - // Since we are here we are not the chosen one but we should clear any vote preference. - n.vote = noVote - n.writeTermVote() - } - } - case EntryAddPeer: - if newPeer := string(e.Data); len(newPeer) == idLen { - // Track directly, but wait for commit to be official - if ps := n.peers[newPeer]; ps != nil { - ps.ts = time.Now().UnixNano() - } else { - n.peers[newPeer] = &lps{time.Now().UnixNano(), 0, false} - } - // Store our peer in our global peer map for all peers. - peers.LoadOrStore(newPeer, newPeer) - } - } - } - - // Make a copy of these values, as the AppendEntry might be cached and returned to the pool in applyCommit. - aeCommit := ae.commit - aeReply := ae.reply - - // Apply anything we need here. - if aeCommit > n.commit { - if n.paused { - n.hcommit = aeCommit - n.debug("Paused, not applying %d", aeCommit) - } else { - for index := n.commit + 1; index <= aeCommit; index++ { - if err := n.applyCommit(index); err != nil { - break - } - } - } - } - - var ar *appendEntryResponse - if sub != nil { - ar = newAppendEntryResponse(n.pterm, n.pindex, n.id, true) - } - n.Unlock() - - // Success. Send our response. - if ar != nil { - n.sendRPC(aeReply, _EMPTY_, ar.encode(arbuf)) - arPool.Put(ar) - } -} - -// processPeerState is called when a peer state entry is received -// over the wire or when we're updating known peers. -// Lock should be held. -func (n *raft) processPeerState(ps *peerState) { - // Update our version of peers to that of the leader. Calculate - // the number of nodes needed to establish a quorum. - n.csz = ps.clusterSize - n.qn = n.csz/2 + 1 - - old := n.peers - n.peers = make(map[string]*lps) - for _, peer := range ps.knownPeers { - if lp := old[peer]; lp != nil { - lp.kp = true - n.peers[peer] = lp - } else { - n.peers[peer] = &lps{0, 0, true} - } - } - n.debug("Update peers from leader to %+v", n.peers) - n.writePeerState(ps) -} - -// processAppendEntryResponse is called when we receive an append entry -// response from another node. They will send a confirmation to tell us -// whether they successfully committed the entry or not. -func (n *raft) processAppendEntryResponse(ar *appendEntryResponse) { - n.trackPeer(ar.peer) - - if ar.success { - // The remote node successfully committed the append entry. - n.trackResponse(ar) - arPool.Put(ar) - } else if ar.term > n.term { - // The remote node didn't commit the append entry, it looks like - // they are on a newer term than we are. Step down. - n.Lock() - n.term = ar.term - n.vote = noVote - n.writeTermVote() - n.warn("Detected another leader with higher term, will stepdown") - n.stepdownLocked(noLeader) - n.Unlock() - arPool.Put(ar) - } else if ar.reply != _EMPTY_ { - // The remote node didn't commit the append entry and they are - // still on the same term, so let's try to catch them up. - n.catchupFollower(ar) - } -} - -// handleAppendEntryResponse processes responses to append entries. -func (n *raft) handleAppendEntryResponse(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) { - ar := n.decodeAppendEntryResponse(msg) - ar.reply = reply - n.resp.push(ar) -} - -func (n *raft) buildAppendEntry(entries []*Entry) *appendEntry { - return newAppendEntry(n.id, n.term, n.commit, n.pterm, n.pindex, entries) -} - -// Determine if we should store an entry. This stops us from storing -// heartbeat messages. -func (ae *appendEntry) shouldStore() bool { - return ae != nil && len(ae.entries) > 0 -} - -// Store our append entry to our WAL. -// lock should be held. -func (n *raft) storeToWAL(ae *appendEntry) error { - if ae == nil { - return fmt.Errorf("raft: Missing append entry for storage") - } - if n.werr != nil { - return n.werr - } - - seq, _, err := n.wal.StoreMsg(_EMPTY_, nil, ae.buf, 0) - if err != nil { - n.setWriteErrLocked(err) - return err - } - - // Sanity checking for now. - if index := ae.pindex + 1; index != seq { - n.warn("Wrong index, ae is %+v, index stored was %d, n.pindex is %d, will reset", ae, seq, n.pindex) - if n.State() == Leader { - n.stepdownLocked(n.selectNextLeader()) - } - // Reset and cancel any catchup. - n.resetWAL() - n.cancelCatchup() - return errEntryStoreFailed - } - - n.pterm = ae.term - n.pindex = seq - return nil -} - -const ( - paeDropThreshold = 20_000 - paeWarnThreshold = 10_000 - paeWarnModulo = 5_000 -) - -func (n *raft) sendAppendEntry(entries []*Entry) { - n.Lock() - defer n.Unlock() - ae := n.buildAppendEntry(entries) - - var err error - var scratch [1024]byte - ae.buf, err = ae.encode(scratch[:]) - if err != nil { - return - } - - // If we have entries store this in our wal. - shouldStore := ae.shouldStore() - if shouldStore { - if err := n.storeToWAL(ae); err != nil { - return - } - n.active = time.Now() - - // Save in memory for faster processing during applyCommit. - n.pae[n.pindex] = ae - if l := len(n.pae); l > paeWarnThreshold && l%paeWarnModulo == 0 { - n.warn("%d append entries pending", len(n.pae)) - } - } - n.sendRPC(n.asubj, n.areply, ae.buf) - if !shouldStore { - ae.returnToPool() - } -} - -type extensionState uint16 - -const ( - extUndetermined = extensionState(iota) - extExtended - extNotExtended -) - -type peerState struct { - knownPeers []string - clusterSize int - domainExt extensionState -} - -func peerStateBufSize(ps *peerState) int { - return 4 + 4 + (idLen * len(ps.knownPeers)) + 2 -} - -func encodePeerState(ps *peerState) []byte { - var le = binary.LittleEndian - buf := make([]byte, peerStateBufSize(ps)) - le.PutUint32(buf[0:], uint32(ps.clusterSize)) - le.PutUint32(buf[4:], uint32(len(ps.knownPeers))) - wi := 8 - for _, peer := range ps.knownPeers { - copy(buf[wi:], peer) - wi += idLen - } - le.PutUint16(buf[wi:], uint16(ps.domainExt)) - return buf -} - -func decodePeerState(buf []byte) (*peerState, error) { - if len(buf) < 8 { - return nil, errCorruptPeers - } - var le = binary.LittleEndian - ps := &peerState{clusterSize: int(le.Uint32(buf[0:]))} - expectedPeers := int(le.Uint32(buf[4:])) - buf = buf[8:] - ri := 0 - for i, n := 0, expectedPeers; i < n && ri < len(buf); i++ { - ps.knownPeers = append(ps.knownPeers, string(buf[ri:ri+idLen])) - ri += idLen - } - if len(ps.knownPeers) != expectedPeers { - return nil, errCorruptPeers - } - if len(buf[ri:]) >= 2 { - ps.domainExt = extensionState(le.Uint16(buf[ri:])) - } - return ps, nil -} - -// Lock should be held. -func (n *raft) peerNames() []string { - var peers []string - for name, peer := range n.peers { - if peer.kp { - peers = append(peers, name) - } - } - return peers -} - -func (n *raft) currentPeerState() *peerState { - n.RLock() - ps := &peerState{n.peerNames(), n.csz, n.extSt} - n.RUnlock() - return ps -} - -// sendPeerState will send our current peer state to the cluster. -func (n *raft) sendPeerState() { - n.sendAppendEntry([]*Entry{{EntryPeerState, encodePeerState(n.currentPeerState())}}) -} - -// Send a heartbeat. -func (n *raft) sendHeartbeat() { - n.sendAppendEntry(nil) -} - -type voteRequest struct { - term uint64 - lastTerm uint64 - lastIndex uint64 - candidate string - // internal only. - reply string -} - -const voteRequestLen = 24 + idLen - -func (vr *voteRequest) encode() []byte { - var buf [voteRequestLen]byte - var le = binary.LittleEndian - le.PutUint64(buf[0:], vr.term) - le.PutUint64(buf[8:], vr.lastTerm) - le.PutUint64(buf[16:], vr.lastIndex) - copy(buf[24:24+idLen], vr.candidate) - - return buf[:voteRequestLen] -} - -func decodeVoteRequest(msg []byte, reply string) *voteRequest { - if len(msg) != voteRequestLen { - return nil - } - - var le = binary.LittleEndian - return &voteRequest{ - term: le.Uint64(msg[0:]), - lastTerm: le.Uint64(msg[8:]), - lastIndex: le.Uint64(msg[16:]), - candidate: string(copyBytes(msg[24 : 24+idLen])), - reply: reply, - } -} - -const peerStateFile = "peers.idx" - -// Lock should be held. -func (n *raft) writePeerState(ps *peerState) { - pse := encodePeerState(ps) - if bytes.Equal(n.wps, pse) { - return - } - // Stamp latest and write the peer state file. - n.wps = pse - if err := writePeerState(n.sd, ps); err != nil && !n.isClosed() { - n.setWriteErrLocked(err) - n.warn("Error writing peer state file for %q: %v", n.group, err) - } -} - -// Writes out our peer state outside of a specific raft context. -func writePeerState(sd string, ps *peerState) error { - psf := filepath.Join(sd, peerStateFile) - if _, err := os.Stat(psf); err != nil && !os.IsNotExist(err) { - return err - } - return writeFileWithSync(psf, encodePeerState(ps), defaultFilePerms) -} - -func readPeerState(sd string) (ps *peerState, err error) { - <-dios - buf, err := os.ReadFile(filepath.Join(sd, peerStateFile)) - dios <- struct{}{} - - if err != nil { - return nil, err - } - return decodePeerState(buf) -} - -const termVoteFile = "tav.idx" -const termLen = 8 // uint64 -const termVoteLen = idLen + termLen - -// Writes out our term & vote outside of a specific raft context. -func writeTermVote(sd string, wtv []byte) error { - psf := filepath.Join(sd, termVoteFile) - if _, err := os.Stat(psf); err != nil && !os.IsNotExist(err) { - return err - } - return writeFileWithSync(psf, wtv, defaultFilePerms) -} - -// readTermVote will read the largest term and who we voted from to stable storage. -// Lock should be held. -func (n *raft) readTermVote() (term uint64, voted string, err error) { - <-dios - buf, err := os.ReadFile(filepath.Join(n.sd, termVoteFile)) - dios <- struct{}{} - - if err != nil { - return 0, noVote, err - } - if len(buf) < termLen { - // Not enough bytes for the uint64 below, so avoid a panic. - return 0, noVote, nil - } - var le = binary.LittleEndian - term = le.Uint64(buf[0:]) - if len(buf) < termVoteLen { - return term, noVote, nil - } - voted = string(buf[8:]) - return term, voted, nil -} - -// Lock should be held. -func (n *raft) setWriteErrLocked(err error) { - // Check if we are closed already. - if n.State() == Closed { - return - } - // Ignore if already set. - if n.werr == err || err == nil { - return - } - // Ignore non-write errors. - if err == ErrStoreClosed || - err == ErrStoreEOF || - err == ErrInvalidSequence || - err == ErrStoreMsgNotFound || - err == errNoPending || - err == errPartialCache { - return - } - // If this is a not found report but do not disable. - if os.IsNotExist(err) { - n.error("Resource not found: %v", err) - return - } - n.error("Critical write error: %v", err) - n.werr = err - - if isPermissionError(err) { - go n.s.handleWritePermissionError() - } - - if isOutOfSpaceErr(err) { - // For now since this can be happening all under the covers, we will call up and disable JetStream. - go n.s.handleOutOfSpace(nil) - } -} - -// Helper to check if we are closed when we do not hold a lock already. -func (n *raft) isClosed() bool { - return n.State() == Closed -} - -// Capture our write error if any and hold. -func (n *raft) setWriteErr(err error) { - n.Lock() - defer n.Unlock() - n.setWriteErrLocked(err) -} - -// writeTermVote will record the largest term and who we voted for to stable storage. -// Lock should be held. -func (n *raft) writeTermVote() { - var buf [termVoteLen]byte - var le = binary.LittleEndian - le.PutUint64(buf[0:], n.term) - copy(buf[8:], n.vote) - b := buf[:8+len(n.vote)] - - // If the term and vote hasn't changed then don't rewrite to disk. - if bytes.Equal(n.wtv, b) { - return - } - // Stamp latest and write the term & vote file. - n.wtv = b - if err := writeTermVote(n.sd, n.wtv); err != nil && !n.isClosed() { - // Clear wtv since we failed. - n.wtv = nil - n.setWriteErrLocked(err) - n.warn("Error writing term and vote file for %q: %v", n.group, err) - } -} - -// voteResponse is a response to a vote request. -type voteResponse struct { - term uint64 - peer string - granted bool -} - -const voteResponseLen = 8 + 8 + 1 - -func (vr *voteResponse) encode() []byte { - var buf [voteResponseLen]byte - var le = binary.LittleEndian - le.PutUint64(buf[0:], vr.term) - copy(buf[8:], vr.peer) - if vr.granted { - buf[16] = 1 - } else { - buf[16] = 0 - } - return buf[:voteResponseLen] -} - -func decodeVoteResponse(msg []byte) *voteResponse { - if len(msg) != voteResponseLen { - return nil - } - var le = binary.LittleEndian - vr := &voteResponse{term: le.Uint64(msg[0:]), peer: string(msg[8:16])} - vr.granted = msg[16] == 1 - return vr -} - -func (n *raft) handleVoteResponse(sub *subscription, c *client, _ *Account, _, reply string, msg []byte) { - vr := decodeVoteResponse(msg) - n.debug("Received a voteResponse %+v", vr) - if vr == nil { - n.error("Received malformed vote response for %q", n.group) - return - } - - if state := n.State(); state != Candidate && state != Leader { - n.debug("Ignoring old vote response, we have stepped down") - return - } - - n.votes.push(vr) -} - -func (n *raft) processVoteRequest(vr *voteRequest) error { - // To simplify calling code, we can possibly pass `nil` to this function. - // If that is the case, does not consider it an error. - if vr == nil { - return nil - } - n.debug("Received a voteRequest %+v", vr) - - if err := n.trackPeer(vr.candidate); err != nil { - return err - } - - n.Lock() - - vresp := &voteResponse{n.term, n.id, false} - defer n.debug("Sending a voteResponse %+v -> %q", vresp, vr.reply) - - // Ignore if we are newer. This is important so that we don't accidentally process - // votes from a previous term if they were still in flight somewhere. - if vr.term < n.term { - n.Unlock() - n.sendReply(vr.reply, vresp.encode()) - return nil - } - - // If this is a higher term go ahead and stepdown. - if vr.term > n.term { - if n.State() != Follower { - n.debug("Stepping down from %s, detected higher term: %d vs %d", - strings.ToLower(n.State().String()), vr.term, n.term) - n.stepdownLocked(noLeader) - } - n.cancelCatchup() - n.term = vr.term - n.vote = noVote - n.writeTermVote() - } - - // Only way we get to yes is through here. - voteOk := n.vote == noVote || n.vote == vr.candidate - if voteOk && (vr.lastTerm > n.pterm || vr.lastTerm == n.pterm && vr.lastIndex >= n.pindex) { - vresp.granted = true - n.term = vr.term - n.vote = vr.candidate - n.writeTermVote() - n.resetElectionTimeout() - } else if n.vote == noVote && n.State() != Candidate { - // We have a more up-to-date log, and haven't voted yet. - // Start campaigning earlier, but only if not candidate already, as that would short-circuit us. - n.resetElect(randCampaignTimeout()) - } - - // Term might have changed, make sure response has the most current - vresp.term = n.term - - n.Unlock() - - n.sendReply(vr.reply, vresp.encode()) - - return nil -} - -func (n *raft) handleVoteRequest(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) { - vr := decodeVoteRequest(msg, reply) - if vr == nil { - n.error("Received malformed vote request for %q", n.group) - return - } - n.reqs.push(vr) -} - -func (n *raft) requestVote() { - n.Lock() - if n.State() != Candidate { - n.Unlock() - return - } - n.vote = n.id - n.writeTermVote() - vr := voteRequest{n.term, n.pterm, n.pindex, n.id, _EMPTY_} - subj, reply := n.vsubj, n.vreply - n.Unlock() - - n.debug("Sending out voteRequest %+v", vr) - - // Now send it out. - n.sendRPC(subj, reply, vr.encode()) -} - -func (n *raft) sendRPC(subject, reply string, msg []byte) { - if n.sq != nil { - n.sq.send(subject, reply, nil, msg) - } -} - -func (n *raft) sendReply(subject string, msg []byte) { - if n.sq != nil { - n.sq.send(subject, _EMPTY_, nil, msg) - } -} - -func (n *raft) wonElection(votes int) bool { - return votes >= n.quorumNeeded() -} - -// Return the quorum size for a given cluster config. -func (n *raft) quorumNeeded() int { - n.RLock() - qn := n.qn - n.RUnlock() - return qn -} - -// Lock should be held. -func (n *raft) updateLeadChange(isLeader bool) { - // We don't care about values that have not been consumed (transitory states), - // so we dequeue any state that is pending and push the new one. - for { - select { - case n.leadc <- isLeader: - return - default: - select { - case <-n.leadc: - default: - // May have been consumed by the "reader" go routine, so go back - // to the top of the loop and try to send again. - } - } - } -} - -// Lock should be held. -func (n *raft) switchState(state RaftState) bool { -retry: - pstate := n.State() - if pstate == Closed { - return false - } - - // Set our state. If something else has changed our state - // then retry, this will either be a Stop or Delete call. - if !n.state.CompareAndSwap(int32(pstate), int32(state)) { - goto retry - } - - // Reset the election timer. - n.resetElectionTimeout() - - var leadChange bool - if pstate == Leader && state != Leader { - leadChange = true - n.updateLeadChange(false) - // Drain the append entry response and proposal queues. - n.resp.drain() - n.prop.drain() - } else if state == Leader && pstate != Leader { - // Don't updateLeadChange here, it will be done in switchToLeader or after initial messages are applied. - leadChange = true - if len(n.pae) > 0 { - n.pae = make(map[uint64]*appendEntry) - } - } - - n.writeTermVote() - return leadChange -} - -const ( - noLeader = _EMPTY_ - noVote = _EMPTY_ -) - -func (n *raft) switchToFollower(leader string) { - n.Lock() - defer n.Unlock() - - n.switchToFollowerLocked(leader) -} - -func (n *raft) switchToFollowerLocked(leader string) { - if n.State() == Closed { - return - } - - n.debug("Switching to follower") - - n.aflr = 0 - n.leaderState.Store(false) - n.lxfer = false - // Reset acks, we can't assume acks from a previous term are still valid in another term. - if len(n.acks) > 0 { - n.acks = make(map[uint64]map[string]struct{}) - } - n.updateLeader(leader) - n.switchState(Follower) -} - -func (n *raft) switchToCandidate() { - if n.State() == Closed { - return - } - - n.Lock() - defer n.Unlock() - - // If we are catching up or are in observer mode we can not switch. - // Avoid petitioning to become leader if we're behind on applies. - if n.observer || n.paused || n.applied < n.commit { - n.resetElect(minElectionTimeout / 4) - return - } - - if n.State() != Candidate { - n.debug("Switching to candidate") - } else { - if n.lostQuorumLocked() && time.Since(n.llqrt) > 20*time.Second { - // We signal to the upper layers such that can alert on quorum lost. - n.updateLeadChange(false) - n.llqrt = time.Now() - } - } - // Increment the term. - n.term++ - // Clear current Leader. - n.updateLeader(noLeader) - n.switchState(Candidate) -} - -func (n *raft) switchToLeader() { - if n.State() == Closed { - return - } - - n.Lock() - - n.debug("Switching to leader") - - var state StreamState - n.wal.FastState(&state) - - // Check if we have items pending as we are taking over. - sendHB := state.LastSeq > n.commit - - n.lxfer = false - n.updateLeader(n.id) - leadChange := n.switchState(Leader) - - if leadChange { - // Wait for messages to be applied if we've stored more, otherwise signal immediately. - // It's important to wait signaling we're leader if we're not up-to-date yet, as that - // would mean we're in a consistent state compared with the previous leader. - if n.pindex > n.applied { - n.aflr = n.pindex - } else { - // We know we have applied all entries in our log and can signal immediately. - // For sanity reset applied floor back down to 0, so we aren't able to signal twice. - n.aflr = 0 - n.leaderState.Store(true) - n.updateLeadChange(true) - } - } - n.Unlock() - - if sendHB { - n.sendHeartbeat() - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/rate_counter.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/rate_counter.go deleted file mode 100644 index 24779374..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/rate_counter.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2021-2021 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "sync" - "time" -) - -type rateCounter struct { - limit int64 - count int64 - blocked uint64 - end time.Time - interval time.Duration - mu sync.Mutex -} - -func newRateCounter(limit int64) *rateCounter { - return &rateCounter{ - limit: limit, - interval: time.Second, - } -} - -func (r *rateCounter) allow() bool { - now := time.Now() - - r.mu.Lock() - - if now.After(r.end) { - r.count = 0 - r.end = now.Add(r.interval) - } else { - r.count++ - } - allow := r.count < r.limit - if !allow { - r.blocked++ - } - - r.mu.Unlock() - - return allow -} - -func (r *rateCounter) countBlocked() uint64 { - r.mu.Lock() - blocked := r.blocked - r.blocked = 0 - r.mu.Unlock() - - return blocked -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/reload.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/reload.go deleted file mode 100644 index 7bf940cd..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/reload.go +++ /dev/null @@ -1,2478 +0,0 @@ -// Copyright 2017-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "cmp" - "crypto/tls" - "errors" - "fmt" - "net/url" - "reflect" - "slices" - "strings" - "sync/atomic" - "time" - - "github.com/klauspost/compress/s2" - - "github.com/nats-io/jwt/v2" - "github.com/nats-io/nuid" -) - -// FlagSnapshot captures the server options as specified by CLI flags at -// startup. This should not be modified once the server has started. -var FlagSnapshot *Options - -type reloadContext struct { - oldClusterPerms *RoutePermissions -} - -// option is a hot-swappable configuration setting. -type option interface { - // Apply the server option. - Apply(server *Server) - - // IsLoggingChange indicates if this option requires reloading the logger. - IsLoggingChange() bool - - // IsTraceLevelChange indicates if this option requires reloading cached trace level. - // Clients store trace level separately. - IsTraceLevelChange() bool - - // IsAuthChange indicates if this option requires reloading authorization. - IsAuthChange() bool - - // IsTLSChange indicates if this option requires reloading TLS. - IsTLSChange() bool - - // IsClusterPermsChange indicates if this option requires reloading - // cluster permissions. - IsClusterPermsChange() bool - - // IsClusterPoolSizeOrAccountsChange indicates if this option requires - // special handling for changes in cluster's pool size or accounts list. - IsClusterPoolSizeOrAccountsChange() bool - - // IsJetStreamChange inidicates a change in the servers config for JetStream. - // Account changes will be handled separately in reloadAuthorization. - IsJetStreamChange() bool - - // Indicates a change in the server that requires publishing the server's statz - IsStatszChange() bool -} - -// noopOption is a base struct that provides default no-op behaviors. -type noopOption struct{} - -func (n noopOption) IsLoggingChange() bool { - return false -} - -func (n noopOption) IsTraceLevelChange() bool { - return false -} - -func (n noopOption) IsAuthChange() bool { - return false -} - -func (n noopOption) IsTLSChange() bool { - return false -} - -func (n noopOption) IsClusterPermsChange() bool { - return false -} - -func (n noopOption) IsClusterPoolSizeOrAccountsChange() bool { - return false -} - -func (n noopOption) IsJetStreamChange() bool { - return false -} - -func (n noopOption) IsStatszChange() bool { - return false -} - -// loggingOption is a base struct that provides default option behaviors for -// logging-related options. -type loggingOption struct { - noopOption -} - -func (l loggingOption) IsLoggingChange() bool { - return true -} - -// traceLevelOption is a base struct that provides default option behaviors for -// tracelevel-related options. -type traceLevelOption struct { - loggingOption -} - -func (l traceLevelOption) IsTraceLevelChange() bool { - return true -} - -// traceOption implements the option interface for the `trace` setting. -type traceOption struct { - traceLevelOption - newValue bool -} - -// Apply is a no-op because logging will be reloaded after options are applied. -func (t *traceOption) Apply(server *Server) { - server.Noticef("Reloaded: trace = %v", t.newValue) -} - -// traceOption implements the option interface for the `trace` setting. -type traceVerboseOption struct { - traceLevelOption - newValue bool -} - -// Apply is a no-op because logging will be reloaded after options are applied. -func (t *traceVerboseOption) Apply(server *Server) { - server.Noticef("Reloaded: trace_verbose = %v", t.newValue) -} - -// debugOption implements the option interface for the `debug` setting. -type debugOption struct { - loggingOption - newValue bool -} - -// Apply is mostly a no-op because logging will be reloaded after options are applied. -// However we will kick the raft nodes if they exist to reload. -func (d *debugOption) Apply(server *Server) { - server.Noticef("Reloaded: debug = %v", d.newValue) - server.reloadDebugRaftNodes(d.newValue) -} - -// logtimeOption implements the option interface for the `logtime` setting. -type logtimeOption struct { - loggingOption - newValue bool -} - -// Apply is a no-op because logging will be reloaded after options are applied. -func (l *logtimeOption) Apply(server *Server) { - server.Noticef("Reloaded: logtime = %v", l.newValue) -} - -// logtimeUTCOption implements the option interface for the `logtime_utc` setting. -type logtimeUTCOption struct { - loggingOption - newValue bool -} - -// Apply is a no-op because logging will be reloaded after options are applied. -func (l *logtimeUTCOption) Apply(server *Server) { - server.Noticef("Reloaded: logtime_utc = %v", l.newValue) -} - -// logfileOption implements the option interface for the `log_file` setting. -type logfileOption struct { - loggingOption - newValue string -} - -// Apply is a no-op because logging will be reloaded after options are applied. -func (l *logfileOption) Apply(server *Server) { - server.Noticef("Reloaded: log_file = %v", l.newValue) -} - -// syslogOption implements the option interface for the `syslog` setting. -type syslogOption struct { - loggingOption - newValue bool -} - -// Apply is a no-op because logging will be reloaded after options are applied. -func (s *syslogOption) Apply(server *Server) { - server.Noticef("Reloaded: syslog = %v", s.newValue) -} - -// remoteSyslogOption implements the option interface for the `remote_syslog` -// setting. -type remoteSyslogOption struct { - loggingOption - newValue string -} - -// Apply is a no-op because logging will be reloaded after options are applied. -func (r *remoteSyslogOption) Apply(server *Server) { - server.Noticef("Reloaded: remote_syslog = %v", r.newValue) -} - -// tlsOption implements the option interface for the `tls` setting. -type tlsOption struct { - noopOption - newValue *tls.Config -} - -// Apply the tls change. -func (t *tlsOption) Apply(server *Server) { - server.mu.Lock() - tlsRequired := t.newValue != nil - server.info.TLSRequired = tlsRequired && !server.getOpts().AllowNonTLS - message := "disabled" - if tlsRequired { - server.info.TLSVerify = (t.newValue.ClientAuth == tls.RequireAndVerifyClientCert) - message = "enabled" - } - server.mu.Unlock() - server.Noticef("Reloaded: tls = %s", message) -} - -func (t *tlsOption) IsTLSChange() bool { - return true -} - -// tlsTimeoutOption implements the option interface for the tls `timeout` -// setting. -type tlsTimeoutOption struct { - noopOption - newValue float64 -} - -// Apply is a no-op because the timeout will be reloaded after options are -// applied. -func (t *tlsTimeoutOption) Apply(server *Server) { - server.Noticef("Reloaded: tls timeout = %v", t.newValue) -} - -// tlsPinnedCertOption implements the option interface for the tls `pinned_certs` setting. -type tlsPinnedCertOption struct { - noopOption - newValue PinnedCertSet -} - -// Apply is a no-op because the pinned certs will be reloaded after options are applied. -func (t *tlsPinnedCertOption) Apply(server *Server) { - server.Noticef("Reloaded: %d pinned_certs", len(t.newValue)) -} - -// tlsHandshakeFirst implements the option interface for the tls `handshake first` setting. -type tlsHandshakeFirst struct { - noopOption - newValue bool -} - -// Apply is a no-op because the timeout will be reloaded after options are applied. -func (t *tlsHandshakeFirst) Apply(server *Server) { - server.Noticef("Reloaded: Client TLS handshake first: %v", t.newValue) -} - -// tlsHandshakeFirstFallback implements the option interface for the tls `handshake first fallback delay` setting. -type tlsHandshakeFirstFallback struct { - noopOption - newValue time.Duration -} - -// Apply is a no-op because the timeout will be reloaded after options are applied. -func (t *tlsHandshakeFirstFallback) Apply(server *Server) { - server.Noticef("Reloaded: Client TLS handshake first fallback delay: %v", t.newValue) -} - -// authOption is a base struct that provides default option behaviors. -type authOption struct { - noopOption -} - -func (o authOption) IsAuthChange() bool { - return true -} - -// usernameOption implements the option interface for the `username` setting. -type usernameOption struct { - authOption -} - -// Apply is a no-op because authorization will be reloaded after options are -// applied. -func (u *usernameOption) Apply(server *Server) { - server.Noticef("Reloaded: authorization username") -} - -// passwordOption implements the option interface for the `password` setting. -type passwordOption struct { - authOption -} - -// Apply is a no-op because authorization will be reloaded after options are -// applied. -func (p *passwordOption) Apply(server *Server) { - server.Noticef("Reloaded: authorization password") -} - -// authorizationOption implements the option interface for the `token` -// authorization setting. -type authorizationOption struct { - authOption -} - -// Apply is a no-op because authorization will be reloaded after options are -// applied. -func (a *authorizationOption) Apply(server *Server) { - server.Noticef("Reloaded: authorization token") -} - -// authTimeoutOption implements the option interface for the authorization -// `timeout` setting. -type authTimeoutOption struct { - noopOption // Not authOption because this is a no-op; will be reloaded with options. - newValue float64 -} - -// Apply is a no-op because the timeout will be reloaded after options are -// applied. -func (a *authTimeoutOption) Apply(server *Server) { - server.Noticef("Reloaded: authorization timeout = %v", a.newValue) -} - -// tagsOption implements the option interface for the `tags` setting. -type tagsOption struct { - noopOption // Not authOption because this is a no-op; will be reloaded with options. -} - -func (u *tagsOption) Apply(server *Server) { - server.Noticef("Reloaded: tags") -} - -func (u *tagsOption) IsStatszChange() bool { - return true -} - -// usersOption implements the option interface for the authorization `users` -// setting. -type usersOption struct { - authOption -} - -func (u *usersOption) Apply(server *Server) { - server.Noticef("Reloaded: authorization users") -} - -// nkeysOption implements the option interface for the authorization `users` -// setting. -type nkeysOption struct { - authOption -} - -func (u *nkeysOption) Apply(server *Server) { - server.Noticef("Reloaded: authorization nkey users") -} - -// clusterOption implements the option interface for the `cluster` setting. -type clusterOption struct { - authOption - newValue ClusterOpts - permsChanged bool - accsAdded []string - accsRemoved []string - poolSizeChanged bool - compressChanged bool -} - -// Apply the cluster change. -func (c *clusterOption) Apply(s *Server) { - // TODO: support enabling/disabling clustering. - s.mu.Lock() - tlsRequired := c.newValue.TLSConfig != nil - s.routeInfo.TLSRequired = tlsRequired - s.routeInfo.TLSVerify = tlsRequired - s.routeInfo.AuthRequired = c.newValue.Username != "" - if c.newValue.NoAdvertise { - s.routeInfo.ClientConnectURLs = nil - s.routeInfo.WSConnectURLs = nil - } else { - s.routeInfo.ClientConnectURLs = s.clientConnectURLs - s.routeInfo.WSConnectURLs = s.websocket.connectURLs - } - s.setRouteInfoHostPortAndIP() - var routes []*client - if c.compressChanged { - co := &s.getOpts().Cluster.Compression - newMode := co.Mode - s.forEachRoute(func(r *client) { - r.mu.Lock() - // Skip routes that are "not supported" (because they will never do - // compression) or the routes that have already the new compression - // mode. - if r.route.compression == CompressionNotSupported || r.route.compression == newMode { - r.mu.Unlock() - return - } - // We need to close the route if it had compression "off" or the new - // mode is compression "off", or if the new mode is "accept", because - // these require negotiation. - if r.route.compression == CompressionOff || newMode == CompressionOff || newMode == CompressionAccept { - routes = append(routes, r) - } else if newMode == CompressionS2Auto { - // If the mode is "s2_auto", we need to check if there is really - // need to change, and at any rate, we want to save the actual - // compression level here, not s2_auto. - r.updateS2AutoCompressionLevel(co, &r.route.compression) - } else { - // Simply change the compression writer - r.out.cw = s2.NewWriter(nil, s2WriterOptions(newMode)...) - r.route.compression = newMode - } - r.mu.Unlock() - }) - } - s.mu.Unlock() - if c.newValue.Name != "" && c.newValue.Name != s.ClusterName() { - s.setClusterName(c.newValue.Name) - } - for _, r := range routes { - r.closeConnection(ClientClosed) - } - s.Noticef("Reloaded: cluster") - if tlsRequired && c.newValue.TLSConfig.InsecureSkipVerify { - s.Warnf(clusterTLSInsecureWarning) - } -} - -func (c *clusterOption) IsClusterPermsChange() bool { - return c.permsChanged -} - -func (c *clusterOption) IsClusterPoolSizeOrAccountsChange() bool { - return c.poolSizeChanged || len(c.accsAdded) > 0 || len(c.accsRemoved) > 0 -} - -func (c *clusterOption) diffPoolAndAccounts(old *ClusterOpts) { - c.poolSizeChanged = c.newValue.PoolSize != old.PoolSize -addLoop: - for _, na := range c.newValue.PinnedAccounts { - for _, oa := range old.PinnedAccounts { - if na == oa { - continue addLoop - } - } - c.accsAdded = append(c.accsAdded, na) - } -removeLoop: - for _, oa := range old.PinnedAccounts { - for _, na := range c.newValue.PinnedAccounts { - if oa == na { - continue removeLoop - } - } - c.accsRemoved = append(c.accsRemoved, oa) - } -} - -// routesOption implements the option interface for the cluster `routes` -// setting. -type routesOption struct { - noopOption - add []*url.URL - remove []*url.URL -} - -// Apply the route changes by adding and removing the necessary routes. -func (r *routesOption) Apply(server *Server) { - server.mu.Lock() - routes := make([]*client, server.numRoutes()) - i := 0 - server.forEachRoute(func(r *client) { - routes[i] = r - i++ - }) - // If there was a change, notify monitoring code that it should - // update the route URLs if /varz endpoint is inspected. - if len(r.add)+len(r.remove) > 0 { - server.varzUpdateRouteURLs = true - } - server.mu.Unlock() - - // Remove routes. - for _, remove := range r.remove { - for _, client := range routes { - var url *url.URL - client.mu.Lock() - if client.route != nil { - url = client.route.url - } - client.mu.Unlock() - if url != nil && urlsAreEqual(url, remove) { - // Do not attempt to reconnect when route is removed. - client.setNoReconnect() - client.closeConnection(RouteRemoved) - server.Noticef("Removed route %v", remove) - } - } - } - - // Add routes. - server.mu.Lock() - server.solicitRoutes(r.add, server.getOpts().Cluster.PinnedAccounts) - server.mu.Unlock() - - server.Noticef("Reloaded: cluster routes") -} - -// maxConnOption implements the option interface for the `max_connections` -// setting. -type maxConnOption struct { - noopOption - newValue int -} - -// Apply the max connections change by closing random connections til we are -// below the limit if necessary. -func (m *maxConnOption) Apply(server *Server) { - server.mu.Lock() - var ( - clients = make([]*client, len(server.clients)) - i = 0 - ) - // Map iteration is random, which allows us to close random connections. - for _, client := range server.clients { - clients[i] = client - i++ - } - server.mu.Unlock() - - if m.newValue > 0 && len(clients) > m.newValue { - // Close connections til we are within the limit. - var ( - numClose = len(clients) - m.newValue - closed = 0 - ) - for _, client := range clients { - client.maxConnExceeded() - closed++ - if closed >= numClose { - break - } - } - server.Noticef("Closed %d connections to fall within max_connections", closed) - } - server.Noticef("Reloaded: max_connections = %v", m.newValue) -} - -// pidFileOption implements the option interface for the `pid_file` setting. -type pidFileOption struct { - noopOption - newValue string -} - -// Apply the setting by logging the pid to the new file. -func (p *pidFileOption) Apply(server *Server) { - if p.newValue == "" { - return - } - if err := server.logPid(); err != nil { - server.Errorf("Failed to write pidfile: %v", err) - } - server.Noticef("Reloaded: pid_file = %v", p.newValue) -} - -// portsFileDirOption implements the option interface for the `portFileDir` setting. -type portsFileDirOption struct { - noopOption - oldValue string - newValue string -} - -func (p *portsFileDirOption) Apply(server *Server) { - server.deletePortsFile(p.oldValue) - server.logPorts() - server.Noticef("Reloaded: ports_file_dir = %v", p.newValue) -} - -// maxControlLineOption implements the option interface for the -// `max_control_line` setting. -type maxControlLineOption struct { - noopOption - newValue int32 -} - -// Apply the setting by updating each client. -func (m *maxControlLineOption) Apply(server *Server) { - mcl := int32(m.newValue) - server.mu.Lock() - for _, client := range server.clients { - atomic.StoreInt32(&client.mcl, mcl) - } - server.mu.Unlock() - server.Noticef("Reloaded: max_control_line = %d", mcl) -} - -// maxPayloadOption implements the option interface for the `max_payload` -// setting. -type maxPayloadOption struct { - noopOption - newValue int32 -} - -// Apply the setting by updating the server info and each client. -func (m *maxPayloadOption) Apply(server *Server) { - server.mu.Lock() - server.info.MaxPayload = m.newValue - for _, client := range server.clients { - atomic.StoreInt32(&client.mpay, int32(m.newValue)) - } - server.mu.Unlock() - server.Noticef("Reloaded: max_payload = %d", m.newValue) -} - -// pingIntervalOption implements the option interface for the `ping_interval` -// setting. -type pingIntervalOption struct { - noopOption - newValue time.Duration -} - -// Apply is a no-op because the ping interval will be reloaded after options -// are applied. -func (p *pingIntervalOption) Apply(server *Server) { - server.Noticef("Reloaded: ping_interval = %s", p.newValue) -} - -// maxPingsOutOption implements the option interface for the `ping_max` -// setting. -type maxPingsOutOption struct { - noopOption - newValue int -} - -// Apply is a no-op because the ping interval will be reloaded after options -// are applied. -func (m *maxPingsOutOption) Apply(server *Server) { - server.Noticef("Reloaded: ping_max = %d", m.newValue) -} - -// writeDeadlineOption implements the option interface for the `write_deadline` -// setting. -type writeDeadlineOption struct { - noopOption - newValue time.Duration -} - -// Apply is a no-op because the write deadline will be reloaded after options -// are applied. -func (w *writeDeadlineOption) Apply(server *Server) { - server.Noticef("Reloaded: write_deadline = %s", w.newValue) -} - -// clientAdvertiseOption implements the option interface for the `client_advertise` setting. -type clientAdvertiseOption struct { - noopOption - newValue string -} - -// Apply the setting by updating the server info and regenerate the infoJSON byte array. -func (c *clientAdvertiseOption) Apply(server *Server) { - server.mu.Lock() - server.setInfoHostPort() - server.mu.Unlock() - server.Noticef("Reload: client_advertise = %s", c.newValue) -} - -// accountsOption implements the option interface. -// Ensure that authorization code is executed if any change in accounts -type accountsOption struct { - authOption -} - -// Apply is a no-op. Changes will be applied in reloadAuthorization -func (a *accountsOption) Apply(s *Server) { - s.Noticef("Reloaded: accounts") -} - -// For changes to a server's config. -type jetStreamOption struct { - noopOption - newValue bool -} - -func (a *jetStreamOption) Apply(s *Server) { - s.Noticef("Reloaded: JetStream") -} - -func (jso jetStreamOption) IsJetStreamChange() bool { - return true -} - -func (jso jetStreamOption) IsStatszChange() bool { - return true -} - -type ocspOption struct { - tlsOption - newValue *OCSPConfig -} - -func (a *ocspOption) Apply(s *Server) { - s.Noticef("Reloaded: OCSP") -} - -type ocspResponseCacheOption struct { - tlsOption - newValue *OCSPResponseCacheConfig -} - -func (a *ocspResponseCacheOption) Apply(s *Server) { - s.Noticef("Reloaded OCSP peer cache") -} - -// connectErrorReports implements the option interface for the `connect_error_reports` -// setting. -type connectErrorReports struct { - noopOption - newValue int -} - -// Apply is a no-op because the value will be reloaded after options are applied. -func (c *connectErrorReports) Apply(s *Server) { - s.Noticef("Reloaded: connect_error_reports = %v", c.newValue) -} - -// connectErrorReports implements the option interface for the `connect_error_reports` -// setting. -type reconnectErrorReports struct { - noopOption - newValue int -} - -// Apply is a no-op because the value will be reloaded after options are applied. -func (r *reconnectErrorReports) Apply(s *Server) { - s.Noticef("Reloaded: reconnect_error_reports = %v", r.newValue) -} - -// maxTracedMsgLenOption implements the option interface for the `max_traced_msg_len` setting. -type maxTracedMsgLenOption struct { - noopOption - newValue int -} - -// Apply the setting by updating the maximum traced message length. -func (m *maxTracedMsgLenOption) Apply(server *Server) { - server.mu.Lock() - defer server.mu.Unlock() - server.opts.MaxTracedMsgLen = m.newValue - server.Noticef("Reloaded: max_traced_msg_len = %d", m.newValue) -} - -type mqttAckWaitReload struct { - noopOption - newValue time.Duration -} - -func (o *mqttAckWaitReload) Apply(s *Server) { - s.Noticef("Reloaded: MQTT ack_wait = %v", o.newValue) -} - -type mqttMaxAckPendingReload struct { - noopOption - newValue uint16 -} - -func (o *mqttMaxAckPendingReload) Apply(s *Server) { - s.mqttUpdateMaxAckPending(o.newValue) - s.Noticef("Reloaded: MQTT max_ack_pending = %v", o.newValue) -} - -type mqttStreamReplicasReload struct { - noopOption - newValue int -} - -func (o *mqttStreamReplicasReload) Apply(s *Server) { - s.Noticef("Reloaded: MQTT stream_replicas = %v", o.newValue) -} - -type mqttConsumerReplicasReload struct { - noopOption - newValue int -} - -func (o *mqttConsumerReplicasReload) Apply(s *Server) { - s.Noticef("Reloaded: MQTT consumer_replicas = %v", o.newValue) -} - -type mqttConsumerMemoryStorageReload struct { - noopOption - newValue bool -} - -func (o *mqttConsumerMemoryStorageReload) Apply(s *Server) { - s.Noticef("Reloaded: MQTT consumer_memory_storage = %v", o.newValue) -} - -type mqttInactiveThresholdReload struct { - noopOption - newValue time.Duration -} - -func (o *mqttInactiveThresholdReload) Apply(s *Server) { - s.Noticef("Reloaded: MQTT consumer_inactive_threshold = %v", o.newValue) -} - -type profBlockRateReload struct { - noopOption - newValue int -} - -func (o *profBlockRateReload) Apply(s *Server) { - s.setBlockProfileRate(o.newValue) - s.Noticef("Reloaded: prof_block_rate = %v", o.newValue) -} - -type leafNodeOption struct { - noopOption - tlsFirstChanged bool - compressionChanged bool -} - -func (l *leafNodeOption) Apply(s *Server) { - opts := s.getOpts() - if l.tlsFirstChanged { - s.Noticef("Reloaded: LeafNode TLS HandshakeFirst value is: %v", opts.LeafNode.TLSHandshakeFirst) - s.Noticef("Reloaded: LeafNode TLS HandshakeFirstFallback value is: %v", opts.LeafNode.TLSHandshakeFirstFallback) - for _, r := range opts.LeafNode.Remotes { - s.Noticef("Reloaded: LeafNode Remote to %v TLS HandshakeFirst value is: %v", r.URLs, r.TLSHandshakeFirst) - } - } - if l.compressionChanged { - var leafs []*client - acceptSideCompOpts := &opts.LeafNode.Compression - - s.mu.RLock() - // First, update our internal leaf remote configurations with the new - // compress options. - // Since changing the remotes (as in adding/removing) is currently not - // supported, we know that we should have the same number in Options - // than in leafRemoteCfgs, but to be sure, use the max size. - max := len(opts.LeafNode.Remotes) - if l := len(s.leafRemoteCfgs); l < max { - max = l - } - for i := 0; i < max; i++ { - lr := s.leafRemoteCfgs[i] - lr.Lock() - lr.Compression = opts.LeafNode.Remotes[i].Compression - lr.Unlock() - } - - for _, l := range s.leafs { - var co *CompressionOpts - - l.mu.Lock() - if r := l.leaf.remote; r != nil { - co = &r.Compression - } else { - co = acceptSideCompOpts - } - newMode := co.Mode - // Skip leaf connections that are "not supported" (because they - // will never do compression) or the ones that have already the - // new compression mode. - if l.leaf.compression == CompressionNotSupported || l.leaf.compression == newMode { - l.mu.Unlock() - continue - } - // We need to close the connections if it had compression "off" or the new - // mode is compression "off", or if the new mode is "accept", because - // these require negotiation. - if l.leaf.compression == CompressionOff || newMode == CompressionOff || newMode == CompressionAccept { - leafs = append(leafs, l) - } else if newMode == CompressionS2Auto { - // If the mode is "s2_auto", we need to check if there is really - // need to change, and at any rate, we want to save the actual - // compression level here, not s2_auto. - l.updateS2AutoCompressionLevel(co, &l.leaf.compression) - } else { - // Simply change the compression writer - l.out.cw = s2.NewWriter(nil, s2WriterOptions(newMode)...) - l.leaf.compression = newMode - } - l.mu.Unlock() - } - s.mu.RUnlock() - // Close the connections for which negotiation is required. - for _, l := range leafs { - l.closeConnection(ClientClosed) - } - s.Noticef("Reloaded: LeafNode compression settings") - } -} - -type noFastProdStallReload struct { - noopOption - noStall bool -} - -func (l *noFastProdStallReload) Apply(s *Server) { - var not string - if l.noStall { - not = "not " - } - s.Noticef("Reloaded: fast producers will %sbe stalled", not) -} - -// Compares options and disconnects clients that are no longer listed in pinned certs. Lock must not be held. -func (s *Server) recheckPinnedCerts(curOpts *Options, newOpts *Options) { - s.mu.Lock() - disconnectClients := []*client{} - protoToPinned := map[int]PinnedCertSet{} - if !reflect.DeepEqual(newOpts.TLSPinnedCerts, curOpts.TLSPinnedCerts) { - protoToPinned[NATS] = curOpts.TLSPinnedCerts - } - if !reflect.DeepEqual(newOpts.MQTT.TLSPinnedCerts, curOpts.MQTT.TLSPinnedCerts) { - protoToPinned[MQTT] = curOpts.MQTT.TLSPinnedCerts - } - if !reflect.DeepEqual(newOpts.Websocket.TLSPinnedCerts, curOpts.Websocket.TLSPinnedCerts) { - protoToPinned[WS] = curOpts.Websocket.TLSPinnedCerts - } - for _, c := range s.clients { - if c.kind != CLIENT { - continue - } - if pinned, ok := protoToPinned[c.clientType()]; ok { - if !c.matchesPinnedCert(pinned) { - disconnectClients = append(disconnectClients, c) - } - } - } - checkClients := func(kind int, clients map[uint64]*client, set PinnedCertSet) { - for _, c := range clients { - if c.kind == kind && !c.matchesPinnedCert(set) { - disconnectClients = append(disconnectClients, c) - } - } - } - if !reflect.DeepEqual(newOpts.LeafNode.TLSPinnedCerts, curOpts.LeafNode.TLSPinnedCerts) { - checkClients(LEAF, s.leafs, newOpts.LeafNode.TLSPinnedCerts) - } - if !reflect.DeepEqual(newOpts.Cluster.TLSPinnedCerts, curOpts.Cluster.TLSPinnedCerts) { - s.forEachRoute(func(c *client) { - if !c.matchesPinnedCert(newOpts.Cluster.TLSPinnedCerts) { - disconnectClients = append(disconnectClients, c) - } - }) - } - if s.gateway.enabled && reflect.DeepEqual(newOpts.Gateway.TLSPinnedCerts, curOpts.Gateway.TLSPinnedCerts) { - gw := s.gateway - gw.RLock() - for _, c := range gw.out { - if !c.matchesPinnedCert(newOpts.Gateway.TLSPinnedCerts) { - disconnectClients = append(disconnectClients, c) - } - } - checkClients(GATEWAY, gw.in, newOpts.Gateway.TLSPinnedCerts) - gw.RUnlock() - } - s.mu.Unlock() - if len(disconnectClients) > 0 { - s.Noticef("Disconnect %d clients due to pinned certs reload", len(disconnectClients)) - for _, c := range disconnectClients { - c.closeConnection(TLSHandshakeError) - } - } -} - -// Reload reads the current configuration file and calls out to ReloadOptions -// to apply the changes. This returns an error if the server was not started -// with a config file or an option which doesn't support hot-swapping was changed. -func (s *Server) Reload() error { - s.mu.Lock() - configFile := s.configFile - s.mu.Unlock() - if configFile == "" { - return errors.New("can only reload config when a file is provided using -c or --config") - } - - newOpts, err := ProcessConfigFile(configFile) - if err != nil { - // TODO: Dump previous good config to a .bak file? - return err - } - return s.ReloadOptions(newOpts) -} - -// ReloadOptions applies any supported options from the provided Options -// type. This returns an error if an option which doesn't support -// hot-swapping was changed. -// The provided Options type should not be re-used afterwards. -// Either use Options.Clone() to pass a copy, or make a new one. -func (s *Server) ReloadOptions(newOpts *Options) error { - s.reloadMu.Lock() - defer s.reloadMu.Unlock() - - s.mu.Lock() - - curOpts := s.getOpts() - - // Wipe trusted keys if needed when we have an operator. - if len(curOpts.TrustedOperators) > 0 && len(curOpts.TrustedKeys) > 0 { - curOpts.TrustedKeys = nil - } - - clientOrgPort := curOpts.Port - clusterOrgPort := curOpts.Cluster.Port - gatewayOrgPort := curOpts.Gateway.Port - leafnodesOrgPort := curOpts.LeafNode.Port - websocketOrgPort := curOpts.Websocket.Port - mqttOrgPort := curOpts.MQTT.Port - - s.mu.Unlock() - - // In case "-cluster ..." was provided through the command line, this will - // properly set the Cluster.Host/Port etc... - if l := curOpts.Cluster.ListenStr; l != _EMPTY_ { - newOpts.Cluster.ListenStr = l - overrideCluster(newOpts) - } - - // Apply flags over config file settings. - newOpts = MergeOptions(newOpts, FlagSnapshot) - - // Need more processing for boolean flags... - if FlagSnapshot != nil { - applyBoolFlags(newOpts, FlagSnapshot) - } - - setBaselineOptions(newOpts) - - // setBaselineOptions sets Port to 0 if set to -1 (RANDOM port) - // If that's the case, set it to the saved value when the accept loop was - // created. - if newOpts.Port == 0 { - newOpts.Port = clientOrgPort - } - // We don't do that for cluster, so check against -1. - if newOpts.Cluster.Port == -1 { - newOpts.Cluster.Port = clusterOrgPort - } - if newOpts.Gateway.Port == -1 { - newOpts.Gateway.Port = gatewayOrgPort - } - if newOpts.LeafNode.Port == -1 { - newOpts.LeafNode.Port = leafnodesOrgPort - } - if newOpts.Websocket.Port == -1 { - newOpts.Websocket.Port = websocketOrgPort - } - if newOpts.MQTT.Port == -1 { - newOpts.MQTT.Port = mqttOrgPort - } - - if err := s.reloadOptions(curOpts, newOpts); err != nil { - return err - } - - s.recheckPinnedCerts(curOpts, newOpts) - - s.mu.Lock() - s.configTime = time.Now().UTC() - s.updateVarzConfigReloadableFields(s.varz) - s.mu.Unlock() - return nil -} -func applyBoolFlags(newOpts, flagOpts *Options) { - // Reset fields that may have been set to `true` in - // MergeOptions() when some of the flags default to `true` - // but have not been explicitly set and therefore value - // from config file should take precedence. - for name, val := range newOpts.inConfig { - f := reflect.ValueOf(newOpts).Elem() - names := strings.Split(name, ".") - for _, name := range names { - f = f.FieldByName(name) - } - f.SetBool(val) - } - // Now apply value (true or false) from flags that have - // been explicitly set in command line - for name, val := range flagOpts.inCmdLine { - f := reflect.ValueOf(newOpts).Elem() - names := strings.Split(name, ".") - for _, name := range names { - f = f.FieldByName(name) - } - f.SetBool(val) - } -} - -// reloadOptions reloads the server config with the provided options. If an -// option that doesn't support hot-swapping is changed, this returns an error. -func (s *Server) reloadOptions(curOpts, newOpts *Options) error { - // Apply to the new options some of the options that may have been set - // that can't be configured in the config file (this can happen in - // applications starting NATS Server programmatically). - newOpts.CustomClientAuthentication = curOpts.CustomClientAuthentication - newOpts.CustomRouterAuthentication = curOpts.CustomRouterAuthentication - - changed, err := s.diffOptions(newOpts) - if err != nil { - return err - } - - if len(changed) != 0 { - if err := validateOptions(newOpts); err != nil { - return err - } - } - - // Create a context that is used to pass special info that we may need - // while applying the new options. - ctx := reloadContext{oldClusterPerms: curOpts.Cluster.Permissions} - s.setOpts(newOpts) - s.applyOptions(&ctx, changed) - return nil -} - -// For the purpose of comparing, impose a order on slice data types where order does not matter -func imposeOrder(value any) error { - switch value := value.(type) { - case []*Account: - slices.SortFunc(value, func(i, j *Account) int { return cmp.Compare(i.Name, j.Name) }) - for _, a := range value { - slices.SortFunc(a.imports.streams, func(i, j *streamImport) int { return cmp.Compare(i.acc.Name, j.acc.Name) }) - } - case []*User: - slices.SortFunc(value, func(i, j *User) int { return cmp.Compare(i.Username, j.Username) }) - case []*NkeyUser: - slices.SortFunc(value, func(i, j *NkeyUser) int { return cmp.Compare(i.Nkey, j.Nkey) }) - case []*url.URL: - slices.SortFunc(value, func(i, j *url.URL) int { return cmp.Compare(i.String(), j.String()) }) - case []string: - slices.Sort(value) - case []*jwt.OperatorClaims: - slices.SortFunc(value, func(i, j *jwt.OperatorClaims) int { return cmp.Compare(i.Issuer, j.Issuer) }) - case GatewayOpts: - slices.SortFunc(value.Gateways, func(i, j *RemoteGatewayOpts) int { return cmp.Compare(i.Name, j.Name) }) - case WebsocketOpts: - slices.Sort(value.AllowedOrigins) - case string, bool, uint8, uint16, int, int32, int64, time.Duration, float64, nil, LeafNodeOpts, ClusterOpts, *tls.Config, PinnedCertSet, - *URLAccResolver, *MemAccResolver, *DirAccResolver, *CacheDirAccResolver, Authentication, MQTTOpts, jwt.TagList, - *OCSPConfig, map[string]string, JSLimitOpts, StoreCipher, *OCSPResponseCacheConfig: - // explicitly skipped types - case *AuthCallout: - case JSTpmOpts: - default: - // this will fail during unit tests - return fmt.Errorf("OnReload, sort or explicitly skip type: %s", - reflect.TypeOf(value)) - } - return nil -} - -// diffOptions returns a slice containing options which have been changed. If -// an option that doesn't support hot-swapping is changed, this returns an -// error. -func (s *Server) diffOptions(newOpts *Options) ([]option, error) { - var ( - oldConfig = reflect.ValueOf(s.getOpts()).Elem() - newConfig = reflect.ValueOf(newOpts).Elem() - diffOpts = []option{} - - // Need to keep track of whether JS is being disabled - // to prevent changing limits at runtime. - jsEnabled = s.JetStreamEnabled() - disableJS bool - jsMemLimitsChanged bool - jsFileLimitsChanged bool - jsStoreDirChanged bool - ) - for i := 0; i < oldConfig.NumField(); i++ { - field := oldConfig.Type().Field(i) - // field.PkgPath is empty for exported fields, and is not for unexported ones. - // We skip the unexported fields. - if field.PkgPath != _EMPTY_ { - continue - } - var ( - oldValue = oldConfig.Field(i).Interface() - newValue = newConfig.Field(i).Interface() - ) - if err := imposeOrder(oldValue); err != nil { - return nil, err - } - if err := imposeOrder(newValue); err != nil { - return nil, err - } - - optName := strings.ToLower(field.Name) - // accounts and users (referencing accounts) will always differ as accounts - // contain internal state, say locks etc..., so we don't bother here. - // This also avoids races with atomic stats counters - if optName != "accounts" && optName != "users" { - if changed := !reflect.DeepEqual(oldValue, newValue); !changed { - // Check to make sure we are running JetStream if we think we should be. - if optName == "jetstream" && newValue.(bool) { - if !jsEnabled { - diffOpts = append(diffOpts, &jetStreamOption{newValue: true}) - } - } - continue - } - } - switch optName { - case "traceverbose": - diffOpts = append(diffOpts, &traceVerboseOption{newValue: newValue.(bool)}) - case "trace": - diffOpts = append(diffOpts, &traceOption{newValue: newValue.(bool)}) - case "debug": - diffOpts = append(diffOpts, &debugOption{newValue: newValue.(bool)}) - case "logtime": - diffOpts = append(diffOpts, &logtimeOption{newValue: newValue.(bool)}) - case "logtimeutc": - diffOpts = append(diffOpts, &logtimeUTCOption{newValue: newValue.(bool)}) - case "logfile": - diffOpts = append(diffOpts, &logfileOption{newValue: newValue.(string)}) - case "syslog": - diffOpts = append(diffOpts, &syslogOption{newValue: newValue.(bool)}) - case "remotesyslog": - diffOpts = append(diffOpts, &remoteSyslogOption{newValue: newValue.(string)}) - case "tlsconfig": - diffOpts = append(diffOpts, &tlsOption{newValue: newValue.(*tls.Config)}) - case "tlstimeout": - diffOpts = append(diffOpts, &tlsTimeoutOption{newValue: newValue.(float64)}) - case "tlspinnedcerts": - diffOpts = append(diffOpts, &tlsPinnedCertOption{newValue: newValue.(PinnedCertSet)}) - case "tlshandshakefirst": - diffOpts = append(diffOpts, &tlsHandshakeFirst{newValue: newValue.(bool)}) - case "tlshandshakefirstfallback": - diffOpts = append(diffOpts, &tlsHandshakeFirstFallback{newValue: newValue.(time.Duration)}) - case "username": - diffOpts = append(diffOpts, &usernameOption{}) - case "password": - diffOpts = append(diffOpts, &passwordOption{}) - case "tags": - diffOpts = append(diffOpts, &tagsOption{}) - case "authorization": - diffOpts = append(diffOpts, &authorizationOption{}) - case "authtimeout": - diffOpts = append(diffOpts, &authTimeoutOption{newValue: newValue.(float64)}) - case "users": - diffOpts = append(diffOpts, &usersOption{}) - case "nkeys": - diffOpts = append(diffOpts, &nkeysOption{}) - case "cluster": - newClusterOpts := newValue.(ClusterOpts) - oldClusterOpts := oldValue.(ClusterOpts) - if err := validateClusterOpts(oldClusterOpts, newClusterOpts); err != nil { - return nil, err - } - co := &clusterOption{ - newValue: newClusterOpts, - permsChanged: !reflect.DeepEqual(newClusterOpts.Permissions, oldClusterOpts.Permissions), - compressChanged: !reflect.DeepEqual(oldClusterOpts.Compression, newClusterOpts.Compression), - } - co.diffPoolAndAccounts(&oldClusterOpts) - // If there are added accounts, first make sure that we can look them up. - // If we can't let's fail the reload. - for _, acc := range co.accsAdded { - if _, err := s.LookupAccount(acc); err != nil { - return nil, fmt.Errorf("unable to add account %q to the list of dedicated routes: %v", acc, err) - } - } - // If pool_size has been set to negative (but was not before), then let's - // add the system account to the list of removed accounts (we don't have - // to check if already there, duplicates are ok in that case). - if newClusterOpts.PoolSize < 0 && oldClusterOpts.PoolSize >= 0 { - if sys := s.SystemAccount(); sys != nil { - co.accsRemoved = append(co.accsRemoved, sys.GetName()) - } - } - diffOpts = append(diffOpts, co) - case "routes": - add, remove := diffRoutes(oldValue.([]*url.URL), newValue.([]*url.URL)) - diffOpts = append(diffOpts, &routesOption{add: add, remove: remove}) - case "maxconn": - diffOpts = append(diffOpts, &maxConnOption{newValue: newValue.(int)}) - case "pidfile": - diffOpts = append(diffOpts, &pidFileOption{newValue: newValue.(string)}) - case "portsfiledir": - diffOpts = append(diffOpts, &portsFileDirOption{newValue: newValue.(string), oldValue: oldValue.(string)}) - case "maxcontrolline": - diffOpts = append(diffOpts, &maxControlLineOption{newValue: newValue.(int32)}) - case "maxpayload": - diffOpts = append(diffOpts, &maxPayloadOption{newValue: newValue.(int32)}) - case "pinginterval": - diffOpts = append(diffOpts, &pingIntervalOption{newValue: newValue.(time.Duration)}) - case "maxpingsout": - diffOpts = append(diffOpts, &maxPingsOutOption{newValue: newValue.(int)}) - case "writedeadline": - diffOpts = append(diffOpts, &writeDeadlineOption{newValue: newValue.(time.Duration)}) - case "clientadvertise": - cliAdv := newValue.(string) - if cliAdv != "" { - // Validate ClientAdvertise syntax - if _, _, err := parseHostPort(cliAdv, 0); err != nil { - return nil, fmt.Errorf("invalid ClientAdvertise value of %s, err=%v", cliAdv, err) - } - } - diffOpts = append(diffOpts, &clientAdvertiseOption{newValue: cliAdv}) - case "accounts": - diffOpts = append(diffOpts, &accountsOption{}) - case "resolver", "accountresolver", "accountsresolver": - // We can't move from no resolver to one. So check for that. - if (oldValue == nil && newValue != nil) || - (oldValue != nil && newValue == nil) { - return nil, fmt.Errorf("config reload does not support moving to or from an account resolver") - } - diffOpts = append(diffOpts, &accountsOption{}) - case "accountresolvertlsconfig": - diffOpts = append(diffOpts, &accountsOption{}) - case "gateway": - // Not supported for now, but report warning if configuration of gateway - // is actually changed so that user knows that it won't take effect. - - // Any deep-equal is likely to fail for when there is a TLSConfig. so - // remove for the test. - tmpOld := oldValue.(GatewayOpts) - tmpNew := newValue.(GatewayOpts) - tmpOld.TLSConfig = nil - tmpNew.TLSConfig = nil - tmpOld.tlsConfigOpts = nil - tmpNew.tlsConfigOpts = nil - - // Need to do the same for remote gateways' TLS configs. - // But we can't just set remotes' TLSConfig to nil otherwise this - // would lose the real TLS configuration. - tmpOld.Gateways = copyRemoteGWConfigsWithoutTLSConfig(tmpOld.Gateways) - tmpNew.Gateways = copyRemoteGWConfigsWithoutTLSConfig(tmpNew.Gateways) - - // If there is really a change prevents reload. - if !reflect.DeepEqual(tmpOld, tmpNew) { - // See TODO(ik) note below about printing old/new values. - return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v", - field.Name, oldValue, newValue) - } - case "leafnode": - // Similar to gateways - tmpOld := oldValue.(LeafNodeOpts) - tmpNew := newValue.(LeafNodeOpts) - tmpOld.TLSConfig = nil - tmpNew.TLSConfig = nil - tmpOld.tlsConfigOpts = nil - tmpNew.tlsConfigOpts = nil - // We will allow TLSHandshakeFirst to be config reloaded. First, - // we just want to detect if there was a change in the leafnodes{} - // block, and if not, we will check the remotes. - handshakeFirstChanged := tmpOld.TLSHandshakeFirst != tmpNew.TLSHandshakeFirst || - tmpOld.TLSHandshakeFirstFallback != tmpNew.TLSHandshakeFirstFallback - // If changed, set them (in the temporary variables) to false so that the - // rest of the comparison does not fail. - if handshakeFirstChanged { - tmpOld.TLSHandshakeFirst, tmpNew.TLSHandshakeFirst = false, false - tmpOld.TLSHandshakeFirstFallback, tmpNew.TLSHandshakeFirstFallback = 0, 0 - } else if len(tmpOld.Remotes) == len(tmpNew.Remotes) { - // Since we don't support changes in the remotes, we will do a - // simple pass to see if there was a change of this field. - for i := 0; i < len(tmpOld.Remotes); i++ { - if tmpOld.Remotes[i].TLSHandshakeFirst != tmpNew.Remotes[i].TLSHandshakeFirst { - handshakeFirstChanged = true - break - } - } - } - // We also support config reload for compression. Check if it changed before - // blanking them out for the deep-equal check at the end. - compressionChanged := !reflect.DeepEqual(tmpOld.Compression, tmpNew.Compression) - if compressionChanged { - tmpOld.Compression, tmpNew.Compression = CompressionOpts{}, CompressionOpts{} - } else if len(tmpOld.Remotes) == len(tmpNew.Remotes) { - // Same that for tls first check, do the remotes now. - for i := 0; i < len(tmpOld.Remotes); i++ { - if !reflect.DeepEqual(tmpOld.Remotes[i].Compression, tmpNew.Remotes[i].Compression) { - compressionChanged = true - break - } - } - } - - // Need to do the same for remote leafnodes' TLS configs. - // But we can't just set remotes' TLSConfig to nil otherwise this - // would lose the real TLS configuration. - tmpOld.Remotes = copyRemoteLNConfigForReloadCompare(tmpOld.Remotes) - tmpNew.Remotes = copyRemoteLNConfigForReloadCompare(tmpNew.Remotes) - - // Special check for leafnode remotes changes which are not supported right now. - leafRemotesChanged := func(a, b LeafNodeOpts) bool { - if len(a.Remotes) != len(b.Remotes) { - return true - } - - // Check whether all remotes URLs are still the same. - for _, oldRemote := range a.Remotes { - var found bool - - if oldRemote.LocalAccount == _EMPTY_ { - oldRemote.LocalAccount = globalAccountName - } - - for _, newRemote := range b.Remotes { - // Bind to global account in case not defined. - if newRemote.LocalAccount == _EMPTY_ { - newRemote.LocalAccount = globalAccountName - } - - if reflect.DeepEqual(oldRemote, newRemote) { - found = true - break - } - } - if !found { - return true - } - } - - return false - } - - // First check whether remotes changed at all. If they did not, - // skip them in the complete equal check. - if !leafRemotesChanged(tmpOld, tmpNew) { - tmpOld.Remotes = nil - tmpNew.Remotes = nil - } - - // Special check for auth users to detect changes. - // If anything is off will fall through and fail below. - // If we detect they are semantically the same we nil them out - // to pass the check below. - if tmpOld.Users != nil || tmpNew.Users != nil { - if len(tmpOld.Users) == len(tmpNew.Users) { - oua := make(map[string]*User, len(tmpOld.Users)) - nua := make(map[string]*User, len(tmpOld.Users)) - for _, u := range tmpOld.Users { - oua[u.Username] = u - } - for _, u := range tmpNew.Users { - nua[u.Username] = u - } - same := true - for uname, u := range oua { - // If we can not find new one with same name, drop through to fail. - nu, ok := nua[uname] - if !ok { - same = false - break - } - // If username or password or account different break. - if u.Username != nu.Username || u.Password != nu.Password || u.Account.GetName() != nu.Account.GetName() { - same = false - break - } - } - // We can nil out here. - if same { - tmpOld.Users, tmpNew.Users = nil, nil - } - } - } - - // If there is really a change prevents reload. - if !reflect.DeepEqual(tmpOld, tmpNew) { - // See TODO(ik) note below about printing old/new values. - return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v", - field.Name, oldValue, newValue) - } - - diffOpts = append(diffOpts, &leafNodeOption{ - tlsFirstChanged: handshakeFirstChanged, - compressionChanged: compressionChanged, - }) - case "jetstream": - new := newValue.(bool) - old := oldValue.(bool) - if new != old { - diffOpts = append(diffOpts, &jetStreamOption{newValue: new}) - } - - // Mark whether JS will be disabled. - disableJS = !new - case "storedir": - new := newValue.(string) - old := oldValue.(string) - modified := new != old - - // Check whether JS is being disabled and/or storage dir attempted to change. - if jsEnabled && modified { - if new == _EMPTY_ { - // This means that either JS is being disabled or it is using an temp dir. - // Allow the change but error in case JS was not disabled. - jsStoreDirChanged = true - } else { - return nil, fmt.Errorf("config reload not supported for jetstream storage directory") - } - } - case "jetstreammaxmemory", "jetstreammaxstore": - old := oldValue.(int64) - new := newValue.(int64) - - // Check whether JS is being disabled and/or limits are being changed. - var ( - modified = new != old - fromUnset = old == -1 - fromSet = !fromUnset - toUnset = new == -1 - toSet = !toUnset - ) - if jsEnabled && modified { - // Cannot change limits from dynamic storage at runtime. - switch { - case fromSet && toUnset: - // Limits changed but it may mean that JS is being disabled, - // keep track of the change and error in case it is not. - switch optName { - case "jetstreammaxmemory": - jsMemLimitsChanged = true - case "jetstreammaxstore": - jsFileLimitsChanged = true - default: - return nil, fmt.Errorf("config reload not supported for jetstream max memory and store") - } - case fromUnset && toSet: - // Prevent changing from dynamic max memory / file at runtime. - return nil, fmt.Errorf("config reload not supported for jetstream dynamic max memory and store") - default: - return nil, fmt.Errorf("config reload not supported for jetstream max memory and store") - } - } - case "websocket": - // Similar to gateways - tmpOld := oldValue.(WebsocketOpts) - tmpNew := newValue.(WebsocketOpts) - tmpOld.TLSConfig, tmpOld.tlsConfigOpts = nil, nil - tmpNew.TLSConfig, tmpNew.tlsConfigOpts = nil, nil - // If there is really a change prevents reload. - if !reflect.DeepEqual(tmpOld, tmpNew) { - // See TODO(ik) note below about printing old/new values. - return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v", - field.Name, oldValue, newValue) - } - case "mqtt": - diffOpts = append(diffOpts, &mqttAckWaitReload{newValue: newValue.(MQTTOpts).AckWait}) - diffOpts = append(diffOpts, &mqttMaxAckPendingReload{newValue: newValue.(MQTTOpts).MaxAckPending}) - diffOpts = append(diffOpts, &mqttStreamReplicasReload{newValue: newValue.(MQTTOpts).StreamReplicas}) - diffOpts = append(diffOpts, &mqttConsumerReplicasReload{newValue: newValue.(MQTTOpts).ConsumerReplicas}) - diffOpts = append(diffOpts, &mqttConsumerMemoryStorageReload{newValue: newValue.(MQTTOpts).ConsumerMemoryStorage}) - diffOpts = append(diffOpts, &mqttInactiveThresholdReload{newValue: newValue.(MQTTOpts).ConsumerInactiveThreshold}) - - // Nil out/set to 0 the options that we allow to be reloaded so that - // we only fail reload if some that we don't support are changed. - tmpOld := oldValue.(MQTTOpts) - tmpNew := newValue.(MQTTOpts) - tmpOld.TLSConfig, tmpOld.tlsConfigOpts, tmpOld.AckWait, tmpOld.MaxAckPending, tmpOld.StreamReplicas, tmpOld.ConsumerReplicas, tmpOld.ConsumerMemoryStorage = nil, nil, 0, 0, 0, 0, false - tmpOld.ConsumerInactiveThreshold = 0 - tmpNew.TLSConfig, tmpNew.tlsConfigOpts, tmpNew.AckWait, tmpNew.MaxAckPending, tmpNew.StreamReplicas, tmpNew.ConsumerReplicas, tmpNew.ConsumerMemoryStorage = nil, nil, 0, 0, 0, 0, false - tmpNew.ConsumerInactiveThreshold = 0 - - if !reflect.DeepEqual(tmpOld, tmpNew) { - // See TODO(ik) note below about printing old/new values. - return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v", - field.Name, oldValue, newValue) - } - tmpNew.AckWait = newValue.(MQTTOpts).AckWait - tmpNew.MaxAckPending = newValue.(MQTTOpts).MaxAckPending - tmpNew.StreamReplicas = newValue.(MQTTOpts).StreamReplicas - tmpNew.ConsumerReplicas = newValue.(MQTTOpts).ConsumerReplicas - tmpNew.ConsumerMemoryStorage = newValue.(MQTTOpts).ConsumerMemoryStorage - tmpNew.ConsumerInactiveThreshold = newValue.(MQTTOpts).ConsumerInactiveThreshold - case "connecterrorreports": - diffOpts = append(diffOpts, &connectErrorReports{newValue: newValue.(int)}) - case "reconnecterrorreports": - diffOpts = append(diffOpts, &reconnectErrorReports{newValue: newValue.(int)}) - case "nolog", "nosigs": - // Ignore NoLog and NoSigs options since they are not parsed and only used in - // testing. - continue - case "disableshortfirstping": - newOpts.DisableShortFirstPing = oldValue.(bool) - continue - case "maxtracedmsglen": - diffOpts = append(diffOpts, &maxTracedMsgLenOption{newValue: newValue.(int)}) - case "port": - // check to see if newValue == 0 and continue if so. - if newValue == 0 { - // ignore RANDOM_PORT - continue - } - fallthrough - case "noauthuser": - if oldValue != _EMPTY_ && newValue == _EMPTY_ { - for _, user := range newOpts.Users { - if user.Username == oldValue { - return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v", - field.Name, oldValue, newValue) - } - } - } else { - return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v", - field.Name, oldValue, newValue) - } - case "systemaccount": - if oldValue != DEFAULT_SYSTEM_ACCOUNT || newValue != _EMPTY_ { - return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v", - field.Name, oldValue, newValue) - } - case "ocspconfig": - diffOpts = append(diffOpts, &ocspOption{newValue: newValue.(*OCSPConfig)}) - case "ocspcacheconfig": - diffOpts = append(diffOpts, &ocspResponseCacheOption{newValue: newValue.(*OCSPResponseCacheConfig)}) - case "profblockrate": - new := newValue.(int) - old := oldValue.(int) - if new != old { - diffOpts = append(diffOpts, &profBlockRateReload{newValue: new}) - } - case "configdigest": - // skip changes in config digest, this is handled already while - // processing the config. - continue - case "nofastproducerstall": - diffOpts = append(diffOpts, &noFastProdStallReload{noStall: newValue.(bool)}) - default: - // TODO(ik): Implement String() on those options to have a nice print. - // %v is difficult to figure what's what, %+v print private fields and - // would print passwords. Tried json.Marshal but it is too verbose for - // the URL array. - - // Bail out if attempting to reload any unsupported options. - return nil, fmt.Errorf("config reload not supported for %s: old=%v, new=%v", - field.Name, oldValue, newValue) - } - } - - // If not disabling JS but limits have changed then it is an error. - if !disableJS { - if jsMemLimitsChanged || jsFileLimitsChanged { - return nil, fmt.Errorf("config reload not supported for jetstream max memory and max store") - } - if jsStoreDirChanged { - return nil, fmt.Errorf("config reload not supported for jetstream storage dir") - } - } - - return diffOpts, nil -} - -func copyRemoteGWConfigsWithoutTLSConfig(current []*RemoteGatewayOpts) []*RemoteGatewayOpts { - l := len(current) - if l == 0 { - return nil - } - rgws := make([]*RemoteGatewayOpts, 0, l) - for _, rcfg := range current { - cp := *rcfg - cp.TLSConfig = nil - cp.tlsConfigOpts = nil - rgws = append(rgws, &cp) - } - return rgws -} - -func copyRemoteLNConfigForReloadCompare(current []*RemoteLeafOpts) []*RemoteLeafOpts { - l := len(current) - if l == 0 { - return nil - } - rlns := make([]*RemoteLeafOpts, 0, l) - for _, rcfg := range current { - cp := *rcfg - cp.TLSConfig = nil - cp.tlsConfigOpts = nil - cp.TLSHandshakeFirst = false - // This is set only when processing a CONNECT, so reset here so that we - // don't fail the DeepEqual comparison. - cp.TLS = false - // For now, remove DenyImports/Exports since those get modified at runtime - // to add JS APIs. - cp.DenyImports, cp.DenyExports = nil, nil - // Remove compression mode - cp.Compression = CompressionOpts{} - rlns = append(rlns, &cp) - } - return rlns -} - -func (s *Server) applyOptions(ctx *reloadContext, opts []option) { - var ( - reloadLogging = false - reloadAuth = false - reloadClusterPerms = false - reloadClientTrcLvl = false - reloadJetstream = false - jsEnabled = false - isStatszChange = false - co *clusterOption - ) - for _, opt := range opts { - opt.Apply(s) - if opt.IsLoggingChange() { - reloadLogging = true - } - if opt.IsTraceLevelChange() { - reloadClientTrcLvl = true - } - if opt.IsAuthChange() { - reloadAuth = true - } - if opt.IsClusterPoolSizeOrAccountsChange() { - co = opt.(*clusterOption) - } - if opt.IsClusterPermsChange() { - reloadClusterPerms = true - } - if opt.IsJetStreamChange() { - reloadJetstream = true - jsEnabled = opt.(*jetStreamOption).newValue - } - if opt.IsStatszChange() { - isStatszChange = true - } - } - - if reloadLogging { - s.ConfigureLogger() - } - if reloadClientTrcLvl { - s.reloadClientTraceLevel() - } - if reloadAuth { - s.reloadAuthorization() - } - if reloadClusterPerms { - s.reloadClusterPermissions(ctx.oldClusterPerms) - } - newOpts := s.getOpts() - // If we need to reload cluster pool/per-account, then co will be not nil - if co != nil { - s.reloadClusterPoolAndAccounts(co, newOpts) - } - if reloadJetstream { - if !jsEnabled { - s.DisableJetStream() - } else if !s.JetStreamEnabled() { - if err := s.restartJetStream(); err != nil { - s.Warnf("Can't start JetStream: %v", err) - } - } - // Make sure to reset the internal loop's version of JS. - s.resetInternalLoopInfo() - } - if isStatszChange { - s.sendStatszUpdate() - } - - // For remote gateways and leafnodes, make sure that their TLS configuration - // is updated (since the config is "captured" early and changes would otherwise - // not be visible). - if s.gateway.enabled { - s.gateway.updateRemotesTLSConfig(newOpts) - } - if len(newOpts.LeafNode.Remotes) > 0 { - s.updateRemoteLeafNodesTLSConfig(newOpts) - } - - // Always restart OCSP monitoring on reload. - if err := s.reloadOCSP(); err != nil { - s.Warnf("Can't restart OCSP features: %v", err) - } - var cd string - if newOpts.configDigest != "" { - cd = fmt.Sprintf("(%s)", newOpts.configDigest) - } - s.Noticef("Reloaded server configuration %s", cd) -} - -// This will send a reset to the internal send loop. -func (s *Server) resetInternalLoopInfo() { - var resetCh chan struct{} - s.mu.Lock() - if s.sys != nil { - // can't hold the lock as go routine reading it may be waiting for lock as well - resetCh = s.sys.resetCh - } - s.mu.Unlock() - - if resetCh != nil { - resetCh <- struct{}{} - } -} - -// Update all cached debug and trace settings for every client -func (s *Server) reloadClientTraceLevel() { - opts := s.getOpts() - - if opts.NoLog { - return - } - - // Create a list of all clients. - // Update their trace level when not holding server or gateway lock - - s.mu.Lock() - clientCnt := 1 + len(s.clients) + len(s.grTmpClients) + s.numRoutes() + len(s.leafs) - s.mu.Unlock() - - s.gateway.RLock() - clientCnt += len(s.gateway.in) + len(s.gateway.outo) - s.gateway.RUnlock() - - clients := make([]*client, 0, clientCnt) - - s.mu.Lock() - if s.eventsEnabled() { - clients = append(clients, s.sys.client) - } - - cMaps := []map[uint64]*client{s.clients, s.grTmpClients, s.leafs} - for _, m := range cMaps { - for _, c := range m { - clients = append(clients, c) - } - } - s.forEachRoute(func(c *client) { - clients = append(clients, c) - }) - s.mu.Unlock() - - s.gateway.RLock() - for _, c := range s.gateway.in { - clients = append(clients, c) - } - clients = append(clients, s.gateway.outo...) - s.gateway.RUnlock() - - for _, c := range clients { - // client.trace is commonly read while holding the lock - c.mu.Lock() - c.setTraceLevel() - c.mu.Unlock() - } -} - -// reloadAuthorization reconfigures the server authorization settings, -// disconnects any clients who are no longer authorized, and removes any -// unauthorized subscriptions. -func (s *Server) reloadAuthorization() { - // This map will contain the names of accounts that have their streams - // import configuration changed. - var awcsti map[string]struct{} - checkJetStream := false - opts := s.getOpts() - s.mu.Lock() - - deletedAccounts := make(map[string]*Account) - - // This can not be changed for now so ok to check server's trustedKeys unlocked. - // If plain configured accounts, process here. - if s.trustedKeys == nil { - // Make a map of the configured account names so we figure out the accounts - // that should be removed later on. - configAccs := make(map[string]struct{}, len(opts.Accounts)) - for _, acc := range opts.Accounts { - configAccs[acc.GetName()] = struct{}{} - } - // Now range over existing accounts and keep track of the ones deleted - // so some cleanup can be made after releasing the server lock. - s.accounts.Range(func(k, v any) bool { - an, acc := k.(string), v.(*Account) - // Exclude default and system account from this test since those - // may not actually be in opts.Accounts. - if an == DEFAULT_GLOBAL_ACCOUNT || an == DEFAULT_SYSTEM_ACCOUNT { - return true - } - // Check check if existing account is still in opts.Accounts. - if _, ok := configAccs[an]; !ok { - deletedAccounts[an] = acc - s.accounts.Delete(k) - } - return true - }) - // This will update existing and add new ones. - awcsti, _ = s.configureAccounts(true) - s.configureAuthorization() - // Double check any JetStream configs. - checkJetStream = s.getJetStream() != nil - } else if opts.AccountResolver != nil { - s.configureResolver() - if _, ok := s.accResolver.(*MemAccResolver); ok { - // Check preloads so we can issue warnings etc if needed. - s.checkResolvePreloads() - // With a memory resolver we want to do something similar to configured accounts. - // We will walk the accounts and delete them if they are no longer present via fetch. - // If they are present we will force a claim update to process changes. - s.accounts.Range(func(k, v any) bool { - acc := v.(*Account) - // Skip global account. - if acc == s.gacc { - return true - } - accName := acc.GetName() - // Release server lock for following actions - s.mu.Unlock() - accClaims, claimJWT, _ := s.fetchAccountClaims(accName) - if accClaims != nil { - if err := s.updateAccountWithClaimJWT(acc, claimJWT); err != nil { - s.Noticef("Reloaded: deleting account [bad claims]: %q", accName) - s.accounts.Delete(k) - } - } else { - s.Noticef("Reloaded: deleting account [removed]: %q", accName) - s.accounts.Delete(k) - } - // Regrab server lock. - s.mu.Lock() - return true - }) - } - } - - var ( - cclientsa [64]*client - cclients = cclientsa[:0] - clientsa [64]*client - clients = clientsa[:0] - routesa [64]*client - routes = routesa[:0] - ) - - // Gather clients that changed accounts. We will close them and they - // will reconnect, doing the right thing. - for _, client := range s.clients { - if s.clientHasMovedToDifferentAccount(client) { - cclients = append(cclients, client) - } else { - clients = append(clients, client) - } - } - s.forEachRoute(func(route *client) { - routes = append(routes, route) - }) - // Check here for any system/internal clients which will not be in the servers map of normal clients. - if s.sys != nil && s.sys.account != nil && !opts.NoSystemAccount { - s.accounts.Store(s.sys.account.Name, s.sys.account) - } - - s.accounts.Range(func(k, v any) bool { - acc := v.(*Account) - acc.mu.RLock() - // Check for sysclients accounting, ignore the system account. - if acc.sysclients > 0 && (s.sys == nil || s.sys.account != acc) { - for c := range acc.clients { - if c.kind != CLIENT && c.kind != LEAF { - clients = append(clients, c) - } - } - } - acc.mu.RUnlock() - return true - }) - - var resetCh chan struct{} - if s.sys != nil { - // can't hold the lock as go routine reading it may be waiting for lock as well - resetCh = s.sys.resetCh - } - s.mu.Unlock() - - // Clear some timers and remove service import subs for deleted accounts. - for _, acc := range deletedAccounts { - acc.mu.Lock() - clearTimer(&acc.etmr) - clearTimer(&acc.ctmr) - for _, se := range acc.exports.services { - se.clearResponseThresholdTimer() - } - acc.mu.Unlock() - acc.removeAllServiceImportSubs() - } - - if resetCh != nil { - resetCh <- struct{}{} - } - - // Check that publish retained messages sources are still allowed to publish. - s.mqttCheckPubRetainedPerms() - - // Close clients that have moved accounts - for _, client := range cclients { - client.closeConnection(ClientClosed) - } - - for _, c := range clients { - // Disconnect any unauthorized clients. - // Ignore internal clients. - if (c.kind == CLIENT || c.kind == LEAF) && !s.isClientAuthorized(c) { - c.authViolation() - continue - } - // Check to make sure account is correct. - c.swapAccountAfterReload() - // Remove any unauthorized subscriptions and check for account imports. - c.processSubsOnConfigReload(awcsti) - } - - for _, route := range routes { - // Disconnect any unauthorized routes. - // Do this only for routes that were accepted, not initiated - // because in the later case, we don't have the user name/password - // of the remote server. - if !route.isSolicitedRoute() && !s.isRouterAuthorized(route) { - route.setNoReconnect() - route.authViolation() - } - } - - if res := s.AccountResolver(); res != nil { - res.Reload() - } - - // We will double check all JetStream configs on a reload. - if checkJetStream { - if err := s.enableJetStreamAccounts(); err != nil { - s.Errorf(err.Error()) - } - } -} - -// Returns true if given client current account has changed (or user -// no longer exist) in the new config, false if the user did not -// change accounts. -// Server lock is held on entry. -func (s *Server) clientHasMovedToDifferentAccount(c *client) bool { - var ( - nu *NkeyUser - u *User - ) - c.mu.Lock() - defer c.mu.Unlock() - if c.opts.Nkey != _EMPTY_ { - if s.nkeys != nil { - nu = s.nkeys[c.opts.Nkey] - } - } else if c.opts.Username != _EMPTY_ { - if s.users != nil { - u = s.users[c.opts.Username] - } - } else { - return false - } - // Get the current account name - var curAccName string - if c.acc != nil { - curAccName = c.acc.Name - } - if nu != nil && nu.Account != nil { - return curAccName != nu.Account.Name - } else if u != nil && u.Account != nil { - return curAccName != u.Account.Name - } - // user/nkey no longer exists. - return true -} - -// reloadClusterPermissions reconfigures the cluster's permssions -// and set the permissions to all existing routes, sending an -// update INFO protocol so that remote can resend their local -// subs if needed, and sending local subs matching cluster's -// import subjects. -func (s *Server) reloadClusterPermissions(oldPerms *RoutePermissions) { - s.mu.Lock() - newPerms := s.getOpts().Cluster.Permissions - routes := make(map[uint64]*client, s.numRoutes()) - // Get all connected routes - s.forEachRoute(func(route *client) { - route.mu.Lock() - routes[route.cid] = route - route.mu.Unlock() - }) - // If new permissions is nil, then clear routeInfo import/export - if newPerms == nil { - s.routeInfo.Import = nil - s.routeInfo.Export = nil - } else { - s.routeInfo.Import = newPerms.Import - s.routeInfo.Export = newPerms.Export - } - infoJSON := generateInfoJSON(&s.routeInfo) - s.mu.Unlock() - - // Close connections for routes that don't understand async INFO. - for _, route := range routes { - route.mu.Lock() - close := route.opts.Protocol < RouteProtoInfo - cid := route.cid - route.mu.Unlock() - if close { - route.closeConnection(RouteRemoved) - delete(routes, cid) - } - } - - // If there are no route left, we are done - if len(routes) == 0 { - return - } - - // Fake clients to test cluster permissions - oldPermsTester := &client{} - oldPermsTester.setRoutePermissions(oldPerms) - newPermsTester := &client{} - newPermsTester.setRoutePermissions(newPerms) - - var ( - _localSubs [4096]*subscription - subsNeedSUB = map[*client][]*subscription{} - subsNeedUNSUB = map[*client][]*subscription{} - deleteRoutedSubs []*subscription - ) - - getRouteForAccount := func(accName string, poolIdx int) *client { - for _, r := range routes { - r.mu.Lock() - ok := (poolIdx >= 0 && poolIdx == r.route.poolIdx) || (string(r.route.accName) == accName) || r.route.noPool - r.mu.Unlock() - if ok { - return r - } - } - return nil - } - - // First set the new permissions on all routes. - for _, route := range routes { - route.mu.Lock() - route.setRoutePermissions(newPerms) - route.mu.Unlock() - } - - // Then, go over all accounts and gather local subscriptions that need to be - // sent over as SUB or removed as UNSUB, and routed subscriptions that need - // to be dropped due to export permissions. - s.accounts.Range(func(_, v any) bool { - acc := v.(*Account) - acc.mu.RLock() - accName, sl, poolIdx := acc.Name, acc.sl, acc.routePoolIdx - acc.mu.RUnlock() - // Get the route handling this account. If no route or sublist, bail out. - route := getRouteForAccount(accName, poolIdx) - if route == nil || sl == nil { - return true - } - localSubs := _localSubs[:0] - sl.localSubs(&localSubs, false) - - // Go through all local subscriptions - for _, sub := range localSubs { - // Get all subs that can now be imported - subj := string(sub.subject) - couldImportThen := oldPermsTester.canImport(subj) - canImportNow := newPermsTester.canImport(subj) - if canImportNow { - // If we could not before, then will need to send a SUB protocol. - if !couldImportThen { - subsNeedSUB[route] = append(subsNeedSUB[route], sub) - } - } else if couldImportThen { - // We were previously able to import this sub, but now - // we can't so we need to send an UNSUB protocol - subsNeedUNSUB[route] = append(subsNeedUNSUB[route], sub) - } - } - deleteRoutedSubs = deleteRoutedSubs[:0] - route.mu.Lock() - pa, _, hasSubType := route.getRoutedSubKeyInfo() - for key, sub := range route.subs { - // If this is not a pinned-account route, we need to get the - // account name from the key to see if we collect this sub. - if !pa { - if an := getAccNameFromRoutedSubKey(sub, key, hasSubType); an != accName { - continue - } - } - // If we can't export, we need to drop the subscriptions that - // we have on behalf of this route. - // Need to make a string cast here since canExport call sl.Match() - subj := string(sub.subject) - if !route.canExport(subj) { - // We can use bytesToString() here. - delete(route.subs, bytesToString(sub.sid)) - deleteRoutedSubs = append(deleteRoutedSubs, sub) - } - } - route.mu.Unlock() - // Remove as a batch all the subs that we have removed from each route. - sl.RemoveBatch(deleteRoutedSubs) - return true - }) - - // Send an update INFO, which will allow remote server to show - // our current route config in monitoring and resend subscriptions - // that we now possibly allow with a change of Export permissions. - for _, route := range routes { - route.mu.Lock() - route.enqueueProto(infoJSON) - // Now send SUB and UNSUB protocols as needed. - if subs, ok := subsNeedSUB[route]; ok && len(subs) > 0 { - route.sendRouteSubProtos(subs, false, nil) - } - if unsubs, ok := subsNeedUNSUB[route]; ok && len(unsubs) > 0 { - route.sendRouteUnSubProtos(unsubs, false, nil) - } - route.mu.Unlock() - } -} - -func (s *Server) reloadClusterPoolAndAccounts(co *clusterOption, opts *Options) { - s.mu.Lock() - // Prevent adding new routes until we are ready to do so. - s.routesReject = true - var ch chan struct{} - // For accounts that have been added to the list of dedicated routes, - // send a protocol to their current assigned routes to allow the - // other side to prepare for the changes. - if len(co.accsAdded) > 0 { - protosSent := 0 - s.accAddedReqID = nuid.Next() - for _, an := range co.accsAdded { - if s.accRoutes == nil { - s.accRoutes = make(map[string]map[string]*client) - } - // In case a config reload was first done on another server, - // we may have already switched this account to a dedicated route. - // But we still want to send the protocol over the routes that - // would have otherwise handled it. - if _, ok := s.accRoutes[an]; !ok { - s.accRoutes[an] = make(map[string]*client) - } - if a, ok := s.accounts.Load(an); ok { - acc := a.(*Account) - acc.mu.Lock() - sl := acc.sl - // Get the current route pool index before calling setRouteInfo. - rpi := acc.routePoolIdx - // Switch to per-account route if not already done. - if rpi >= 0 { - s.setRouteInfo(acc) - } else { - // If it was transitioning, make sure we set it to the state - // that indicates that it has a dedicated route - if rpi == accTransitioningToDedicatedRoute { - acc.routePoolIdx = accDedicatedRoute - } - // Otherwise get the route pool index it would have been before - // the move so we can send the protocol to those routes. - rpi = s.computeRoutePoolIdx(acc) - } - acc.mu.Unlock() - // Generate the INFO protocol to send indicating that this account - // is being moved to a dedicated route. - ri := Info{ - RoutePoolSize: s.routesPoolSize, - RouteAccount: an, - RouteAccReqID: s.accAddedReqID, - } - proto := generateInfoJSON(&ri) - // Go over each remote's route at pool index `rpi` and remove - // remote subs for this account and send the protocol. - s.forEachRouteIdx(rpi, func(r *client) bool { - r.mu.Lock() - // Exclude routes to servers that don't support pooling. - if !r.route.noPool { - if subs := r.removeRemoteSubsForAcc(an); len(subs) > 0 { - sl.RemoveBatch(subs) - } - r.enqueueProto(proto) - protosSent++ - } - r.mu.Unlock() - return true - }) - } - } - if protosSent > 0 { - s.accAddedCh = make(chan struct{}, protosSent) - ch = s.accAddedCh - } - } - // Collect routes that need to be closed. - routes := make(map[*client]struct{}) - // Collect the per-account routes that need to be closed. - if len(co.accsRemoved) > 0 { - for _, an := range co.accsRemoved { - if remotes, ok := s.accRoutes[an]; ok && remotes != nil { - for _, r := range remotes { - if r != nil { - r.setNoReconnect() - routes[r] = struct{}{} - } - } - } - } - } - // If the pool size has changed, we need to close all pooled routes. - if co.poolSizeChanged { - s.forEachNonPerAccountRoute(func(r *client) { - routes[r] = struct{}{} - }) - } - // If there are routes to close, we need to release the server lock. - // Same if we need to wait on responses from the remotes when - // processing new per-account routes. - if len(routes) > 0 || len(ch) > 0 { - s.mu.Unlock() - - for done := false; !done && len(ch) > 0; { - select { - case <-ch: - case <-time.After(2 * time.Second): - s.Warnf("Timed out waiting for confirmation from all routes regarding per-account routes changes") - done = true - } - } - - for r := range routes { - r.closeConnection(RouteRemoved) - } - - s.mu.Lock() - } - // Clear the accAddedCh/ReqID fields in case they were set. - s.accAddedReqID, s.accAddedCh = _EMPTY_, nil - // Now that per-account routes that needed to be closed are closed, - // remove them from s.accRoutes. Doing so before would prevent - // removeRoute() to do proper cleanup because the route would not - // be found in s.accRoutes. - for _, an := range co.accsRemoved { - delete(s.accRoutes, an) - // Do not lookup and call setRouteInfo() on the accounts here. - // We need first to set the new s.routesPoolSize value and - // anyway, there is no need to do here if the pool size has - // changed (since it will be called for all accounts). - } - // We have already added the accounts to s.accRoutes that needed to - // be added. - - // We should always have at least the system account with a dedicated route, - // but in case we have a configuration that disables pooling and without - // a system account, possibly set the accRoutes to nil. - if len(opts.Cluster.PinnedAccounts) == 0 { - s.accRoutes = nil - } - // Now deal with pool size updates. - if ps := opts.Cluster.PoolSize; ps > 0 { - s.routesPoolSize = ps - s.routeInfo.RoutePoolSize = ps - } else { - s.routesPoolSize = 1 - s.routeInfo.RoutePoolSize = 0 - } - // If the pool size has changed, we need to recompute all accounts' route - // pool index. Note that the added/removed accounts will be reset there - // too, but that's ok (we could use a map to exclude them, but not worth it). - if co.poolSizeChanged { - s.accounts.Range(func(_, v any) bool { - acc := v.(*Account) - acc.mu.Lock() - s.setRouteInfo(acc) - acc.mu.Unlock() - return true - }) - } else if len(co.accsRemoved) > 0 { - // For accounts that no longer have a dedicated route, we need to send - // the subsriptions on the existing pooled routes for those accounts. - for _, an := range co.accsRemoved { - if a, ok := s.accounts.Load(an); ok { - acc := a.(*Account) - acc.mu.Lock() - // First call this which will assign a new route pool index. - s.setRouteInfo(acc) - // Get the value so we can send the subscriptions interest - // on all routes with this pool index. - rpi := acc.routePoolIdx - acc.mu.Unlock() - s.forEachRouteIdx(rpi, func(r *client) bool { - // We have the guarantee that if the route exists, it - // is not a new one that would have been created when - // we released the server lock if some routes needed - // to be closed, because we have set s.routesReject - // to `true` at the top of this function. - s.sendSubsToRoute(r, rpi, an) - return true - }) - } - } - } - // Allow routes to be accepted now. - s.routesReject = false - // If there is a pool size change or added accounts, solicit routes now. - if co.poolSizeChanged || len(co.accsAdded) > 0 { - s.solicitRoutes(opts.Routes, co.accsAdded) - } - s.mu.Unlock() -} - -// validateClusterOpts ensures the new ClusterOpts does not change some of the -// fields that do not support reload. -func validateClusterOpts(old, new ClusterOpts) error { - if old.Host != new.Host { - return fmt.Errorf("config reload not supported for cluster host: old=%s, new=%s", - old.Host, new.Host) - } - if old.Port != new.Port { - return fmt.Errorf("config reload not supported for cluster port: old=%d, new=%d", - old.Port, new.Port) - } - // Validate Cluster.Advertise syntax - if new.Advertise != "" { - if _, _, err := parseHostPort(new.Advertise, 0); err != nil { - return fmt.Errorf("invalid Cluster.Advertise value of %s, err=%v", new.Advertise, err) - } - } - return nil -} - -// diffRoutes diffs the old routes and the new routes and returns the ones that -// should be added and removed from the server. -func diffRoutes(old, new []*url.URL) (add, remove []*url.URL) { - // Find routes to remove. -removeLoop: - for _, oldRoute := range old { - for _, newRoute := range new { - if urlsAreEqual(oldRoute, newRoute) { - continue removeLoop - } - } - remove = append(remove, oldRoute) - } - - // Find routes to add. -addLoop: - for _, newRoute := range new { - for _, oldRoute := range old { - if urlsAreEqual(oldRoute, newRoute) { - continue addLoop - } - } - add = append(add, newRoute) - } - - return add, remove -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ring.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ring.go deleted file mode 100644 index 1db39613..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/ring.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2018-2020 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -// We wrap to hold onto optional items for /connz. -type closedClient struct { - ConnInfo - subs []SubDetail - user string - acc string -} - -// Fixed sized ringbuffer for closed connections. -type closedRingBuffer struct { - total uint64 - conns []*closedClient -} - -// Create a new ring buffer with at most max items. -func newClosedRingBuffer(max int) *closedRingBuffer { - rb := &closedRingBuffer{} - rb.conns = make([]*closedClient, max) - return rb -} - -// Adds in a new closed connection. If there is no more room, -// remove the oldest. -func (rb *closedRingBuffer) append(cc *closedClient) { - rb.conns[rb.next()] = cc - rb.total++ -} - -func (rb *closedRingBuffer) next() int { - return int(rb.total % uint64(cap(rb.conns))) -} - -func (rb *closedRingBuffer) len() int { - if rb.total > uint64(cap(rb.conns)) { - return cap(rb.conns) - } - return int(rb.total) -} - -func (rb *closedRingBuffer) totalConns() uint64 { - return rb.total -} - -// This will return a sorted copy of the list which recipient can -// modify. If the contents of the client itself need to be modified, -// meaning swapping in any optional items, a copy should be made. We -// could introduce a new lock and hold that but since we return this -// list inside monitor which allows programatic access, we do not -// know when it would be done. -func (rb *closedRingBuffer) closedClients() []*closedClient { - dup := make([]*closedClient, rb.len()) - head := rb.next() - if rb.total <= uint64(cap(rb.conns)) || head == 0 { - copy(dup, rb.conns[:rb.len()]) - } else { - fp := rb.conns[head:] - sp := rb.conns[:head] - copy(dup, fp) - copy(dup[len(fp):], sp) - } - return dup -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/route.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/route.go deleted file mode 100644 index d56cdf97..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/route.go +++ /dev/null @@ -1,3225 +0,0 @@ -// Copyright 2013-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "crypto/tls" - "encoding/json" - "fmt" - "math/rand" - "net" - "net/url" - "reflect" - "runtime" - "strconv" - "strings" - "sync/atomic" - "time" - - "github.com/klauspost/compress/s2" -) - -// RouteType designates the router type -type RouteType int - -// Type of Route -const ( - // This route we learned from speaking to other routes. - Implicit RouteType = iota - // This route was explicitly configured. - Explicit -) - -// Include the space for the proto -var ( - aSubBytes = []byte{'A', '+', ' '} - aUnsubBytes = []byte{'A', '-', ' '} - rSubBytes = []byte{'R', 'S', '+', ' '} - rUnsubBytes = []byte{'R', 'S', '-', ' '} - lSubBytes = []byte{'L', 'S', '+', ' '} - lUnsubBytes = []byte{'L', 'S', '-', ' '} -) - -type route struct { - remoteID string - remoteName string - didSolicit bool - retry bool - lnoc bool - lnocu bool - routeType RouteType - url *url.URL - authRequired bool - tlsRequired bool - jetstream bool - connectURLs []string - wsConnURLs []string - gatewayURL string - leafnodeURL string - hash string - idHash string - // Location of the route in the slice: s.routes[remoteID][]*client. - // Initialized to -1 on creation, as to indicate that it is not - // added to the list. - poolIdx int - // If this is set, it means that the route is dedicated for this - // account and the account name will not be included in protocols. - accName []byte - // This is set to true if this is a route connection to an old - // server or a server that has pooling completely disabled. - noPool bool - // Selected compression mode, which may be different from the - // server configured mode. - compression string - // Transient value used to set the Info.GossipMode when initiating - // an implicit route and sending to the remote. - gossipMode byte -} - -// Do not change the values/order since they are exchanged between servers. -const ( - gossipDefault = byte(iota) - gossipDisabled - gossipOverride -) - -type connectInfo struct { - Echo bool `json:"echo"` - Verbose bool `json:"verbose"` - Pedantic bool `json:"pedantic"` - User string `json:"user,omitempty"` - Pass string `json:"pass,omitempty"` - TLS bool `json:"tls_required"` - Headers bool `json:"headers"` - Name string `json:"name"` - Cluster string `json:"cluster"` - Dynamic bool `json:"cluster_dynamic,omitempty"` - LNOC bool `json:"lnoc,omitempty"` - LNOCU bool `json:"lnocu,omitempty"` // Support for LS- with origin cluster name - Gateway string `json:"gateway,omitempty"` -} - -// Route protocol constants -const ( - ConProto = "CONNECT %s" + _CRLF_ - InfoProto = "INFO %s" + _CRLF_ -) - -const ( - // Warning when user configures cluster TLS insecure - clusterTLSInsecureWarning = "TLS certificate chain and hostname of solicited routes will not be verified. DO NOT USE IN PRODUCTION!" - - // The default ping interval is set to 2 minutes, which is fine for client - // connections, etc.. but for route compression, the CompressionS2Auto - // mode uses RTT measurements (ping/pong) to decide which compression level - // to use, we want the interval to not be that high. - defaultRouteMaxPingInterval = 30 * time.Second -) - -// Can be changed for tests -var ( - routeConnectDelay = DEFAULT_ROUTE_CONNECT - routeMaxPingInterval = defaultRouteMaxPingInterval -) - -// removeReplySub is called when we trip the max on remoteReply subs. -func (c *client) removeReplySub(sub *subscription) { - if sub == nil { - return - } - // Lookup the account based on sub.sid. - if i := bytes.Index(sub.sid, []byte(" ")); i > 0 { - // First part of SID for route is account name. - if v, ok := c.srv.accounts.Load(bytesToString(sub.sid[:i])); ok { - (v.(*Account)).sl.Remove(sub) - } - c.mu.Lock() - delete(c.subs, bytesToString(sub.sid)) - c.mu.Unlock() - } -} - -func (c *client) processAccountSub(arg []byte) error { - if c.kind == GATEWAY { - return c.processGatewayAccountSub(string(arg)) - } - return nil -} - -func (c *client) processAccountUnsub(arg []byte) { - if c.kind == GATEWAY { - c.processGatewayAccountUnsub(string(arg)) - } -} - -// Process an inbound LMSG specification from the remote route. This means -// we have an origin cluster and we force header semantics. -func (c *client) processRoutedOriginClusterMsgArgs(arg []byte) error { - // Unroll splitArgs to avoid runtime/heap issues - a := [MAX_HMSG_ARGS + 1][]byte{} - args := a[:0] - start := -1 - for i, b := range arg { - switch b { - case ' ', '\t', '\r', '\n': - if start >= 0 { - args = append(args, arg[start:i]) - start = -1 - } - default: - if start < 0 { - start = i - } - } - } - if start >= 0 { - args = append(args, arg[start:]) - } - - var an []byte - if c.kind == ROUTER { - if an = c.route.accName; len(an) > 0 && len(args) > 2 { - args = append(args[:2], args[1:]...) - args[1] = an - } - } - c.pa.arg = arg - switch len(args) { - case 0, 1, 2, 3, 4: - return fmt.Errorf("processRoutedOriginClusterMsgArgs Parse Error: '%s'", args) - case 5: - c.pa.reply = nil - c.pa.queues = nil - c.pa.hdb = args[3] - c.pa.hdr = parseSize(args[3]) - c.pa.szb = args[4] - c.pa.size = parseSize(args[4]) - case 6: - c.pa.reply = args[3] - c.pa.queues = nil - c.pa.hdb = args[4] - c.pa.hdr = parseSize(args[4]) - c.pa.szb = args[5] - c.pa.size = parseSize(args[5]) - default: - // args[2] is our reply indicator. Should be + or | normally. - if len(args[3]) != 1 { - return fmt.Errorf("processRoutedOriginClusterMsgArgs Bad or Missing Reply Indicator: '%s'", args[3]) - } - switch args[3][0] { - case '+': - c.pa.reply = args[4] - case '|': - c.pa.reply = nil - default: - return fmt.Errorf("processRoutedOriginClusterMsgArgs Bad or Missing Reply Indicator: '%s'", args[3]) - } - - // Grab header size. - c.pa.hdb = args[len(args)-2] - c.pa.hdr = parseSize(c.pa.hdb) - - // Grab size. - c.pa.szb = args[len(args)-1] - c.pa.size = parseSize(c.pa.szb) - - // Grab queue names. - if c.pa.reply != nil { - c.pa.queues = args[5 : len(args)-2] - } else { - c.pa.queues = args[4 : len(args)-2] - } - } - if c.pa.hdr < 0 { - return fmt.Errorf("processRoutedOriginClusterMsgArgs Bad or Missing Header Size: '%s'", arg) - } - if c.pa.size < 0 { - return fmt.Errorf("processRoutedOriginClusterMsgArgs Bad or Missing Size: '%s'", args) - } - if c.pa.hdr > c.pa.size { - return fmt.Errorf("processRoutedOriginClusterMsgArgs Header Size larger then TotalSize: '%s'", arg) - } - - // Common ones processed after check for arg length - c.pa.origin = args[0] - c.pa.account = args[1] - c.pa.subject = args[2] - if len(an) > 0 { - c.pa.pacache = c.pa.subject - } else { - c.pa.pacache = arg[len(args[0])+1 : len(args[0])+len(args[1])+len(args[2])+2] - } - return nil -} - -// Process an inbound HMSG specification from the remote route. -func (c *client) processRoutedHeaderMsgArgs(arg []byte) error { - // Unroll splitArgs to avoid runtime/heap issues - a := [MAX_HMSG_ARGS][]byte{} - args := a[:0] - var an []byte - if c.kind == ROUTER { - if an = c.route.accName; len(an) > 0 { - args = append(args, an) - } - } - start := -1 - for i, b := range arg { - switch b { - case ' ', '\t', '\r', '\n': - if start >= 0 { - args = append(args, arg[start:i]) - start = -1 - } - default: - if start < 0 { - start = i - } - } - } - if start >= 0 { - args = append(args, arg[start:]) - } - - c.pa.arg = arg - switch len(args) { - case 0, 1, 2, 3: - return fmt.Errorf("processRoutedHeaderMsgArgs Parse Error: '%s'", args) - case 4: - c.pa.reply = nil - c.pa.queues = nil - c.pa.hdb = args[2] - c.pa.hdr = parseSize(args[2]) - c.pa.szb = args[3] - c.pa.size = parseSize(args[3]) - case 5: - c.pa.reply = args[2] - c.pa.queues = nil - c.pa.hdb = args[3] - c.pa.hdr = parseSize(args[3]) - c.pa.szb = args[4] - c.pa.size = parseSize(args[4]) - default: - // args[2] is our reply indicator. Should be + or | normally. - if len(args[2]) != 1 { - return fmt.Errorf("processRoutedHeaderMsgArgs Bad or Missing Reply Indicator: '%s'", args[2]) - } - switch args[2][0] { - case '+': - c.pa.reply = args[3] - case '|': - c.pa.reply = nil - default: - return fmt.Errorf("processRoutedHeaderMsgArgs Bad or Missing Reply Indicator: '%s'", args[2]) - } - - // Grab header size. - c.pa.hdb = args[len(args)-2] - c.pa.hdr = parseSize(c.pa.hdb) - - // Grab size. - c.pa.szb = args[len(args)-1] - c.pa.size = parseSize(c.pa.szb) - - // Grab queue names. - if c.pa.reply != nil { - c.pa.queues = args[4 : len(args)-2] - } else { - c.pa.queues = args[3 : len(args)-2] - } - } - if c.pa.hdr < 0 { - return fmt.Errorf("processRoutedHeaderMsgArgs Bad or Missing Header Size: '%s'", arg) - } - if c.pa.size < 0 { - return fmt.Errorf("processRoutedHeaderMsgArgs Bad or Missing Size: '%s'", args) - } - if c.pa.hdr > c.pa.size { - return fmt.Errorf("processRoutedHeaderMsgArgs Header Size larger then TotalSize: '%s'", arg) - } - - // Common ones processed after check for arg length - c.pa.account = args[0] - c.pa.subject = args[1] - if len(an) > 0 { - c.pa.pacache = c.pa.subject - } else { - c.pa.pacache = arg[:len(args[0])+len(args[1])+1] - } - return nil -} - -// Process an inbound RMSG or LMSG specification from the remote route. -func (c *client) processRoutedMsgArgs(arg []byte) error { - // Unroll splitArgs to avoid runtime/heap issues - a := [MAX_RMSG_ARGS][]byte{} - args := a[:0] - var an []byte - if c.kind == ROUTER { - if an = c.route.accName; len(an) > 0 { - args = append(args, an) - } - } - start := -1 - for i, b := range arg { - switch b { - case ' ', '\t', '\r', '\n': - if start >= 0 { - args = append(args, arg[start:i]) - start = -1 - } - default: - if start < 0 { - start = i - } - } - } - if start >= 0 { - args = append(args, arg[start:]) - } - - c.pa.arg = arg - switch len(args) { - case 0, 1, 2: - return fmt.Errorf("processRoutedMsgArgs Parse Error: '%s'", args) - case 3: - c.pa.reply = nil - c.pa.queues = nil - c.pa.szb = args[2] - c.pa.size = parseSize(args[2]) - case 4: - c.pa.reply = args[2] - c.pa.queues = nil - c.pa.szb = args[3] - c.pa.size = parseSize(args[3]) - default: - // args[2] is our reply indicator. Should be + or | normally. - if len(args[2]) != 1 { - return fmt.Errorf("processRoutedMsgArgs Bad or Missing Reply Indicator: '%s'", args[2]) - } - switch args[2][0] { - case '+': - c.pa.reply = args[3] - case '|': - c.pa.reply = nil - default: - return fmt.Errorf("processRoutedMsgArgs Bad or Missing Reply Indicator: '%s'", args[2]) - } - // Grab size. - c.pa.szb = args[len(args)-1] - c.pa.size = parseSize(c.pa.szb) - - // Grab queue names. - if c.pa.reply != nil { - c.pa.queues = args[4 : len(args)-1] - } else { - c.pa.queues = args[3 : len(args)-1] - } - } - if c.pa.size < 0 { - return fmt.Errorf("processRoutedMsgArgs Bad or Missing Size: '%s'", args) - } - - // Common ones processed after check for arg length - c.pa.account = args[0] - c.pa.subject = args[1] - if len(an) > 0 { - c.pa.pacache = c.pa.subject - } else { - c.pa.pacache = arg[:len(args[0])+len(args[1])+1] - } - return nil -} - -// processInboundRoutedMsg is called to process an inbound msg from a route. -func (c *client) processInboundRoutedMsg(msg []byte) { - // Update statistics - c.in.msgs++ - // The msg includes the CR_LF, so pull back out for accounting. - c.in.bytes += int32(len(msg) - LEN_CR_LF) - - if c.opts.Verbose { - c.sendOK() - } - - // Mostly under testing scenarios. - if c.srv == nil { - return - } - - // If the subject (c.pa.subject) has the gateway prefix, this function will handle it. - if c.handleGatewayReply(msg) { - // We are done here. - return - } - - acc, r := c.getAccAndResultFromCache() - if acc == nil { - c.Debugf("Unknown account %q for routed message on subject: %q", c.pa.account, c.pa.subject) - return - } - - // Check for no interest, short circuit if so. - // This is the fanout scale. - if len(r.psubs)+len(r.qsubs) > 0 { - c.processMsgResults(acc, r, msg, nil, c.pa.subject, c.pa.reply, pmrNoFlag) - } -} - -// Lock should be held entering here. -func (c *client) sendRouteConnect(clusterName string, tlsRequired bool) error { - var user, pass string - if userInfo := c.route.url.User; userInfo != nil { - user = userInfo.Username() - pass, _ = userInfo.Password() - } - s := c.srv - cinfo := connectInfo{ - Echo: true, - Verbose: false, - Pedantic: false, - User: user, - Pass: pass, - TLS: tlsRequired, - Name: s.info.ID, - Headers: s.supportsHeaders(), - Cluster: clusterName, - Dynamic: s.isClusterNameDynamic(), - LNOC: true, - } - - b, err := json.Marshal(cinfo) - if err != nil { - c.Errorf("Error marshaling CONNECT to route: %v\n", err) - return err - } - c.enqueueProto([]byte(fmt.Sprintf(ConProto, b))) - return nil -} - -// Process the info message if we are a route. -func (c *client) processRouteInfo(info *Info) { - - supportsHeaders := c.srv.supportsHeaders() - clusterName := c.srv.ClusterName() - srvName := c.srv.Name() - - c.mu.Lock() - // Connection can be closed at any time (by auth timeout, etc). - // Does not make sense to continue here if connection is gone. - if c.route == nil || c.isClosed() { - c.mu.Unlock() - return - } - - s := c.srv - - // Detect route to self. - if info.ID == s.info.ID { - // Need to set this so that the close does the right thing - c.route.remoteID = info.ID - c.mu.Unlock() - c.closeConnection(DuplicateRoute) - return - } - - // Detect if we have a mismatch of cluster names. - if info.Cluster != "" && info.Cluster != clusterName { - c.mu.Unlock() - // If we are dynamic we may update our cluster name. - // Use other if remote is non dynamic or their name is "bigger" - if s.isClusterNameDynamic() && (!info.Dynamic || (strings.Compare(clusterName, info.Cluster) < 0)) { - s.setClusterName(info.Cluster) - s.removeAllRoutesExcept(info.ID) - c.mu.Lock() - } else { - c.closeConnection(ClusterNameConflict) - return - } - } - - opts := s.getOpts() - - didSolicit := c.route.didSolicit - - // If this is an async INFO from an existing route... - if c.flags.isSet(infoReceived) { - remoteID := c.route.remoteID - - // Check if this is an INFO about adding a per-account route during - // a configuration reload. - if info.RouteAccReqID != _EMPTY_ { - c.mu.Unlock() - - // If there is an account name, then the remote server is telling - // us that this account will now have its dedicated route. - if an := info.RouteAccount; an != _EMPTY_ { - acc, err := s.LookupAccount(an) - if err != nil { - s.Errorf("Error looking up account %q: %v", an, err) - return - } - s.mu.Lock() - // If running without system account and adding a dedicated - // route for an account for the first time, it could be that - // the map is nil. If so, create it. - if s.accRoutes == nil { - s.accRoutes = make(map[string]map[string]*client) - } - if _, ok := s.accRoutes[an]; !ok { - s.accRoutes[an] = make(map[string]*client) - } - acc.mu.Lock() - sl := acc.sl - rpi := acc.routePoolIdx - // Make sure that the account was not already switched. - if rpi >= 0 { - s.setRouteInfo(acc) - // Change the route pool index to indicate that this - // account is actually transitioning. This will be used - // to suppress possible remote subscription interest coming - // in while the transition is happening. - acc.routePoolIdx = accTransitioningToDedicatedRoute - } else if info.RoutePoolSize == s.routesPoolSize { - // Otherwise, and if the other side's pool size matches - // ours, get the route pool index that was handling this - // account. - rpi = s.computeRoutePoolIdx(acc) - } - acc.mu.Unlock() - // Go over each remote's route at pool index `rpi` and remove - // remote subs for this account. - s.forEachRouteIdx(rpi, func(r *client) bool { - r.mu.Lock() - // Exclude routes to servers that don't support pooling. - if !r.route.noPool { - if subs := r.removeRemoteSubsForAcc(an); len(subs) > 0 { - sl.RemoveBatch(subs) - } - } - r.mu.Unlock() - return true - }) - // Respond to the remote by clearing the RouteAccount field. - info.RouteAccount = _EMPTY_ - proto := generateInfoJSON(info) - c.mu.Lock() - c.enqueueProto(proto) - c.mu.Unlock() - s.mu.Unlock() - } else { - // If no account name is specified, this is a response from the - // remote. Simply send to the communication channel, if the - // request ID matches the current one. - s.mu.Lock() - if info.RouteAccReqID == s.accAddedReqID && s.accAddedCh != nil { - select { - case s.accAddedCh <- struct{}{}: - default: - } - } - s.mu.Unlock() - } - // In both cases, we are done here. - return - } - - // Check if this is an INFO for gateways... - if info.Gateway != _EMPTY_ { - c.mu.Unlock() - // If this server has no gateway configured, report error and return. - if !s.gateway.enabled { - // FIXME: Should this be a Fatalf()? - s.Errorf("Received information about gateway %q from %s, but gateway is not configured", - info.Gateway, remoteID) - return - } - s.processGatewayInfoFromRoute(info, remoteID) - return - } - - // We receive an INFO from a server that informs us about another server, - // so the info.ID in the INFO protocol does not match the ID of this route. - if remoteID != _EMPTY_ && remoteID != info.ID { - // We want to know if the existing route supports pooling/pinned-account - // or not when processing the implicit route. - noPool := c.route.noPool - c.mu.Unlock() - - // Process this implicit route. We will check that it is not an explicit - // route and/or that it has not been connected already. - s.processImplicitRoute(info, noPool) - return - } - - var connectURLs []string - var wsConnectURLs []string - var updateRoutePerms bool - - // If we are notified that the remote is going into LDM mode, capture route's connectURLs. - if info.LameDuckMode { - connectURLs = c.route.connectURLs - wsConnectURLs = c.route.wsConnURLs - } else { - // Update only if we detect a difference - updateRoutePerms = !reflect.DeepEqual(c.opts.Import, info.Import) || !reflect.DeepEqual(c.opts.Export, info.Export) - } - c.mu.Unlock() - - if updateRoutePerms { - s.updateRemoteRoutePerms(c, info) - } - - // If the remote is going into LDM and there are client connect URLs - // associated with this route and we are allowed to advertise, remove - // those URLs and update our clients. - if (len(connectURLs) > 0 || len(wsConnectURLs) > 0) && !opts.Cluster.NoAdvertise { - s.mu.Lock() - s.removeConnectURLsAndSendINFOToClients(connectURLs, wsConnectURLs) - s.mu.Unlock() - } - return - } - - // Check if remote has same server name than this server. - if !didSolicit && info.Name == srvName { - c.mu.Unlock() - // This is now an error and we close the connection. We need unique names for JetStream clustering. - c.Errorf("Remote server has a duplicate name: %q", info.Name) - c.closeConnection(DuplicateServerName) - return - } - - var sendDelayedInfo bool - - // First INFO, check if this server is configured for compression because - // if that is the case, we need to negotiate it with the remote server. - if needsCompression(opts.Cluster.Compression.Mode) { - accName := bytesToString(c.route.accName) - // If we did not yet negotiate... - compNeg := c.flags.isSet(compressionNegotiated) - if !compNeg { - // Prevent from getting back here. - c.flags.set(compressionNegotiated) - // Release client lock since following function will need server lock. - c.mu.Unlock() - compress, err := s.negotiateRouteCompression(c, didSolicit, accName, info.Compression, opts) - if err != nil { - c.sendErrAndErr(err.Error()) - c.closeConnection(ProtocolViolation) - return - } - if compress { - // Done for now, will get back another INFO protocol... - return - } - // No compression because one side does not want/can't, so proceed. - c.mu.Lock() - // Check that the connection did not close if the lock was released. - if c.isClosed() { - c.mu.Unlock() - return - } - } - // We can set the ping timer after we just negotiated compression above, - // or for solicited routes if we already negotiated. - if !compNeg || didSolicit { - c.setFirstPingTimer() - } - // When compression is configured, we delay the initial INFO for any - // solicited route. So we need to send the delayed INFO simply based - // on the didSolicit boolean. - sendDelayedInfo = didSolicit - } else { - // Coming from an old server, the Compression field would be the empty - // string. For servers that are configured with CompressionNotSupported, - // this makes them behave as old servers. - if info.Compression == _EMPTY_ || opts.Cluster.Compression.Mode == CompressionNotSupported { - c.route.compression = CompressionNotSupported - } else { - c.route.compression = CompressionOff - } - // When compression is not configured, we delay the initial INFO only - // for solicited pooled routes, so use the same check that we did when - // we decided to delay in createRoute(). - sendDelayedInfo = didSolicit && routeShouldDelayInfo(bytesToString(c.route.accName), opts) - } - - // Mark that the INFO protocol has been received, so we can detect updates. - c.flags.set(infoReceived) - - // Get the route's proto version. It will be used to check if the connection - // supports certain features, such as message tracing. - c.opts.Protocol = info.Proto - - // Headers - c.headers = supportsHeaders && info.Headers - - // Copy over important information. - c.route.remoteID = info.ID - c.route.authRequired = info.AuthRequired - c.route.tlsRequired = info.TLSRequired - c.route.gatewayURL = info.GatewayURL - c.route.remoteName = info.Name - c.route.lnoc = info.LNOC - c.route.lnocu = info.LNOCU - c.route.jetstream = info.JetStream - - // When sent through route INFO, if the field is set, it should be of size 1. - if len(info.LeafNodeURLs) == 1 { - c.route.leafnodeURL = info.LeafNodeURLs[0] - } - // Compute the hash of this route based on remote server name - c.route.hash = getHash(info.Name) - // Same with remote server ID (used for GW mapped replies routing). - // Use getGWHash since we don't use the same hash len for that - // for backward compatibility. - c.route.idHash = string(getGWHash(info.ID)) - - // Copy over permissions as well. - c.opts.Import = info.Import - c.opts.Export = info.Export - - // If we do not know this route's URL, construct one on the fly - // from the information provided. - if c.route.url == nil { - // Add in the URL from host and port - hp := net.JoinHostPort(info.Host, strconv.Itoa(info.Port)) - url, err := url.Parse(fmt.Sprintf("nats-route://%s/", hp)) - if err != nil { - c.Errorf("Error parsing URL from INFO: %v\n", err) - c.mu.Unlock() - c.closeConnection(ParseError) - return - } - c.route.url = url - } - // The incoming INFO from the route will have IP set - // if it has Cluster.Advertise. In that case, use that - // otherwise construct it from the remote TCP address. - if info.IP == _EMPTY_ { - // Need to get the remote IP address. - switch conn := c.nc.(type) { - case *net.TCPConn, *tls.Conn: - addr := conn.RemoteAddr().(*net.TCPAddr) - info.IP = fmt.Sprintf("nats-route://%s/", net.JoinHostPort(addr.IP.String(), - strconv.Itoa(info.Port))) - default: - info.IP = c.route.url.String() - } - } - // For accounts that are configured to have their own route: - // If this is a solicited route, we already have c.route.accName set in createRoute. - // For non solicited route (the accept side), we will set the account name that - // is present in the INFO protocol. - if didSolicit && len(c.route.accName) > 0 { - // Set it in the info.RouteAccount so that addRoute can use that - // and we properly gossip that this is a route for an account. - info.RouteAccount = string(c.route.accName) - } else if !didSolicit && info.RouteAccount != _EMPTY_ { - c.route.accName = []byte(info.RouteAccount) - } - accName := string(c.route.accName) - - // Capture the noGossip value and reset it here. - gossipMode := c.route.gossipMode - c.route.gossipMode = 0 - - // Check to see if we have this remote already registered. - // This can happen when both servers have routes to each other. - c.mu.Unlock() - - if added := s.addRoute(c, didSolicit, sendDelayedInfo, gossipMode, info, accName); added { - if accName != _EMPTY_ { - c.Debugf("Registering remote route %q for account %q", info.ID, accName) - } else { - c.Debugf("Registering remote route %q", info.ID) - } - } else { - c.Debugf("Detected duplicate remote route %q", info.ID) - c.closeConnection(DuplicateRoute) - } -} - -func (s *Server) negotiateRouteCompression(c *client, didSolicit bool, accName, infoCompression string, opts *Options) (bool, error) { - // Negotiate the appropriate compression mode (or no compression) - cm, err := selectCompressionMode(opts.Cluster.Compression.Mode, infoCompression) - if err != nil { - return false, err - } - c.mu.Lock() - // For "auto" mode, set the initial compression mode based on RTT - if cm == CompressionS2Auto { - if c.rttStart.IsZero() { - c.rtt = computeRTT(c.start) - } - cm = selectS2AutoModeBasedOnRTT(c.rtt, opts.Cluster.Compression.RTTThresholds) - } - // Keep track of the negotiated compression mode. - c.route.compression = cm - c.mu.Unlock() - - // If we end-up doing compression... - if needsCompression(cm) { - // Generate an INFO with the chosen compression mode. - s.mu.Lock() - infoProto := s.generateRouteInitialInfoJSON(accName, cm, 0, gossipDefault) - s.mu.Unlock() - - // If we solicited, then send this INFO protocol BEFORE switching - // to compression writer. However, if we did not, we send it after. - c.mu.Lock() - if didSolicit { - c.enqueueProto(infoProto) - // Make sure it is completely flushed (the pending bytes goes to - // 0) before proceeding. - for c.out.pb > 0 && !c.isClosed() { - c.flushOutbound() - } - } - // This is to notify the readLoop that it should switch to a - // (de)compression reader. - c.in.flags.set(switchToCompression) - // Create the compress writer before queueing the INFO protocol for - // a route that did not solicit. It will make sure that that proto - // is sent with compression on. - c.out.cw = s2.NewWriter(nil, s2WriterOptions(cm)...) - if !didSolicit { - c.enqueueProto(infoProto) - } - // We can now set the ping timer. - c.setFirstPingTimer() - c.mu.Unlock() - return true, nil - } - return false, nil -} - -// Possibly sends local subscriptions interest to this route -// based on changes in the remote's Export permissions. -func (s *Server) updateRemoteRoutePerms(c *client, info *Info) { - c.mu.Lock() - // Interested only on Export permissions for the remote server. - // Create "fake" clients that we will use to check permissions - // using the old permissions... - oldPerms := &RoutePermissions{Export: c.opts.Export} - oldPermsTester := &client{} - oldPermsTester.setRoutePermissions(oldPerms) - // and the new ones. - newPerms := &RoutePermissions{Export: info.Export} - newPermsTester := &client{} - newPermsTester.setRoutePermissions(newPerms) - - c.opts.Import = info.Import - c.opts.Export = info.Export - - routeAcc, poolIdx, noPool := bytesToString(c.route.accName), c.route.poolIdx, c.route.noPool - c.mu.Unlock() - - var ( - _localSubs [4096]*subscription - _allSubs [4096]*subscription - allSubs = _allSubs[:0] - ) - - s.accounts.Range(func(_, v any) bool { - acc := v.(*Account) - acc.mu.RLock() - accName, sl, accPoolIdx := acc.Name, acc.sl, acc.routePoolIdx - acc.mu.RUnlock() - - // Do this only for accounts handled by this route - if (accPoolIdx >= 0 && accPoolIdx == poolIdx) || (routeAcc == accName) || noPool { - localSubs := _localSubs[:0] - sl.localSubs(&localSubs, false) - if len(localSubs) > 0 { - allSubs = append(allSubs, localSubs...) - } - } - return true - }) - - if len(allSubs) == 0 { - return - } - - c.mu.Lock() - c.sendRouteSubProtos(allSubs, false, func(sub *subscription) bool { - subj := string(sub.subject) - // If the remote can now export but could not before, and this server can import this - // subject, then send SUB protocol. - if newPermsTester.canExport(subj) && !oldPermsTester.canExport(subj) && c.canImport(subj) { - return true - } - return false - }) - c.mu.Unlock() -} - -// sendAsyncInfoToClients sends an INFO protocol to all -// connected clients that accept async INFO updates. -// The server lock is held on entry. -func (s *Server) sendAsyncInfoToClients(regCli, wsCli bool) { - // If there are no clients supporting async INFO protocols, we are done. - // Also don't send if we are shutting down... - if s.cproto == 0 || s.isShuttingDown() { - return - } - info := s.copyInfo() - - for _, c := range s.clients { - c.mu.Lock() - // Here, we are going to send only to the clients that are fully - // registered (server has received CONNECT and first PING). For - // clients that are not at this stage, this will happen in the - // processing of the first PING (see client.processPing) - if ((regCli && !c.isWebsocket()) || (wsCli && c.isWebsocket())) && - c.opts.Protocol >= ClientProtoInfo && - c.flags.isSet(firstPongSent) { - // sendInfo takes care of checking if the connection is still - // valid or not, so don't duplicate tests here. - c.enqueueProto(c.generateClientInfoJSON(info)) - } - c.mu.Unlock() - } -} - -// This will process implicit route information received from another server. -// We will check to see if we have configured or are already connected, -// and if so we will ignore. Otherwise we will attempt to connect. -func (s *Server) processImplicitRoute(info *Info, routeNoPool bool) { - remoteID := info.ID - - s.mu.Lock() - defer s.mu.Unlock() - - // Don't connect to ourself - if remoteID == s.info.ID { - return - } - - // Snapshot server options. - opts := s.getOpts() - - // Check if this route already exists - if accName := info.RouteAccount; accName != _EMPTY_ { - // If we don't support pooling/pinned account, bail. - if opts.Cluster.PoolSize <= 0 { - return - } - if remotes, ok := s.accRoutes[accName]; ok { - if r := remotes[remoteID]; r != nil { - return - } - } - } else if _, exists := s.routes[remoteID]; exists { - return - } - // Check if we have this route as a configured route - if s.hasThisRouteConfigured(info) { - return - } - - // Initiate the connection, using info.IP instead of info.URL here... - r, err := url.Parse(info.IP) - if err != nil { - s.Errorf("Error parsing URL from INFO: %v\n", err) - return - } - - if info.AuthRequired { - r.User = url.UserPassword(opts.Cluster.Username, opts.Cluster.Password) - } - s.startGoRoutine(func() { s.connectToRoute(r, Implicit, true, info.GossipMode, info.RouteAccount) }) - // If we are processing an implicit route from a route that does not - // support pooling/pinned-accounts, we won't receive an INFO for each of - // the pinned-accounts that we would normally receive. In that case, just - // initiate routes for all our configured pinned accounts. - if routeNoPool && info.RouteAccount == _EMPTY_ && len(opts.Cluster.PinnedAccounts) > 0 { - // Copy since we are going to pass as closure to a go routine. - rURL := r - for _, an := range opts.Cluster.PinnedAccounts { - accName := an - s.startGoRoutine(func() { s.connectToRoute(rURL, Implicit, true, info.GossipMode, accName) }) - } - } -} - -// hasThisRouteConfigured returns true if info.Host:info.Port is present -// in the server's opts.Routes, false otherwise. -// Server lock is assumed to be held by caller. -func (s *Server) hasThisRouteConfigured(info *Info) bool { - routes := s.getOpts().Routes - if len(routes) == 0 { - return false - } - // This could possibly be a 0.0.0.0 host so we will also construct a second - // url with the host section of the `info.IP` (if present). - sPort := strconv.Itoa(info.Port) - urlOne := strings.ToLower(net.JoinHostPort(info.Host, sPort)) - var urlTwo string - if info.IP != _EMPTY_ { - if u, _ := url.Parse(info.IP); u != nil { - urlTwo = strings.ToLower(net.JoinHostPort(u.Hostname(), sPort)) - // Ignore if same than the first - if urlTwo == urlOne { - urlTwo = _EMPTY_ - } - } - } - for _, ri := range routes { - rHost := strings.ToLower(ri.Host) - if rHost == urlOne { - return true - } - if urlTwo != _EMPTY_ && rHost == urlTwo { - return true - } - } - return false -} - -// forwardNewRouteInfoToKnownServers possibly sends the INFO protocol of the -// new route to all routes known by this server. In turn, each server will -// contact this new route. -// Server lock held on entry. -func (s *Server) forwardNewRouteInfoToKnownServers(info *Info, rtype RouteType, didSolicit bool, localGossipMode byte) { - // Determine if this connection is resulting from a gossip notification. - fromGossip := didSolicit && rtype == Implicit - // If from gossip (but we are not overriding it) or if the remote disabled gossip, bail out. - if (fromGossip && localGossipMode != gossipOverride) || info.GossipMode == gossipDisabled { - return - } - - // Note: nonce is not used in routes. - // That being said, the info we get is the initial INFO which - // contains a nonce, but we now forward this to existing routes, - // so clear it now. - info.Nonce = _EMPTY_ - - var ( - infoGMDefault []byte - infoGMDisabled []byte - infoGMOverride []byte - ) - - generateJSON := func(gm byte) []byte { - info.GossipMode = gm - b, _ := json.Marshal(info) - return []byte(fmt.Sprintf(InfoProto, b)) - } - - getJSON := func(r *client) []byte { - if (!didSolicit && r.route.routeType == Explicit) || (didSolicit && rtype == Explicit) { - if infoGMOverride == nil { - infoGMOverride = generateJSON(gossipOverride) - } - return infoGMOverride - } else if !didSolicit { - if infoGMDisabled == nil { - infoGMDisabled = generateJSON(gossipDisabled) - } - return infoGMDisabled - } - if infoGMDefault == nil { - infoGMDefault = generateJSON(0) - } - return infoGMDefault - } - - var accRemotes map[string]*client - pinnedAccount := info.RouteAccount != _EMPTY_ - // If this is for a pinned account, we will try to send the gossip - // through our pinned account routes, but fall back to the other - // routes in case we don't have one for a given remote. - if pinnedAccount { - var ok bool - if accRemotes, ok = s.accRoutes[info.RouteAccount]; ok { - for remoteID, r := range accRemotes { - if r == nil { - continue - } - r.mu.Lock() - // Do not send to a remote that does not support pooling/pinned-accounts. - if remoteID != info.ID && !r.route.noPool { - r.enqueueProto(getJSON(r)) - } - r.mu.Unlock() - } - } - } - - s.forEachRemote(func(r *client) { - r.mu.Lock() - remoteID := r.route.remoteID - if pinnedAccount { - if _, processed := accRemotes[remoteID]; processed { - r.mu.Unlock() - return - } - } - // If this is a new route for a given account, do not send to a server - // that does not support pooling/pinned-accounts. - if remoteID != info.ID && (!pinnedAccount || !r.route.noPool) { - r.enqueueProto(getJSON(r)) - } - r.mu.Unlock() - }) -} - -// canImport is whether or not we will send a SUB for interest to the other side. -// This is for ROUTER connections only. -// Lock is held on entry. -func (c *client) canImport(subject string) bool { - // Use pubAllowed() since this checks Publish permissions which - // is what Import maps to. - return c.pubAllowedFullCheck(subject, false, true) -} - -// canExport is whether or not we will accept a SUB from the remote for a given subject. -// This is for ROUTER connections only. -// Lock is held on entry -func (c *client) canExport(subject string) bool { - // Use canSubscribe() since this checks Subscribe permissions which - // is what Export maps to. - return c.canSubscribe(subject) -} - -// Initialize or reset cluster's permissions. -// This is for ROUTER connections only. -// Client lock is held on entry -func (c *client) setRoutePermissions(perms *RoutePermissions) { - // Reset if some were set - if perms == nil { - c.perms = nil - c.mperms = nil - return - } - // Convert route permissions to user permissions. - // The Import permission is mapped to Publish - // and Export permission is mapped to Subscribe. - // For meaning of Import/Export, see canImport and canExport. - p := &Permissions{ - Publish: perms.Import, - Subscribe: perms.Export, - } - c.setPermissions(p) -} - -// Type used to hold a list of subs on a per account basis. -type asubs struct { - acc *Account - subs []*subscription -} - -// Returns the account name from the subscription's key. -// This is invoked knowing that the key contains an account name, so for a sub -// that is not from a pinned-account route. -// The `keyHasSubType` boolean indicates that the key starts with the indicator -// for leaf or regular routed subscriptions. -func getAccNameFromRoutedSubKey(sub *subscription, key string, keyHasSubType bool) string { - var accIdx int - if keyHasSubType { - // Start after the sub type indicator. - accIdx = 1 - // But if there is an origin, bump its index. - if len(sub.origin) > 0 { - accIdx = 2 - } - } - return strings.Fields(key)[accIdx] -} - -// Returns if the route is dedicated to an account, its name, and a boolean -// that indicates if this route uses the routed subscription indicator at -// the beginning of the subscription key. -// Lock held on entry. -func (c *client) getRoutedSubKeyInfo() (bool, string, bool) { - var accName string - if an := c.route.accName; len(an) > 0 { - accName = string(an) - } - return accName != _EMPTY_, accName, c.route.lnocu -} - -// removeRemoteSubs will walk the subs and remove them from the appropriate account. -func (c *client) removeRemoteSubs() { - // We need to gather these on a per account basis. - // FIXME(dlc) - We should be smarter about this.. - as := map[string]*asubs{} - c.mu.Lock() - srv := c.srv - subs := c.subs - c.subs = nil - pa, accountName, hasSubType := c.getRoutedSubKeyInfo() - c.mu.Unlock() - - for key, sub := range subs { - c.mu.Lock() - sub.max = 0 - c.mu.Unlock() - // If not a pinned-account route, we need to find the account - // name from the sub's key. - if !pa { - accountName = getAccNameFromRoutedSubKey(sub, key, hasSubType) - } - ase := as[accountName] - if ase == nil { - if v, ok := srv.accounts.Load(accountName); ok { - ase = &asubs{acc: v.(*Account), subs: []*subscription{sub}} - as[accountName] = ase - } else { - continue - } - } else { - ase.subs = append(ase.subs, sub) - } - delta := int32(1) - if len(sub.queue) > 0 { - delta = sub.qw - } - if srv.gateway.enabled { - srv.gatewayUpdateSubInterest(accountName, sub, -delta) - } - ase.acc.updateLeafNodes(sub, -delta) - } - - // Now remove the subs by batch for each account sublist. - for _, ase := range as { - c.Debugf("Removing %d subscriptions for account %q", len(ase.subs), ase.acc.Name) - ase.acc.mu.Lock() - ase.acc.sl.RemoveBatch(ase.subs) - ase.acc.mu.Unlock() - } -} - -// Removes (and returns) the subscriptions from this route's subscriptions map -// that belong to the given account. -// Lock is held on entry -func (c *client) removeRemoteSubsForAcc(name string) []*subscription { - var subs []*subscription - _, _, hasSubType := c.getRoutedSubKeyInfo() - for key, sub := range c.subs { - an := getAccNameFromRoutedSubKey(sub, key, hasSubType) - if an == name { - sub.max = 0 - subs = append(subs, sub) - delete(c.subs, key) - } - } - return subs -} - -func (c *client) parseUnsubProto(arg []byte, accInProto, hasOrigin bool) ([]byte, string, []byte, []byte, error) { - // Indicate any activity, so pub and sub or unsubs. - c.in.subs++ - - args := splitArg(arg) - - var ( - origin []byte - accountName string - queue []byte - subjIdx int - ) - // If `hasOrigin` is true, then it means this is a LS- with origin in proto. - if hasOrigin { - // We would not be here if there was not at least 1 field. - origin = args[0] - subjIdx = 1 - } - // If there is an account in the protocol, bump the subject index. - if accInProto { - subjIdx++ - } - - switch len(args) { - case subjIdx + 1: - case subjIdx + 2: - queue = args[subjIdx+1] - default: - return nil, _EMPTY_, nil, nil, fmt.Errorf("parse error: '%s'", arg) - } - if accInProto { - // If there is an account in the protocol, it is before the subject. - accountName = string(args[subjIdx-1]) - } - return origin, accountName, args[subjIdx], queue, nil -} - -// Indicates no more interest in the given account/subject for the remote side. -func (c *client) processRemoteUnsub(arg []byte, leafUnsub bool) (err error) { - srv := c.srv - if srv == nil { - return nil - } - - var accountName string - // Assume the account will be in the protocol. - accInProto := true - - c.mu.Lock() - originSupport := c.route.lnocu - if c.route != nil && len(c.route.accName) > 0 { - accountName, accInProto = string(c.route.accName), false - } - c.mu.Unlock() - - hasOrigin := leafUnsub && originSupport - _, accNameFromProto, subject, _, err := c.parseUnsubProto(arg, accInProto, hasOrigin) - if err != nil { - return fmt.Errorf("processRemoteUnsub %s", err.Error()) - } - if accInProto { - accountName = accNameFromProto - } - // Lookup the account - var acc *Account - if v, ok := srv.accounts.Load(accountName); ok { - acc = v.(*Account) - } else { - c.Debugf("Unknown account %q for subject %q", accountName, subject) - return nil - } - - c.mu.Lock() - if c.isClosed() { - c.mu.Unlock() - return nil - } - - _keya := [128]byte{} - _key := _keya[:0] - - var key string - if !originSupport { - // If it is an LS- or RS-, we use the protocol as-is as the key. - key = bytesToString(arg) - } else { - // We need to prefix with the sub type. - if leafUnsub { - _key = append(_key, keyRoutedLeafSubByte) - } else { - _key = append(_key, keyRoutedSubByte) - } - _key = append(_key, ' ') - _key = append(_key, arg...) - key = bytesToString(_key) - } - delta := int32(1) - sub, ok := c.subs[key] - if ok { - delete(c.subs, key) - acc.sl.Remove(sub) - if len(sub.queue) > 0 { - delta = sub.qw - } - } - c.mu.Unlock() - - // Update gateways and leaf nodes only if the subscription was found. - if ok { - if srv.gateway.enabled { - srv.gatewayUpdateSubInterest(accountName, sub, -delta) - } - - // Now check on leafnode updates. - acc.updateLeafNodes(sub, -delta) - } - - if c.opts.Verbose { - c.sendOK() - } - return nil -} - -func (c *client) processRemoteSub(argo []byte, hasOrigin bool) (err error) { - // Indicate activity. - c.in.subs++ - - srv := c.srv - if srv == nil { - return nil - } - - // We copy `argo` to not reference the read buffer. However, we will - // prefix with a code that says if the remote sub is for a leaf - // (hasOrigin == true) or not to prevent key collisions. Imagine: - // "RS+ foo bar baz 1\r\n" => "foo bar baz" (a routed queue sub) - // "LS+ foo bar baz\r\n" => "foo bar baz" (a route leaf sub on "baz", - // for account "bar" with origin "foo"). - // - // The sub.sid/key will be set respectively to "R foo bar baz" and - // "L foo bar baz". - // - // We also no longer add the account if it was not present (due to - // pinned-account route) since there is no need really. - // - // For routes to older server, we will still create the "arg" with - // the above layout, but we will create the sub.sid/key as before, - // that is, not including the origin for LS+ because older server - // only send LS- without origin, so we would not be able to find - // the sub in the map. - c.mu.Lock() - accountName := string(c.route.accName) - oldStyle := !c.route.lnocu - c.mu.Unlock() - - // Indicate if the account name should be in the protocol. It would be the - // case if accountName is empty. - accInProto := accountName == _EMPTY_ - - // Copy so we do not reference a potentially large buffer. - // Add 2 more bytes for the routed sub type. - arg := make([]byte, 0, 2+len(argo)) - if hasOrigin { - arg = append(arg, keyRoutedLeafSubByte) - } else { - arg = append(arg, keyRoutedSubByte) - } - arg = append(arg, ' ') - arg = append(arg, argo...) - - // Now split to get all fields. Unroll splitArgs to avoid runtime/heap issues. - a := [MAX_RSUB_ARGS][]byte{} - args := a[:0] - start := -1 - for i, b := range arg { - switch b { - case ' ', '\t', '\r', '\n': - if start >= 0 { - args = append(args, arg[start:i]) - start = -1 - } - default: - if start < 0 { - start = i - } - } - } - if start >= 0 { - args = append(args, arg[start:]) - } - - delta := int32(1) - sub := &subscription{client: c} - - // There will always be at least a subject, but its location will depend - // on if there is an origin, an account name, etc.. Since we know that - // we have added the sub type indicator as the first field, the subject - // position will be at minimum at index 1. - subjIdx := 1 - if hasOrigin { - subjIdx++ - } - if accInProto { - subjIdx++ - } - switch len(args) { - case subjIdx + 1: - sub.queue = nil - case subjIdx + 3: - sub.queue = args[subjIdx+1] - sub.qw = int32(parseSize(args[subjIdx+2])) - // TODO: (ik) We should have a non empty queue name and a queue - // weight >= 1. For 2.11, we may want to return an error if that - // is not the case, but for now just overwrite `delta` if queue - // weight is greater than 1 (it is possible after a reconnect/ - // server restart to receive a queue weight > 1 for a new sub). - if sub.qw > 1 { - delta = sub.qw - } - default: - return fmt.Errorf("processRemoteSub Parse Error: '%s'", arg) - } - // We know that the number of fields is correct. So we can access args[] based - // on where we expect the fields to be. - - // If there is an origin, it will be at index 1. - if hasOrigin { - sub.origin = args[1] - } - // For subject, use subjIdx. - sub.subject = args[subjIdx] - // If the account name is in the protocol, it will be before the subject. - if accInProto { - accountName = bytesToString(args[subjIdx-1]) - } - // Now set the sub.sid from the arg slice. However, we will have a different - // one if we use the origin or not. - start = 0 - end := len(arg) - if sub.queue != nil { - // Remove the ' ' from the arg length. - end -= 1 + len(args[subjIdx+2]) - } - if oldStyle { - // We will start at the account (if present) or at the subject. - // We first skip the "R " or "L " - start = 2 - // And if there is an origin skip that. - if hasOrigin { - start += len(sub.origin) + 1 - } - // Here we are pointing at the account (if present), or at the subject. - } - sub.sid = arg[start:end] - - // Lookup account while avoiding fetch. - // A slow fetch delays subsequent remote messages. It also avoids the expired check (see below). - // With all but memory resolver lookup can be delayed or fail. - // It is also possible that the account can't be resolved yet. - // This does not apply to the memory resolver. - // When used, perform the fetch. - staticResolver := true - if res := srv.AccountResolver(); res != nil { - if _, ok := res.(*MemAccResolver); !ok { - staticResolver = false - } - } - var acc *Account - if staticResolver { - acc, _ = srv.LookupAccount(accountName) - } else if v, ok := srv.accounts.Load(accountName); ok { - acc = v.(*Account) - } - if acc == nil { - // if the option of retrieving accounts later exists, create an expired one. - // When a client comes along, expiration will prevent it from being used, - // cause a fetch and update the account to what is should be. - if staticResolver { - c.Errorf("Unknown account %q for remote subject %q", accountName, sub.subject) - return - } - c.Debugf("Unknown account %q for remote subject %q", accountName, sub.subject) - - var isNew bool - if acc, isNew = srv.LookupOrRegisterAccount(accountName); isNew { - acc.mu.Lock() - acc.expired.Store(true) - acc.incomplete = true - acc.mu.Unlock() - } - } - - c.mu.Lock() - if c.isClosed() { - c.mu.Unlock() - return nil - } - - // Check permissions if applicable. - if c.perms != nil && !c.canExport(string(sub.subject)) { - c.mu.Unlock() - c.Debugf("Can not export %q, ignoring remote subscription request", sub.subject) - return nil - } - - // Check if we have a maximum on the number of subscriptions. - if c.subsAtLimit() { - c.mu.Unlock() - c.maxSubsExceeded() - return nil - } - - acc.mu.RLock() - // For routes (this can be called by leafnodes), check if the account is - // transitioning (from pool to dedicated route) and this route is not a - // per-account route (route.poolIdx >= 0). If so, ignore this subscription. - // Exclude "no pool" routes from this check. - if c.kind == ROUTER && !c.route.noPool && - acc.routePoolIdx == accTransitioningToDedicatedRoute && c.route.poolIdx >= 0 { - acc.mu.RUnlock() - c.mu.Unlock() - // Do not return an error, which would cause the connection to be closed. - return nil - } - sl := acc.sl - acc.mu.RUnlock() - - // We use the sub.sid for the key of the c.subs map. - key := bytesToString(sub.sid) - osub := c.subs[key] - if osub == nil { - c.subs[key] = sub - // Now place into the account sl. - if err = sl.Insert(sub); err != nil { - delete(c.subs, key) - c.mu.Unlock() - c.Errorf("Could not insert subscription: %v", err) - c.sendErr("Invalid Subscription") - return nil - } - } else if sub.queue != nil { - // For a queue we need to update the weight. - delta = sub.qw - atomic.LoadInt32(&osub.qw) - atomic.StoreInt32(&osub.qw, sub.qw) - sl.UpdateRemoteQSub(osub) - } - c.mu.Unlock() - - if srv.gateway.enabled { - srv.gatewayUpdateSubInterest(acc.Name, sub, delta) - } - - // Now check on leafnode updates. - acc.updateLeafNodes(sub, delta) - - if c.opts.Verbose { - c.sendOK() - } - - return nil -} - -// Lock is held on entry -func (c *client) addRouteSubOrUnsubProtoToBuf(buf []byte, accName string, sub *subscription, isSubProto bool) []byte { - // If we have an origin cluster and the other side supports leafnode origin clusters - // send an LS+/LS- version instead. - if len(sub.origin) > 0 && c.route.lnoc { - if isSubProto { - buf = append(buf, lSubBytes...) - buf = append(buf, sub.origin...) - buf = append(buf, ' ') - } else { - buf = append(buf, lUnsubBytes...) - if c.route.lnocu { - buf = append(buf, sub.origin...) - buf = append(buf, ' ') - } - } - } else { - if isSubProto { - buf = append(buf, rSubBytes...) - } else { - buf = append(buf, rUnsubBytes...) - } - } - if len(c.route.accName) == 0 { - buf = append(buf, accName...) - buf = append(buf, ' ') - } - buf = append(buf, sub.subject...) - if len(sub.queue) > 0 { - buf = append(buf, ' ') - buf = append(buf, sub.queue...) - // Send our weight if we are a sub proto - if isSubProto { - buf = append(buf, ' ') - var b [12]byte - var i = len(b) - for l := sub.qw; l > 0; l /= 10 { - i-- - b[i] = digits[l%10] - } - buf = append(buf, b[i:]...) - } - } - buf = append(buf, CR_LF...) - return buf -} - -// sendSubsToRoute will send over our subject interest to -// the remote side. For each account we will send the -// complete interest for all subjects, both normal as a binary -// and queue group weights. -// -// Server lock held on entry. -func (s *Server) sendSubsToRoute(route *client, idx int, account string) { - var noPool bool - if idx >= 0 { - // We need to check if this route is "no_pool" in which case we - // need to select all accounts. - route.mu.Lock() - noPool = route.route.noPool - route.mu.Unlock() - } - // Estimated size of all protocols. It does not have to be accurate at all. - var eSize int - estimateProtosSize := func(a *Account, addAccountName bool) { - if ns := len(a.rm); ns > 0 { - var accSize int - if addAccountName { - accSize = len(a.Name) + 1 - } - // Proto looks like: "RS+ [ ][ ]\r\n" - eSize += ns * (len(rSubBytes) + 1 + accSize) - for key := range a.rm { - // Key contains "[ ]" - eSize += len(key) - // In case this is a queue, just add some bytes for the queue weight. - // If we want to be accurate, would have to check if "key" has a space, - // if so, then figure out how many bytes we need to represent the weight. - eSize += 5 - } - } - } - // Send over our account subscriptions. - accs := make([]*Account, 0, 1024) - if idx < 0 || account != _EMPTY_ { - if ai, ok := s.accounts.Load(account); ok { - a := ai.(*Account) - a.mu.RLock() - // Estimate size and add account name in protocol if idx is not -1 - estimateProtosSize(a, idx >= 0) - accs = append(accs, a) - a.mu.RUnlock() - } - } else { - s.accounts.Range(func(k, v any) bool { - a := v.(*Account) - a.mu.RLock() - // We are here for regular or pooled routes (not per-account). - // So we collect all accounts whose routePoolIdx matches the - // one for this route, or only the account provided, or all - // accounts if dealing with a "no pool" route. - if a.routePoolIdx == idx || noPool { - estimateProtosSize(a, true) - accs = append(accs, a) - } - a.mu.RUnlock() - return true - }) - } - - buf := make([]byte, 0, eSize) - - route.mu.Lock() - for _, a := range accs { - a.mu.RLock() - for key, n := range a.rm { - var origin, qn []byte - s := strings.Fields(key) - // Subject will always be the second field (index 1). - subj := stringToBytes(s[1]) - // Check if the key is for a leaf (will be field 0). - forLeaf := s[0] == keyRoutedLeafSub - // For queue, if not for a leaf, we need 3 fields "R foo bar", - // but if for a leaf, we need 4 fields "L foo bar leaf_origin". - if l := len(s); (!forLeaf && l == 3) || (forLeaf && l == 4) { - qn = stringToBytes(s[2]) - } - if forLeaf { - // The leaf origin will be the last field. - origin = stringToBytes(s[len(s)-1]) - } - // s[1] is the subject and already as a string, so use that - // instead of converting back `subj` to a string. - if !route.canImport(s[1]) { - continue - } - sub := subscription{origin: origin, subject: subj, queue: qn, qw: n} - buf = route.addRouteSubOrUnsubProtoToBuf(buf, a.Name, &sub, true) - } - a.mu.RUnlock() - } - if len(buf) > 0 { - route.enqueueProto(buf) - route.Debugf("Sent local subscriptions to route") - } - route.mu.Unlock() -} - -// Sends SUBs protocols for the given subscriptions. If a filter is specified, it is -// invoked for each subscription. If the filter returns false, the subscription is skipped. -// This function may release the route's lock due to flushing of outbound data. A boolean -// is returned to indicate if the connection has been closed during this call. -// Lock is held on entry. -func (c *client) sendRouteSubProtos(subs []*subscription, trace bool, filter func(sub *subscription) bool) { - c.sendRouteSubOrUnSubProtos(subs, true, trace, filter) -} - -// Sends UNSUBs protocols for the given subscriptions. If a filter is specified, it is -// invoked for each subscription. If the filter returns false, the subscription is skipped. -// This function may release the route's lock due to flushing of outbound data. A boolean -// is returned to indicate if the connection has been closed during this call. -// Lock is held on entry. -func (c *client) sendRouteUnSubProtos(subs []*subscription, trace bool, filter func(sub *subscription) bool) { - c.sendRouteSubOrUnSubProtos(subs, false, trace, filter) -} - -// Low-level function that sends RS+ or RS- protocols for the given subscriptions. -// This can now also send LS+ and LS- for origin cluster based leafnode subscriptions for cluster no-echo. -// Use sendRouteSubProtos or sendRouteUnSubProtos instead for clarity. -// Lock is held on entry. -func (c *client) sendRouteSubOrUnSubProtos(subs []*subscription, isSubProto, trace bool, filter func(sub *subscription) bool) { - var ( - _buf [1024]byte - buf = _buf[:0] - ) - - for _, sub := range subs { - if filter != nil && !filter(sub) { - continue - } - // Determine the account. If sub has an ImportMap entry, use that, otherwise scoped to - // client. Default to global if all else fails. - var accName string - if sub.client != nil && sub.client != c { - sub.client.mu.Lock() - } - if sub.im != nil { - accName = sub.im.acc.Name - } else if sub.client != nil && sub.client.acc != nil { - accName = sub.client.acc.Name - } else { - c.Debugf("Falling back to default account for sending subs") - accName = globalAccountName - } - if sub.client != nil && sub.client != c { - sub.client.mu.Unlock() - } - - as := len(buf) - buf = c.addRouteSubOrUnsubProtoToBuf(buf, accName, sub, isSubProto) - if trace { - c.traceOutOp("", buf[as:len(buf)-LEN_CR_LF]) - } - } - c.enqueueProto(buf) -} - -func (s *Server) createRoute(conn net.Conn, rURL *url.URL, rtype RouteType, gossipMode byte, accName string) *client { - // Snapshot server options. - opts := s.getOpts() - - didSolicit := rURL != nil - r := &route{routeType: rtype, didSolicit: didSolicit, poolIdx: -1, gossipMode: gossipMode} - - c := &client{srv: s, nc: conn, opts: ClientOpts{}, kind: ROUTER, msubs: -1, mpay: -1, route: r, start: time.Now()} - - // Is the server configured for compression? - compressionConfigured := needsCompression(opts.Cluster.Compression.Mode) - - var infoJSON []byte - // Grab server variables and generates route INFO Json. Note that we set - // and reset some of s.routeInfo fields when that happens, so we need - // the server write lock. - s.mu.Lock() - // If we are creating a pooled connection and this is the server soliciting - // the connection, we will delay sending the INFO after we have processed - // the incoming INFO from the remote. Also delay if configured for compression. - delayInfo := didSolicit && (compressionConfigured || routeShouldDelayInfo(accName, opts)) - if !delayInfo { - infoJSON = s.generateRouteInitialInfoJSON(accName, opts.Cluster.Compression.Mode, 0, gossipMode) - } - authRequired := s.routeInfo.AuthRequired - tlsRequired := s.routeInfo.TLSRequired - clusterName := s.info.Cluster - tlsName := s.routeTLSName - s.mu.Unlock() - - // Grab lock - c.mu.Lock() - - // Initialize - c.initClient() - - if didSolicit { - // Do this before the TLS code, otherwise, in case of failure - // and if route is explicit, it would try to reconnect to 'nil'... - r.url = rURL - r.accName = []byte(accName) - } else { - c.flags.set(expectConnect) - } - - // Check for TLS - if tlsRequired { - tlsConfig := opts.Cluster.TLSConfig - if didSolicit { - // Copy off the config to add in ServerName if we need to. - tlsConfig = tlsConfig.Clone() - } - // Perform (server or client side) TLS handshake. - if resetTLSName, err := c.doTLSHandshake("route", didSolicit, rURL, tlsConfig, tlsName, opts.Cluster.TLSTimeout, opts.Cluster.TLSPinnedCerts); err != nil { - c.mu.Unlock() - if resetTLSName { - s.mu.Lock() - s.routeTLSName = _EMPTY_ - s.mu.Unlock() - } - return nil - } - } - - // Do final client initialization - - // Initialize the per-account cache. - c.in.pacache = make(map[string]*perAccountCache) - if didSolicit { - // Set permissions associated with the route user (if applicable). - // No lock needed since we are already under client lock. - c.setRoutePermissions(opts.Cluster.Permissions) - } - - // We can't safely send the pings until we have negotiated compression - // with the remote, but we want to protect against a connection that - // does not perform the handshake. We will start a timer that will close - // the connection as stale based on the ping interval and max out values, - // but without actually sending pings. - if compressionConfigured { - pingInterval := opts.PingInterval - pingMax := opts.MaxPingsOut - if opts.Cluster.PingInterval > 0 { - pingInterval = opts.Cluster.PingInterval - } - if opts.Cluster.MaxPingsOut > 0 { - pingMax = opts.MaxPingsOut - } - c.watchForStaleConnection(adjustPingInterval(ROUTER, pingInterval), pingMax) - } else { - // Set the Ping timer - c.setFirstPingTimer() - } - - // For routes, the "client" is added to s.routes only when processing - // the INFO protocol, that is much later. - // In the meantime, if the server shutsdown, there would be no reference - // to the client (connection) to be closed, leaving this readLoop - // uinterrupted, causing the Shutdown() to wait indefinitively. - // We need to store the client in a special map, under a special lock. - if !s.addToTempClients(c.cid, c) { - c.mu.Unlock() - c.setNoReconnect() - c.closeConnection(ServerShutdown) - return nil - } - - // Check for Auth required state for incoming connections. - // Make sure to do this before spinning up readLoop. - if authRequired && !didSolicit { - ttl := secondsToDuration(opts.Cluster.AuthTimeout) - c.setAuthTimer(ttl) - } - - // Spin up the read loop. - s.startGoRoutine(func() { c.readLoop(nil) }) - - // Spin up the write loop. - s.startGoRoutine(func() { c.writeLoop() }) - - if tlsRequired { - c.Debugf("TLS handshake complete") - cs := c.nc.(*tls.Conn).ConnectionState() - c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tlsCipher(cs.CipherSuite)) - } - - // Queue Connect proto if we solicited the connection. - if didSolicit { - c.Debugf("Route connect msg sent") - if err := c.sendRouteConnect(clusterName, tlsRequired); err != nil { - c.mu.Unlock() - c.closeConnection(ProtocolViolation) - return nil - } - } - - if !delayInfo { - // Send our info to the other side. - // Our new version requires dynamic information for accounts and a nonce. - c.enqueueProto(infoJSON) - } - c.mu.Unlock() - - c.Noticef("Route connection created") - return c -} - -func routeShouldDelayInfo(accName string, opts *Options) bool { - return accName == _EMPTY_ && opts.Cluster.PoolSize >= 1 -} - -// Generates a nonce and set some route info's fields before marshal'ing into JSON. -// To be used only when a route is created (to send the initial INFO protocol). -// -// Server lock held on entry. -func (s *Server) generateRouteInitialInfoJSON(accName, compression string, poolIdx int, gossipMode byte) []byte { - // New proto wants a nonce (although not used in routes, that is, not signed in CONNECT) - var raw [nonceLen]byte - nonce := raw[:] - s.generateNonce(nonce) - ri := &s.routeInfo - // Override compression with s2_auto instead of actual compression level. - if s.getOpts().Cluster.Compression.Mode == CompressionS2Auto { - compression = CompressionS2Auto - } - ri.Nonce, ri.RouteAccount, ri.RoutePoolIdx, ri.Compression, ri.GossipMode = string(nonce), accName, poolIdx, compression, gossipMode - infoJSON := generateInfoJSON(&s.routeInfo) - // Clear now that it has been serialized. Will prevent nonce to be included in async INFO that we may send. - // Same for some other fields. - ri.Nonce, ri.RouteAccount, ri.RoutePoolIdx, ri.Compression, ri.GossipMode = _EMPTY_, _EMPTY_, 0, _EMPTY_, 0 - return infoJSON -} - -const ( - _CRLF_ = "\r\n" - _EMPTY_ = "" -) - -func (s *Server) addRoute(c *client, didSolicit, sendDelayedInfo bool, gossipMode byte, info *Info, accName string) bool { - id := info.ID - - var acc *Account - if accName != _EMPTY_ { - var err error - acc, err = s.LookupAccount(accName) - if err != nil { - c.sendErrAndErr(fmt.Sprintf("Unable to lookup account %q: %v", accName, err)) - c.closeConnection(MissingAccount) - return false - } - } - - s.mu.Lock() - if !s.isRunning() || s.routesReject { - s.mu.Unlock() - return false - } - var invProtoErr string - - opts := s.getOpts() - - // Assume we are in pool mode if info.RoutePoolSize is set. We may disable - // in some cases. - pool := info.RoutePoolSize > 0 - // This is used to prevent a server with pooling to constantly trying - // to connect to a server with no pooling (for instance old server) after - // the first connection is established. - var noReconnectForOldServer bool - - // If the remote is an old server, info.RoutePoolSize will be 0, or if - // this server's Cluster.PoolSize is negative, we will behave as an old - // server and need to handle things differently. - if info.RoutePoolSize <= 0 || opts.Cluster.PoolSize < 0 { - if accName != _EMPTY_ { - invProtoErr = fmt.Sprintf("Not possible to have a dedicated route for account %q between those servers", accName) - // In this case, make sure this route does not attempt to reconnect - c.setNoReconnect() - } else { - // We will accept, but treat this remote has "no pool" - pool, noReconnectForOldServer = false, true - c.mu.Lock() - c.route.poolIdx = 0 - c.route.noPool = true - c.mu.Unlock() - // Keep track of number of routes like that. We will use that when - // sending subscriptions over routes. - s.routesNoPool++ - } - } else if s.routesPoolSize != info.RoutePoolSize { - // The cluster's PoolSize configuration must be an exact match with the remote server. - invProtoErr = fmt.Sprintf("Mismatch route pool size: %v vs %v", s.routesPoolSize, info.RoutePoolSize) - } else if didSolicit { - // For solicited route, the incoming's RoutePoolIdx should not be set. - if info.RoutePoolIdx != 0 { - invProtoErr = fmt.Sprintf("Route pool index should not be set but is set to %v", info.RoutePoolIdx) - } - } else if info.RoutePoolIdx < 0 || info.RoutePoolIdx >= s.routesPoolSize { - // For non solicited routes, if the remote sends a RoutePoolIdx, make - // sure it is a valid one (in range of the pool size). - invProtoErr = fmt.Sprintf("Invalid route pool index: %v - pool size is %v", info.RoutePoolIdx, info.RoutePoolSize) - } - if invProtoErr != _EMPTY_ { - s.mu.Unlock() - c.sendErrAndErr(invProtoErr) - c.closeConnection(ProtocolViolation) - return false - } - // If accName is set, we are dealing with a per-account connection. - if accName != _EMPTY_ { - // When an account has its own route, it will be an error if the given - // account name is not found in s.accRoutes map. - conns, exists := s.accRoutes[accName] - if !exists { - s.mu.Unlock() - c.sendErrAndErr(fmt.Sprintf("No route for account %q", accName)) - c.closeConnection(ProtocolViolation) - return false - } - remote, exists := conns[id] - if !exists { - conns[id] = c - c.mu.Lock() - idHash := c.route.idHash - cid := c.cid - rtype := c.route.routeType - if sendDelayedInfo { - cm := compressionModeForInfoProtocol(&opts.Cluster.Compression, c.route.compression) - c.enqueueProto(s.generateRouteInitialInfoJSON(accName, cm, 0, gossipMode)) - } - if c.last.IsZero() { - c.last = time.Now() - } - if acc != nil { - c.acc = acc - } - c.mu.Unlock() - - // Store this route with key being the route id hash + account name - s.storeRouteByHash(idHash+accName, c) - - // Now that we have registered the route, we can remove from the temp map. - s.removeFromTempClients(cid) - - // We don't need to send if the only route is the one we just accepted. - if len(conns) > 1 { - s.forwardNewRouteInfoToKnownServers(info, rtype, didSolicit, gossipMode) - } - - // Send subscription interest - s.sendSubsToRoute(c, -1, accName) - } else { - handleDuplicateRoute(remote, c, true) - } - s.mu.Unlock() - return !exists - } - var remote *client - // That will be the position of the connection in the slice, we initialize - // to -1 to indicate that no space was found. - idx := -1 - // This will be the size (or number of connections) in a given slice. - sz := 0 - // Check if we know about the remote server - conns, exists := s.routes[id] - if !exists { - // No, create a slice for route connections of the size of the pool - // or 1 when not in pool mode. - conns = make([]*client, s.routesPoolSize) - // Track this slice for this remote server. - s.routes[id] = conns - // Set the index to info.RoutePoolIdx because if this is a solicited - // route, this value will be 0, which is what we want, otherwise, we - // will use whatever index the remote has chosen. - idx = info.RoutePoolIdx - } else if pool { - // The remote was found. If this is a non solicited route, we will place - // the connection in the pool at the index given by info.RoutePoolIdx. - // But if there is already one, close this incoming connection as a - // duplicate. - if !didSolicit { - idx = info.RoutePoolIdx - if remote = conns[idx]; remote != nil { - handleDuplicateRoute(remote, c, false) - s.mu.Unlock() - return false - } - // Look if there is a solicited route in the pool. If there is one, - // they should all be, so stop at the first. - if url, rtype, hasSolicited := hasSolicitedRoute(conns); hasSolicited { - upgradeRouteToSolicited(c, url, rtype) - } - } else { - // If we solicit, upgrade to solicited all non-solicited routes that - // we may have registered. - c.mu.Lock() - url := c.route.url - rtype := c.route.routeType - c.mu.Unlock() - for _, r := range conns { - upgradeRouteToSolicited(r, url, rtype) - } - } - // For all cases (solicited and not) we need to count how many connections - // we already have, and for solicited route, we will find a free spot in - // the slice. - for i, r := range conns { - if idx == -1 && r == nil { - idx = i - } else if r != nil { - sz++ - } - } - } else { - remote = conns[0] - } - // If there is a spot, idx will be greater or equal to 0. - if idx >= 0 { - c.mu.Lock() - c.route.connectURLs = info.ClientConnectURLs - c.route.wsConnURLs = info.WSConnectURLs - c.route.poolIdx = idx - rtype := c.route.routeType - cid := c.cid - idHash := c.route.idHash - rHash := c.route.hash - rn := c.route.remoteName - url := c.route.url - if sendDelayedInfo { - cm := compressionModeForInfoProtocol(&opts.Cluster.Compression, c.route.compression) - c.enqueueProto(s.generateRouteInitialInfoJSON(_EMPTY_, cm, idx, gossipMode)) - } - if c.last.IsZero() { - c.last = time.Now() - } - c.mu.Unlock() - - // Add to the slice and bump the count of connections for this remote - conns[idx] = c - sz++ - // This boolean will indicate that we are registering the only - // connection in non pooled situation or we stored the very first - // connection for a given remote server. - doOnce := !pool || sz == 1 - if doOnce { - // check to be consistent and future proof. but will be same domain - if s.sameDomain(info.Domain) { - s.nodeToInfo.Store(rHash, - nodeInfo{rn, s.info.Version, s.info.Cluster, info.Domain, id, nil, nil, nil, false, info.JetStream, false, false}) - } - } - - // Store this route using the hash as the key - if pool { - idHash += strconv.Itoa(idx) - } - s.storeRouteByHash(idHash, c) - - // Now that we have registered the route, we can remove from the temp map. - s.removeFromTempClients(cid) - - if doOnce { - // If the INFO contains a Gateway URL, add it to the list for our cluster. - if info.GatewayURL != _EMPTY_ && s.addGatewayURL(info.GatewayURL) { - s.sendAsyncGatewayInfo() - } - - // We don't need to send if the only route is the one we just accepted. - if len(s.routes) > 1 { - s.forwardNewRouteInfoToKnownServers(info, rtype, didSolicit, gossipMode) - } - - // Send info about the known gateways to this route. - s.sendGatewayConfigsToRoute(c) - - // Unless disabled, possibly update the server's INFO protocol - // and send to clients that know how to handle async INFOs. - if !opts.Cluster.NoAdvertise { - s.addConnectURLsAndSendINFOToClients(info.ClientConnectURLs, info.WSConnectURLs) - } - - // Add the remote's leafnodeURL to our list of URLs and send the update - // to all LN connections. (Note that when coming from a route, LeafNodeURLs - // is an array of size 1 max). - if len(info.LeafNodeURLs) == 1 && s.addLeafNodeURL(info.LeafNodeURLs[0]) { - s.sendAsyncLeafNodeInfo() - } - } - - // Send the subscriptions interest. - s.sendSubsToRoute(c, idx, _EMPTY_) - - // In pool mode, if we did not yet reach the cap, try to connect a new connection - if pool && didSolicit && sz != s.routesPoolSize { - s.startGoRoutine(func() { - select { - case <-time.After(time.Duration(rand.Intn(100)) * time.Millisecond): - case <-s.quitCh: - // Doing this here and not as a defer because connectToRoute is also - // calling s.grWG.Done() on exit, so we do this only if we don't - // invoke connectToRoute(). - s.grWG.Done() - return - } - s.connectToRoute(url, rtype, true, gossipMode, _EMPTY_) - }) - } - } - s.mu.Unlock() - if pool { - if idx == -1 { - // Was full, so need to close connection - c.Debugf("Route pool size reached, closing extra connection to %q", id) - handleDuplicateRoute(nil, c, true) - return false - } - return true - } - // This is for non-pool mode at this point. - if exists { - handleDuplicateRoute(remote, c, noReconnectForOldServer) - } - - return !exists -} - -func hasSolicitedRoute(conns []*client) (*url.URL, RouteType, bool) { - var url *url.URL - var rtype RouteType - for _, r := range conns { - if r == nil { - continue - } - r.mu.Lock() - if r.route.didSolicit { - url = r.route.url - rtype = r.route.routeType - } - r.mu.Unlock() - if url != nil { - return url, rtype, true - } - } - return nil, 0, false -} - -func upgradeRouteToSolicited(r *client, url *url.URL, rtype RouteType) { - if r == nil { - return - } - r.mu.Lock() - if !r.route.didSolicit { - r.route.didSolicit = true - r.route.url = url - } - if rtype == Explicit { - r.route.routeType = Explicit - } - r.mu.Unlock() -} - -func handleDuplicateRoute(remote, c *client, setNoReconnect bool) { - // We used to clear some fields when closing a duplicate connection - // to prevent sending INFO protocols for the remotes to update - // their leafnode/gateway URLs. This is no longer needed since - // removeRoute() now does the right thing of doing that only when - // the closed connection was an added route connection. - c.mu.Lock() - didSolicit := c.route.didSolicit - url := c.route.url - rtype := c.route.routeType - if setNoReconnect { - c.flags.set(noReconnect) - } - c.mu.Unlock() - - if remote == nil { - return - } - - remote.mu.Lock() - if didSolicit && !remote.route.didSolicit { - remote.route.didSolicit = true - remote.route.url = url - } - // The extra route might be an configured explicit route - // so keep the state that the remote was configured. - if rtype == Explicit { - remote.route.routeType = rtype - } - // This is to mitigate the issue where both sides add the route - // on the opposite connection, and therefore end-up with both - // connections being dropped. - remote.route.retry = true - remote.mu.Unlock() -} - -// Import filter check. -func (c *client) importFilter(sub *subscription) bool { - if c.perms == nil { - return true - } - return c.canImport(string(sub.subject)) -} - -// updateRouteSubscriptionMap will make sure to update the route map for the subscription. Will -// also forward to all routes if needed. -func (s *Server) updateRouteSubscriptionMap(acc *Account, sub *subscription, delta int32) { - if acc == nil || sub == nil { - return - } - - // We only store state on local subs for transmission across all other routes. - if sub.client == nil || sub.client.kind == ROUTER || sub.client.kind == GATEWAY { - return - } - - if sub.si { - return - } - - // Copy to hold outside acc lock. - var n int32 - var ok bool - - isq := len(sub.queue) > 0 - - accLock := func() { - // Not required for code correctness, but helps reduce the number of - // updates sent to the routes when processing high number of concurrent - // queue subscriptions updates (sub/unsub). - // See https://github.com/nats-io/nats-server/pull/1126 for more details. - if isq { - acc.sqmu.Lock() - } - acc.mu.Lock() - } - accUnlock := func() { - acc.mu.Unlock() - if isq { - acc.sqmu.Unlock() - } - } - - accLock() - - // This is non-nil when we know we are in cluster mode. - rm, lqws := acc.rm, acc.lqws - if rm == nil { - accUnlock() - return - } - - // Create the subscription key which will prevent collisions between regular - // and leaf routed subscriptions. See keyFromSubWithOrigin() for details. - key := keyFromSubWithOrigin(sub) - - // Decide whether we need to send an update out to all the routes. - update := isq - - // This is where we do update to account. For queues we need to take - // special care that this order of updates is same as what is sent out - // over routes. - if n, ok = rm[key]; ok { - n += delta - if n <= 0 { - delete(rm, key) - if isq { - delete(lqws, key) - } - update = true // Update for deleting (N->0) - } else { - rm[key] = n - } - } else if delta > 0 { - n = delta - rm[key] = delta - update = true // Adding a new entry for normal sub means update (0->1) - } - - accUnlock() - - if !update { - return - } - - // If we are sending a queue sub, make a copy and place in the queue weight. - // FIXME(dlc) - We can be smarter here and avoid copying and acquiring the lock. - if isq { - sub.client.mu.Lock() - nsub := *sub - sub.client.mu.Unlock() - nsub.qw = n - sub = &nsub - } - - // We need to send out this update. Gather routes - var _routes [32]*client - routes := _routes[:0] - - s.mu.RLock() - // The account's routePoolIdx field is set/updated under the server lock - // (but also the account's lock). So we don't need to acquire the account's - // lock here to get the value. - if poolIdx := acc.routePoolIdx; poolIdx < 0 { - if conns, ok := s.accRoutes[acc.Name]; ok { - for _, r := range conns { - routes = append(routes, r) - } - } - if s.routesNoPool > 0 { - // We also need to look for "no pool" remotes (that is, routes to older - // servers or servers that have explicitly disabled pooling). - s.forEachRemote(func(r *client) { - r.mu.Lock() - if r.route.noPool { - routes = append(routes, r) - } - r.mu.Unlock() - }) - } - } else { - // We can't use s.forEachRouteIdx here since we want to check/get the - // "no pool" route ONLY if we don't find a route at the given `poolIdx`. - for _, conns := range s.routes { - if r := conns[poolIdx]; r != nil { - routes = append(routes, r) - } else if s.routesNoPool > 0 { - // Check if we have a "no pool" route at index 0, and if so, it - // means that for this remote, we have a single connection because - // that server does not have pooling. - if r := conns[0]; r != nil { - r.mu.Lock() - if r.route.noPool { - routes = append(routes, r) - } - r.mu.Unlock() - } - } - } - } - trace := atomic.LoadInt32(&s.logging.trace) == 1 - s.mu.RUnlock() - - // If we are a queue subscriber we need to make sure our updates are serialized from - // potential multiple connections. We want to make sure that the order above is preserved - // here but not necessarily all updates need to be sent. We need to block and recheck the - // n count with the lock held through sending here. We will suppress duplicate sends of same qw. - if isq { - // However, we can't hold the acc.mu lock since we allow client.mu.Lock -> acc.mu.Lock - // but not the opposite. So use a dedicated lock while holding the route's lock. - acc.sqmu.Lock() - defer acc.sqmu.Unlock() - - acc.mu.Lock() - n = rm[key] - sub.qw = n - // Check the last sent weight here. If same, then someone - // beat us to it and we can just return here. Otherwise update - if ls, ok := lqws[key]; ok && ls == n { - acc.mu.Unlock() - return - } else if n > 0 { - lqws[key] = n - } - acc.mu.Unlock() - } - - // Snapshot into array - subs := []*subscription{sub} - - // Deliver to all routes. - for _, route := range routes { - route.mu.Lock() - // Note that queue unsubs where n > 0 are still - // subscribes with a smaller weight. - route.sendRouteSubOrUnSubProtos(subs, n > 0, trace, route.importFilter) - route.mu.Unlock() - } -} - -// This starts the route accept loop in a go routine, unless it -// is detected that the server has already been shutdown. -// It will also start soliciting explicit routes. -func (s *Server) startRouteAcceptLoop() { - if s.isShuttingDown() { - return - } - - // Snapshot server options. - opts := s.getOpts() - - // Snapshot server options. - port := opts.Cluster.Port - - if port == -1 { - port = 0 - } - - // This requires lock, so do this outside of may block. - clusterName := s.ClusterName() - - s.mu.Lock() - s.Noticef("Cluster name is %s", clusterName) - if s.isClusterNameDynamic() { - s.Warnf("Cluster name was dynamically generated, consider setting one") - } - - hp := net.JoinHostPort(opts.Cluster.Host, strconv.Itoa(port)) - l, e := natsListen("tcp", hp) - s.routeListenerErr = e - if e != nil { - s.mu.Unlock() - s.Fatalf("Error listening on router port: %d - %v", opts.Cluster.Port, e) - return - } - s.Noticef("Listening for route connections on %s", - net.JoinHostPort(opts.Cluster.Host, strconv.Itoa(l.Addr().(*net.TCPAddr).Port))) - - // Check for TLSConfig - tlsReq := opts.Cluster.TLSConfig != nil - info := Info{ - ID: s.info.ID, - Name: s.info.Name, - Version: s.info.Version, - GoVersion: runtime.Version(), - AuthRequired: false, - TLSRequired: tlsReq, - TLSVerify: tlsReq, - MaxPayload: s.info.MaxPayload, - JetStream: s.info.JetStream, - Proto: s.getServerProto(), - GatewayURL: s.getGatewayURL(), - Headers: s.supportsHeaders(), - Cluster: s.info.Cluster, - Domain: s.info.Domain, - Dynamic: s.isClusterNameDynamic(), - LNOC: true, - LNOCU: true, - } - // For tests that want to simulate old servers, do not set the compression - // on the INFO protocol if configured with CompressionNotSupported. - if cm := opts.Cluster.Compression.Mode; cm != CompressionNotSupported { - info.Compression = cm - } - if ps := opts.Cluster.PoolSize; ps > 0 { - info.RoutePoolSize = ps - } - // Set this if only if advertise is not disabled - if !opts.Cluster.NoAdvertise { - info.ClientConnectURLs = s.clientConnectURLs - info.WSConnectURLs = s.websocket.connectURLs - } - // If we have selected a random port... - if port == 0 { - // Write resolved port back to options. - opts.Cluster.Port = l.Addr().(*net.TCPAddr).Port - } - // Check for Auth items - if opts.Cluster.Username != "" { - info.AuthRequired = true - } - // Check for permissions. - if opts.Cluster.Permissions != nil { - info.Import = opts.Cluster.Permissions.Import - info.Export = opts.Cluster.Permissions.Export - } - // If this server has a LeafNode accept loop, s.leafNodeInfo.IP is, - // at this point, set to the host:port for the leafnode accept URL, - // taking into account possible advertise setting. Use the LeafNodeURLs - // and set this server's leafnode accept URL. This will be sent to - // routed servers. - if !opts.LeafNode.NoAdvertise && s.leafNodeInfo.IP != _EMPTY_ { - info.LeafNodeURLs = []string{s.leafNodeInfo.IP} - } - s.routeInfo = info - // Possibly override Host/Port and set IP based on Cluster.Advertise - if err := s.setRouteInfoHostPortAndIP(); err != nil { - s.Fatalf("Error setting route INFO with Cluster.Advertise value of %s, err=%v", opts.Cluster.Advertise, err) - l.Close() - s.mu.Unlock() - return - } - // Setup state that can enable shutdown - s.routeListener = l - // Warn if using Cluster.Insecure - if tlsReq && opts.Cluster.TLSConfig.InsecureSkipVerify { - s.Warnf(clusterTLSInsecureWarning) - } - - // Now that we have the port, keep track of all ip:port that resolve to this server. - if interfaceAddr, err := net.InterfaceAddrs(); err == nil { - var localIPs []string - for i := 0; i < len(interfaceAddr); i++ { - interfaceIP, _, _ := net.ParseCIDR(interfaceAddr[i].String()) - ipStr := interfaceIP.String() - if net.ParseIP(ipStr) != nil { - localIPs = append(localIPs, ipStr) - } - } - var portStr = strconv.FormatInt(int64(s.routeInfo.Port), 10) - for _, ip := range localIPs { - ipPort := net.JoinHostPort(ip, portStr) - s.routesToSelf[ipPort] = struct{}{} - } - } - - // Start the accept loop in a different go routine. - go s.acceptConnections(l, "Route", func(conn net.Conn) { s.createRoute(conn, nil, Implicit, gossipDefault, _EMPTY_) }, nil) - - // Solicit Routes if applicable. This will not block. - s.solicitRoutes(opts.Routes, opts.Cluster.PinnedAccounts) - - s.mu.Unlock() -} - -// Similar to setInfoHostPortAndGenerateJSON, but for routeInfo. -func (s *Server) setRouteInfoHostPortAndIP() error { - opts := s.getOpts() - if opts.Cluster.Advertise != _EMPTY_ { - advHost, advPort, err := parseHostPort(opts.Cluster.Advertise, opts.Cluster.Port) - if err != nil { - return err - } - s.routeInfo.Host = advHost - s.routeInfo.Port = advPort - s.routeInfo.IP = fmt.Sprintf("nats-route://%s/", net.JoinHostPort(advHost, strconv.Itoa(advPort))) - } else { - s.routeInfo.Host = opts.Cluster.Host - s.routeInfo.Port = opts.Cluster.Port - s.routeInfo.IP = "" - } - return nil -} - -// StartRouting will start the accept loop on the cluster host:port -// and will actively try to connect to listed routes. -func (s *Server) StartRouting(clientListenReady chan struct{}) { - defer s.grWG.Done() - - // Wait for the client and leafnode listen ports to be opened, - // and the possible ephemeral ports to be selected. - <-clientListenReady - - // Start the accept loop and solicitation of explicit routes (if applicable) - s.startRouteAcceptLoop() - -} - -func (s *Server) reConnectToRoute(rURL *url.URL, rtype RouteType, accName string) { - // If A connects to B, and B to A (regardless if explicit or - // implicit - due to auto-discovery), and if each server first - // registers the route on the opposite TCP connection, the - // two connections will end-up being closed. - // Add some random delay to reduce risk of repeated failures. - delay := time.Duration(rand.Intn(100)) * time.Millisecond - if rtype == Explicit { - delay += DEFAULT_ROUTE_RECONNECT - } - select { - case <-time.After(delay): - case <-s.quitCh: - s.grWG.Done() - return - } - s.connectToRoute(rURL, rtype, false, gossipDefault, accName) -} - -// Checks to make sure the route is still valid. -func (s *Server) routeStillValid(rURL *url.URL) bool { - for _, ri := range s.getOpts().Routes { - if urlsAreEqual(ri, rURL) { - return true - } - } - return false -} - -func (s *Server) connectToRoute(rURL *url.URL, rtype RouteType, firstConnect bool, gossipMode byte, accName string) { - defer s.grWG.Done() - if rURL == nil { - return - } - // For explicit routes, we will try to connect until we succeed. For implicit - // we will try only based on the number of ConnectRetries optin. - tryForEver := rtype == Explicit - - // Snapshot server options. - opts := s.getOpts() - - const connErrFmt = "Error trying to connect to route (attempt %v): %v" - - s.mu.RLock() - resolver := s.routeResolver - excludedAddresses := s.routesToSelf - s.mu.RUnlock() - - for attempts := 0; s.isRunning(); { - if tryForEver { - if !s.routeStillValid(rURL) { - return - } - if accName != _EMPTY_ { - s.mu.RLock() - _, valid := s.accRoutes[accName] - s.mu.RUnlock() - if !valid { - return - } - } - } - var conn net.Conn - address, err := s.getRandomIP(resolver, rURL.Host, excludedAddresses) - if err == errNoIPAvail { - // This is ok, we are done. - return - } - if err == nil { - s.Debugf("Trying to connect to route on %s (%s)", rURL.Host, address) - conn, err = natsDialTimeout("tcp", address, DEFAULT_ROUTE_DIAL) - } - if err != nil { - attempts++ - if s.shouldReportConnectErr(firstConnect, attempts) { - s.Errorf(connErrFmt, attempts, err) - } else { - s.Debugf(connErrFmt, attempts, err) - } - if !tryForEver { - if opts.Cluster.ConnectRetries <= 0 { - return - } - if attempts > opts.Cluster.ConnectRetries { - return - } - } - select { - case <-s.quitCh: - return - case <-time.After(routeConnectDelay): - continue - } - } - - if tryForEver && !s.routeStillValid(rURL) { - conn.Close() - return - } - - // We have a route connection here. - // Go ahead and create it and exit this func. - s.createRoute(conn, rURL, rtype, gossipMode, accName) - return - } -} - -func (c *client) isSolicitedRoute() bool { - c.mu.Lock() - defer c.mu.Unlock() - return c.kind == ROUTER && c.route != nil && c.route.didSolicit -} - -// Save the first hostname found in route URLs. This will be used in gossip mode -// when trying to create a TLS connection by setting the tlsConfig.ServerName. -// Lock is held on entry -func (s *Server) saveRouteTLSName(routes []*url.URL) { - for _, u := range routes { - if s.routeTLSName == _EMPTY_ && net.ParseIP(u.Hostname()) == nil { - s.routeTLSName = u.Hostname() - } - } -} - -// Start connection process to provided routes. Each route connection will -// be started in a dedicated go routine. -// Lock is held on entry -func (s *Server) solicitRoutes(routes []*url.URL, accounts []string) { - s.saveRouteTLSName(routes) - for _, r := range routes { - route := r - s.startGoRoutine(func() { s.connectToRoute(route, Explicit, true, gossipDefault, _EMPTY_) }) - } - // Now go over possible per-account routes and create them. - for _, an := range accounts { - for _, r := range routes { - route, accName := r, an - s.startGoRoutine(func() { s.connectToRoute(route, Explicit, true, gossipDefault, accName) }) - } - } -} - -func (c *client) processRouteConnect(srv *Server, arg []byte, lang string) error { - // Way to detect clients that incorrectly connect to the route listen - // port. Client provide Lang in the CONNECT protocol while ROUTEs don't. - if lang != "" { - c.sendErrAndErr(ErrClientConnectedToRoutePort.Error()) - c.closeConnection(WrongPort) - return ErrClientConnectedToRoutePort - } - // Unmarshal as a route connect protocol - proto := &connectInfo{} - - if err := json.Unmarshal(arg, proto); err != nil { - return err - } - // Reject if this has Gateway which means that it would be from a gateway - // connection that incorrectly connects to the Route port. - if proto.Gateway != "" { - errTxt := fmt.Sprintf("Rejecting connection from gateway %q on the Route port", proto.Gateway) - c.Errorf(errTxt) - c.sendErr(errTxt) - c.closeConnection(WrongGateway) - return ErrWrongGateway - } - - if srv == nil { - return ErrServerNotRunning - } - - perms := srv.getOpts().Cluster.Permissions - clusterName := srv.ClusterName() - - // If we have a cluster name set, make sure it matches ours. - if proto.Cluster != clusterName { - shouldReject := true - // If we have a dynamic name we will do additional checks. - if srv.isClusterNameDynamic() { - if !proto.Dynamic || strings.Compare(clusterName, proto.Cluster) < 0 { - // We will take on their name since theirs is configured or higher then ours. - srv.setClusterName(proto.Cluster) - if !proto.Dynamic { - srv.optsMu.Lock() - srv.opts.Cluster.Name = proto.Cluster - srv.optsMu.Unlock() - } - c.mu.Lock() - remoteID := c.opts.Name - c.mu.Unlock() - srv.removeAllRoutesExcept(remoteID) - shouldReject = false - } - } - if shouldReject { - errTxt := fmt.Sprintf("Rejecting connection, cluster name %q does not match %q", proto.Cluster, clusterName) - c.Errorf(errTxt) - c.sendErr(errTxt) - c.closeConnection(ClusterNameConflict) - return ErrClusterNameRemoteConflict - } - } - - supportsHeaders := c.srv.supportsHeaders() - - // Grab connection name of remote route. - c.mu.Lock() - c.route.remoteID = c.opts.Name - c.route.lnoc = proto.LNOC - c.route.lnocu = proto.LNOCU - c.setRoutePermissions(perms) - c.headers = supportsHeaders && proto.Headers - c.mu.Unlock() - return nil -} - -// Called when we update our cluster name during negotiations with remotes. -func (s *Server) removeAllRoutesExcept(remoteID string) { - s.mu.Lock() - routes := make([]*client, 0, s.numRoutes()) - for rID, conns := range s.routes { - if rID == remoteID { - continue - } - for _, r := range conns { - if r != nil { - routes = append(routes, r) - } - } - } - for _, conns := range s.accRoutes { - for rID, r := range conns { - if rID == remoteID { - continue - } - routes = append(routes, r) - } - } - s.mu.Unlock() - - for _, r := range routes { - r.closeConnection(ClusterNameConflict) - } -} - -func (s *Server) removeRoute(c *client) { - s.mu.Lock() - defer s.mu.Unlock() - - var ( - rID string - lnURL string - gwURL string - idHash string - accName string - poolIdx = -1 - connectURLs []string - wsConnectURLs []string - opts = s.getOpts() - rURL *url.URL - noPool bool - rtype RouteType - ) - c.mu.Lock() - cid := c.cid - r := c.route - if r != nil { - rID = r.remoteID - lnURL = r.leafnodeURL - idHash = r.idHash - gwURL = r.gatewayURL - poolIdx = r.poolIdx - accName = bytesToString(r.accName) - if r.noPool { - s.routesNoPool-- - noPool = true - } - connectURLs = r.connectURLs - wsConnectURLs = r.wsConnURLs - rURL = r.url - rtype = r.routeType - } - c.mu.Unlock() - if accName != _EMPTY_ { - if conns, ok := s.accRoutes[accName]; ok { - if r := conns[rID]; r == c { - s.removeRouteByHash(idHash + accName) - delete(conns, rID) - // Do not remove or set to nil when all remotes have been - // removed from the map. The configured accounts must always - // be in the accRoutes map and addRoute expects "conns" map - // to be created. - } - } - } - // If this is still -1, it means that it was not added to the routes - // so simply remove from temp clients and we are done. - if poolIdx == -1 || accName != _EMPTY_ { - s.removeFromTempClients(cid) - return - } - if conns, ok := s.routes[rID]; ok { - // If this route was not the one stored, simply remove from the - // temporary map and be done. - if conns[poolIdx] != c { - s.removeFromTempClients(cid) - return - } - conns[poolIdx] = nil - // Now check if this was the last connection to be removed. - empty := true - for _, c := range conns { - if c != nil { - empty = false - break - } - } - // This was the last route for this remote. Remove the remote entry - // and possibly send some async INFO protocols regarding gateway - // and leafnode URLs. - if empty { - delete(s.routes, rID) - - // Since this is the last route for this remote, possibly update - // the client connect URLs and send an update to connected - // clients. - if (len(connectURLs) > 0 || len(wsConnectURLs) > 0) && !opts.Cluster.NoAdvertise { - s.removeConnectURLsAndSendINFOToClients(connectURLs, wsConnectURLs) - } - // Remove the remote's gateway URL from our list and - // send update to inbound Gateway connections. - if gwURL != _EMPTY_ && s.removeGatewayURL(gwURL) { - s.sendAsyncGatewayInfo() - } - // Remove the remote's leafNode URL from - // our list and send update to LN connections. - if lnURL != _EMPTY_ && s.removeLeafNodeURL(lnURL) { - s.sendAsyncLeafNodeInfo() - } - // If this server has pooling/pinned accounts and the route for - // this remote was a "no pool" route, attempt to reconnect. - if noPool { - if s.routesPoolSize > 1 { - s.startGoRoutine(func() { s.connectToRoute(rURL, rtype, true, gossipDefault, _EMPTY_) }) - } - if len(opts.Cluster.PinnedAccounts) > 0 { - for _, an := range opts.Cluster.PinnedAccounts { - accName := an - s.startGoRoutine(func() { s.connectToRoute(rURL, rtype, true, gossipDefault, accName) }) - } - } - } - } - // This is for gateway code. Remove this route from a map that uses - // the route hash in combination with the pool index as the key. - if s.routesPoolSize > 1 { - idHash += strconv.Itoa(poolIdx) - } - s.removeRouteByHash(idHash) - } - s.removeFromTempClients(cid) -} - -func (s *Server) isDuplicateServerName(name string) bool { - if name == _EMPTY_ { - return false - } - s.mu.RLock() - defer s.mu.RUnlock() - - if s.info.Name == name { - return true - } - for _, conns := range s.routes { - for _, r := range conns { - if r != nil { - r.mu.Lock() - duplicate := r.route.remoteName == name - r.mu.Unlock() - if duplicate { - return true - } - break - } - } - } - return false -} - -// Goes over each non-nil route connection for all remote servers -// and invokes the function `f`. It does not go over per-account -// routes. -// Server lock is held on entry. -func (s *Server) forEachNonPerAccountRoute(f func(r *client)) { - for _, conns := range s.routes { - for _, r := range conns { - if r != nil { - f(r) - } - } - } -} - -// Goes over each non-nil route connection for all remote servers -// and invokes the function `f`. This also includes the per-account -// routes. -// Server lock is held on entry. -func (s *Server) forEachRoute(f func(r *client)) { - s.forEachNonPerAccountRoute(f) - for _, conns := range s.accRoutes { - for _, r := range conns { - f(r) - } - } -} - -// Goes over each non-nil route connection at the given pool index -// location in the slice and invokes the function `f`. If the -// callback returns `true`, this function moves to the next remote. -// Otherwise, the iteration over removes stops. -// This does not include per-account routes. -// Server lock is held on entry. -func (s *Server) forEachRouteIdx(idx int, f func(r *client) bool) { - for _, conns := range s.routes { - if r := conns[idx]; r != nil { - if !f(r) { - return - } - } - } -} - -// Goes over each remote and for the first non nil route connection, -// invokes the function `f`. -// Server lock is held on entry. -func (s *Server) forEachRemote(f func(r *client)) { - for _, conns := range s.routes { - for _, r := range conns { - if r != nil { - f(r) - break - } - } - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sendq.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sendq.go deleted file mode 100644 index 5018482d..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sendq.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2020-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "strconv" - "sync" -) - -type outMsg struct { - subj string - rply string - hdr []byte - msg []byte -} - -type sendq struct { - mu sync.Mutex - q *ipQueue[*outMsg] - s *Server - a *Account -} - -func (s *Server) newSendQ(acc *Account) *sendq { - sq := &sendq{s: s, q: newIPQueue[*outMsg](s, "SendQ"), a: acc} - s.startGoRoutine(sq.internalLoop) - return sq -} - -func (sq *sendq) internalLoop() { - sq.mu.Lock() - s, q := sq.s, sq.q - sq.mu.Unlock() - - defer s.grWG.Done() - - c := s.createInternalSystemClient() - c.registerWithAccount(sq.a) - c.noIcb = true - - defer c.closeConnection(ClientClosed) - - // To optimize for not converting a string to a []byte slice. - var ( - subj [256]byte - rply [256]byte - szb [10]byte - hdb [10]byte - _msg [4096]byte - msg = _msg[:0] - ) - - for s.isRunning() { - select { - case <-s.quitCh: - return - case <-q.ch: - pms := q.pop() - for _, pm := range pms { - c.pa.subject = append(subj[:0], pm.subj...) - c.pa.size = len(pm.msg) + len(pm.hdr) - c.pa.szb = append(szb[:0], strconv.Itoa(c.pa.size)...) - if len(pm.rply) > 0 { - c.pa.reply = append(rply[:0], pm.rply...) - } else { - c.pa.reply = nil - } - msg = msg[:0] - if len(pm.hdr) > 0 { - c.pa.hdr = len(pm.hdr) - c.pa.hdb = append(hdb[:0], strconv.Itoa(c.pa.hdr)...) - msg = append(msg, pm.hdr...) - msg = append(msg, pm.msg...) - msg = append(msg, _CRLF_...) - } else { - c.pa.hdr = -1 - c.pa.hdb = nil - msg = append(msg, pm.msg...) - msg = append(msg, _CRLF_...) - } - c.processInboundClientMsg(msg) - c.pa.szb = nil - outMsgPool.Put(pm) - } - // TODO: should this be in the for-loop instead? - c.flushClients(0) - q.recycle(&pms) - } - } -} - -var outMsgPool = sync.Pool{ - New: func() any { - return &outMsg{} - }, -} - -func (sq *sendq) send(subj, rply string, hdr, msg []byte) { - if sq == nil { - return - } - out := outMsgPool.Get().(*outMsg) - out.subj, out.rply = subj, rply - out.hdr = append(out.hdr[:0], hdr...) - out.msg = append(out.msg[:0], msg...) - sq.q.push(out) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/server.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/server.go deleted file mode 100644 index dd2867d6..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/server.go +++ /dev/null @@ -1,4593 +0,0 @@ -// Copyright 2012-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "context" - "crypto/tls" - "encoding/json" - "errors" - "flag" - "fmt" - "hash/fnv" - "io" - "log" - "math/rand" - "net" - "net/http" - "net/url" - "os" - "path" - "path/filepath" - "regexp" - "runtime" - "runtime/pprof" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - // Allow dynamic profiling. - _ "net/http/pprof" - - "github.com/klauspost/compress/s2" - "github.com/nats-io/jwt/v2" - "github.com/nats-io/nats-server/v2/logger" - "github.com/nats-io/nkeys" - "github.com/nats-io/nuid" -) - -const ( - // Interval for the first PING for non client connections. - firstPingInterval = time.Second - - // This is for the first ping for client connections. - firstClientPingInterval = 2 * time.Second -) - -// These are protocol versions sent between server connections: ROUTER, LEAF and -// GATEWAY. We may have protocol versions that have a meaning only for a certain -// type of connections, but we don't have to have separate enums for that. -// However, it is CRITICAL to not change the order of those constants since they -// are exchanged between servers. When adding a new protocol version, add to the -// end of the list, don't try to group them by connection types. -const ( - // RouteProtoZero is the original Route protocol from 2009. - // http://nats.io/documentation/internals/nats-protocol/ - RouteProtoZero = iota - // RouteProtoInfo signals a route can receive more then the original INFO block. - // This can be used to update remote cluster permissions, etc... - RouteProtoInfo - // RouteProtoV2 is the new route/cluster protocol that provides account support. - RouteProtoV2 - // MsgTraceProto indicates that this server understands distributed message tracing. - MsgTraceProto -) - -// Will return the latest server-to-server protocol versions, unless the -// option to override it is set. -func (s *Server) getServerProto() int { - opts := s.getOpts() - // Initialize with the latest protocol version. - proto := MsgTraceProto - // For tests, we want to be able to make this server behave - // as an older server so check this option to see if we should override. - if opts.overrideProto < 0 { - // The option overrideProto is set to 0 by default (when creating an - // Options structure). Since this is the same value than the original - // proto RouteProtoZero, tests call setServerProtoForTest() with the - // desired protocol level, which sets it as negative value equal to: - // (wantedProto + 1) * -1. Here we compute back the real value. - proto = (opts.overrideProto * -1) - 1 - } - return proto -} - -// Used by tests. -func setServerProtoForTest(wantedProto int) int { - return (wantedProto + 1) * -1 -} - -// Info is the information sent to clients, routes, gateways, and leaf nodes, -// to help them understand information about this server. -type Info struct { - ID string `json:"server_id"` - Name string `json:"server_name"` - Version string `json:"version"` - Proto int `json:"proto"` - GitCommit string `json:"git_commit,omitempty"` - GoVersion string `json:"go"` - Host string `json:"host"` - Port int `json:"port"` - Headers bool `json:"headers"` - AuthRequired bool `json:"auth_required,omitempty"` - TLSRequired bool `json:"tls_required,omitempty"` - TLSVerify bool `json:"tls_verify,omitempty"` - TLSAvailable bool `json:"tls_available,omitempty"` - MaxPayload int32 `json:"max_payload"` - JetStream bool `json:"jetstream,omitempty"` - IP string `json:"ip,omitempty"` - CID uint64 `json:"client_id,omitempty"` - ClientIP string `json:"client_ip,omitempty"` - Nonce string `json:"nonce,omitempty"` - Cluster string `json:"cluster,omitempty"` - Dynamic bool `json:"cluster_dynamic,omitempty"` - Domain string `json:"domain,omitempty"` - ClientConnectURLs []string `json:"connect_urls,omitempty"` // Contains URLs a client can connect to. - WSConnectURLs []string `json:"ws_connect_urls,omitempty"` // Contains URLs a ws client can connect to. - LameDuckMode bool `json:"ldm,omitempty"` - Compression string `json:"compression,omitempty"` - - // Route Specific - Import *SubjectPermission `json:"import,omitempty"` - Export *SubjectPermission `json:"export,omitempty"` - LNOC bool `json:"lnoc,omitempty"` - LNOCU bool `json:"lnocu,omitempty"` - InfoOnConnect bool `json:"info_on_connect,omitempty"` // When true the server will respond to CONNECT with an INFO - ConnectInfo bool `json:"connect_info,omitempty"` // When true this is the server INFO response to CONNECT - RoutePoolSize int `json:"route_pool_size,omitempty"` - RoutePoolIdx int `json:"route_pool_idx,omitempty"` - RouteAccount string `json:"route_account,omitempty"` - RouteAccReqID string `json:"route_acc_add_reqid,omitempty"` - GossipMode byte `json:"gossip_mode,omitempty"` - - // Gateways Specific - Gateway string `json:"gateway,omitempty"` // Name of the origin Gateway (sent by gateway's INFO) - GatewayURLs []string `json:"gateway_urls,omitempty"` // Gateway URLs in the originating cluster (sent by gateway's INFO) - GatewayURL string `json:"gateway_url,omitempty"` // Gateway URL on that server (sent by route's INFO) - GatewayCmd byte `json:"gateway_cmd,omitempty"` // Command code for the receiving server to know what to do - GatewayCmdPayload []byte `json:"gateway_cmd_payload,omitempty"` // Command payload when needed - GatewayNRP bool `json:"gateway_nrp,omitempty"` // Uses new $GNR. prefix for mapped replies - GatewayIOM bool `json:"gateway_iom,omitempty"` // Indicate that all accounts will be switched to InterestOnly mode "right away" - - // LeafNode Specific - LeafNodeURLs []string `json:"leafnode_urls,omitempty"` // LeafNode URLs that the server can reconnect to. - RemoteAccount string `json:"remote_account,omitempty"` // Lets the other side know the remote account that they bind to. - - XKey string `json:"xkey,omitempty"` // Public server's x25519 key. -} - -// Server is our main struct. -type Server struct { - // Fields accessed with atomic operations need to be 64-bit aligned - gcid uint64 - // How often user logon fails due to the issuer account not being pinned. - pinnedAccFail uint64 - stats - scStats - mu sync.RWMutex - reloadMu sync.RWMutex // Write-locked when a config reload is taking place ONLY - kp nkeys.KeyPair - xkp nkeys.KeyPair - xpub string - info Info - configFile string - optsMu sync.RWMutex - opts *Options - running atomic.Bool - shutdown atomic.Bool - listener net.Listener - listenerErr error - gacc *Account - sys *internal - sysAcc atomic.Pointer[Account] - js atomic.Pointer[jetStream] - isMetaLeader atomic.Bool - jsClustered atomic.Bool - accounts sync.Map - tmpAccounts sync.Map // Temporarily stores accounts that are being built - activeAccounts int32 - accResolver AccountResolver - clients map[uint64]*client - routes map[string][]*client - routesPoolSize int // Configured pool size - routesReject bool // During reload, we may want to reject adding routes until some conditions are met - routesNoPool int // Number of routes that don't use pooling (connecting to older server for instance) - accRoutes map[string]map[string]*client // Key is account name, value is key=remoteID/value=route connection - accRouteByHash sync.Map // Key is account name, value is nil or a pool index - accAddedCh chan struct{} - accAddedReqID string - leafs map[uint64]*client - users map[string]*User - nkeys map[string]*NkeyUser - totalClients uint64 - closed *closedRingBuffer - done chan bool - start time.Time - http net.Listener - httpHandler http.Handler - httpBasePath string - profiler net.Listener - httpReqStats map[string]uint64 - routeListener net.Listener - routeListenerErr error - routeInfo Info - routeResolver netResolver - routesToSelf map[string]struct{} - routeTLSName string - leafNodeListener net.Listener - leafNodeListenerErr error - leafNodeInfo Info - leafNodeInfoJSON []byte - leafURLsMap refCountedUrlSet - leafNodeOpts struct { - resolver netResolver - dialTimeout time.Duration - } - leafRemoteCfgs []*leafNodeCfg - leafRemoteAccounts sync.Map - leafNodeEnabled bool - leafDisableConnect bool // Used in test only - leafNoCluster bool // Indicate that this server has only remotes and no cluster defined - - quitCh chan struct{} - startupComplete chan struct{} - shutdownComplete chan struct{} - - // Tracking Go routines - grMu sync.Mutex - grTmpClients map[uint64]*client - grRunning bool - grWG sync.WaitGroup // to wait on various go routines - - cproto int64 // number of clients supporting async INFO - configTime time.Time // last time config was loaded - - logging struct { - sync.RWMutex - logger Logger - trace int32 - debug int32 - traceSysAcc int32 - } - - clientConnectURLs []string - - // Used internally for quick look-ups. - clientConnectURLsMap refCountedUrlSet - - lastCURLsUpdate int64 - - // For Gateways - gatewayListener net.Listener // Accept listener - gatewayListenerErr error - gateway *srvGateway - - // Used by tests to check that http.Servers do - // not set any timeout. - monitoringServer *http.Server - profilingServer *http.Server - - // LameDuck mode - ldm bool - ldmCh chan bool - - // Trusted public operator keys. - trustedKeys []string - // map of trusted keys to operator setting StrictSigningKeyUsage - strictSigningKeyUsage map[string]struct{} - - // We use this to minimize mem copies for requests to monitoring - // endpoint /varz (when it comes from http). - varzMu sync.Mutex - varz *Varz - // This is set during a config reload if we detect that we have - // added/removed routes. The monitoring code then check that - // to know if it should update the cluster's URLs array. - varzUpdateRouteURLs bool - - // Keeps a sublist of of subscriptions attached to leafnode connections - // for the $GNR.*.*.*.> subject so that a server can send back a mapped - // gateway reply. - gwLeafSubs *Sublist - - // Used for expiration of mapped GW replies - gwrm struct { - w int32 - ch chan time.Duration - m sync.Map - } - - // For eventIDs - eventIds *nuid.NUID - - // Websocket structure - websocket srvWebsocket - - // MQTT structure - mqtt srvMQTT - - // OCSP monitoring - ocsps []*OCSPMonitor - - // OCSP peer verification (at least one TLS block) - ocspPeerVerify bool - - // OCSP response cache - ocsprc OCSPResponseCache - - // exporting account name the importer experienced issues with - incompleteAccExporterMap sync.Map - - // Holds cluster name under different lock for mapping - cnMu sync.RWMutex - cn string - - // For registering raft nodes with the server. - rnMu sync.RWMutex - raftNodes map[string]RaftNode - - // For mapping from a raft node name back to a server name and cluster. Node has to be in the same domain. - nodeToInfo sync.Map - - // For out of resources to not log errors too fast. - rerrMu sync.Mutex - rerrLast time.Time - - connRateCounter *rateCounter - - // If there is a system account configured, to still support the $G account, - // the server will create a fake user and add it to the list of users. - // Keep track of what that user name is for config reload purposes. - sysAccOnlyNoAuthUser string - - // IPQueues map - ipQueues sync.Map - - // To limit logging frequency - rateLimitLogging sync.Map - rateLimitLoggingCh chan time.Duration - - // Total outstanding catchup bytes in flight. - gcbMu sync.RWMutex - gcbOut int64 - gcbOutMax int64 // Taken from JetStreamMaxCatchup or defaultMaxTotalCatchupOutBytes - // A global chanel to kick out stalled catchup sequences. - gcbKick chan struct{} - - // Total outbound syncRequests - syncOutSem chan struct{} - - // Queue to process JS API requests that come from routes (or gateways) - jsAPIRoutedReqs *ipQueue[*jsAPIRoutedReq] - - // Delayed API responses. - delayedAPIResponses *ipQueue[*delayedAPIResponse] - - // Whether moving NRG traffic into accounts is permitted on this server. - // Controls whether or not the account NRG capability is set in statsz. - // Currently used by unit tests to simulate nodes not supporting account NRG. - accountNRGAllowed atomic.Bool -} - -// For tracking JS nodes. -type nodeInfo struct { - name string - version string - cluster string - domain string - id string - tags jwt.TagList - cfg *JetStreamConfig - stats *JetStreamStats - offline bool - js bool - binarySnapshots bool - accountNRG bool -} - -// Make sure all are 64bits for atomic use -type stats struct { - inMsgs int64 - outMsgs int64 - inBytes int64 - outBytes int64 - slowConsumers int64 -} - -// scStats includes the total and per connection counters of Slow Consumers. -type scStats struct { - clients atomic.Uint64 - routes atomic.Uint64 - leafs atomic.Uint64 - gateways atomic.Uint64 -} - -// This is used by tests so we can run all server tests with a default route -// or leafnode compression mode. For instance: -// go test -race -v ./server -cluster_compression=fast -var ( - testDefaultClusterCompression string - testDefaultLeafNodeCompression string -) - -// Compression modes. -const ( - CompressionNotSupported = "not supported" - CompressionOff = "off" - CompressionAccept = "accept" - CompressionS2Auto = "s2_auto" - CompressionS2Uncompressed = "s2_uncompressed" - CompressionS2Fast = "s2_fast" - CompressionS2Better = "s2_better" - CompressionS2Best = "s2_best" -) - -// defaultCompressionS2AutoRTTThresholds is the default of RTT thresholds for -// the CompressionS2Auto mode. -var defaultCompressionS2AutoRTTThresholds = []time.Duration{ - // [0..10ms] -> CompressionS2Uncompressed - 10 * time.Millisecond, - // ]10ms..50ms] -> CompressionS2Fast - 50 * time.Millisecond, - // ]50ms..100ms] -> CompressionS2Better - 100 * time.Millisecond, - // ]100ms..] -> CompressionS2Best -} - -// For a given user provided string, matches to one of the compression mode -// constant and updates the provided string to that constant. Returns an -// error if the provided compression mode is not known. -// The parameter `chosenModeForOn` indicates which compression mode to use -// when the user selects "on" (or enabled, true, etc..). This is because -// we may have different defaults depending on where the compression is used. -func validateAndNormalizeCompressionOption(c *CompressionOpts, chosenModeForOn string) error { - if c == nil { - return nil - } - cmtl := strings.ToLower(c.Mode) - // First, check for the "on" case so that we set to the default compression - // mode for that. The other switch/case will finish setup if needed (for - // instance if the default mode is s2Auto). - switch cmtl { - case "on", "enabled", "true": - cmtl = chosenModeForOn - default: - } - // Check (again) with the proper mode. - switch cmtl { - case "not supported", "not_supported": - c.Mode = CompressionNotSupported - case "disabled", "off", "false": - c.Mode = CompressionOff - case "accept": - c.Mode = CompressionAccept - case "auto", "s2_auto": - var rtts []time.Duration - if len(c.RTTThresholds) == 0 { - rtts = defaultCompressionS2AutoRTTThresholds - } else { - for _, n := range c.RTTThresholds { - // Do not error on negative, but simply set to 0 - if n < 0 { - n = 0 - } - // Make sure they are properly ordered. However, it is possible - // to have a "0" anywhere in the list to indicate that this - // compression level should not be used. - if l := len(rtts); l > 0 && n != 0 { - for _, v := range rtts { - if n < v { - return fmt.Errorf("RTT threshold values %v should be in ascending order", c.RTTThresholds) - } - } - } - rtts = append(rtts, n) - } - if len(rtts) > 0 { - // Trim 0 that are at the end. - stop := -1 - for i := len(rtts) - 1; i >= 0; i-- { - if rtts[i] != 0 { - stop = i - break - } - } - rtts = rtts[:stop+1] - } - if len(rtts) > 4 { - // There should be at most values for "uncompressed", "fast", - // "better" and "best" (when some 0 are present). - return fmt.Errorf("compression mode %q should have no more than 4 RTT thresholds: %v", c.Mode, c.RTTThresholds) - } else if len(rtts) == 0 { - // But there should be at least 1 if the user provided the slice. - // We would be here only if it was provided by say with values - // being a single or all zeros. - return fmt.Errorf("compression mode %q requires at least one RTT threshold", c.Mode) - } - } - c.Mode = CompressionS2Auto - c.RTTThresholds = rtts - case "fast", "s2_fast": - c.Mode = CompressionS2Fast - case "better", "s2_better": - c.Mode = CompressionS2Better - case "best", "s2_best": - c.Mode = CompressionS2Best - default: - return fmt.Errorf("unsupported compression mode %q", c.Mode) - } - return nil -} - -// Returns `true` if the compression mode `m` indicates that the server -// will negotiate compression with the remote server, `false` otherwise. -// Note that the provided compression mode is assumed to have been -// normalized and validated. -func needsCompression(m string) bool { - return m != _EMPTY_ && m != CompressionOff && m != CompressionNotSupported -} - -// Compression is asymmetric, meaning that one side can have a different -// compression level than the other. However, we need to check for cases -// when this server `scm` or the remote `rcm` do not support compression -// (say older server, or test to make it behave as it is not), or have -// the compression off. -// Note that `scm` is assumed to not be "off" or "not supported". -func selectCompressionMode(scm, rcm string) (mode string, err error) { - if rcm == CompressionNotSupported || rcm == _EMPTY_ { - return CompressionNotSupported, nil - } - switch rcm { - case CompressionOff: - // If the remote explicitly disables compression, then we won't - // use compression. - return CompressionOff, nil - case CompressionAccept: - // If the remote is ok with compression (but is not initiating it), - // and if we too are in this mode, then it means no compression. - if scm == CompressionAccept { - return CompressionOff, nil - } - // Otherwise use our compression mode. - return scm, nil - case CompressionS2Auto, CompressionS2Uncompressed, CompressionS2Fast, CompressionS2Better, CompressionS2Best: - // This case is here to make sure that if we don't recognize a - // compression setting, we error out. - if scm == CompressionAccept { - // If our compression mode is "accept", then we will use the remote - // compression mode, except if it is "auto", in which case we will - // default to "fast". This is not a configuration (auto in one - // side and accept in the other) that would be recommended. - if rcm == CompressionS2Auto { - return CompressionS2Fast, nil - } - // Use their compression mode. - return rcm, nil - } - // Otherwise use our compression mode. - return scm, nil - default: - return _EMPTY_, fmt.Errorf("unsupported route compression mode %q", rcm) - } -} - -// If the configured compression mode is "auto" then will return that, -// otherwise will return the given `cm` compression mode. -func compressionModeForInfoProtocol(co *CompressionOpts, cm string) string { - if co.Mode == CompressionS2Auto { - return CompressionS2Auto - } - return cm -} - -// Given a connection RTT and a list of thresholds durations, this -// function will return an S2 compression level such as "uncompressed", -// "fast", "better" or "best". For instance, with the following slice: -// [5ms, 10ms, 15ms, 20ms], a RTT of up to 5ms will result -// in the compression level "uncompressed", ]5ms..10ms] will result in -// "fast" compression, etc.. -// However, the 0 value allows for disabling of some compression levels. -// For instance, the following slice: [0, 0, 20, 30] means that a RTT of -// [0..20ms] would result in the "better" compression - effectively disabling -// the use of "uncompressed" and "fast", then anything above 20ms would -// result in the use of "best" level (the 30 in the list has no effect -// and the list could have been simplified to [0, 0, 20]). -func selectS2AutoModeBasedOnRTT(rtt time.Duration, rttThresholds []time.Duration) string { - var idx int - var found bool - for i, d := range rttThresholds { - if rtt <= d { - idx = i - found = true - break - } - } - if !found { - // If we did not find but we have all levels, then use "best", - // otherwise use the last one in array. - if l := len(rttThresholds); l >= 3 { - idx = 3 - } else { - idx = l - 1 - } - } - switch idx { - case 0: - return CompressionS2Uncompressed - case 1: - return CompressionS2Fast - case 2: - return CompressionS2Better - } - return CompressionS2Best -} - -// Returns an array of s2 WriterOption based on the route compression mode. -// So far we return a single option, but this way we can call s2.NewWriter() -// with a nil []s2.WriterOption, but not with a nil s2.WriterOption, so -// this is more versatile. -func s2WriterOptions(cm string) []s2.WriterOption { - _opts := [2]s2.WriterOption{} - opts := append( - _opts[:0], - s2.WriterConcurrency(1), // Stop asynchronous flushing in separate goroutines - ) - switch cm { - case CompressionS2Uncompressed: - return append(opts, s2.WriterUncompressed()) - case CompressionS2Best: - return append(opts, s2.WriterBestCompression()) - case CompressionS2Better: - return append(opts, s2.WriterBetterCompression()) - default: - return nil - } -} - -// New will setup a new server struct after parsing the options. -// DEPRECATED: Use NewServer(opts) -func New(opts *Options) *Server { - s, _ := NewServer(opts) - return s -} - -// NewServer will setup a new server struct after parsing the options. -// Could return an error if options can not be validated. -// The provided Options type should not be re-used afterwards. -// Either use Options.Clone() to pass a copy, or make a new one. -func NewServer(opts *Options) (*Server, error) { - setBaselineOptions(opts) - - // Process TLS options, including whether we require client certificates. - tlsReq := opts.TLSConfig != nil - verify := (tlsReq && opts.TLSConfig.ClientAuth == tls.RequireAndVerifyClientCert) - - // Create our server's nkey identity. - kp, _ := nkeys.CreateServer() - pub, _ := kp.PublicKey() - - // Create an xkey for encrypting messages from this server. - xkp, _ := nkeys.CreateCurveKeys() - xpub, _ := xkp.PublicKey() - - serverName := pub - if opts.ServerName != _EMPTY_ { - serverName = opts.ServerName - } - - httpBasePath := normalizeBasePath(opts.HTTPBasePath) - - // Validate some options. This is here because we cannot assume that - // server will always be started with configuration parsing (that could - // report issues). Its options can be (incorrectly) set by hand when - // server is embedded. If there is an error, return nil. - if err := validateOptions(opts); err != nil { - return nil, err - } - - info := Info{ - ID: pub, - XKey: xpub, - Version: VERSION, - Proto: PROTO, - GitCommit: gitCommit, - GoVersion: runtime.Version(), - Name: serverName, - Host: opts.Host, - Port: opts.Port, - AuthRequired: false, - TLSRequired: tlsReq && !opts.AllowNonTLS, - TLSVerify: verify, - MaxPayload: opts.MaxPayload, - JetStream: opts.JetStream, - Headers: !opts.NoHeaderSupport, - Cluster: opts.Cluster.Name, - Domain: opts.JetStreamDomain, - } - - if tlsReq && !info.TLSRequired { - info.TLSAvailable = true - } - - now := time.Now() - - s := &Server{ - kp: kp, - xkp: xkp, - xpub: xpub, - configFile: opts.ConfigFile, - info: info, - opts: opts, - done: make(chan bool, 1), - start: now, - configTime: now, - gwLeafSubs: NewSublistWithCache(), - httpBasePath: httpBasePath, - eventIds: nuid.New(), - routesToSelf: make(map[string]struct{}), - httpReqStats: make(map[string]uint64), // Used to track HTTP requests - rateLimitLoggingCh: make(chan time.Duration, 1), - leafNodeEnabled: opts.LeafNode.Port != 0 || len(opts.LeafNode.Remotes) > 0, - syncOutSem: make(chan struct{}, maxConcurrentSyncRequests), - } - - // Delayed API response queue. Create regardless if JetStream is configured - // or not (since it can be enabled/disabled with config reload, we want this - // queue to exist at all times). - s.delayedAPIResponses = newIPQueue[*delayedAPIResponse](s, "delayed API responses") - - // By default we'll allow account NRG. - s.accountNRGAllowed.Store(true) - - // Fill up the maximum in flight syncRequests for this server. - // Used in JetStream catchup semantics. - for i := 0; i < maxConcurrentSyncRequests; i++ { - s.syncOutSem <- struct{}{} - } - - if opts.TLSRateLimit > 0 { - s.connRateCounter = newRateCounter(opts.tlsConfigOpts.RateLimit) - } - - // Trusted root operator keys. - if !s.processTrustedKeys() { - return nil, fmt.Errorf("Error processing trusted operator keys") - } - - // If we have solicited leafnodes but no clustering and no clustername. - // However we may need a stable clustername so use the server name. - if len(opts.LeafNode.Remotes) > 0 && opts.Cluster.Port == 0 && opts.Cluster.Name == _EMPTY_ { - s.leafNoCluster = true - opts.Cluster.Name = opts.ServerName - } - - if opts.Cluster.Name != _EMPTY_ { - // Also place into mapping cn with cnMu lock. - s.cnMu.Lock() - s.cn = opts.Cluster.Name - s.cnMu.Unlock() - } - - s.mu.Lock() - defer s.mu.Unlock() - - // Place ourselves in the JetStream nodeInfo if needed. - if opts.JetStream { - ourNode := getHash(serverName) - s.nodeToInfo.Store(ourNode, nodeInfo{ - serverName, - VERSION, - opts.Cluster.Name, - opts.JetStreamDomain, - info.ID, - opts.Tags, - &JetStreamConfig{MaxMemory: opts.JetStreamMaxMemory, MaxStore: opts.JetStreamMaxStore, CompressOK: true}, - nil, - false, true, true, true, - }) - } - - s.routeResolver = opts.Cluster.resolver - if s.routeResolver == nil { - s.routeResolver = net.DefaultResolver - } - - // Used internally for quick look-ups. - s.clientConnectURLsMap = make(refCountedUrlSet) - s.websocket.connectURLsMap = make(refCountedUrlSet) - s.leafURLsMap = make(refCountedUrlSet) - - // Ensure that non-exported options (used in tests) are properly set. - s.setLeafNodeNonExportedOptions() - - // Setup OCSP Stapling and OCSP Peer. This will abort server from starting if there - // are no valid staples and OCSP Stapling policy is set to Always or MustStaple. - if err := s.enableOCSP(); err != nil { - return nil, err - } - - // Call this even if there is no gateway defined. It will - // initialize the structure so we don't have to check for - // it to be nil or not in various places in the code. - if err := s.newGateway(opts); err != nil { - return nil, err - } - - // If we have a cluster definition but do not have a cluster name, create one. - if opts.Cluster.Port != 0 && opts.Cluster.Name == _EMPTY_ { - s.info.Cluster = nuid.Next() - } else if opts.Cluster.Name != _EMPTY_ { - // Likewise here if we have a cluster name set. - s.info.Cluster = opts.Cluster.Name - } - - // This is normally done in the AcceptLoop, once the - // listener has been created (possibly with random port), - // but since some tests may expect the INFO to be properly - // set after New(), let's do it now. - s.setInfoHostPort() - - // For tracking clients - s.clients = make(map[uint64]*client) - - // For tracking closed clients. - s.closed = newClosedRingBuffer(opts.MaxClosedClients) - - // For tracking connections that are not yet registered - // in s.routes, but for which readLoop has started. - s.grTmpClients = make(map[uint64]*client) - - // For tracking routes and their remote ids - s.initRouteStructures(opts) - - // For tracking leaf nodes. - s.leafs = make(map[uint64]*client) - - // Used to kick out all go routines possibly waiting on server - // to shutdown. - s.quitCh = make(chan struct{}) - - // Closed when startup is complete. ReadyForConnections() will block on - // this before checking the presence of listening sockets. - s.startupComplete = make(chan struct{}) - - // Closed when Shutdown() is complete. Allows WaitForShutdown() to block - // waiting for complete shutdown. - s.shutdownComplete = make(chan struct{}) - - // Check for configured account resolvers. - if err := s.configureResolver(); err != nil { - return nil, err - } - // If there is an URL account resolver, do basic test to see if anyone is home. - if ar := opts.AccountResolver; ar != nil { - if ur, ok := ar.(*URLAccResolver); ok { - if _, err := ur.Fetch(_EMPTY_); err != nil { - return nil, err - } - } - } - // For other resolver: - // In operator mode, when the account resolver depends on an external system and - // the system account can't be fetched, inject a temporary one. - if ar := s.accResolver; len(opts.TrustedOperators) == 1 && ar != nil && - opts.SystemAccount != _EMPTY_ && opts.SystemAccount != DEFAULT_SYSTEM_ACCOUNT { - if _, ok := ar.(*MemAccResolver); !ok { - s.mu.Unlock() - var a *Account - // perform direct lookup to avoid warning trace - if _, err := fetchAccount(ar, opts.SystemAccount); err == nil { - a, _ = s.lookupAccount(opts.SystemAccount) - } - s.mu.Lock() - if a == nil { - sac := NewAccount(opts.SystemAccount) - sac.Issuer = opts.TrustedOperators[0].Issuer - sac.signingKeys = map[string]jwt.Scope{} - sac.signingKeys[opts.SystemAccount] = nil - s.registerAccountNoLock(sac) - } - } - } - - // For tracking accounts - if _, err := s.configureAccounts(false); err != nil { - return nil, err - } - - // Used to setup Authorization. - s.configureAuthorization() - - // Start signal handler - s.handleSignals() - - return s, nil -} - -// Initializes route structures based on pooling and/or per-account routes. -// -// Server lock is held on entry -func (s *Server) initRouteStructures(opts *Options) { - s.routes = make(map[string][]*client) - if ps := opts.Cluster.PoolSize; ps > 0 { - s.routesPoolSize = ps - } else { - s.routesPoolSize = 1 - } - // If we have per-account routes, we create accRoutes and initialize it - // with nil values. The presence of an account as the key will allow us - // to know if a given account is supposed to have dedicated routes. - if l := len(opts.Cluster.PinnedAccounts); l > 0 { - s.accRoutes = make(map[string]map[string]*client, l) - for _, acc := range opts.Cluster.PinnedAccounts { - s.accRoutes[acc] = make(map[string]*client) - } - } -} - -func (s *Server) logRejectedTLSConns() { - defer s.grWG.Done() - t := time.NewTicker(time.Second) - defer t.Stop() - for { - select { - case <-s.quitCh: - return - case <-t.C: - blocked := s.connRateCounter.countBlocked() - if blocked > 0 { - s.Warnf("Rejected %d connections due to TLS rate limiting", blocked) - } - } - } -} - -// clusterName returns our cluster name which could be dynamic. -func (s *Server) ClusterName() string { - s.mu.RLock() - cn := s.info.Cluster - s.mu.RUnlock() - return cn -} - -// Grabs cluster name with cluster name specific lock. -func (s *Server) cachedClusterName() string { - s.cnMu.RLock() - cn := s.cn - s.cnMu.RUnlock() - return cn -} - -// setClusterName will update the cluster name for this server. -func (s *Server) setClusterName(name string) { - s.mu.Lock() - var resetCh chan struct{} - if s.sys != nil && s.info.Cluster != name { - // can't hold the lock as go routine reading it may be waiting for lock as well - resetCh = s.sys.resetCh - } - s.info.Cluster = name - s.routeInfo.Cluster = name - - // Need to close solicited leaf nodes. The close has to be done outside of the server lock. - var leafs []*client - for _, c := range s.leafs { - c.mu.Lock() - if c.leaf != nil && c.leaf.remote != nil { - leafs = append(leafs, c) - } - c.mu.Unlock() - } - s.mu.Unlock() - - // Also place into mapping cn with cnMu lock. - s.cnMu.Lock() - s.cn = name - s.cnMu.Unlock() - - for _, l := range leafs { - l.closeConnection(ClusterNameConflict) - } - if resetCh != nil { - resetCh <- struct{}{} - } - s.Noticef("Cluster name updated to %s", name) -} - -// Return whether the cluster name is dynamic. -func (s *Server) isClusterNameDynamic() bool { - // We need to lock the whole "Cluster.Name" check and not use s.getOpts() - // because otherwise this could cause a data race with setting the name in - // route.go's processRouteConnect(). - s.optsMu.RLock() - dynamic := s.opts.Cluster.Name == _EMPTY_ - s.optsMu.RUnlock() - return dynamic -} - -// Returns our configured serverName. -func (s *Server) serverName() string { - return s.getOpts().ServerName -} - -// ClientURL returns the URL used to connect clients. Helpful in testing -// when we designate a random client port (-1). -func (s *Server) ClientURL() string { - // FIXME(dlc) - should we add in user and pass if defined single? - opts := s.getOpts() - var u url.URL - u.Scheme = "nats" - if opts.TLSConfig != nil { - u.Scheme = "tls" - } - u.Host = net.JoinHostPort(opts.Host, fmt.Sprintf("%d", opts.Port)) - return u.String() -} - -func validateCluster(o *Options) error { - if o.Cluster.Name != _EMPTY_ && strings.Contains(o.Cluster.Name, " ") { - return ErrClusterNameHasSpaces - } - if o.Cluster.Compression.Mode != _EMPTY_ { - if err := validateAndNormalizeCompressionOption(&o.Cluster.Compression, CompressionS2Fast); err != nil { - return err - } - } - if err := validatePinnedCerts(o.Cluster.TLSPinnedCerts); err != nil { - return fmt.Errorf("cluster: %v", err) - } - // Check that cluster name if defined matches any gateway name. - // Note that we have already verified that the gateway name does not have spaces. - if o.Gateway.Name != _EMPTY_ && o.Gateway.Name != o.Cluster.Name { - if o.Cluster.Name != _EMPTY_ { - return ErrClusterNameConfigConflict - } - // Set this here so we do not consider it dynamic. - o.Cluster.Name = o.Gateway.Name - } - if l := len(o.Cluster.PinnedAccounts); l > 0 { - if o.Cluster.PoolSize < 0 { - return fmt.Errorf("pool_size cannot be negative if pinned accounts are specified") - } - m := make(map[string]struct{}, l) - for _, a := range o.Cluster.PinnedAccounts { - if _, exists := m[a]; exists { - return fmt.Errorf("found duplicate account name %q in pinned accounts list %q", a, o.Cluster.PinnedAccounts) - } - m[a] = struct{}{} - } - } - return nil -} - -func validatePinnedCerts(pinned PinnedCertSet) error { - re := regexp.MustCompile("^[a-f0-9]{64}$") - for certId := range pinned { - entry := strings.ToLower(certId) - if !re.MatchString(entry) { - return fmt.Errorf("error parsing 'pinned_certs' key %s does not look like lower case hex-encoded sha256 of DER encoded SubjectPublicKeyInfo", entry) - } - } - return nil -} - -func validateOptions(o *Options) error { - if o.LameDuckDuration > 0 && o.LameDuckGracePeriod >= o.LameDuckDuration { - return fmt.Errorf("lame duck grace period (%v) should be strictly lower than lame duck duration (%v)", - o.LameDuckGracePeriod, o.LameDuckDuration) - } - if int64(o.MaxPayload) > o.MaxPending { - return fmt.Errorf("max_payload (%v) cannot be higher than max_pending (%v)", - o.MaxPayload, o.MaxPending) - } - if o.ServerName != _EMPTY_ && strings.Contains(o.ServerName, " ") { - return errors.New("server name cannot contain spaces") - } - // Check that the trust configuration is correct. - if err := validateTrustedOperators(o); err != nil { - return err - } - // Check on leaf nodes which will require a system - // account when gateways are also configured. - if err := validateLeafNode(o); err != nil { - return err - } - // Check that authentication is properly configured. - if err := validateAuth(o); err != nil { - return err - } - // Check that gateway is properly configured. Returns no error - // if there is no gateway defined. - if err := validateGatewayOptions(o); err != nil { - return err - } - // Check that cluster name if defined matches any gateway name. - if err := validateCluster(o); err != nil { - return err - } - if err := validateMQTTOptions(o); err != nil { - return err - } - if err := validateJetStreamOptions(o); err != nil { - return err - } - // Finally check websocket options. - return validateWebsocketOptions(o) -} - -func (s *Server) getOpts() *Options { - s.optsMu.RLock() - opts := s.opts - s.optsMu.RUnlock() - return opts -} - -func (s *Server) setOpts(opts *Options) { - s.optsMu.Lock() - s.opts = opts - s.optsMu.Unlock() -} - -func (s *Server) globalAccount() *Account { - s.mu.RLock() - gacc := s.gacc - s.mu.RUnlock() - return gacc -} - -// Used to setup or update Accounts. -// Returns a map that indicates which accounts have had their stream imports -// changed (in case of an update in configuration reload). -// Lock is held upon entry, but will be released/reacquired in this function. -func (s *Server) configureAccounts(reloading bool) (map[string]struct{}, error) { - awcsti := make(map[string]struct{}) - - // Create the global account. - if s.gacc == nil { - s.gacc = NewAccount(globalAccountName) - s.registerAccountNoLock(s.gacc) - } - - opts := s.getOpts() - - // We need to track service imports since we can not swap them out (unsub and re-sub) - // until the proper server struct accounts have been swapped in properly. Doing it in - // place could lead to data loss or server panic since account under new si has no real - // account and hence no sublist, so will panic on inbound message. - siMap := make(map[*Account][][]byte) - - // Check opts and walk through them. We need to copy them here - // so that we do not keep a real one sitting in the options. - for _, acc := range opts.Accounts { - var a *Account - create := true - // For the global account, we want to skip the reload process - // and fall back into the "create" case which will in that - // case really be just an update (shallowCopy will make sure - // that mappings are copied over). - if reloading && acc.Name != globalAccountName { - if ai, ok := s.accounts.Load(acc.Name); ok { - a = ai.(*Account) - // Before updating the account, check if stream imports have changed. - if !a.checkStreamImportsEqual(acc) { - awcsti[acc.Name] = struct{}{} - } - a.mu.Lock() - // Collect the sids for the service imports since we are going to - // replace with new ones. - var sids [][]byte - for _, si := range a.imports.services { - if si.sid != nil { - sids = append(sids, si.sid) - } - } - // Setup to process later if needed. - if len(sids) > 0 || len(acc.imports.services) > 0 { - siMap[a] = sids - } - - // Now reset all export/imports fields since they are going to be - // filled in shallowCopy() - a.imports.streams, a.imports.services = nil, nil - a.exports.streams, a.exports.services = nil, nil - // We call shallowCopy from the account `acc` (the one in Options) - // and pass `a` (our existing account) to get it updated. - acc.shallowCopy(a) - a.mu.Unlock() - create = false - } - } - // Track old mappings if global account. - var oldGMappings []*mapping - if create { - if acc.Name == globalAccountName { - a = s.gacc - a.mu.Lock() - oldGMappings = append(oldGMappings, a.mappings...) - a.mu.Unlock() - } else { - a = NewAccount(acc.Name) - } - // Locking matters in the case of an update of the global account - a.mu.Lock() - acc.shallowCopy(a) - a.mu.Unlock() - // Will be a no-op in case of the global account since it is already registered. - s.registerAccountNoLock(a) - } - - // The `acc` account is stored in options, not in the server, and these can be cleared. - acc.sl, acc.clients, acc.mappings = nil, nil, nil - - // Check here if we have been reloaded and we have a global account with mappings that may have changed. - // If we have leafnodes they need to be updated. - if reloading && a == s.gacc { - a.mu.Lock() - mappings := make(map[string]*mapping) - if len(a.mappings) > 0 && a.nleafs > 0 { - for _, em := range a.mappings { - mappings[em.src] = em - } - } - a.mu.Unlock() - if len(mappings) > 0 || len(oldGMappings) > 0 { - a.lmu.RLock() - for _, lc := range a.lleafs { - for _, em := range mappings { - lc.forceAddToSmap(em.src) - } - // Remove any old ones if needed. - for _, em := range oldGMappings { - // Only remove if not in the new ones. - if _, ok := mappings[em.src]; !ok { - lc.forceRemoveFromSmap(em.src) - } - } - } - a.lmu.RUnlock() - } - } - - // If we see an account defined using $SYS we will make sure that is set as system account. - if acc.Name == DEFAULT_SYSTEM_ACCOUNT && opts.SystemAccount == _EMPTY_ { - opts.SystemAccount = DEFAULT_SYSTEM_ACCOUNT - } - } - - // Now that we have this we need to remap any referenced accounts in - // import or export maps to the new ones. - swapApproved := func(ea *exportAuth) { - for sub, a := range ea.approved { - var acc *Account - if v, ok := s.accounts.Load(a.Name); ok { - acc = v.(*Account) - } - ea.approved[sub] = acc - } - } - var numAccounts int - s.accounts.Range(func(k, v any) bool { - numAccounts++ - acc := v.(*Account) - acc.mu.Lock() - // Exports - for _, se := range acc.exports.streams { - if se != nil { - swapApproved(&se.exportAuth) - } - } - for _, se := range acc.exports.services { - if se != nil { - // Swap over the bound account for service exports. - if se.acc != nil { - if v, ok := s.accounts.Load(se.acc.Name); ok { - se.acc = v.(*Account) - } - } - swapApproved(&se.exportAuth) - } - } - // Imports - for _, si := range acc.imports.streams { - if v, ok := s.accounts.Load(si.acc.Name); ok { - si.acc = v.(*Account) - } - } - for _, si := range acc.imports.services { - if v, ok := s.accounts.Load(si.acc.Name); ok { - si.acc = v.(*Account) - - // It is possible to allow for latency tracking inside your - // own account, so lock only when not the same account. - if si.acc == acc { - si.se = si.acc.getServiceExport(si.to) - continue - } - si.acc.mu.RLock() - si.se = si.acc.getServiceExport(si.to) - si.acc.mu.RUnlock() - } - } - // Make sure the subs are running, but only if not reloading. - if len(acc.imports.services) > 0 && acc.ic == nil && !reloading { - acc.ic = s.createInternalAccountClient() - acc.ic.acc = acc - // Need to release locks to invoke this function. - acc.mu.Unlock() - s.mu.Unlock() - acc.addAllServiceImportSubs() - s.mu.Lock() - acc.mu.Lock() - } - acc.updated = time.Now() - acc.mu.Unlock() - return true - }) - - // Check if we need to process service imports pending from above. - // This processing needs to be after we swap in the real accounts above. - for acc, sids := range siMap { - c := acc.ic - for _, sid := range sids { - c.processUnsub(sid) - } - acc.addAllServiceImportSubs() - s.mu.Unlock() - s.registerSystemImports(acc) - s.mu.Lock() - } - - // Set the system account if it was configured. - // Otherwise create a default one. - if opts.SystemAccount != _EMPTY_ { - // Lock may be acquired in lookupAccount, so release to call lookupAccount. - s.mu.Unlock() - acc, err := s.lookupAccount(opts.SystemAccount) - s.mu.Lock() - if err == nil && s.sys != nil && acc != s.sys.account { - // sys.account.clients (including internal client)/respmap/etc... are transferred separately - s.sys.account = acc - s.sysAcc.Store(acc) - } - if err != nil { - return awcsti, fmt.Errorf("error resolving system account: %v", err) - } - - // If we have defined a system account here check to see if its just us and the $G account. - // We would do this to add user/pass to the system account. If this is the case add in - // no-auth-user for $G. - // Only do this if non-operator mode and we did not have an authorization block defined. - if len(opts.TrustedOperators) == 0 && numAccounts == 2 && opts.NoAuthUser == _EMPTY_ && !opts.authBlockDefined { - // If we come here from config reload, let's not recreate the fake user name otherwise - // it will cause currently clients to be disconnected. - uname := s.sysAccOnlyNoAuthUser - if uname == _EMPTY_ { - // Create a unique name so we do not collide. - var b [8]byte - rn := rand.Int63() - for i, l := 0, rn; i < len(b); i++ { - b[i] = digits[l%base] - l /= base - } - uname = fmt.Sprintf("nats-%s", b[:]) - s.sysAccOnlyNoAuthUser = uname - } - opts.Users = append(opts.Users, &User{Username: uname, Password: uname[6:], Account: s.gacc}) - opts.NoAuthUser = uname - } - } - - // Add any required exports from system account. - if s.sys != nil { - sysAcc := s.sys.account - s.mu.Unlock() - s.addSystemAccountExports(sysAcc) - s.mu.Lock() - } - - return awcsti, nil -} - -// Setup the account resolver. For memory resolver, make sure the JWTs are -// properly formed but do not enforce expiration etc. -// Lock is held on entry, but may be released/reacquired during this call. -func (s *Server) configureResolver() error { - opts := s.getOpts() - s.accResolver = opts.AccountResolver - if opts.AccountResolver != nil { - // For URL resolver, set the TLSConfig if specified. - if opts.AccountResolverTLSConfig != nil { - if ar, ok := opts.AccountResolver.(*URLAccResolver); ok { - if t, ok := ar.c.Transport.(*http.Transport); ok { - t.CloseIdleConnections() - t.TLSClientConfig = opts.AccountResolverTLSConfig.Clone() - } - } - } - if len(opts.resolverPreloads) > 0 { - // Lock ordering is account resolver -> server, so we need to release - // the lock and reacquire it when done with account resolver's calls. - ar := s.accResolver - s.mu.Unlock() - defer s.mu.Lock() - if ar.IsReadOnly() { - return fmt.Errorf("resolver preloads only available for writeable resolver types MEM/DIR/CACHE_DIR") - } - for k, v := range opts.resolverPreloads { - _, err := jwt.DecodeAccountClaims(v) - if err != nil { - return fmt.Errorf("preload account error for %q: %v", k, err) - } - ar.Store(k, v) - } - } - } - return nil -} - -// This will check preloads for validation issues. -func (s *Server) checkResolvePreloads() { - opts := s.getOpts() - // We can just check the read-only opts versions here, that way we do not need - // to grab server lock or access s.accResolver. - for k, v := range opts.resolverPreloads { - claims, err := jwt.DecodeAccountClaims(v) - if err != nil { - s.Errorf("Preloaded account [%s] not valid", k) - continue - } - // Check if it is expired. - vr := jwt.CreateValidationResults() - claims.Validate(vr) - if vr.IsBlocking(true) { - s.Warnf("Account [%s] has validation issues:", k) - for _, v := range vr.Issues { - s.Warnf(" - %s", v.Description) - } - } - } -} - -// Determines if we are in pre NATS 2.0 setup with no accounts. -func (s *Server) globalAccountOnly() bool { - var hasOthers bool - - if s.trustedKeys != nil { - return false - } - - s.mu.RLock() - s.accounts.Range(func(k, v any) bool { - acc := v.(*Account) - // Ignore global and system - if acc == s.gacc || (s.sys != nil && acc == s.sys.account) { - return true - } - hasOthers = true - return false - }) - s.mu.RUnlock() - - return !hasOthers -} - -// Determines if this server is in standalone mode, meaning no routes or gateways. -func (s *Server) standAloneMode() bool { - opts := s.getOpts() - return opts.Cluster.Port == 0 && opts.Gateway.Port == 0 -} - -func (s *Server) configuredRoutes() int { - return len(s.getOpts().Routes) -} - -// activePeers is used in bootstrapping raft groups like the JetStream meta controller. -func (s *Server) ActivePeers() (peers []string) { - s.nodeToInfo.Range(func(k, v any) bool { - si := v.(nodeInfo) - if !si.offline { - peers = append(peers, k.(string)) - } - return true - }) - return peers -} - -// isTrustedIssuer will check that the issuer is a trusted public key. -// This is used to make sure an account was signed by a trusted operator. -func (s *Server) isTrustedIssuer(issuer string) bool { - s.mu.RLock() - defer s.mu.RUnlock() - // If we are not running in trusted mode and there is no issuer, that is ok. - if s.trustedKeys == nil && issuer == _EMPTY_ { - return true - } - for _, tk := range s.trustedKeys { - if tk == issuer { - return true - } - } - return false -} - -// processTrustedKeys will process binary stamped and -// options-based trusted nkeys. Returns success. -func (s *Server) processTrustedKeys() bool { - s.strictSigningKeyUsage = map[string]struct{}{} - opts := s.getOpts() - if trustedKeys != _EMPTY_ && !s.initStampedTrustedKeys() { - return false - } else if opts.TrustedKeys != nil { - for _, key := range opts.TrustedKeys { - if !nkeys.IsValidPublicOperatorKey(key) { - return false - } - } - s.trustedKeys = append([]string(nil), opts.TrustedKeys...) - for _, claim := range opts.TrustedOperators { - if !claim.StrictSigningKeyUsage { - continue - } - for _, key := range claim.SigningKeys { - s.strictSigningKeyUsage[key] = struct{}{} - } - } - } - return true -} - -// checkTrustedKeyString will check that the string is a valid array -// of public operator nkeys. -func checkTrustedKeyString(keys string) []string { - tks := strings.Fields(keys) - if len(tks) == 0 { - return nil - } - // Walk all the keys and make sure they are valid. - for _, key := range tks { - if !nkeys.IsValidPublicOperatorKey(key) { - return nil - } - } - return tks -} - -// initStampedTrustedKeys will check the stamped trusted keys -// and will set the server field 'trustedKeys'. Returns whether -// it succeeded or not. -func (s *Server) initStampedTrustedKeys() bool { - // Check to see if we have an override in options, which will cause us to fail. - if len(s.getOpts().TrustedKeys) > 0 { - return false - } - tks := checkTrustedKeyString(trustedKeys) - if len(tks) == 0 { - return false - } - s.trustedKeys = tks - return true -} - -// PrintAndDie is exported for access in other packages. -func PrintAndDie(msg string) { - fmt.Fprintln(os.Stderr, msg) - os.Exit(1) -} - -// PrintServerAndExit will print our version and exit. -func PrintServerAndExit() { - fmt.Printf("nats-server: v%s\n", VERSION) - os.Exit(0) -} - -// ProcessCommandLineArgs takes the command line arguments -// validating and setting flags for handling in case any -// sub command was present. -func ProcessCommandLineArgs(cmd *flag.FlagSet) (showVersion bool, showHelp bool, err error) { - if len(cmd.Args()) > 0 { - arg := cmd.Args()[0] - switch strings.ToLower(arg) { - case "version": - return true, false, nil - case "help": - return false, true, nil - default: - return false, false, fmt.Errorf("unrecognized command: %q", arg) - } - } - - return false, false, nil -} - -// Public version. -func (s *Server) Running() bool { - return s.isRunning() -} - -// Protected check on running state -func (s *Server) isRunning() bool { - return s.running.Load() -} - -func (s *Server) logPid() error { - pidStr := strconv.Itoa(os.Getpid()) - return os.WriteFile(s.getOpts().PidFile, []byte(pidStr), defaultFilePerms) -} - -// numReservedAccounts will return the number of reserved accounts configured in the server. -// Currently this is 1, one for the global default account. -func (s *Server) numReservedAccounts() int { - return 1 -} - -// NumActiveAccounts reports number of active accounts on this server. -func (s *Server) NumActiveAccounts() int32 { - return atomic.LoadInt32(&s.activeAccounts) -} - -// incActiveAccounts() just adds one under lock. -func (s *Server) incActiveAccounts() { - atomic.AddInt32(&s.activeAccounts, 1) -} - -// decActiveAccounts() just subtracts one under lock. -func (s *Server) decActiveAccounts() { - atomic.AddInt32(&s.activeAccounts, -1) -} - -// This should be used for testing only. Will be slow since we have to -// range over all accounts in the sync.Map to count. -func (s *Server) numAccounts() int { - count := 0 - s.mu.RLock() - s.accounts.Range(func(k, v any) bool { - count++ - return true - }) - s.mu.RUnlock() - return count -} - -// NumLoadedAccounts returns the number of loaded accounts. -func (s *Server) NumLoadedAccounts() int { - return s.numAccounts() -} - -// LookupOrRegisterAccount will return the given account if known or create a new entry. -func (s *Server) LookupOrRegisterAccount(name string) (account *Account, isNew bool) { - s.mu.Lock() - defer s.mu.Unlock() - if v, ok := s.accounts.Load(name); ok { - return v.(*Account), false - } - acc := NewAccount(name) - s.registerAccountNoLock(acc) - return acc, true -} - -// RegisterAccount will register an account. The account must be new -// or this call will fail. -func (s *Server) RegisterAccount(name string) (*Account, error) { - s.mu.Lock() - defer s.mu.Unlock() - if _, ok := s.accounts.Load(name); ok { - return nil, ErrAccountExists - } - acc := NewAccount(name) - s.registerAccountNoLock(acc) - return acc, nil -} - -// SetSystemAccount will set the internal system account. -// If root operators are present it will also check validity. -func (s *Server) SetSystemAccount(accName string) error { - // Lookup from sync.Map first. - if v, ok := s.accounts.Load(accName); ok { - return s.setSystemAccount(v.(*Account)) - } - - // If we are here we do not have local knowledge of this account. - // Do this one by hand to return more useful error. - ac, jwt, err := s.fetchAccountClaims(accName) - if err != nil { - return err - } - acc := s.buildInternalAccount(ac) - acc.claimJWT = jwt - // Due to race, we need to make sure that we are not - // registering twice. - if racc := s.registerAccount(acc); racc != nil { - return nil - } - return s.setSystemAccount(acc) -} - -// SystemAccount returns the system account if set. -func (s *Server) SystemAccount() *Account { - return s.sysAcc.Load() -} - -// GlobalAccount returns the global account. -// Default clients will use the global account. -func (s *Server) GlobalAccount() *Account { - s.mu.RLock() - defer s.mu.RUnlock() - return s.gacc -} - -// SetDefaultSystemAccount will create a default system account if one is not present. -func (s *Server) SetDefaultSystemAccount() error { - if _, isNew := s.LookupOrRegisterAccount(DEFAULT_SYSTEM_ACCOUNT); !isNew { - return nil - } - s.Debugf("Created system account: %q", DEFAULT_SYSTEM_ACCOUNT) - return s.SetSystemAccount(DEFAULT_SYSTEM_ACCOUNT) -} - -// Assign a system account. Should only be called once. -// This sets up a server to send and receive messages from -// inside the server itself. -func (s *Server) setSystemAccount(acc *Account) error { - if acc == nil { - return ErrMissingAccount - } - // Don't try to fix this here. - if acc.IsExpired() { - return ErrAccountExpired - } - // If we are running with trusted keys for an operator - // make sure we check the account is legit. - if !s.isTrustedIssuer(acc.Issuer) { - return ErrAccountValidation - } - - s.mu.Lock() - - if s.sys != nil { - s.mu.Unlock() - return ErrAccountExists - } - - // This is here in an attempt to quiet the race detector and not have to place - // locks on fast path for inbound messages and checking service imports. - acc.mu.Lock() - if acc.imports.services == nil { - acc.imports.services = make(map[string]*serviceImport) - } - acc.mu.Unlock() - - s.sys = &internal{ - account: acc, - client: s.createInternalSystemClient(), - seq: 1, - sid: 1, - servers: make(map[string]*serverUpdate), - replies: make(map[string]msgHandler), - sendq: newIPQueue[*pubMsg](s, "System sendQ"), - recvq: newIPQueue[*inSysMsg](s, "System recvQ"), - recvqp: newIPQueue[*inSysMsg](s, "System recvQ Pings"), - resetCh: make(chan struct{}), - sq: s.newSendQ(acc), - statsz: statsHBInterval, - orphMax: 5 * eventsHBInterval, - chkOrph: 3 * eventsHBInterval, - } - recvq, recvqp := s.sys.recvq, s.sys.recvqp - s.sys.wg.Add(1) - s.mu.Unlock() - - // Store in atomic for fast lookup. - s.sysAcc.Store(acc) - - // Register with the account. - s.sys.client.registerWithAccount(acc) - - s.addSystemAccountExports(acc) - - // Start our internal loop to serialize outbound messages. - // We do our own wg here since we will stop first during shutdown. - go s.internalSendLoop(&s.sys.wg) - - // Start the internal loop for inbound messages. - go s.internalReceiveLoop(recvq) - // Start the internal loop for inbound STATSZ/Ping messages. - go s.internalReceiveLoop(recvqp) - - // Start up our general subscriptions - s.initEventTracking() - - // Track for dead remote servers. - s.wrapChk(s.startRemoteServerSweepTimer)() - - // Send out statsz updates periodically. - s.wrapChk(s.startStatszTimer)() - - // If we have existing accounts make sure we enable account tracking. - s.mu.Lock() - s.accounts.Range(func(k, v any) bool { - acc := v.(*Account) - s.enableAccountTracking(acc) - return true - }) - s.mu.Unlock() - - return nil -} - -// Creates an internal system client. -func (s *Server) createInternalSystemClient() *client { - return s.createInternalClient(SYSTEM) -} - -// Creates an internal jetstream client. -func (s *Server) createInternalJetStreamClient() *client { - return s.createInternalClient(JETSTREAM) -} - -// Creates an internal client for Account. -func (s *Server) createInternalAccountClient() *client { - return s.createInternalClient(ACCOUNT) -} - -// Internal clients. kind should be SYSTEM or JETSTREAM -func (s *Server) createInternalClient(kind int) *client { - if kind != SYSTEM && kind != JETSTREAM && kind != ACCOUNT { - return nil - } - now := time.Now() - c := &client{srv: s, kind: kind, opts: internalOpts, msubs: -1, mpay: -1, start: now, last: now} - c.initClient() - c.echo = false - c.headers = true - c.flags.set(noReconnect) - return c -} - -// Determine if accounts should track subscriptions for -// efficient propagation. -// Lock should be held on entry. -func (s *Server) shouldTrackSubscriptions() bool { - opts := s.getOpts() - return (opts.Cluster.Port != 0 || opts.Gateway.Port != 0) -} - -// Invokes registerAccountNoLock under the protection of the server lock. -// That is, server lock is acquired/released in this function. -// See registerAccountNoLock for comment on returned value. -func (s *Server) registerAccount(acc *Account) *Account { - s.mu.Lock() - racc := s.registerAccountNoLock(acc) - s.mu.Unlock() - return racc -} - -// Helper to set the sublist based on preferences. -func (s *Server) setAccountSublist(acc *Account) { - if acc != nil && acc.sl == nil { - opts := s.getOpts() - if opts != nil && opts.NoSublistCache { - acc.sl = NewSublistNoCache() - } else { - acc.sl = NewSublistWithCache() - } - } -} - -// Registers an account in the server. -// Due to some locking considerations, we may end-up trying -// to register the same account twice. This function will -// then return the already registered account. -// Lock should be held on entry. -func (s *Server) registerAccountNoLock(acc *Account) *Account { - // We are under the server lock. Lookup from map, if present - // return existing account. - if a, _ := s.accounts.Load(acc.Name); a != nil { - s.tmpAccounts.Delete(acc.Name) - return a.(*Account) - } - // Finish account setup and store. - s.setAccountSublist(acc) - - acc.mu.Lock() - s.setRouteInfo(acc) - if acc.clients == nil { - acc.clients = make(map[*client]struct{}) - } - - // If we are capable of routing we will track subscription - // information for efficient interest propagation. - // During config reload, it is possible that account was - // already created (global account), so use locking and - // make sure we create only if needed. - // TODO(dlc)- Double check that we need this for GWs. - if acc.rm == nil && s.opts != nil && s.shouldTrackSubscriptions() { - acc.rm = make(map[string]int32) - acc.lqws = make(map[string]int32) - } - acc.srv = s - acc.updated = time.Now() - accName := acc.Name - jsEnabled := len(acc.jsLimits) > 0 - acc.mu.Unlock() - - if opts := s.getOpts(); opts != nil && len(opts.JsAccDefaultDomain) > 0 { - if defDomain, ok := opts.JsAccDefaultDomain[accName]; ok { - if jsEnabled { - s.Warnf("Skipping Default Domain %q, set for JetStream enabled account %q", defDomain, accName) - } else if defDomain != _EMPTY_ { - for src, dest := range generateJSMappingTable(defDomain) { - // flip src and dest around so the domain is inserted - s.Noticef("Adding default domain mapping %q -> %q to account %q %p", dest, src, accName, acc) - if err := acc.AddMapping(dest, src); err != nil { - s.Errorf("Error adding JetStream default domain mapping: %v", err) - } - } - } - } - } - - s.accounts.Store(acc.Name, acc) - s.tmpAccounts.Delete(acc.Name) - s.enableAccountTracking(acc) - - // Can not have server lock here. - s.mu.Unlock() - s.registerSystemImports(acc) - // Starting 2.9.0, we are phasing out the optimistic mode, so change - // the account to interest-only mode (except if instructed not to do - // it in some tests). - if s.gateway.enabled && !gwDoNotForceInterestOnlyMode { - s.switchAccountToInterestMode(acc.GetName()) - } - s.mu.Lock() - - return nil -} - -// Sets the account's routePoolIdx depending on presence or not of -// pooling or per-account routes. Also updates a map used by -// gateway code to retrieve a route based on some route hash. -// -// Both Server and Account lock held on entry. -func (s *Server) setRouteInfo(acc *Account) { - // If there is a dedicated route configured for this account - if _, ok := s.accRoutes[acc.Name]; ok { - // We want the account name to be in the map, but we don't - // need a value (we could store empty string) - s.accRouteByHash.Store(acc.Name, nil) - // Set the route pool index to -1 so that it is easy when - // ranging over accounts to exclude those accounts when - // trying to get accounts for a given pool index. - acc.routePoolIdx = accDedicatedRoute - } else { - // If pool size more than 1, we will compute a hash code and - // use modulo to assign to an index of the pool slice. For 1 - // and below, all accounts will be bound to the single connection - // at index 0. - acc.routePoolIdx = s.computeRoutePoolIdx(acc) - if s.routesPoolSize > 1 { - s.accRouteByHash.Store(acc.Name, acc.routePoolIdx) - } - } -} - -// Returns a route pool index for this account based on the given pool size. -// Account lock is held on entry (account's name is accessed but immutable -// so could be called without account's lock). -// Server lock held on entry. -func (s *Server) computeRoutePoolIdx(acc *Account) int { - if s.routesPoolSize <= 1 { - return 0 - } - h := fnv.New32a() - h.Write([]byte(acc.Name)) - sum32 := h.Sum32() - return int((sum32 % uint32(s.routesPoolSize))) -} - -// lookupAccount is a function to return the account structure -// associated with an account name. -// Lock MUST NOT be held upon entry. -func (s *Server) lookupAccount(name string) (*Account, error) { - var acc *Account - if v, ok := s.accounts.Load(name); ok { - acc = v.(*Account) - } - if acc != nil { - // If we are expired and we have a resolver, then - // return the latest information from the resolver. - if acc.IsExpired() { - s.Debugf("Requested account [%s] has expired", name) - if s.AccountResolver() != nil { - if err := s.updateAccount(acc); err != nil { - // This error could mask expired, so just return expired here. - return nil, ErrAccountExpired - } - } else { - return nil, ErrAccountExpired - } - } - return acc, nil - } - // If we have a resolver see if it can fetch the account. - if s.AccountResolver() == nil { - return nil, ErrMissingAccount - } - return s.fetchAccount(name) -} - -// LookupAccount is a public function to return the account structure -// associated with name. -func (s *Server) LookupAccount(name string) (*Account, error) { - return s.lookupAccount(name) -} - -// This will fetch new claims and if found update the account with new claims. -// Lock MUST NOT be held upon entry. -func (s *Server) updateAccount(acc *Account) error { - acc.mu.RLock() - // TODO(dlc) - Make configurable - if !acc.incomplete && time.Since(acc.updated) < time.Second { - acc.mu.RUnlock() - s.Debugf("Requested account update for [%s] ignored, too soon", acc.Name) - return ErrAccountResolverUpdateTooSoon - } - acc.mu.RUnlock() - claimJWT, err := s.fetchRawAccountClaims(acc.Name) - if err != nil { - return err - } - return s.updateAccountWithClaimJWT(acc, claimJWT) -} - -// updateAccountWithClaimJWT will check and apply the claim update. -// Lock MUST NOT be held upon entry. -func (s *Server) updateAccountWithClaimJWT(acc *Account, claimJWT string) error { - if acc == nil { - return ErrMissingAccount - } - acc.mu.RLock() - sameClaim := acc.claimJWT != _EMPTY_ && acc.claimJWT == claimJWT && !acc.incomplete - acc.mu.RUnlock() - if sameClaim { - s.Debugf("Requested account update for [%s], same claims detected", acc.Name) - return nil - } - accClaims, _, err := s.verifyAccountClaims(claimJWT) - if err == nil && accClaims != nil { - acc.mu.Lock() - // if an account is updated with a different operator signing key, we want to - // show a consistent issuer. - acc.Issuer = accClaims.Issuer - if acc.Name != accClaims.Subject { - acc.mu.Unlock() - return ErrAccountValidation - } - acc.mu.Unlock() - s.UpdateAccountClaims(acc, accClaims) - acc.mu.Lock() - // needs to be set after update completed. - // This causes concurrent calls to return with sameClaim=true if the change is effective. - acc.claimJWT = claimJWT - acc.mu.Unlock() - return nil - } - return err -} - -// fetchRawAccountClaims will grab raw account claims iff we have a resolver. -// Lock is NOT held upon entry. -func (s *Server) fetchRawAccountClaims(name string) (string, error) { - accResolver := s.AccountResolver() - if accResolver == nil { - return _EMPTY_, ErrNoAccountResolver - } - // Need to do actual Fetch - start := time.Now() - claimJWT, err := fetchAccount(accResolver, name) - fetchTime := time.Since(start) - if fetchTime > time.Second { - s.Warnf("Account [%s] fetch took %v", name, fetchTime) - } else { - s.Debugf("Account [%s] fetch took %v", name, fetchTime) - } - if err != nil { - s.Warnf("Account fetch failed: %v", err) - return "", err - } - return claimJWT, nil -} - -// fetchAccountClaims will attempt to fetch new claims if a resolver is present. -// Lock is NOT held upon entry. -func (s *Server) fetchAccountClaims(name string) (*jwt.AccountClaims, string, error) { - claimJWT, err := s.fetchRawAccountClaims(name) - if err != nil { - return nil, _EMPTY_, err - } - var claim *jwt.AccountClaims - claim, claimJWT, err = s.verifyAccountClaims(claimJWT) - if claim != nil && claim.Subject != name { - return nil, _EMPTY_, ErrAccountValidation - } - return claim, claimJWT, err -} - -// verifyAccountClaims will decode and validate any account claims. -func (s *Server) verifyAccountClaims(claimJWT string) (*jwt.AccountClaims, string, error) { - accClaims, err := jwt.DecodeAccountClaims(claimJWT) - if err != nil { - return nil, _EMPTY_, err - } - if !s.isTrustedIssuer(accClaims.Issuer) { - return nil, _EMPTY_, ErrAccountValidation - } - vr := jwt.CreateValidationResults() - accClaims.Validate(vr) - if vr.IsBlocking(true) { - return nil, _EMPTY_, ErrAccountValidation - } - return accClaims, claimJWT, nil -} - -// This will fetch an account from a resolver if defined. -// Lock is NOT held upon entry. -func (s *Server) fetchAccount(name string) (*Account, error) { - accClaims, claimJWT, err := s.fetchAccountClaims(name) - if accClaims == nil { - return nil, err - } - acc := s.buildInternalAccount(accClaims) - // Due to possible race, if registerAccount() returns a non - // nil account, it means the same account was already - // registered and we should use this one. - if racc := s.registerAccount(acc); racc != nil { - // Update with the new claims in case they are new. - if err = s.updateAccountWithClaimJWT(racc, claimJWT); err != nil { - return nil, err - } - return racc, nil - } - // The sub imports may have been setup but will not have had their - // subscriptions properly setup. Do that here. - var needImportSubs bool - - acc.mu.Lock() - acc.claimJWT = claimJWT - if len(acc.imports.services) > 0 { - if acc.ic == nil { - acc.ic = s.createInternalAccountClient() - acc.ic.acc = acc - } - needImportSubs = true - } - acc.mu.Unlock() - - // Do these outside the lock. - if needImportSubs { - acc.addAllServiceImportSubs() - } - - return acc, nil -} - -// Start up the server, this will not block. -// -// WaitForShutdown can be used to block and wait for the server to shutdown properly if needed -// after calling s.Shutdown() -func (s *Server) Start() { - s.Noticef("Starting nats-server") - - gc := gitCommit - if gc == _EMPTY_ { - gc = "not set" - } - - // Snapshot server options. - opts := s.getOpts() - - // Capture if this server is a leaf that has no cluster, so we don't - // display the cluster name if that is the case. - s.mu.RLock() - leafNoCluster := s.leafNoCluster - s.mu.RUnlock() - - var clusterName string - if !leafNoCluster { - clusterName = s.ClusterName() - } - - s.Noticef(" Version: %s", VERSION) - s.Noticef(" Git: [%s]", gc) - s.Debugf(" Go build: %s", s.info.GoVersion) - if clusterName != _EMPTY_ { - s.Noticef(" Cluster: %s", clusterName) - } - s.Noticef(" Name: %s", s.info.Name) - if opts.JetStream { - s.Noticef(" Node: %s", getHash(s.info.Name)) - } - s.Noticef(" ID: %s", s.info.ID) - - defer s.Noticef("Server is ready") - - // Check for insecure configurations. - s.checkAuthforWarnings() - - // Avoid RACE between Start() and Shutdown() - s.running.Store(true) - s.mu.Lock() - // Update leafNodeEnabled in case options have changed post NewServer() - // and before Start() (we should not be able to allow that, but server has - // direct reference to user-provided options - at least before a Reload() is - // performed. - s.leafNodeEnabled = opts.LeafNode.Port != 0 || len(opts.LeafNode.Remotes) > 0 - s.mu.Unlock() - - s.grMu.Lock() - s.grRunning = true - s.grMu.Unlock() - - s.startRateLimitLogExpiration() - - // Pprof http endpoint for the profiler. - if opts.ProfPort != 0 { - s.StartProfiler() - } else { - // It's still possible to access this profile via a SYS endpoint, so set - // this anyway. (Otherwise StartProfiler would have called it.) - s.setBlockProfileRate(opts.ProfBlockRate) - } - - if opts.ConfigFile != _EMPTY_ { - var cd string - if opts.configDigest != "" { - cd = fmt.Sprintf("(%s)", opts.configDigest) - } - s.Noticef("Using configuration file: %s %s", opts.ConfigFile, cd) - } - - hasOperators := len(opts.TrustedOperators) > 0 - if hasOperators { - s.Noticef("Trusted Operators") - } - for _, opc := range opts.TrustedOperators { - s.Noticef(" System : %q", opc.Audience) - s.Noticef(" Operator: %q", opc.Name) - s.Noticef(" Issued : %v", time.Unix(opc.IssuedAt, 0)) - switch opc.Expires { - case 0: - s.Noticef(" Expires : Never") - default: - s.Noticef(" Expires : %v", time.Unix(opc.Expires, 0)) - } - } - if hasOperators && opts.SystemAccount == _EMPTY_ { - s.Warnf("Trusted Operators should utilize a System Account") - } - if opts.MaxPayload > MAX_PAYLOAD_MAX_SIZE { - s.Warnf("Maximum payloads over %v are generally discouraged and could lead to poor performance", - friendlyBytes(int64(MAX_PAYLOAD_MAX_SIZE))) - } - - if len(opts.JsAccDefaultDomain) > 0 { - s.Warnf("The option `default_js_domain` is a temporary backwards compatibility measure and will be removed") - } - - // If we have a memory resolver, check the accounts here for validation exceptions. - // This allows them to be logged right away vs when they are accessed via a client. - if hasOperators && len(opts.resolverPreloads) > 0 { - s.checkResolvePreloads() - } - - // Log the pid to a file. - if opts.PidFile != _EMPTY_ { - if err := s.logPid(); err != nil { - s.Fatalf("Could not write pidfile: %v", err) - return - } - } - - // Setup system account which will start the eventing stack. - if sa := opts.SystemAccount; sa != _EMPTY_ { - if err := s.SetSystemAccount(sa); err != nil { - s.Fatalf("Can't set system account: %v", err) - return - } - } else if !opts.NoSystemAccount { - // We will create a default system account here. - s.SetDefaultSystemAccount() - } - - // Start monitoring before enabling other subsystems of the - // server to be able to monitor during startup. - if err := s.StartMonitoring(); err != nil { - s.Fatalf("Can't start monitoring: %v", err) - return - } - - // Start up resolver machinery. - if ar := s.AccountResolver(); ar != nil { - if err := ar.Start(s); err != nil { - s.Fatalf("Could not start resolver: %v", err) - return - } - // In operator mode, when the account resolver depends on an external system and - // the system account is the bootstrapping account, start fetching it. - if len(opts.TrustedOperators) == 1 && opts.SystemAccount != _EMPTY_ && opts.SystemAccount != DEFAULT_SYSTEM_ACCOUNT { - opts := s.getOpts() - _, isMemResolver := ar.(*MemAccResolver) - if v, ok := s.accounts.Load(opts.SystemAccount); !isMemResolver && ok && v.(*Account).claimJWT == _EMPTY_ { - s.Noticef("Using bootstrapping system account") - s.startGoRoutine(func() { - defer s.grWG.Done() - t := time.NewTicker(time.Second) - defer t.Stop() - for { - select { - case <-s.quitCh: - return - case <-t.C: - sacc := s.SystemAccount() - if claimJWT, err := fetchAccount(ar, opts.SystemAccount); err != nil { - continue - } else if err = s.updateAccountWithClaimJWT(sacc, claimJWT); err != nil { - continue - } - s.Noticef("System account fetched and updated") - return - } - } - }) - } - } - } - - // Start expiration of mapped GW replies, regardless if - // this server is configured with gateway or not. - s.startGWReplyMapExpiration() - - // Check if JetStream has been enabled. This needs to be after - // the system account setup above. JetStream will create its - // own system account if one is not present. - if opts.JetStream { - // Make sure someone is not trying to enable on the system account. - if sa := s.SystemAccount(); sa != nil && len(sa.jsLimits) > 0 { - s.Fatalf("Not allowed to enable JetStream on the system account") - } - cfg := &JetStreamConfig{ - StoreDir: opts.StoreDir, - SyncInterval: opts.SyncInterval, - SyncAlways: opts.SyncAlways, - Strict: opts.JetStreamStrict, - MaxMemory: opts.JetStreamMaxMemory, - MaxStore: opts.JetStreamMaxStore, - Domain: opts.JetStreamDomain, - CompressOK: true, - UniqueTag: opts.JetStreamUniqueTag, - } - if err := s.EnableJetStream(cfg); err != nil { - s.Fatalf("Can't start JetStream: %v", err) - return - } - } else { - // Check to see if any configured accounts have JetStream enabled. - sa, ga := s.SystemAccount(), s.GlobalAccount() - var hasSys, hasGlobal bool - var total int - - s.accounts.Range(func(k, v any) bool { - total++ - acc := v.(*Account) - if acc == sa { - hasSys = true - } else if acc == ga { - hasGlobal = true - } - acc.mu.RLock() - hasJs := len(acc.jsLimits) > 0 - acc.mu.RUnlock() - if hasJs { - s.checkJetStreamExports() - acc.enableAllJetStreamServiceImportsAndMappings() - } - return true - }) - // If we only have the system account and the global account and we are not standalone, - // go ahead and enable JS on $G in case we are in simple mixed mode setup. - if total == 2 && hasSys && hasGlobal && !s.standAloneMode() { - ga.mu.Lock() - ga.jsLimits = map[string]JetStreamAccountLimits{ - _EMPTY_: dynamicJSAccountLimits, - } - ga.mu.Unlock() - s.checkJetStreamExports() - ga.enableAllJetStreamServiceImportsAndMappings() - } - } - - // Delayed API response handling. Start regardless of JetStream being - // currently configured or not (since it can be enabled/disabled with - // configuration reload). - s.startGoRoutine(s.delayedAPIResponder) - - // Start OCSP Stapling monitoring for TLS certificates if enabled. Hook TLS handshake for - // OCSP check on peers (LEAF and CLIENT kind) if enabled. - s.startOCSPMonitoring() - - // Configure OCSP Response Cache for peer OCSP checks if enabled. - s.initOCSPResponseCache() - - // Start up gateway if needed. Do this before starting the routes, because - // we want to resolve the gateway host:port so that this information can - // be sent to other routes. - if opts.Gateway.Port != 0 { - s.startGateways() - } - - // Start websocket server if needed. Do this before starting the routes, and - // leaf node because we want to resolve the gateway host:port so that this - // information can be sent to other routes. - if opts.Websocket.Port != 0 { - s.startWebsocketServer() - } - - // Start up listen if we want to accept leaf node connections. - if opts.LeafNode.Port != 0 { - // Will resolve or assign the advertise address for the leafnode listener. - // We need that in StartRouting(). - s.startLeafNodeAcceptLoop() - } - - // Solicit remote servers for leaf node connections. - if len(opts.LeafNode.Remotes) > 0 { - s.solicitLeafNodeRemotes(opts.LeafNode.Remotes) - } - - // TODO (ik): I wanted to refactor this by starting the client - // accept loop first, that is, it would resolve listen spec - // in place, but start the accept-for-loop in a different go - // routine. This would get rid of the synchronization between - // this function and StartRouting, which I also would have wanted - // to refactor, but both AcceptLoop() and StartRouting() have - // been exported and not sure if that would break users using them. - // We could mark them as deprecated and remove in a release or two... - - // The Routing routine needs to wait for the client listen - // port to be opened and potential ephemeral port selected. - clientListenReady := make(chan struct{}) - - // MQTT - if opts.MQTT.Port != 0 { - s.startMQTT() - } - - // Start up routing as well if needed. - if opts.Cluster.Port != 0 { - s.startGoRoutine(func() { - s.StartRouting(clientListenReady) - }) - } - - if opts.PortsFileDir != _EMPTY_ { - s.logPorts() - } - - if opts.TLSRateLimit > 0 { - s.startGoRoutine(s.logRejectedTLSConns) - } - - // We've finished starting up. - close(s.startupComplete) - - // Wait for clients. - if !opts.DontListen { - s.AcceptLoop(clientListenReady) - } - - // Bring OSCP Response cache online after accept loop started in anticipation of NATS-enabled cache types - s.startOCSPResponseCache() -} - -func (s *Server) isShuttingDown() bool { - return s.shutdown.Load() -} - -// Shutdown will shutdown the server instance by kicking out the AcceptLoop -// and closing all associated clients. -func (s *Server) Shutdown() { - if s == nil { - return - } - // This is for JetStream R1 Pull Consumers to allow signaling - // that pending pull requests are invalid. - s.signalPullConsumers() - - // Transfer off any raft nodes that we are a leader by stepping them down. - s.stepdownRaftNodes() - - // Shutdown the eventing system as needed. - // This is done first to send out any messages for - // account status. We will also clean up any - // eventing items associated with accounts. - s.shutdownEventing() - - // Prevent issues with multiple calls. - if s.isShuttingDown() { - return - } - - s.mu.Lock() - s.Noticef("Initiating Shutdown...") - - accRes := s.accResolver - - opts := s.getOpts() - - s.shutdown.Store(true) - s.running.Store(false) - s.grMu.Lock() - s.grRunning = false - s.grMu.Unlock() - s.mu.Unlock() - - if accRes != nil { - accRes.Close() - } - - // Now check and shutdown jetstream. - s.shutdownJetStream() - - // Now shutdown the nodes - s.shutdownRaftNodes() - - s.mu.Lock() - conns := make(map[uint64]*client) - - // Copy off the clients - for i, c := range s.clients { - conns[i] = c - } - // Copy off the connections that are not yet registered - // in s.routes, but for which the readLoop has started - s.grMu.Lock() - for i, c := range s.grTmpClients { - conns[i] = c - } - s.grMu.Unlock() - // Copy off the routes - s.forEachRoute(func(r *client) { - r.mu.Lock() - conns[r.cid] = r - r.mu.Unlock() - }) - // Copy off the gateways - s.getAllGatewayConnections(conns) - - // Copy off the leaf nodes - for i, c := range s.leafs { - conns[i] = c - } - - // Number of done channel responses we expect. - doneExpected := 0 - - // Kick client AcceptLoop() - if s.listener != nil { - doneExpected++ - s.listener.Close() - s.listener = nil - } - - // Kick websocket server - doneExpected += s.closeWebsocketServer() - - // Kick MQTT accept loop - if s.mqtt.listener != nil { - doneExpected++ - s.mqtt.listener.Close() - s.mqtt.listener = nil - } - - // Kick leafnodes AcceptLoop() - if s.leafNodeListener != nil { - doneExpected++ - s.leafNodeListener.Close() - s.leafNodeListener = nil - } - - // Kick route AcceptLoop() - if s.routeListener != nil { - doneExpected++ - s.routeListener.Close() - s.routeListener = nil - } - - // Kick Gateway AcceptLoop() - if s.gatewayListener != nil { - doneExpected++ - s.gatewayListener.Close() - s.gatewayListener = nil - } - - // Kick HTTP monitoring if its running - if s.http != nil { - doneExpected++ - s.http.Close() - s.http = nil - } - - // Kick Profiling if its running - if s.profiler != nil { - doneExpected++ - s.profiler.Close() - } - - s.mu.Unlock() - - // Release go routines that wait on that channel - close(s.quitCh) - - // Close client and route connections - for _, c := range conns { - c.setNoReconnect() - c.closeConnection(ServerShutdown) - } - - // Block until the accept loops exit - for doneExpected > 0 { - <-s.done - doneExpected-- - } - - // Wait for go routines to be done. - s.grWG.Wait() - - if opts.PortsFileDir != _EMPTY_ { - s.deletePortsFile(opts.PortsFileDir) - } - - s.Noticef("Server Exiting..") - - // Stop OCSP Response Cache - if s.ocsprc != nil { - s.ocsprc.Stop(s) - } - - // Close logger if applicable. It allows tests on Windows - // to be able to do proper cleanup (delete log file). - s.logging.RLock() - log := s.logging.logger - s.logging.RUnlock() - if log != nil { - if l, ok := log.(*logger.Logger); ok { - l.Close() - } - } - // Notify that the shutdown is complete - close(s.shutdownComplete) -} - -// Close the websocket server if running. If so, returns 1, else 0. -// Server lock held on entry. -func (s *Server) closeWebsocketServer() int { - ws := &s.websocket - ws.mu.Lock() - hs := ws.server - if hs != nil { - ws.server = nil - ws.listener = nil - } - ws.mu.Unlock() - if hs != nil { - hs.Close() - return 1 - } - return 0 -} - -// WaitForShutdown will block until the server has been fully shutdown. -func (s *Server) WaitForShutdown() { - <-s.shutdownComplete -} - -// AcceptLoop is exported for easier testing. -func (s *Server) AcceptLoop(clr chan struct{}) { - // If we were to exit before the listener is setup properly, - // make sure we close the channel. - defer func() { - if clr != nil { - close(clr) - } - }() - - if s.isShuttingDown() { - return - } - - // Snapshot server options. - opts := s.getOpts() - - // Setup state that can enable shutdown - s.mu.Lock() - hp := net.JoinHostPort(opts.Host, strconv.Itoa(opts.Port)) - l, e := natsListen("tcp", hp) - s.listenerErr = e - if e != nil { - s.mu.Unlock() - s.Fatalf("Error listening on port: %s, %q", hp, e) - return - } - s.Noticef("Listening for client connections on %s", - net.JoinHostPort(opts.Host, strconv.Itoa(l.Addr().(*net.TCPAddr).Port))) - - // Alert of TLS enabled. - if opts.TLSConfig != nil { - s.Noticef("TLS required for client connections") - if opts.TLSHandshakeFirst && opts.TLSHandshakeFirstFallback == 0 { - s.Warnf("Clients that are not using \"TLS Handshake First\" option will fail to connect") - } - } - - // If server was started with RANDOM_PORT (-1), opts.Port would be equal - // to 0 at the beginning this function. So we need to get the actual port - if opts.Port == 0 { - // Write resolved port back to options. - opts.Port = l.Addr().(*net.TCPAddr).Port - } - - // Now that port has been set (if it was set to RANDOM), set the - // server's info Host/Port with either values from Options or - // ClientAdvertise. - if err := s.setInfoHostPort(); err != nil { - s.Fatalf("Error setting server INFO with ClientAdvertise value of %s, err=%v", opts.ClientAdvertise, err) - l.Close() - s.mu.Unlock() - return - } - // Keep track of client connect URLs. We may need them later. - s.clientConnectURLs = s.getClientConnectURLs() - s.listener = l - - go s.acceptConnections(l, "Client", func(conn net.Conn) { s.createClient(conn) }, - func(_ error) bool { - if s.isLameDuckMode() { - // Signal that we are not accepting new clients - s.ldmCh <- true - // Now wait for the Shutdown... - <-s.quitCh - return true - } - return false - }) - s.mu.Unlock() - - // Let the caller know that we are ready - close(clr) - clr = nil -} - -// InProcessConn returns an in-process connection to the server, -// avoiding the need to use a TCP listener for local connectivity -// within the same process. This can be used regardless of the -// state of the DontListen option. -func (s *Server) InProcessConn() (net.Conn, error) { - pl, pr := net.Pipe() - if !s.startGoRoutine(func() { - s.createClientInProcess(pl) - s.grWG.Done() - }) { - pl.Close() - pr.Close() - return nil, fmt.Errorf("failed to create connection") - } - return pr, nil -} - -func (s *Server) acceptConnections(l net.Listener, acceptName string, createFunc func(conn net.Conn), errFunc func(err error) bool) { - tmpDelay := ACCEPT_MIN_SLEEP - - for { - conn, err := l.Accept() - if err != nil { - if errFunc != nil && errFunc(err) { - return - } - if tmpDelay = s.acceptError(acceptName, err, tmpDelay); tmpDelay < 0 { - break - } - continue - } - tmpDelay = ACCEPT_MIN_SLEEP - if !s.startGoRoutine(func() { - s.reloadMu.RLock() - createFunc(conn) - s.reloadMu.RUnlock() - s.grWG.Done() - }) { - conn.Close() - } - } - s.Debugf(acceptName + " accept loop exiting..") - s.done <- true -} - -// This function sets the server's info Host/Port based on server Options. -// Note that this function may be called during config reload, this is why -// Host/Port may be reset to original Options if the ClientAdvertise option -// is not set (since it may have previously been). -func (s *Server) setInfoHostPort() error { - // When this function is called, opts.Port is set to the actual listen - // port (if option was originally set to RANDOM), even during a config - // reload. So use of s.opts.Port is safe. - opts := s.getOpts() - if opts.ClientAdvertise != _EMPTY_ { - h, p, err := parseHostPort(opts.ClientAdvertise, opts.Port) - if err != nil { - return err - } - s.info.Host = h - s.info.Port = p - } else { - s.info.Host = opts.Host - s.info.Port = opts.Port - } - return nil -} - -// StartProfiler is called to enable dynamic profiling. -func (s *Server) StartProfiler() { - if s.isShuttingDown() { - return - } - - // Snapshot server options. - opts := s.getOpts() - - port := opts.ProfPort - - // Check for Random Port - if port == -1 { - port = 0 - } - - s.mu.Lock() - hp := net.JoinHostPort(opts.Host, strconv.Itoa(port)) - l, err := net.Listen("tcp", hp) - - if err != nil { - s.mu.Unlock() - s.Fatalf("error starting profiler: %s", err) - return - } - s.Noticef("profiling port: %d", l.Addr().(*net.TCPAddr).Port) - - srv := &http.Server{ - Addr: hp, - Handler: http.DefaultServeMux, - MaxHeaderBytes: 1 << 20, - ReadTimeout: time.Second * 5, - } - s.profiler = l - s.profilingServer = srv - - s.setBlockProfileRate(opts.ProfBlockRate) - - go func() { - // if this errors out, it's probably because the server is being shutdown - err := srv.Serve(l) - if err != nil { - if !s.isShuttingDown() { - s.Fatalf("error starting profiler: %s", err) - } - } - srv.Close() - s.done <- true - }() - s.mu.Unlock() -} - -func (s *Server) setBlockProfileRate(rate int) { - // Passing i ProfBlockRate <= 0 here will disable or > 0 will enable. - runtime.SetBlockProfileRate(rate) - - if rate > 0 { - s.Warnf("Block profiling is enabled (rate %d), this may have a performance impact", rate) - } -} - -// StartHTTPMonitoring will enable the HTTP monitoring port. -// DEPRECATED: Should use StartMonitoring. -func (s *Server) StartHTTPMonitoring() { - s.startMonitoring(false) -} - -// StartHTTPSMonitoring will enable the HTTPS monitoring port. -// DEPRECATED: Should use StartMonitoring. -func (s *Server) StartHTTPSMonitoring() { - s.startMonitoring(true) -} - -// StartMonitoring starts the HTTP or HTTPs server if needed. -func (s *Server) StartMonitoring() error { - // Snapshot server options. - opts := s.getOpts() - - // Specifying both HTTP and HTTPS ports is a misconfiguration - if opts.HTTPPort != 0 && opts.HTTPSPort != 0 { - return fmt.Errorf("can't specify both HTTP (%v) and HTTPs (%v) ports", opts.HTTPPort, opts.HTTPSPort) - } - var err error - if opts.HTTPPort != 0 { - err = s.startMonitoring(false) - } else if opts.HTTPSPort != 0 { - if opts.TLSConfig == nil { - return fmt.Errorf("TLS cert and key required for HTTPS") - } - err = s.startMonitoring(true) - } - return err -} - -// HTTP endpoints -const ( - RootPath = "/" - VarzPath = "/varz" - ConnzPath = "/connz" - RoutezPath = "/routez" - GatewayzPath = "/gatewayz" - LeafzPath = "/leafz" - SubszPath = "/subsz" - StackszPath = "/stacksz" - AccountzPath = "/accountz" - AccountStatzPath = "/accstatz" - JszPath = "/jsz" - HealthzPath = "/healthz" - IPQueuesPath = "/ipqueuesz" - RaftzPath = "/raftz" -) - -func (s *Server) basePath(p string) string { - return path.Join(s.httpBasePath, p) -} - -type captureHTTPServerLog struct { - s *Server - prefix string -} - -func (cl *captureHTTPServerLog) Write(p []byte) (int, error) { - var buf [128]byte - var b = buf[:0] - - b = append(b, []byte(cl.prefix)...) - offset := 0 - if bytes.HasPrefix(p, []byte("http:")) { - offset = 6 - } - b = append(b, p[offset:]...) - cl.s.Errorf(string(b)) - return len(p), nil -} - -// The TLS configuration is passed to the listener when the monitoring -// "server" is setup. That prevents TLS configuration updates on reload -// from being used. By setting this function in tls.Config.GetConfigForClient -// we instruct the TLS handshake to ask for the tls configuration to be -// used for a specific client. We don't care which client, we always use -// the same TLS configuration. -func (s *Server) getMonitoringTLSConfig(_ *tls.ClientHelloInfo) (*tls.Config, error) { - opts := s.getOpts() - tc := opts.TLSConfig.Clone() - tc.ClientAuth = tls.NoClientCert - return tc, nil -} - -// Start the monitoring server -func (s *Server) startMonitoring(secure bool) error { - if s.isShuttingDown() { - return nil - } - - // Snapshot server options. - opts := s.getOpts() - - var ( - hp string - err error - httpListener net.Listener - port int - ) - - monitorProtocol := "http" - - if secure { - monitorProtocol += "s" - port = opts.HTTPSPort - if port == -1 { - port = 0 - } - hp = net.JoinHostPort(opts.HTTPHost, strconv.Itoa(port)) - config := opts.TLSConfig.Clone() - if !s.ocspPeerVerify { - config.GetConfigForClient = s.getMonitoringTLSConfig - config.ClientAuth = tls.NoClientCert - } - httpListener, err = tls.Listen("tcp", hp, config) - - } else { - port = opts.HTTPPort - if port == -1 { - port = 0 - } - hp = net.JoinHostPort(opts.HTTPHost, strconv.Itoa(port)) - httpListener, err = net.Listen("tcp", hp) - } - - if err != nil { - return fmt.Errorf("can't listen to the monitor port: %v", err) - } - - rport := httpListener.Addr().(*net.TCPAddr).Port - s.Noticef("Starting %s monitor on %s", monitorProtocol, net.JoinHostPort(opts.HTTPHost, strconv.Itoa(rport))) - - mux := http.NewServeMux() - - // Root - mux.HandleFunc(s.basePath(RootPath), s.HandleRoot) - // Varz - mux.HandleFunc(s.basePath(VarzPath), s.HandleVarz) - // Connz - mux.HandleFunc(s.basePath(ConnzPath), s.HandleConnz) - // Routez - mux.HandleFunc(s.basePath(RoutezPath), s.HandleRoutez) - // Gatewayz - mux.HandleFunc(s.basePath(GatewayzPath), s.HandleGatewayz) - // Leafz - mux.HandleFunc(s.basePath(LeafzPath), s.HandleLeafz) - // Subz - mux.HandleFunc(s.basePath(SubszPath), s.HandleSubsz) - // Subz alias for backwards compatibility - mux.HandleFunc(s.basePath("/subscriptionsz"), s.HandleSubsz) - // Stacksz - mux.HandleFunc(s.basePath(StackszPath), s.HandleStacksz) - // Accountz - mux.HandleFunc(s.basePath(AccountzPath), s.HandleAccountz) - // Accstatz - mux.HandleFunc(s.basePath(AccountStatzPath), s.HandleAccountStatz) - // Jsz - mux.HandleFunc(s.basePath(JszPath), s.HandleJsz) - // Healthz - mux.HandleFunc(s.basePath(HealthzPath), s.HandleHealthz) - // IPQueuesz - mux.HandleFunc(s.basePath(IPQueuesPath), s.HandleIPQueuesz) - // Raftz - mux.HandleFunc(s.basePath(RaftzPath), s.HandleRaftz) - - // Do not set a WriteTimeout because it could cause cURL/browser - // to return empty response or unable to display page if the - // server needs more time to build the response. - srv := &http.Server{ - Addr: hp, - Handler: mux, - MaxHeaderBytes: 1 << 20, - ErrorLog: log.New(&captureHTTPServerLog{s, "monitoring: "}, _EMPTY_, 0), - ReadHeaderTimeout: time.Second * 5, - } - s.mu.Lock() - s.http = httpListener - s.httpHandler = mux - s.monitoringServer = srv - s.mu.Unlock() - - go func() { - if err := srv.Serve(httpListener); err != nil { - if !s.isShuttingDown() { - s.Fatalf("Error starting monitor on %q: %v", hp, err) - } - } - srv.Close() - s.mu.Lock() - s.httpHandler = nil - s.mu.Unlock() - s.done <- true - }() - - return nil -} - -// HTTPHandler returns the http.Handler object used to handle monitoring -// endpoints. It will return nil if the server is not configured for -// monitoring, or if the server has not been started yet (Server.Start()). -func (s *Server) HTTPHandler() http.Handler { - s.mu.Lock() - defer s.mu.Unlock() - return s.httpHandler -} - -// Perform a conditional deep copy due to reference nature of [Client|WS]ConnectURLs. -// If updates are made to Info, this function should be consulted and updated. -// Assume lock is held. -func (s *Server) copyInfo() Info { - info := s.info - if len(info.ClientConnectURLs) > 0 { - info.ClientConnectURLs = append([]string(nil), s.info.ClientConnectURLs...) - } - if len(info.WSConnectURLs) > 0 { - info.WSConnectURLs = append([]string(nil), s.info.WSConnectURLs...) - } - return info -} - -// tlsMixConn is used when we can receive both TLS and non-TLS connections on same port. -type tlsMixConn struct { - net.Conn - pre *bytes.Buffer -} - -// Read for our mixed multi-reader. -func (c *tlsMixConn) Read(b []byte) (int, error) { - if c.pre != nil { - n, err := c.pre.Read(b) - if c.pre.Len() == 0 { - c.pre = nil - } - return n, err - } - return c.Conn.Read(b) -} - -func (s *Server) createClient(conn net.Conn) *client { - return s.createClientEx(conn, false) -} - -func (s *Server) createClientInProcess(conn net.Conn) *client { - return s.createClientEx(conn, true) -} - -func (s *Server) createClientEx(conn net.Conn, inProcess bool) *client { - // Snapshot server options. - opts := s.getOpts() - - maxPay := int32(opts.MaxPayload) - maxSubs := int32(opts.MaxSubs) - // For system, maxSubs of 0 means unlimited, so re-adjust here. - if maxSubs == 0 { - maxSubs = -1 - } - now := time.Now() - - c := &client{ - srv: s, - nc: conn, - opts: defaultOpts, - mpay: maxPay, - msubs: maxSubs, - start: now, - last: now, - iproc: inProcess, - } - - c.registerWithAccount(s.globalAccount()) - - var info Info - var authRequired bool - - s.mu.Lock() - // Grab JSON info string - info = s.copyInfo() - if s.nonceRequired() { - // Nonce handling - var raw [nonceLen]byte - nonce := raw[:] - s.generateNonce(nonce) - info.Nonce = string(nonce) - } - c.nonce = []byte(info.Nonce) - authRequired = info.AuthRequired - - // Check to see if we have auth_required set but we also have a no_auth_user. - // If so set back to false. - if info.AuthRequired && opts.NoAuthUser != _EMPTY_ && opts.NoAuthUser != s.sysAccOnlyNoAuthUser { - info.AuthRequired = false - } - - // Check to see if this is an in-process connection with tls_required. - // If so, set as not required, but available. - if inProcess && info.TLSRequired { - info.TLSRequired = false - info.TLSAvailable = true - } - - s.totalClients++ - s.mu.Unlock() - - // Grab lock - c.mu.Lock() - if authRequired { - c.flags.set(expectConnect) - } - - // Initialize - c.initClient() - - c.Debugf("Client connection created") - - // Save info.TLSRequired value since we may neeed to change it back and forth. - orgInfoTLSReq := info.TLSRequired - - var tlsFirstFallback time.Duration - // Check if we should do TLS first. - tlsFirst := opts.TLSConfig != nil && opts.TLSHandshakeFirst - if tlsFirst { - // Make sure info.TLSRequired is set to true (it could be false - // if AllowNonTLS is enabled). - info.TLSRequired = true - // Get the fallback delay value if applicable. - if f := opts.TLSHandshakeFirstFallback; f > 0 { - tlsFirstFallback = f - } else if inProcess { - // For in-process connection, we will always have a fallback - // delay. It allows support for non-TLS, TLS and "TLS First" - // in-process clients to successfully connect. - tlsFirstFallback = DEFAULT_TLS_HANDSHAKE_FIRST_FALLBACK_DELAY - } - } - - // Decide if we are going to require TLS or not and generate INFO json. - tlsRequired := info.TLSRequired - infoBytes := c.generateClientInfoJSON(info) - - // Send our information, except if TLS and TLSHandshakeFirst is requested. - if !tlsFirst { - // Need to be sent in place since writeLoop cannot be started until - // TLS handshake is done (if applicable). - c.sendProtoNow(infoBytes) - } - - // Unlock to register - c.mu.Unlock() - - // Register with the server. - s.mu.Lock() - // If server is not running, Shutdown() may have already gathered the - // list of connections to close. It won't contain this one, so we need - // to bail out now otherwise the readLoop started down there would not - // be interrupted. Skip also if in lame duck mode. - if !s.isRunning() || s.ldm { - // There are some tests that create a server but don't start it, - // and use "async" clients and perform the parsing manually. Such - // clients would branch here (since server is not running). However, - // when a server was really running and has been shutdown, we must - // close this connection. - if s.isShuttingDown() { - conn.Close() - } - s.mu.Unlock() - return c - } - - // If there is a max connections specified, check that adding - // this new client would not push us over the max - if opts.MaxConn > 0 && len(s.clients) >= opts.MaxConn { - s.mu.Unlock() - c.maxConnExceeded() - return nil - } - s.clients[c.cid] = c - - s.mu.Unlock() - - // Re-Grab lock - c.mu.Lock() - - isClosed := c.isClosed() - var pre []byte - // We need first to check for "TLS First" fallback delay. - if !isClosed && tlsFirstFallback > 0 { - // We wait and see if we are getting any data. Since we did not send - // the INFO protocol yet, only clients that use TLS first should be - // sending data (the TLS handshake). We don't really check the content: - // if it is a rogue agent and not an actual client performing the - // TLS handshake, the error will be detected when performing the - // handshake on our side. - pre = make([]byte, 4) - c.nc.SetReadDeadline(time.Now().Add(tlsFirstFallback)) - n, _ := io.ReadFull(c.nc, pre[:]) - c.nc.SetReadDeadline(time.Time{}) - // If we get any data (regardless of possible timeout), we will proceed - // with the TLS handshake. - if n > 0 { - pre = pre[:n] - } else { - // We did not get anything so we will send the INFO protocol. - pre = nil - - // Restore the original info.TLSRequired value if it is - // different that the current value and regenerate infoBytes. - if orgInfoTLSReq != info.TLSRequired { - info.TLSRequired = orgInfoTLSReq - infoBytes = c.generateClientInfoJSON(info) - } - c.sendProtoNow(infoBytes) - // Set the boolean to false for the rest of the function. - tlsFirst = false - // Check closed status again - isClosed = c.isClosed() - } - } - // If we have both TLS and non-TLS allowed we need to see which - // one the client wants. We'll always allow this for in-process - // connections. - if !isClosed && !tlsFirst && opts.TLSConfig != nil && (inProcess || opts.AllowNonTLS) { - pre = make([]byte, 4) - c.nc.SetReadDeadline(time.Now().Add(secondsToDuration(opts.TLSTimeout))) - n, _ := io.ReadFull(c.nc, pre[:]) - c.nc.SetReadDeadline(time.Time{}) - pre = pre[:n] - if n > 0 && pre[0] == 0x16 { - tlsRequired = true - } else { - tlsRequired = false - } - } - - // Check for TLS - if !isClosed && tlsRequired { - if s.connRateCounter != nil && !s.connRateCounter.allow() { - c.mu.Unlock() - c.sendErr("Connection throttling is active. Please try again later.") - c.closeConnection(MaxConnectionsExceeded) - return nil - } - - // If we have a prebuffer create a multi-reader. - if len(pre) > 0 { - c.nc = &tlsMixConn{c.nc, bytes.NewBuffer(pre)} - // Clear pre so it is not parsed. - pre = nil - } - // Performs server-side TLS handshake. - if err := c.doTLSServerHandshake(_EMPTY_, opts.TLSConfig, opts.TLSTimeout, opts.TLSPinnedCerts); err != nil { - c.mu.Unlock() - return nil - } - } - - // Now, send the INFO if it was delayed - if !isClosed && tlsFirst { - c.flags.set(didTLSFirst) - c.sendProtoNow(infoBytes) - // Check closed status - isClosed = c.isClosed() - } - - // Connection could have been closed while sending the INFO proto. - if isClosed { - c.mu.Unlock() - // We need to call closeConnection() to make sure that proper cleanup is done. - c.closeConnection(WriteError) - return nil - } - - // Check for Auth. We schedule this timer after the TLS handshake to avoid - // the race where the timer fires during the handshake and causes the - // server to write bad data to the socket. See issue #432. - if authRequired { - c.setAuthTimer(secondsToDuration(opts.AuthTimeout)) - } - - // Do final client initialization - - // Set the Ping timer. Will be reset once connect was received. - c.setPingTimer() - - // Spin up the read loop. - s.startGoRoutine(func() { c.readLoop(pre) }) - - // Spin up the write loop. - s.startGoRoutine(func() { c.writeLoop() }) - - if tlsRequired { - c.Debugf("TLS handshake complete") - cs := c.nc.(*tls.Conn).ConnectionState() - c.Debugf("TLS version %s, cipher suite %s", tlsVersion(cs.Version), tlsCipher(cs.CipherSuite)) - } - - c.mu.Unlock() - - return c -} - -// This will save off a closed client in a ring buffer such that -// /connz can inspect. Useful for debugging, etc. -func (s *Server) saveClosedClient(c *client, nc net.Conn, subs map[string]*subscription, reason ClosedState) { - now := time.Now() - - s.accountDisconnectEvent(c, now, reason.String()) - - c.mu.Lock() - - cc := &closedClient{} - cc.fill(c, nc, now, false) - // Note that cc.fill is using len(c.subs), which may have been set to nil by now, - // so replace cc.NumSubs with len(subs). - cc.NumSubs = uint32(len(subs)) - cc.Stop = &now - cc.Reason = reason.String() - - // Do subs, do not place by default in main ConnInfo - if len(subs) > 0 { - cc.subs = make([]SubDetail, 0, len(subs)) - for _, sub := range subs { - cc.subs = append(cc.subs, newSubDetail(sub)) - } - } - // Hold user as well. - cc.user = c.getRawAuthUser() - // Hold account name if not the global account. - if c.acc != nil && c.acc.Name != globalAccountName { - cc.acc = c.acc.Name - } - cc.JWT = c.opts.JWT - cc.IssuerKey = issuerForClient(c) - cc.Tags = c.tags - cc.NameTag = c.nameTag - c.mu.Unlock() - - // Place in the ring buffer - s.mu.Lock() - if s.closed != nil { - s.closed.append(cc) - } - s.mu.Unlock() -} - -// Adds to the list of client and websocket clients connect URLs. -// If there was a change, an INFO protocol is sent to registered clients -// that support async INFO protocols. -// Server lock held on entry. -func (s *Server) addConnectURLsAndSendINFOToClients(curls, wsurls []string) { - s.updateServerINFOAndSendINFOToClients(curls, wsurls, true) -} - -// Removes from the list of client and websocket clients connect URLs. -// If there was a change, an INFO protocol is sent to registered clients -// that support async INFO protocols. -// Server lock held on entry. -func (s *Server) removeConnectURLsAndSendINFOToClients(curls, wsurls []string) { - s.updateServerINFOAndSendINFOToClients(curls, wsurls, false) -} - -// Updates the list of client and websocket clients connect URLs and if any change -// sends an async INFO update to clients that support it. -// Server lock held on entry. -func (s *Server) updateServerINFOAndSendINFOToClients(curls, wsurls []string, add bool) { - remove := !add - // Will return true if we need alter the server's Info object. - updateMap := func(urls []string, m refCountedUrlSet) bool { - wasUpdated := false - for _, url := range urls { - if add && m.addUrl(url) { - wasUpdated = true - } else if remove && m.removeUrl(url) { - wasUpdated = true - } - } - return wasUpdated - } - cliUpdated := updateMap(curls, s.clientConnectURLsMap) - wsUpdated := updateMap(wsurls, s.websocket.connectURLsMap) - - updateInfo := func(infoURLs *[]string, urls []string, m refCountedUrlSet) { - // Recreate the info's slice from the map - *infoURLs = (*infoURLs)[:0] - // Add this server client connect ULRs first... - *infoURLs = append(*infoURLs, urls...) - // Then the ones from the map - for url := range m { - *infoURLs = append(*infoURLs, url) - } - } - if cliUpdated { - updateInfo(&s.info.ClientConnectURLs, s.clientConnectURLs, s.clientConnectURLsMap) - } - if wsUpdated { - updateInfo(&s.info.WSConnectURLs, s.websocket.connectURLs, s.websocket.connectURLsMap) - } - if cliUpdated || wsUpdated { - // Update the time of this update - s.lastCURLsUpdate = time.Now().UnixNano() - // Send to all registered clients that support async INFO protocols. - s.sendAsyncInfoToClients(cliUpdated, wsUpdated) - } -} - -// Handle closing down a connection when the handshake has timedout. -func tlsTimeout(c *client, conn *tls.Conn) { - c.mu.Lock() - closed := c.isClosed() - c.mu.Unlock() - // Check if already closed - if closed { - return - } - cs := conn.ConnectionState() - if !cs.HandshakeComplete { - c.Errorf("TLS handshake timeout") - c.sendErr("Secure Connection - TLS Required") - c.closeConnection(TLSHandshakeError) - } -} - -// Seems silly we have to write these -func tlsVersion(ver uint16) string { - switch ver { - case tls.VersionTLS10: - return "1.0" - case tls.VersionTLS11: - return "1.1" - case tls.VersionTLS12: - return "1.2" - case tls.VersionTLS13: - return "1.3" - } - return fmt.Sprintf("Unknown [0x%x]", ver) -} - -func tlsVersionFromString(ver string) (uint16, error) { - switch ver { - case "1.0": - return tls.VersionTLS10, nil - case "1.1": - return tls.VersionTLS11, nil - case "1.2": - return tls.VersionTLS12, nil - case "1.3": - return tls.VersionTLS13, nil - } - return 0, fmt.Errorf("unknown version: %v", ver) -} - -// We use hex here so we don't need multiple versions -func tlsCipher(cs uint16) string { - name, present := cipherMapByID[cs] - if present { - return name - } - return fmt.Sprintf("Unknown [0x%x]", cs) -} - -// Remove a client or route from our internal accounting. -func (s *Server) removeClient(c *client) { - // kind is immutable, so can check without lock - switch c.kind { - case CLIENT: - c.mu.Lock() - cid := c.cid - updateProtoInfoCount := false - if c.kind == CLIENT && c.opts.Protocol >= ClientProtoInfo { - updateProtoInfoCount = true - } - c.mu.Unlock() - - s.mu.Lock() - delete(s.clients, cid) - if updateProtoInfoCount { - s.cproto-- - } - s.mu.Unlock() - case ROUTER: - s.removeRoute(c) - case GATEWAY: - s.removeRemoteGatewayConnection(c) - case LEAF: - s.removeLeafNodeConnection(c) - } -} - -func (s *Server) removeFromTempClients(cid uint64) { - s.grMu.Lock() - delete(s.grTmpClients, cid) - s.grMu.Unlock() -} - -func (s *Server) addToTempClients(cid uint64, c *client) bool { - added := false - s.grMu.Lock() - if s.grRunning { - s.grTmpClients[cid] = c - added = true - } - s.grMu.Unlock() - return added -} - -///////////////////////////////////////////////////////////////// -// These are some helpers for accounting in functional tests. -///////////////////////////////////////////////////////////////// - -// NumRoutes will report the number of registered routes. -func (s *Server) NumRoutes() int { - s.mu.RLock() - defer s.mu.RUnlock() - return s.numRoutes() -} - -// numRoutes will report the number of registered routes. -// Server lock held on entry -func (s *Server) numRoutes() int { - var nr int - s.forEachRoute(func(c *client) { - nr++ - }) - return nr -} - -// NumRemotes will report number of registered remotes. -func (s *Server) NumRemotes() int { - s.mu.RLock() - defer s.mu.RUnlock() - return s.numRemotes() -} - -// numRemotes will report number of registered remotes. -// Server lock held on entry -func (s *Server) numRemotes() int { - return len(s.routes) -} - -// NumLeafNodes will report number of leaf node connections. -func (s *Server) NumLeafNodes() int { - s.mu.RLock() - defer s.mu.RUnlock() - return len(s.leafs) -} - -// NumClients will report the number of registered clients. -func (s *Server) NumClients() int { - s.mu.RLock() - defer s.mu.RUnlock() - return len(s.clients) -} - -// GetClient will return the client associated with cid. -func (s *Server) GetClient(cid uint64) *client { - return s.getClient(cid) -} - -// getClient will return the client associated with cid. -func (s *Server) getClient(cid uint64) *client { - s.mu.RLock() - defer s.mu.RUnlock() - return s.clients[cid] -} - -// GetLeafNode returns the leafnode associated with the cid. -func (s *Server) GetLeafNode(cid uint64) *client { - s.mu.RLock() - defer s.mu.RUnlock() - return s.leafs[cid] -} - -// NumSubscriptions will report how many subscriptions are active. -func (s *Server) NumSubscriptions() uint32 { - s.mu.RLock() - defer s.mu.RUnlock() - return s.numSubscriptions() -} - -// numSubscriptions will report how many subscriptions are active. -// Lock should be held. -func (s *Server) numSubscriptions() uint32 { - var subs int - s.accounts.Range(func(k, v any) bool { - acc := v.(*Account) - subs += acc.TotalSubs() - return true - }) - return uint32(subs) -} - -// NumSlowConsumers will report the number of slow consumers. -func (s *Server) NumSlowConsumers() int64 { - return atomic.LoadInt64(&s.slowConsumers) -} - -// NumSlowConsumersClients will report the number of slow consumers clients. -func (s *Server) NumSlowConsumersClients() uint64 { - return s.scStats.clients.Load() -} - -// NumSlowConsumersRoutes will report the number of slow consumers routes. -func (s *Server) NumSlowConsumersRoutes() uint64 { - return s.scStats.routes.Load() -} - -// NumSlowConsumersGateways will report the number of slow consumers leafs. -func (s *Server) NumSlowConsumersGateways() uint64 { - return s.scStats.gateways.Load() -} - -// NumSlowConsumersLeafs will report the number of slow consumers leafs. -func (s *Server) NumSlowConsumersLeafs() uint64 { - return s.scStats.leafs.Load() -} - -// ConfigTime will report the last time the server configuration was loaded. -func (s *Server) ConfigTime() time.Time { - s.mu.RLock() - defer s.mu.RUnlock() - return s.configTime -} - -// Addr will return the net.Addr object for the current listener. -func (s *Server) Addr() net.Addr { - s.mu.RLock() - defer s.mu.RUnlock() - if s.listener == nil { - return nil - } - return s.listener.Addr() -} - -// MonitorAddr will return the net.Addr object for the monitoring listener. -func (s *Server) MonitorAddr() *net.TCPAddr { - s.mu.RLock() - defer s.mu.RUnlock() - if s.http == nil { - return nil - } - return s.http.Addr().(*net.TCPAddr) -} - -// ClusterAddr returns the net.Addr object for the route listener. -func (s *Server) ClusterAddr() *net.TCPAddr { - s.mu.RLock() - defer s.mu.RUnlock() - if s.routeListener == nil { - return nil - } - return s.routeListener.Addr().(*net.TCPAddr) -} - -// ProfilerAddr returns the net.Addr object for the profiler listener. -func (s *Server) ProfilerAddr() *net.TCPAddr { - s.mu.RLock() - defer s.mu.RUnlock() - if s.profiler == nil { - return nil - } - return s.profiler.Addr().(*net.TCPAddr) -} - -func (s *Server) readyForConnections(d time.Duration) error { - // Snapshot server options. - opts := s.getOpts() - - type info struct { - ok bool - err error - } - chk := make(map[string]info) - - end := time.Now().Add(d) - for time.Now().Before(end) { - s.mu.RLock() - chk["server"] = info{ok: s.listener != nil || opts.DontListen, err: s.listenerErr} - chk["route"] = info{ok: (opts.Cluster.Port == 0 || s.routeListener != nil), err: s.routeListenerErr} - chk["gateway"] = info{ok: (opts.Gateway.Name == _EMPTY_ || s.gatewayListener != nil), err: s.gatewayListenerErr} - chk["leafnode"] = info{ok: (opts.LeafNode.Port == 0 || s.leafNodeListener != nil), err: s.leafNodeListenerErr} - chk["websocket"] = info{ok: (opts.Websocket.Port == 0 || s.websocket.listener != nil), err: s.websocket.listenerErr} - chk["mqtt"] = info{ok: (opts.MQTT.Port == 0 || s.mqtt.listener != nil), err: s.mqtt.listenerErr} - s.mu.RUnlock() - - var numOK int - for _, inf := range chk { - if inf.ok { - numOK++ - } - } - if numOK == len(chk) { - // In the case of DontListen option (no accept loop), we still want - // to make sure that Start() has done all the work, so we wait on - // that. - if opts.DontListen { - select { - case <-s.startupComplete: - case <-time.After(d): - return fmt.Errorf("failed to be ready for connections after %s: startup did not complete", d) - } - } - return nil - } - if d > 25*time.Millisecond { - time.Sleep(25 * time.Millisecond) - } - } - - failed := make([]string, 0, len(chk)) - for name, inf := range chk { - if inf.ok && inf.err != nil { - failed = append(failed, fmt.Sprintf("%s(ok, but %s)", name, inf.err)) - } - if !inf.ok && inf.err == nil { - failed = append(failed, name) - } - if !inf.ok && inf.err != nil { - failed = append(failed, fmt.Sprintf("%s(%s)", name, inf.err)) - } - } - - return fmt.Errorf( - "failed to be ready for connections after %s: %s", - d, strings.Join(failed, ", "), - ) -} - -// ReadyForConnections returns `true` if the server is ready to accept clients -// and, if routing is enabled, route connections. If after the duration -// `dur` the server is still not ready, returns `false`. -func (s *Server) ReadyForConnections(dur time.Duration) bool { - return s.readyForConnections(dur) == nil -} - -// Quick utility to function to tell if the server supports headers. -func (s *Server) supportsHeaders() bool { - if s == nil { - return false - } - return !(s.getOpts().NoHeaderSupport) -} - -// ID returns the server's ID -func (s *Server) ID() string { - return s.info.ID -} - -// NodeName returns the node name for this server. -func (s *Server) NodeName() string { - return getHash(s.info.Name) -} - -// Name returns the server's name. This will be the same as the ID if it was not set. -func (s *Server) Name() string { - return s.info.Name -} - -func (s *Server) String() string { - return s.info.Name -} - -type pprofLabels map[string]string - -func setGoRoutineLabels(tags ...pprofLabels) { - var labels []string - for _, m := range tags { - for k, v := range m { - labels = append(labels, k, v) - } - } - if len(labels) > 0 { - pprof.SetGoroutineLabels( - pprof.WithLabels(context.Background(), pprof.Labels(labels...)), - ) - } -} - -func (s *Server) startGoRoutine(f func(), tags ...pprofLabels) bool { - var started bool - s.grMu.Lock() - defer s.grMu.Unlock() - if s.grRunning { - s.grWG.Add(1) - go func() { - setGoRoutineLabels(tags...) - f() - }() - started = true - } - return started -} - -func (s *Server) numClosedConns() int { - s.mu.RLock() - defer s.mu.RUnlock() - return s.closed.len() -} - -func (s *Server) totalClosedConns() uint64 { - s.mu.RLock() - defer s.mu.RUnlock() - return s.closed.totalConns() -} - -func (s *Server) closedClients() []*closedClient { - s.mu.RLock() - defer s.mu.RUnlock() - return s.closed.closedClients() -} - -// getClientConnectURLs returns suitable URLs for clients to connect to the listen -// port based on the server options' Host and Port. If the Host corresponds to -// "any" interfaces, this call returns the list of resolved IP addresses. -// If ClientAdvertise is set, returns the client advertise host and port. -// The server lock is assumed held on entry. -func (s *Server) getClientConnectURLs() []string { - // Snapshot server options. - opts := s.getOpts() - // Ignore error here since we know that if there is client advertise, the - // parseHostPort is correct because we did it right before calling this - // function in Server.New(). - urls, _ := s.getConnectURLs(opts.ClientAdvertise, opts.Host, opts.Port) - return urls -} - -// Generic version that will return an array of URLs based on the given -// advertise, host and port values. -func (s *Server) getConnectURLs(advertise, host string, port int) ([]string, error) { - urls := make([]string, 0, 1) - - // short circuit if advertise is set - if advertise != "" { - h, p, err := parseHostPort(advertise, port) - if err != nil { - return nil, err - } - urls = append(urls, net.JoinHostPort(h, strconv.Itoa(p))) - } else { - sPort := strconv.Itoa(port) - _, ips, err := s.getNonLocalIPsIfHostIsIPAny(host, true) - for _, ip := range ips { - urls = append(urls, net.JoinHostPort(ip, sPort)) - } - if err != nil || len(urls) == 0 { - // We are here if s.opts.Host is not "0.0.0.0" nor "::", or if for some - // reason we could not add any URL in the loop above. - // We had a case where a Windows VM was hosed and would have err == nil - // and not add any address in the array in the loop above, and we - // ended-up returning 0.0.0.0, which is problematic for Windows clients. - // Check for 0.0.0.0 or :: specifically, and ignore if that's the case. - if host == "0.0.0.0" || host == "::" { - s.Errorf("Address %q can not be resolved properly", host) - } else { - urls = append(urls, net.JoinHostPort(host, sPort)) - } - } - } - return urls, nil -} - -// Returns an array of non local IPs if the provided host is -// 0.0.0.0 or ::. It returns the first resolved if `all` is -// false. -// The boolean indicate if the provided host was 0.0.0.0 (or ::) -// so that if the returned array is empty caller can decide -// what to do next. -func (s *Server) getNonLocalIPsIfHostIsIPAny(host string, all bool) (bool, []string, error) { - ip := net.ParseIP(host) - // If this is not an IP, we are done - if ip == nil { - return false, nil, nil - } - // If this is not 0.0.0.0 or :: we have nothing to do. - if !ip.IsUnspecified() { - return false, nil, nil - } - s.Debugf("Get non local IPs for %q", host) - var ips []string - ifaces, _ := net.Interfaces() - for _, i := range ifaces { - addrs, _ := i.Addrs() - for _, addr := range addrs { - switch v := addr.(type) { - case *net.IPNet: - ip = v.IP - case *net.IPAddr: - ip = v.IP - } - ipStr := ip.String() - // Skip non global unicast addresses - if !ip.IsGlobalUnicast() || ip.IsUnspecified() { - ip = nil - continue - } - s.Debugf(" ip=%s", ipStr) - ips = append(ips, ipStr) - if !all { - break - } - } - } - return true, ips, nil -} - -// if the ip is not specified, attempt to resolve it -func resolveHostPorts(addr net.Listener) []string { - hostPorts := make([]string, 0) - hp := addr.Addr().(*net.TCPAddr) - port := strconv.Itoa(hp.Port) - if hp.IP.IsUnspecified() { - var ip net.IP - ifaces, _ := net.Interfaces() - for _, i := range ifaces { - addrs, _ := i.Addrs() - for _, addr := range addrs { - switch v := addr.(type) { - case *net.IPNet: - ip = v.IP - hostPorts = append(hostPorts, net.JoinHostPort(ip.String(), port)) - case *net.IPAddr: - ip = v.IP - hostPorts = append(hostPorts, net.JoinHostPort(ip.String(), port)) - default: - continue - } - } - } - } else { - hostPorts = append(hostPorts, net.JoinHostPort(hp.IP.String(), port)) - } - return hostPorts -} - -// format the address of a net.Listener with a protocol -func formatURL(protocol string, addr net.Listener) []string { - hostports := resolveHostPorts(addr) - for i, hp := range hostports { - hostports[i] = fmt.Sprintf("%s://%s", protocol, hp) - } - return hostports -} - -// Ports describes URLs that the server can be contacted in -type Ports struct { - Nats []string `json:"nats,omitempty"` - Monitoring []string `json:"monitoring,omitempty"` - Cluster []string `json:"cluster,omitempty"` - Profile []string `json:"profile,omitempty"` - WebSocket []string `json:"websocket,omitempty"` -} - -// PortsInfo attempts to resolve all the ports. If after maxWait the ports are not -// resolved, it returns nil. Otherwise it returns a Ports struct -// describing ports where the server can be contacted -func (s *Server) PortsInfo(maxWait time.Duration) *Ports { - if s.readyForListeners(maxWait) { - opts := s.getOpts() - - s.mu.RLock() - tls := s.info.TLSRequired - listener := s.listener - httpListener := s.http - clusterListener := s.routeListener - profileListener := s.profiler - wsListener := s.websocket.listener - wss := s.websocket.tls - s.mu.RUnlock() - - ports := Ports{} - - if listener != nil { - natsProto := "nats" - if tls { - natsProto = "tls" - } - ports.Nats = formatURL(natsProto, listener) - } - - if httpListener != nil { - monProto := "http" - if opts.HTTPSPort != 0 { - monProto = "https" - } - ports.Monitoring = formatURL(monProto, httpListener) - } - - if clusterListener != nil { - clusterProto := "nats" - if opts.Cluster.TLSConfig != nil { - clusterProto = "tls" - } - ports.Cluster = formatURL(clusterProto, clusterListener) - } - - if profileListener != nil { - ports.Profile = formatURL("http", profileListener) - } - - if wsListener != nil { - protocol := wsSchemePrefix - if wss { - protocol = wsSchemePrefixTLS - } - ports.WebSocket = formatURL(protocol, wsListener) - } - - return &ports - } - - return nil -} - -// Returns the portsFile. If a non-empty dirHint is provided, the dirHint -// path is used instead of the server option value -func (s *Server) portFile(dirHint string) string { - dirname := s.getOpts().PortsFileDir - if dirHint != "" { - dirname = dirHint - } - if dirname == _EMPTY_ { - return _EMPTY_ - } - return filepath.Join(dirname, fmt.Sprintf("%s_%d.ports", filepath.Base(os.Args[0]), os.Getpid())) -} - -// Delete the ports file. If a non-empty dirHint is provided, the dirHint -// path is used instead of the server option value -func (s *Server) deletePortsFile(hintDir string) { - portsFile := s.portFile(hintDir) - if portsFile != "" { - if err := os.Remove(portsFile); err != nil { - s.Errorf("Error cleaning up ports file %s: %v", portsFile, err) - } - } -} - -// Writes a file with a serialized Ports to the specified ports_file_dir. -// The name of the file is `exename_pid.ports`, typically nats-server_pid.ports. -// if ports file is not set, this function has no effect -func (s *Server) logPorts() { - opts := s.getOpts() - portsFile := s.portFile(opts.PortsFileDir) - if portsFile != _EMPTY_ { - go func() { - info := s.PortsInfo(5 * time.Second) - if info == nil { - s.Errorf("Unable to resolve the ports in the specified time") - return - } - data, err := json.Marshal(info) - if err != nil { - s.Errorf("Error marshaling ports file: %v", err) - return - } - if err := os.WriteFile(portsFile, data, 0666); err != nil { - s.Errorf("Error writing ports file (%s): %v", portsFile, err) - return - } - - }() - } -} - -// waits until a calculated list of listeners is resolved or a timeout -func (s *Server) readyForListeners(dur time.Duration) bool { - end := time.Now().Add(dur) - for time.Now().Before(end) { - s.mu.RLock() - listeners := s.serviceListeners() - s.mu.RUnlock() - if len(listeners) == 0 { - return false - } - - ok := true - for _, l := range listeners { - if l == nil { - ok = false - break - } - } - if ok { - return true - } - select { - case <-s.quitCh: - return false - case <-time.After(25 * time.Millisecond): - // continue - unable to select from quit - we are still running - } - } - return false -} - -// returns a list of listeners that are intended for the process -// if the entry is nil, the interface is yet to be resolved -func (s *Server) serviceListeners() []net.Listener { - listeners := make([]net.Listener, 0) - opts := s.getOpts() - listeners = append(listeners, s.listener) - if opts.Cluster.Port != 0 { - listeners = append(listeners, s.routeListener) - } - if opts.HTTPPort != 0 || opts.HTTPSPort != 0 { - listeners = append(listeners, s.http) - } - if opts.ProfPort != 0 { - listeners = append(listeners, s.profiler) - } - if opts.Websocket.Port != 0 { - listeners = append(listeners, s.websocket.listener) - } - return listeners -} - -// Returns true if in lame duck mode. -func (s *Server) isLameDuckMode() bool { - s.mu.RLock() - defer s.mu.RUnlock() - return s.ldm -} - -// LameDuckShutdown will perform a lame duck shutdown of NATS, whereby -// the client listener is closed, existing client connections are -// kicked, Raft leaderships are transferred, JetStream is shutdown -// and then finally shutdown the the NATS Server itself. -// This function blocks and will not return until the NATS Server -// has completed the entire shutdown operation. -func (s *Server) LameDuckShutdown() { - s.lameDuckMode() -} - -// This function will close the client listener then close the clients -// at some interval to avoid a reconnect storm. -// We will also transfer any raft leaders and shutdown JetStream. -func (s *Server) lameDuckMode() { - s.mu.Lock() - // Check if there is actually anything to do - if s.isShuttingDown() || s.ldm || s.listener == nil { - s.mu.Unlock() - return - } - s.Noticef("Entering lame duck mode, stop accepting new clients") - s.ldm = true - s.sendLDMShutdownEventLocked() - expected := 1 - s.listener.Close() - s.listener = nil - expected += s.closeWebsocketServer() - s.ldmCh = make(chan bool, expected) - opts := s.getOpts() - gp := opts.LameDuckGracePeriod - // For tests, we want the grace period to be in some cases bigger - // than the ldm duration, so to by-pass the validateOptions() check, - // we use negative number and flip it here. - if gp < 0 { - gp *= -1 - } - s.mu.Unlock() - - // If we are running any raftNodes transfer leaders. - if hadTransfers := s.transferRaftLeaders(); hadTransfers { - // They will transfer leadership quickly, but wait here for a second. - select { - case <-time.After(time.Second): - case <-s.quitCh: - return - } - } - - // Now check and shutdown jetstream. - s.shutdownJetStream() - - // Now shutdown the nodes - s.shutdownRaftNodes() - - // Wait for accept loops to be done to make sure that no new - // client can connect - for i := 0; i < expected; i++ { - <-s.ldmCh - } - - s.mu.Lock() - // Need to recheck few things - if s.isShuttingDown() || len(s.clients) == 0 { - s.mu.Unlock() - // If there is no client, we need to call Shutdown() to complete - // the LDMode. If server has been shutdown while lock was released, - // calling Shutdown() should be no-op. - s.Shutdown() - return - } - dur := int64(opts.LameDuckDuration) - dur -= int64(gp) - if dur <= 0 { - dur = int64(time.Second) - } - numClients := int64(len(s.clients)) - batch := 1 - // Sleep interval between each client connection close. - var si int64 - if numClients != 0 { - si = dur / numClients - } - if si < 1 { - // Should not happen (except in test with very small LD duration), but - // if there are too many clients, batch the number of close and - // use a tiny sleep interval that will result in yield likely. - si = 1 - batch = int(numClients / dur) - } else if si > int64(time.Second) { - // Conversely, there is no need to sleep too long between clients - // and spread say 10 clients for the 2min duration. Sleeping no - // more than 1sec. - si = int64(time.Second) - } - - // Now capture all clients - clients := make([]*client, 0, len(s.clients)) - for _, client := range s.clients { - clients = append(clients, client) - } - // Now that we know that no new client can be accepted, - // send INFO to routes and clients to notify this state. - s.sendLDMToRoutes() - s.sendLDMToClients() - s.mu.Unlock() - - t := time.NewTimer(gp) - // Delay start of closing of client connections in case - // we have several servers that we want to signal to enter LD mode - // and not have their client reconnect to each other. - select { - case <-t.C: - s.Noticef("Closing existing clients") - case <-s.quitCh: - t.Stop() - return - } - for i, client := range clients { - client.closeConnection(ServerShutdown) - if i == len(clients)-1 { - break - } - if batch == 1 || i%batch == 0 { - // We pick a random interval which will be at least si/2 - v := rand.Int63n(si) - if v < si/2 { - v = si / 2 - } - t.Reset(time.Duration(v)) - // Sleep for given interval or bail out if kicked by Shutdown(). - select { - case <-t.C: - case <-s.quitCh: - t.Stop() - return - } - } - } - s.Shutdown() - s.WaitForShutdown() -} - -// Send an INFO update to routes with the indication that this server is in LDM mode. -// Server lock is held on entry. -func (s *Server) sendLDMToRoutes() { - s.routeInfo.LameDuckMode = true - infoJSON := generateInfoJSON(&s.routeInfo) - s.forEachRemote(func(r *client) { - r.mu.Lock() - r.enqueueProto(infoJSON) - r.mu.Unlock() - }) - // Clear now so that we notify only once, should we have to send other INFOs. - s.routeInfo.LameDuckMode = false -} - -// Send an INFO update to clients with the indication that this server is in -// LDM mode and with only URLs of other nodes. -// Server lock is held on entry. -func (s *Server) sendLDMToClients() { - s.info.LameDuckMode = true - // Clear this so that if there are further updates, we don't send our URLs. - s.clientConnectURLs = s.clientConnectURLs[:0] - if s.websocket.connectURLs != nil { - s.websocket.connectURLs = s.websocket.connectURLs[:0] - } - // Reset content first. - s.info.ClientConnectURLs = s.info.ClientConnectURLs[:0] - s.info.WSConnectURLs = s.info.WSConnectURLs[:0] - // Only add the other nodes if we are allowed to. - if !s.getOpts().Cluster.NoAdvertise { - for url := range s.clientConnectURLsMap { - s.info.ClientConnectURLs = append(s.info.ClientConnectURLs, url) - } - for url := range s.websocket.connectURLsMap { - s.info.WSConnectURLs = append(s.info.WSConnectURLs, url) - } - } - // Send to all registered clients that support async INFO protocols. - s.sendAsyncInfoToClients(true, true) - // We now clear the info.LameDuckMode flag so that if there are - // cluster updates and we send the INFO, we don't have the boolean - // set which would cause multiple LDM notifications to clients. - s.info.LameDuckMode = false -} - -// If given error is a net.Error and is temporary, sleeps for the given -// delay and double it, but cap it to ACCEPT_MAX_SLEEP. The sleep is -// interrupted if the server is shutdown. -// An error message is displayed depending on the type of error. -// Returns the new (or unchanged) delay, or a negative value if the -// server has been or is being shutdown. -func (s *Server) acceptError(acceptName string, err error, tmpDelay time.Duration) time.Duration { - if !s.isRunning() { - return -1 - } - //lint:ignore SA1019 We want to retry on a bunch of errors here. - if ne, ok := err.(net.Error); ok && ne.Temporary() { // nolint:staticcheck - s.Errorf("Temporary %s Accept Error(%v), sleeping %dms", acceptName, ne, tmpDelay/time.Millisecond) - select { - case <-time.After(tmpDelay): - case <-s.quitCh: - return -1 - } - tmpDelay *= 2 - if tmpDelay > ACCEPT_MAX_SLEEP { - tmpDelay = ACCEPT_MAX_SLEEP - } - } else { - s.Errorf("%s Accept error: %v", acceptName, err) - } - return tmpDelay -} - -var errNoIPAvail = errors.New("no IP available") - -func (s *Server) getRandomIP(resolver netResolver, url string, excludedAddresses map[string]struct{}) (string, error) { - host, port, err := net.SplitHostPort(url) - if err != nil { - return "", err - } - // If already an IP, skip. - if net.ParseIP(host) != nil { - return url, nil - } - ips, err := resolver.LookupHost(context.Background(), host) - if err != nil { - return "", fmt.Errorf("lookup for host %q: %v", host, err) - } - if len(excludedAddresses) > 0 { - for i := 0; i < len(ips); i++ { - ip := ips[i] - addr := net.JoinHostPort(ip, port) - if _, excluded := excludedAddresses[addr]; excluded { - if len(ips) == 1 { - ips = nil - break - } - ips[i] = ips[len(ips)-1] - ips = ips[:len(ips)-1] - i-- - } - } - if len(ips) == 0 { - return "", errNoIPAvail - } - } - var address string - if len(ips) == 0 { - s.Warnf("Unable to get IP for %s, will try with %s: %v", host, url, err) - address = url - } else { - var ip string - if len(ips) == 1 { - ip = ips[0] - } else { - ip = ips[rand.Int31n(int32(len(ips)))] - } - // add the port - address = net.JoinHostPort(ip, port) - } - return address, nil -} - -// Returns true for the first attempt and depending on the nature -// of the attempt (first connect or a reconnect), when the number -// of attempts is equal to the configured report attempts. -func (s *Server) shouldReportConnectErr(firstConnect bool, attempts int) bool { - opts := s.getOpts() - if firstConnect { - if attempts == 1 || attempts%opts.ConnectErrorReports == 0 { - return true - } - return false - } - if attempts == 1 || attempts%opts.ReconnectErrorReports == 0 { - return true - } - return false -} - -func (s *Server) updateRemoteSubscription(acc *Account, sub *subscription, delta int32) { - s.updateRouteSubscriptionMap(acc, sub, delta) - if s.gateway.enabled { - s.gatewayUpdateSubInterest(acc.Name, sub, delta) - } - - acc.updateLeafNodes(sub, delta) -} - -func (s *Server) startRateLimitLogExpiration() { - interval := time.Second - s.startGoRoutine(func() { - defer s.grWG.Done() - - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - for { - select { - case <-s.quitCh: - return - case interval = <-s.rateLimitLoggingCh: - ticker.Reset(interval) - case <-ticker.C: - s.rateLimitLogging.Range(func(k, v any) bool { - start := v.(time.Time) - if time.Since(start) >= interval { - s.rateLimitLogging.Delete(k) - } - return true - }) - } - } - }) -} - -func (s *Server) changeRateLimitLogInterval(d time.Duration) { - if d <= 0 { - return - } - select { - case s.rateLimitLoggingCh <- d: - default: - } -} - -// DisconnectClientByID disconnects a client by connection ID -func (s *Server) DisconnectClientByID(id uint64) error { - if s == nil { - return ErrServerNotRunning - } - if client := s.getClient(id); client != nil { - client.closeConnection(Kicked) - return nil - } else if client = s.GetLeafNode(id); client != nil { - client.closeConnection(Kicked) - return nil - } - return errors.New("no such client or leafnode id") -} - -// LDMClientByID sends a Lame Duck Mode info message to a client by connection ID -func (s *Server) LDMClientByID(id uint64) error { - if s == nil { - return ErrServerNotRunning - } - s.mu.RLock() - c := s.clients[id] - if c == nil { - s.mu.RUnlock() - return errors.New("no such client id") - } - info := s.copyInfo() - info.LameDuckMode = true - s.mu.RUnlock() - c.mu.Lock() - defer c.mu.Unlock() - if c.opts.Protocol >= ClientProtoInfo && c.flags.isSet(firstPongSent) { - // sendInfo takes care of checking if the connection is still - // valid or not, so don't duplicate tests here. - c.Debugf("Sending Lame Duck Mode info to client") - c.enqueueProto(c.generateClientInfoJSON(info)) - return nil - } else { - return errors.New("client does not support Lame Duck Mode or is not ready to receive the notification") - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/service.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/service.go deleted file mode 100644 index 7822206a..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/service.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2012-2021 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build !windows - -package server - -// Run starts the NATS server. This wrapper function allows Windows to add a -// hook for running NATS as a service. -func Run(server *Server) error { - server.Start() - return nil -} - -// isWindowsService indicates if NATS is running as a Windows service. -func isWindowsService() bool { - return false -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/service_windows.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/service_windows.go deleted file mode 100644 index eed399f6..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/service_windows.go +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2012-2022 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "os" - "time" - - "golang.org/x/sys/windows/svc" -) - -const ( - reopenLogCode = 128 - reopenLogCmd = svc.Cmd(reopenLogCode) - ldmCode = 129 - ldmCmd = svc.Cmd(ldmCode) - acceptReopenLog = svc.Accepted(reopenLogCode) -) - -var serviceName = "nats-server" - -// SetServiceName allows setting a different service name -func SetServiceName(name string) { - serviceName = name -} - -// winServiceWrapper implements the svc.Handler interface for implementing -// nats-server as a Windows service. -type winServiceWrapper struct { - server *Server -} - -var dockerized = false -var startupDelay = 10 * time.Second - -func init() { - if v, exists := os.LookupEnv("NATS_DOCKERIZED"); exists && v == "1" { - dockerized = true - } -} - -// Execute will be called by the package code at the start of -// the service, and the service will exit once Execute completes. -// Inside Execute you must read service change requests from r and -// act accordingly. You must keep service control manager up to date -// about state of your service by writing into s as required. -// args contains service name followed by argument strings passed -// to the service. -// You can provide service exit code in exitCode return parameter, -// with 0 being "no error". You can also indicate if exit code, -// if any, is service specific or not by using svcSpecificEC -// parameter. -func (w *winServiceWrapper) Execute(args []string, changes <-chan svc.ChangeRequest, - status chan<- svc.Status) (bool, uint32) { - - status <- svc.Status{State: svc.StartPending} - go w.server.Start() - - if v, exists := os.LookupEnv("NATS_STARTUP_DELAY"); exists { - if delay, err := time.ParseDuration(v); err == nil { - startupDelay = delay - } else { - w.server.Errorf("Failed to parse \"%v\" as a duration for startup: %s", v, err) - } - } - // Wait for accept loop(s) to be started - if !w.server.ReadyForConnections(startupDelay) { - // Failed to start. - return false, 1 - } - - status <- svc.Status{ - State: svc.Running, - Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.AcceptParamChange | acceptReopenLog, - } - -loop: - for change := range changes { - switch change.Cmd { - case svc.Interrogate: - status <- change.CurrentStatus - case svc.Stop, svc.Shutdown: - w.server.Shutdown() - break loop - case reopenLogCmd: - // File log re-open for rotating file logs. - w.server.ReOpenLogFile() - case ldmCmd: - go w.server.lameDuckMode() - case svc.ParamChange: - if err := w.server.Reload(); err != nil { - w.server.Errorf("Failed to reload server configuration: %s", err) - } - default: - w.server.Debugf("Unexpected control request: %v", change.Cmd) - } - } - - status <- svc.Status{State: svc.StopPending} - return false, 0 -} - -// Run starts the NATS server as a Windows service. -func Run(server *Server) error { - if dockerized { - server.Start() - return nil - } - isWindowsService, err := svc.IsWindowsService() - if err != nil { - return err - } - if !isWindowsService { - server.Start() - return nil - } - return svc.Run(serviceName, &winServiceWrapper{server}) -} - -// isWindowsService indicates if NATS is running as a Windows service. -func isWindowsService() bool { - if dockerized { - return false - } - isWindowsService, _ := svc.IsWindowsService() - return isWindowsService -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/signal.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/signal.go deleted file mode 100644 index 24c0827f..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/signal.go +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2012-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build !windows && !wasm - -package server - -import ( - "errors" - "fmt" - "os" - "os/exec" - "os/signal" - "strconv" - "strings" - "syscall" -) - -var processName = "nats-server" - -// SetProcessName allows to change the expected name of the process. -func SetProcessName(name string) { - processName = name -} - -// Signal Handling -func (s *Server) handleSignals() { - if s.getOpts().NoSigs { - return - } - c := make(chan os.Signal, 1) - - signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGHUP) - - go func() { - for { - select { - case sig := <-c: - s.Debugf("Trapped %q signal", sig) - switch sig { - case syscall.SIGINT: - s.Shutdown() - s.WaitForShutdown() - os.Exit(0) - case syscall.SIGTERM: - // Shutdown unless graceful shutdown already in progress. - s.mu.Lock() - ldm := s.ldm - s.mu.Unlock() - - if !ldm { - s.Shutdown() - s.WaitForShutdown() - os.Exit(0) - } - case syscall.SIGUSR1: - // File log re-open for rotating file logs. - s.ReOpenLogFile() - case syscall.SIGUSR2: - go s.lameDuckMode() - case syscall.SIGHUP: - // Config reload. - if err := s.Reload(); err != nil { - s.Errorf("Failed to reload server configuration: %s", err) - } - } - case <-s.quitCh: - return - } - } - }() -} - -// ProcessSignal sends the given signal command to the given process. If pidStr -// is empty, this will send the signal to the single running instance of -// nats-server. If multiple instances are running, pidStr can be a globular -// expression ending with '*'. This returns an error if the given process is -// not running or the command is invalid. -func ProcessSignal(command Command, pidExpr string) error { - var ( - err error - errStr string - pids = make([]int, 1) - pidStr = strings.TrimSuffix(pidExpr, "*") - isGlob = strings.HasSuffix(pidExpr, "*") - ) - - // Validate input if given - if pidStr != "" { - if pids[0], err = strconv.Atoi(pidStr); err != nil { - return fmt.Errorf("invalid pid: %s", pidStr) - } - } - // Gather all PIDs unless the input is specific - if pidStr == "" || isGlob { - if pids, err = resolvePids(); err != nil { - return err - } - } - // Multiple instances are running and the input is not an expression - if len(pids) > 1 && !isGlob { - errStr = fmt.Sprintf("multiple %s processes running:", processName) - for _, p := range pids { - errStr += fmt.Sprintf("\n%d", p) - } - return errors.New(errStr) - } - // No instances are running - if len(pids) == 0 { - return fmt.Errorf("no %s processes running", processName) - } - - var signum syscall.Signal - if signum, err = CommandToSignal(command); err != nil { - return err - } - - for _, pid := range pids { - if _pidStr := strconv.Itoa(pid); _pidStr != pidStr && pidStr != "" { - if !isGlob || !strings.HasPrefix(_pidStr, pidStr) { - continue - } - } - if err = kill(pid, signum); err != nil { - errStr += fmt.Sprintf("\nsignal %q %d: %s", command, pid, err) - } - } - if errStr != "" { - return errors.New(errStr) - } - return nil -} - -// Translates a command to a signal number -func CommandToSignal(command Command) (syscall.Signal, error) { - switch command { - case CommandStop: - return syscall.SIGKILL, nil - case CommandQuit: - return syscall.SIGINT, nil - case CommandReopen: - return syscall.SIGUSR1, nil - case CommandReload: - return syscall.SIGHUP, nil - case commandLDMode: - return syscall.SIGUSR2, nil - case commandTerm: - return syscall.SIGTERM, nil - default: - return 0, fmt.Errorf("unknown signal %q", command) - } -} - -// resolvePids returns the pids for all running nats-server processes. -func resolvePids() ([]int, error) { - // If pgrep isn't available, this will just bail out and the user will be - // required to specify a pid. - output, err := pgrep() - if err != nil { - switch err.(type) { - case *exec.ExitError: - // ExitError indicates non-zero exit code, meaning no processes - // found. - break - default: - return nil, errors.New("unable to resolve pid, try providing one") - } - } - var ( - myPid = os.Getpid() - pidStrs = strings.Split(string(output), "\n") - pids = make([]int, 0, len(pidStrs)) - ) - for _, pidStr := range pidStrs { - if pidStr == "" { - continue - } - pid, err := strconv.Atoi(pidStr) - if err != nil { - return nil, errors.New("unable to resolve pid, try providing one") - } - // Ignore the current process. - if pid == myPid { - continue - } - pids = append(pids, pid) - } - return pids, nil -} - -var kill = func(pid int, signal syscall.Signal) error { - return syscall.Kill(pid, signal) -} - -var pgrep = func() ([]byte, error) { - return exec.Command("pgrep", processName).Output() -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/signal_wasm.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/signal_wasm.go deleted file mode 100644 index 7788d3ff..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/signal_wasm.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2022 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build wasm - -package server - -func (s *Server) handleSignals() { - -} - -func ProcessSignal(command Command, service string) error { - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/signal_windows.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/signal_windows.go deleted file mode 100644 index 2f5a27c5..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/signal_windows.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2012-2023 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "fmt" - "os" - "os/signal" - "syscall" - "time" - - "golang.org/x/sys/windows/svc" - "golang.org/x/sys/windows/svc/mgr" -) - -// Signal Handling -func (s *Server) handleSignals() { - if s.getOpts().NoSigs { - return - } - c := make(chan os.Signal, 1) - - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - - go func() { - for { - select { - case sig := <-c: - s.Debugf("Trapped %q signal", sig) - s.Shutdown() - os.Exit(0) - case <-s.quitCh: - return - } - } - }() -} - -// ProcessSignal sends the given signal command to the running nats-server service. -// If service is empty, this signals the "nats-server" service. This returns an -// error is the given service is not running or the command is invalid. -func ProcessSignal(command Command, service string) error { - if service == "" { - service = serviceName - } - - m, err := mgr.Connect() - if err != nil { - return err - } - defer m.Disconnect() - - s, err := m.OpenService(service) - if err != nil { - return fmt.Errorf("could not access service: %v", err) - } - defer s.Close() - - var ( - cmd svc.Cmd - to svc.State - ) - - switch command { - case CommandStop, CommandQuit: - cmd = svc.Stop - to = svc.Stopped - case CommandReopen: - cmd = reopenLogCmd - to = svc.Running - case CommandReload: - cmd = svc.ParamChange - to = svc.Running - case commandLDMode: - cmd = ldmCmd - to = svc.Running - default: - return fmt.Errorf("unknown signal %q", command) - } - - status, err := s.Control(cmd) - if err != nil { - return fmt.Errorf("could not send control=%d: %v", cmd, err) - } - - timeout := time.Now().Add(10 * time.Second) - for status.State != to { - if timeout.Before(time.Now()) { - return fmt.Errorf("timeout waiting for service to go to state=%d", to) - } - time.Sleep(300 * time.Millisecond) - status, err = s.Query() - if err != nil { - return fmt.Errorf("could not retrieve service status: %v", err) - } - } - - return nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/store.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/store.go deleted file mode 100644 index 6f05561e..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/store.go +++ /dev/null @@ -1,797 +0,0 @@ -// Copyright 2019-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "encoding/binary" - "errors" - "fmt" - "io" - "os" - "strings" - "time" - "unsafe" - - "github.com/nats-io/nats-server/v2/server/avl" -) - -// StorageType determines how messages are stored for retention. -type StorageType int - -const ( - // File specifies on disk, designated by the JetStream config StoreDir. - FileStorage = StorageType(22) - // MemoryStorage specifies in memory only. - MemoryStorage = StorageType(33) - // Any is for internals. - AnyStorage = StorageType(44) -) - -var ( - // ErrStoreClosed is returned when the store has been closed - ErrStoreClosed = errors.New("store is closed") - // ErrStoreMsgNotFound when message was not found but was expected to be. - ErrStoreMsgNotFound = errors.New("no message found") - // ErrStoreEOF is returned when message seq is greater than the last sequence. - ErrStoreEOF = errors.New("stream store EOF") - // ErrMaxMsgs is returned when we have discard new as a policy and we reached the message limit. - ErrMaxMsgs = errors.New("maximum messages exceeded") - // ErrMaxBytes is returned when we have discard new as a policy and we reached the bytes limit. - ErrMaxBytes = errors.New("maximum bytes exceeded") - // ErrMaxMsgsPerSubject is returned when we have discard new as a policy and we reached the message limit per subject. - ErrMaxMsgsPerSubject = errors.New("maximum messages per subject exceeded") - // ErrStoreSnapshotInProgress is returned when RemoveMsg or EraseMsg is called - // while a snapshot is in progress. - ErrStoreSnapshotInProgress = errors.New("snapshot in progress") - // ErrMsgTooLarge is returned when a message is considered too large. - ErrMsgTooLarge = errors.New("message to large") - // ErrStoreWrongType is for when you access the wrong storage type. - ErrStoreWrongType = errors.New("wrong storage type") - // ErrNoAckPolicy is returned when trying to update a consumer's acks with no ack policy. - ErrNoAckPolicy = errors.New("ack policy is none") - // ErrInvalidSequence is returned when the sequence is not present in the stream store. - ErrInvalidSequence = errors.New("invalid sequence") - // ErrSequenceMismatch is returned when storing a raw message and the expected sequence is wrong. - ErrSequenceMismatch = errors.New("expected sequence does not match store") - // ErrCorruptStreamState - ErrCorruptStreamState = errors.New("stream state snapshot is corrupt") - // ErrTooManyResults - ErrTooManyResults = errors.New("too many matching results for request") -) - -// StoreMsg is the stored message format for messages that are retained by the Store layer. -type StoreMsg struct { - subj string - hdr []byte - msg []byte - buf []byte - seq uint64 - ts int64 -} - -// Used to call back into the upper layers to report on changes in storage resources. -// For the cases where its a single message we will also supply sequence number and subject. -type StorageUpdateHandler func(msgs, bytes int64, seq uint64, subj string) - -// Used to call back into the upper layers to report on newly created subject delete markers. -type SubjectDeleteMarkerUpdateHandler func(*inMsg) - -type StreamStore interface { - StoreMsg(subject string, hdr, msg []byte, ttl int64) (uint64, int64, error) - StoreRawMsg(subject string, hdr, msg []byte, seq uint64, ts int64, ttl int64) error - SkipMsg() uint64 - SkipMsgs(seq uint64, num uint64) error - LoadMsg(seq uint64, sm *StoreMsg) (*StoreMsg, error) - LoadNextMsg(filter string, wc bool, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error) - LoadNextMsgMulti(sl *Sublist, start uint64, smp *StoreMsg) (sm *StoreMsg, skip uint64, err error) - LoadLastMsg(subject string, sm *StoreMsg) (*StoreMsg, error) - LoadPrevMsg(start uint64, smp *StoreMsg) (sm *StoreMsg, err error) - RemoveMsg(seq uint64) (bool, error) - EraseMsg(seq uint64) (bool, error) - Purge() (uint64, error) - PurgeEx(subject string, seq, keep uint64, noMarkers bool) (uint64, error) - Compact(seq uint64) (uint64, error) - Truncate(seq uint64) error - GetSeqFromTime(t time.Time) uint64 - FilteredState(seq uint64, subject string) SimpleState - SubjectsState(filterSubject string) map[string]SimpleState - SubjectsTotals(filterSubject string) map[string]uint64 - MultiLastSeqs(filters []string, maxSeq uint64, maxAllowed int) ([]uint64, error) - NumPending(sseq uint64, filter string, lastPerSubject bool) (total, validThrough uint64) - NumPendingMulti(sseq uint64, sl *Sublist, lastPerSubject bool) (total, validThrough uint64) - State() StreamState - FastState(*StreamState) - EncodedStreamState(failed uint64) (enc []byte, err error) - SyncDeleted(dbs DeleteBlocks) - Type() StorageType - RegisterStorageUpdates(StorageUpdateHandler) - RegisterSubjectDeleteMarkerUpdates(SubjectDeleteMarkerUpdateHandler) - UpdateConfig(cfg *StreamConfig) error - Delete() error - Stop() error - ConsumerStore(name string, cfg *ConsumerConfig) (ConsumerStore, error) - AddConsumer(o ConsumerStore) error - RemoveConsumer(o ConsumerStore) error - Snapshot(deadline time.Duration, includeConsumers, checkMsgs bool) (*SnapshotResult, error) - Utilization() (total, reported uint64, err error) -} - -// RetentionPolicy determines how messages in a set are retained. -type RetentionPolicy int - -const ( - // LimitsPolicy (default) means that messages are retained until any given limit is reached. - // This could be one of MaxMsgs, MaxBytes, or MaxAge. - LimitsPolicy RetentionPolicy = iota - // InterestPolicy specifies that when all known consumers have acknowledged a message it can be removed. - InterestPolicy - // WorkQueuePolicy specifies that when the first worker or subscriber acknowledges the message it can be removed. - WorkQueuePolicy -) - -// Discard Policy determines how we proceed when limits of messages or bytes are hit. The default, DicscardOld will -// remove older messages. DiscardNew will fail to store the new message. -type DiscardPolicy int - -const ( - // DiscardOld will remove older messages to return to the limits. - DiscardOld = iota - // DiscardNew will error on a StoreMsg call - DiscardNew -) - -// StreamState is information about the given stream. -type StreamState struct { - Msgs uint64 `json:"messages"` - Bytes uint64 `json:"bytes"` - FirstSeq uint64 `json:"first_seq"` - FirstTime time.Time `json:"first_ts"` - LastSeq uint64 `json:"last_seq"` - LastTime time.Time `json:"last_ts"` - NumSubjects int `json:"num_subjects,omitempty"` - Subjects map[string]uint64 `json:"subjects,omitempty"` - NumDeleted int `json:"num_deleted,omitempty"` - Deleted []uint64 `json:"deleted,omitempty"` - Lost *LostStreamData `json:"lost,omitempty"` - Consumers int `json:"consumer_count"` -} - -// SimpleState for filtered subject specific state. -type SimpleState struct { - Msgs uint64 `json:"messages"` - First uint64 `json:"first_seq"` - Last uint64 `json:"last_seq"` - - // Internal usage for when the first needs to be updated before use. - firstNeedsUpdate bool - // Internal usage for when the last needs to be updated before use. - lastNeedsUpdate bool -} - -// LostStreamData indicates msgs that have been lost. -type LostStreamData struct { - Msgs []uint64 `json:"msgs"` - Bytes uint64 `json:"bytes"` -} - -// SnapshotResult contains information about the snapshot. -type SnapshotResult struct { - Reader io.ReadCloser - State StreamState - errCh chan string -} - -const ( - // Magic is used to identify stream state encodings. - streamStateMagic = uint8(42) - // Version - streamStateVersion = uint8(1) - // Magic / Identifier for run length encodings. - runLengthMagic = uint8(33) - // Magic / Identifier for AVL seqsets. - seqSetMagic = uint8(22) -) - -// Interface for DeleteBlock. -// These will be of three types: -// 1. AVL seqsets. -// 2. Run length encoding of a deleted range. -// 3. Legacy []uint64 -type DeleteBlock interface { - State() (first, last, num uint64) - Range(f func(uint64) bool) -} - -type DeleteBlocks []DeleteBlock - -// StreamReplicatedState represents what is encoded in a binary stream snapshot used -// for stream replication in an NRG. -type StreamReplicatedState struct { - Msgs uint64 - Bytes uint64 - FirstSeq uint64 - LastSeq uint64 - Failed uint64 - Deleted DeleteBlocks -} - -// Determine if this is an encoded stream state. -func IsEncodedStreamState(buf []byte) bool { - return len(buf) >= hdrLen && buf[0] == streamStateMagic && buf[1] == streamStateVersion -} - -var ErrBadStreamStateEncoding = errors.New("bad stream state encoding") - -func DecodeStreamState(buf []byte) (*StreamReplicatedState, error) { - ss := &StreamReplicatedState{} - if len(buf) < hdrLen || buf[0] != streamStateMagic || buf[1] != streamStateVersion { - return nil, ErrBadStreamStateEncoding - } - var bi = hdrLen - - readU64 := func() uint64 { - if bi < 0 || bi >= len(buf) { - bi = -1 - return 0 - } - num, n := binary.Uvarint(buf[bi:]) - if n <= 0 { - bi = -1 - return 0 - } - bi += n - return num - } - - parserFailed := func() bool { - return bi < 0 - } - - ss.Msgs = readU64() - ss.Bytes = readU64() - ss.FirstSeq = readU64() - ss.LastSeq = readU64() - ss.Failed = readU64() - - if parserFailed() { - return nil, ErrCorruptStreamState - } - - if numDeleted := readU64(); numDeleted > 0 { - // If we have some deleted blocks. - for l := len(buf); l > bi; { - switch buf[bi] { - case seqSetMagic: - dmap, n, err := avl.Decode(buf[bi:]) - if err != nil { - return nil, ErrCorruptStreamState - } - bi += n - ss.Deleted = append(ss.Deleted, dmap) - case runLengthMagic: - bi++ - var rl DeleteRange - rl.First = readU64() - rl.Num = readU64() - if parserFailed() { - return nil, ErrCorruptStreamState - } - ss.Deleted = append(ss.Deleted, &rl) - default: - return nil, ErrCorruptStreamState - } - } - } - - return ss, nil -} - -// DeleteRange is a run length encoded delete range. -type DeleteRange struct { - First uint64 - Num uint64 -} - -func (dr *DeleteRange) State() (first, last, num uint64) { - deletesAfterFirst := dr.Num - if deletesAfterFirst > 0 { - deletesAfterFirst-- - } - return dr.First, dr.First + deletesAfterFirst, dr.Num -} - -// Range will range over all the deleted sequences represented by this block. -func (dr *DeleteRange) Range(f func(uint64) bool) { - for seq := dr.First; seq < dr.First+dr.Num; seq++ { - if !f(seq) { - return - } - } -} - -// Legacy []uint64 -type DeleteSlice []uint64 - -func (ds DeleteSlice) State() (first, last, num uint64) { - if len(ds) == 0 { - return 0, 0, 0 - } - return ds[0], ds[len(ds)-1], uint64(len(ds)) -} - -// Range will range over all the deleted sequences represented by this []uint64. -func (ds DeleteSlice) Range(f func(uint64) bool) { - for _, seq := range ds { - if !f(seq) { - return - } - } -} - -func (dbs DeleteBlocks) NumDeleted() (total uint64) { - for _, db := range dbs { - _, _, num := db.State() - total += num - } - return total -} - -// ConsumerStore stores state on consumers for streams. -type ConsumerStore interface { - SetStarting(sseq uint64) error - UpdateStarting(sseq uint64) - HasState() bool - UpdateDelivered(dseq, sseq, dc uint64, ts int64) error - UpdateAcks(dseq, sseq uint64) error - UpdateConfig(cfg *ConsumerConfig) error - Update(*ConsumerState) error - State() (*ConsumerState, error) - BorrowState() (*ConsumerState, error) - EncodedState() ([]byte, error) - Type() StorageType - Stop() error - Delete() error - StreamDelete() error -} - -// SequencePair has both the consumer and the stream sequence. They point to same message. -type SequencePair struct { - Consumer uint64 `json:"consumer_seq"` - Stream uint64 `json:"stream_seq"` -} - -// ConsumerState represents a stored state for a consumer. -type ConsumerState struct { - // Delivered keeps track of last delivered sequence numbers for both the stream and the consumer. - Delivered SequencePair `json:"delivered"` - // AckFloor keeps track of the ack floors for both the stream and the consumer. - AckFloor SequencePair `json:"ack_floor"` - // These are both in stream sequence context. - // Pending is for all messages pending and the timestamp for the delivered time. - // This will only be present when the AckPolicy is ExplicitAck. - Pending map[uint64]*Pending `json:"pending,omitempty"` - // This is for messages that have been redelivered, so count > 1. - Redelivered map[uint64]uint64 `json:"redelivered,omitempty"` -} - -// Encode consumer state. -func encodeConsumerState(state *ConsumerState) []byte { - var hdr [seqsHdrSize]byte - var buf []byte - - maxSize := seqsHdrSize - if lp := len(state.Pending); lp > 0 { - maxSize += lp*(3*binary.MaxVarintLen64) + binary.MaxVarintLen64 - } - if lr := len(state.Redelivered); lr > 0 { - maxSize += lr*(2*binary.MaxVarintLen64) + binary.MaxVarintLen64 - } - if maxSize == seqsHdrSize { - buf = hdr[:seqsHdrSize] - } else { - buf = make([]byte, maxSize) - } - - // Write header - buf[0] = magic - buf[1] = 2 - - n := hdrLen - n += binary.PutUvarint(buf[n:], state.AckFloor.Consumer) - n += binary.PutUvarint(buf[n:], state.AckFloor.Stream) - n += binary.PutUvarint(buf[n:], state.Delivered.Consumer) - n += binary.PutUvarint(buf[n:], state.Delivered.Stream) - n += binary.PutUvarint(buf[n:], uint64(len(state.Pending))) - - asflr := state.AckFloor.Stream - adflr := state.AckFloor.Consumer - - // These are optional, but always write len. This is to avoid a truncate inline. - if len(state.Pending) > 0 { - // To save space we will use now rounded to seconds to be our base timestamp. - mints := time.Now().Round(time.Second).Unix() - // Write minimum timestamp we found from above. - n += binary.PutVarint(buf[n:], mints) - - for k, v := range state.Pending { - n += binary.PutUvarint(buf[n:], k-asflr) - n += binary.PutUvarint(buf[n:], v.Sequence-adflr) - // Downsample to seconds to save on space. - // Subsecond resolution not needed for recovery etc. - ts := v.Timestamp / int64(time.Second) - n += binary.PutVarint(buf[n:], mints-ts) - } - } - - // We always write the redelivered len. - n += binary.PutUvarint(buf[n:], uint64(len(state.Redelivered))) - - // We expect these to be small. - if len(state.Redelivered) > 0 { - for k, v := range state.Redelivered { - n += binary.PutUvarint(buf[n:], k-asflr) - n += binary.PutUvarint(buf[n:], v) - } - } - - return buf[:n] -} - -// Represents a pending message for explicit ack or ack all. -// Sequence is the original consumer sequence. -type Pending struct { - Sequence uint64 - Timestamp int64 -} - -// TemplateStore stores templates. -type TemplateStore interface { - Store(*streamTemplate) error - Delete(*streamTemplate) error -} - -const ( - limitsPolicyJSONString = `"limits"` - interestPolicyJSONString = `"interest"` - workQueuePolicyJSONString = `"workqueue"` -) - -var ( - limitsPolicyJSONBytes = []byte(limitsPolicyJSONString) - interestPolicyJSONBytes = []byte(interestPolicyJSONString) - workQueuePolicyJSONBytes = []byte(workQueuePolicyJSONString) -) - -func (rp RetentionPolicy) String() string { - switch rp { - case LimitsPolicy: - return "Limits" - case InterestPolicy: - return "Interest" - case WorkQueuePolicy: - return "WorkQueue" - default: - return "Unknown Retention Policy" - } -} - -func (rp RetentionPolicy) MarshalJSON() ([]byte, error) { - switch rp { - case LimitsPolicy: - return limitsPolicyJSONBytes, nil - case InterestPolicy: - return interestPolicyJSONBytes, nil - case WorkQueuePolicy: - return workQueuePolicyJSONBytes, nil - default: - return nil, fmt.Errorf("can not marshal %v", rp) - } -} - -func (rp *RetentionPolicy) UnmarshalJSON(data []byte) error { - switch string(data) { - case limitsPolicyJSONString: - *rp = LimitsPolicy - case interestPolicyJSONString: - *rp = InterestPolicy - case workQueuePolicyJSONString: - *rp = WorkQueuePolicy - default: - return fmt.Errorf("can not unmarshal %q", data) - } - return nil -} - -func (dp DiscardPolicy) String() string { - switch dp { - case DiscardOld: - return "DiscardOld" - case DiscardNew: - return "DiscardNew" - default: - return "Unknown Discard Policy" - } -} - -func (dp DiscardPolicy) MarshalJSON() ([]byte, error) { - switch dp { - case DiscardOld: - return []byte(`"old"`), nil - case DiscardNew: - return []byte(`"new"`), nil - default: - return nil, fmt.Errorf("can not marshal %v", dp) - } -} - -func (dp *DiscardPolicy) UnmarshalJSON(data []byte) error { - switch strings.ToLower(string(data)) { - case `"old"`: - *dp = DiscardOld - case `"new"`: - *dp = DiscardNew - default: - return fmt.Errorf("can not unmarshal %q", data) - } - return nil -} - -const ( - memoryStorageJSONString = `"memory"` - fileStorageJSONString = `"file"` - anyStorageJSONString = `"any"` -) - -var ( - memoryStorageJSONBytes = []byte(memoryStorageJSONString) - fileStorageJSONBytes = []byte(fileStorageJSONString) - anyStorageJSONBytes = []byte(anyStorageJSONString) -) - -func (st StorageType) String() string { - switch st { - case MemoryStorage: - return "Memory" - case FileStorage: - return "File" - case AnyStorage: - return "Any" - default: - return "Unknown Storage Type" - } -} - -func (st StorageType) MarshalJSON() ([]byte, error) { - switch st { - case MemoryStorage: - return memoryStorageJSONBytes, nil - case FileStorage: - return fileStorageJSONBytes, nil - case AnyStorage: - return anyStorageJSONBytes, nil - default: - return nil, fmt.Errorf("can not marshal %v", st) - } -} - -func (st *StorageType) UnmarshalJSON(data []byte) error { - switch string(data) { - case memoryStorageJSONString: - *st = MemoryStorage - case fileStorageJSONString: - *st = FileStorage - case anyStorageJSONString: - *st = AnyStorage - default: - return fmt.Errorf("can not unmarshal %q", data) - } - return nil -} - -const ( - ackNonePolicyJSONString = `"none"` - ackAllPolicyJSONString = `"all"` - ackExplicitPolicyJSONString = `"explicit"` -) - -var ( - ackNonePolicyJSONBytes = []byte(ackNonePolicyJSONString) - ackAllPolicyJSONBytes = []byte(ackAllPolicyJSONString) - ackExplicitPolicyJSONBytes = []byte(ackExplicitPolicyJSONString) -) - -func (ap AckPolicy) MarshalJSON() ([]byte, error) { - switch ap { - case AckNone: - return ackNonePolicyJSONBytes, nil - case AckAll: - return ackAllPolicyJSONBytes, nil - case AckExplicit: - return ackExplicitPolicyJSONBytes, nil - default: - return nil, fmt.Errorf("can not marshal %v", ap) - } -} - -func (ap *AckPolicy) UnmarshalJSON(data []byte) error { - switch string(data) { - case ackNonePolicyJSONString: - *ap = AckNone - case ackAllPolicyJSONString: - *ap = AckAll - case ackExplicitPolicyJSONString: - *ap = AckExplicit - default: - return fmt.Errorf("can not unmarshal %q", data) - } - return nil -} - -const ( - replayInstantPolicyJSONString = `"instant"` - replayOriginalPolicyJSONString = `"original"` -) - -var ( - replayInstantPolicyJSONBytes = []byte(replayInstantPolicyJSONString) - replayOriginalPolicyJSONBytes = []byte(replayOriginalPolicyJSONString) -) - -func (rp ReplayPolicy) MarshalJSON() ([]byte, error) { - switch rp { - case ReplayInstant: - return replayInstantPolicyJSONBytes, nil - case ReplayOriginal: - return replayOriginalPolicyJSONBytes, nil - default: - return nil, fmt.Errorf("can not marshal %v", rp) - } -} - -func (rp *ReplayPolicy) UnmarshalJSON(data []byte) error { - switch string(data) { - case replayInstantPolicyJSONString: - *rp = ReplayInstant - case replayOriginalPolicyJSONString: - *rp = ReplayOriginal - default: - return fmt.Errorf("can not unmarshal %q", data) - } - return nil -} - -const ( - deliverAllPolicyJSONString = `"all"` - deliverLastPolicyJSONString = `"last"` - deliverNewPolicyJSONString = `"new"` - deliverByStartSequenceJSONString = `"by_start_sequence"` - deliverByStartTimeJSONString = `"by_start_time"` - deliverLastPerPolicyJSONString = `"last_per_subject"` - deliverUndefinedJSONString = `"undefined"` -) - -var ( - deliverAllPolicyJSONBytes = []byte(deliverAllPolicyJSONString) - deliverLastPolicyJSONBytes = []byte(deliverLastPolicyJSONString) - deliverNewPolicyJSONBytes = []byte(deliverNewPolicyJSONString) - deliverByStartSequenceJSONBytes = []byte(deliverByStartSequenceJSONString) - deliverByStartTimeJSONBytes = []byte(deliverByStartTimeJSONString) - deliverLastPerPolicyJSONBytes = []byte(deliverLastPerPolicyJSONString) - deliverUndefinedJSONBytes = []byte(deliverUndefinedJSONString) -) - -func (p *DeliverPolicy) UnmarshalJSON(data []byte) error { - switch string(data) { - case deliverAllPolicyJSONString, deliverUndefinedJSONString: - *p = DeliverAll - case deliverLastPolicyJSONString: - *p = DeliverLast - case deliverLastPerPolicyJSONString: - *p = DeliverLastPerSubject - case deliverNewPolicyJSONString: - *p = DeliverNew - case deliverByStartSequenceJSONString: - *p = DeliverByStartSequence - case deliverByStartTimeJSONString: - *p = DeliverByStartTime - default: - return fmt.Errorf("can not unmarshal %q", data) - } - - return nil -} - -func (p DeliverPolicy) MarshalJSON() ([]byte, error) { - switch p { - case DeliverAll: - return deliverAllPolicyJSONBytes, nil - case DeliverLast: - return deliverLastPolicyJSONBytes, nil - case DeliverLastPerSubject: - return deliverLastPerPolicyJSONBytes, nil - case DeliverNew: - return deliverNewPolicyJSONBytes, nil - case DeliverByStartSequence: - return deliverByStartSequenceJSONBytes, nil - case DeliverByStartTime: - return deliverByStartTimeJSONBytes, nil - default: - return deliverUndefinedJSONBytes, nil - } -} - -func isOutOfSpaceErr(err error) bool { - return err != nil && (strings.Contains(err.Error(), "no space left")) -} - -// For when our upper layer catchup detects its missing messages from the beginning of the stream. -var errFirstSequenceMismatch = errors.New("first sequence mismatch") - -func isClusterResetErr(err error) bool { - return err == errLastSeqMismatch || err == ErrStoreEOF || err == errFirstSequenceMismatch || errors.Is(err, errCatchupAbortedNoLeader) || err == errCatchupTooManyRetries -} - -// Copy all fields. -func (smo *StoreMsg) copy(sm *StoreMsg) { - if sm.buf != nil { - sm.buf = sm.buf[:0] - } - sm.buf = append(sm.buf, smo.buf...) - // We set cap on header in case someone wants to expand it. - sm.hdr, sm.msg = sm.buf[:len(smo.hdr):len(smo.hdr)], sm.buf[len(smo.hdr):] - sm.subj, sm.seq, sm.ts = smo.subj, smo.seq, smo.ts -} - -// Clear all fields except underlying buffer but reset that if present to [:0]. -func (sm *StoreMsg) clear() { - if sm == nil { - return - } - *sm = StoreMsg{_EMPTY_, nil, nil, sm.buf, 0, 0} - if len(sm.buf) > 0 { - sm.buf = sm.buf[:0] - } -} - -// Note this will avoid a copy of the data used for the string, but it will also reference the existing slice's data pointer. -// So this should be used sparingly when we know the encompassing byte slice's lifetime is the same. -func bytesToString(b []byte) string { - if len(b) == 0 { - return _EMPTY_ - } - p := unsafe.SliceData(b) - return unsafe.String(p, len(b)) -} - -// Same in reverse. Used less often. -func stringToBytes(s string) []byte { - if len(s) == 0 { - return nil - } - p := unsafe.StringData(s) - b := unsafe.Slice(p, len(s)) - return b -} - -// Forces a copy of a string, for use in the case that you might have been passed a value when bytesToString was used, -// but now you need a separate copy of it to store for longer-term use. -func copyString(s string) string { - b := make([]byte, len(s)) - copy(b, s) - return bytesToString(b) -} - -func isPermissionError(err error) bool { - return err != nil && os.IsPermission(err) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stream.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stream.go deleted file mode 100644 index bf70f5c4..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stream.go +++ /dev/null @@ -1,6553 +0,0 @@ -// Copyright 2019-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "archive/tar" - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "math" - "math/rand" - "os" - "path/filepath" - "reflect" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/klauspost/compress/s2" - "github.com/nats-io/nats-server/v2/server/gsl" - "github.com/nats-io/nuid" -) - -// StreamConfigRequest is used to create or update a stream. -type StreamConfigRequest struct { - StreamConfig - // This is not part of the StreamConfig, because its scoped to request, - // and not to the stream itself. - Pedantic bool `json:"pedantic,omitempty"` -} - -// StreamConfig will determine the name, subjects and retention policy -// for a given stream. If subjects is empty the name will be used. -type StreamConfig struct { - Name string `json:"name"` - Description string `json:"description,omitempty"` - Subjects []string `json:"subjects,omitempty"` - Retention RetentionPolicy `json:"retention"` - MaxConsumers int `json:"max_consumers"` - MaxMsgs int64 `json:"max_msgs"` - MaxBytes int64 `json:"max_bytes"` - MaxAge time.Duration `json:"max_age"` - MaxMsgsPer int64 `json:"max_msgs_per_subject"` - MaxMsgSize int32 `json:"max_msg_size,omitempty"` - Discard DiscardPolicy `json:"discard"` - Storage StorageType `json:"storage"` - Replicas int `json:"num_replicas"` - NoAck bool `json:"no_ack,omitempty"` - Template string `json:"template_owner,omitempty"` - Duplicates time.Duration `json:"duplicate_window,omitempty"` - Placement *Placement `json:"placement,omitempty"` - Mirror *StreamSource `json:"mirror,omitempty"` - Sources []*StreamSource `json:"sources,omitempty"` - Compression StoreCompression `json:"compression"` - FirstSeq uint64 `json:"first_seq,omitempty"` - - // Allow applying a subject transform to incoming messages before doing anything else - SubjectTransform *SubjectTransformConfig `json:"subject_transform,omitempty"` - - // Allow republish of the message after being sequenced and stored. - RePublish *RePublish `json:"republish,omitempty"` - - // Allow higher performance, direct access to get individual messages. E.g. KeyValue - AllowDirect bool `json:"allow_direct"` - // Allow higher performance and unified direct access for mirrors as well. - MirrorDirect bool `json:"mirror_direct"` - - // Allow KV like semantics to also discard new on a per subject basis - DiscardNewPer bool `json:"discard_new_per_subject,omitempty"` - - // Optional qualifiers. These can not be modified after set to true. - - // Sealed will seal a stream so no messages can get out or in. - Sealed bool `json:"sealed"` - // DenyDelete will restrict the ability to delete messages. - DenyDelete bool `json:"deny_delete"` - // DenyPurge will restrict the ability to purge messages. - DenyPurge bool `json:"deny_purge"` - // AllowRollup allows messages to be placed into the system and purge - // all older messages using a special msg header. - AllowRollup bool `json:"allow_rollup_hdrs"` - - // The following defaults will apply to consumers when created against - // this stream, unless overridden manually. - // TODO(nat): Can/should we name these better? - ConsumerLimits StreamConsumerLimits `json:"consumer_limits"` - - // AllowMsgTTL allows header initiated per-message TTLs. If disabled, - // then the `NATS-TTL` header will be ignored. - AllowMsgTTL bool `json:"allow_msg_ttl"` - - // SubjectDeleteMarkerTTL sets the TTL of delete marker messages left behind by - // subject delete markers. - SubjectDeleteMarkerTTL time.Duration `json:"subject_delete_marker_ttl,omitempty"` - - // Metadata is additional metadata for the Stream. - Metadata map[string]string `json:"metadata,omitempty"` -} - -// clone performs a deep copy of the StreamConfig struct, returning a new clone with -// all values copied. -func (cfg *StreamConfig) clone() *StreamConfig { - clone := *cfg - if cfg.Placement != nil { - placement := *cfg.Placement - clone.Placement = &placement - } - if cfg.Mirror != nil { - mirror := *cfg.Mirror - clone.Mirror = &mirror - } - if len(cfg.Sources) > 0 { - clone.Sources = make([]*StreamSource, len(cfg.Sources)) - for i, cfgSource := range cfg.Sources { - source := *cfgSource - clone.Sources[i] = &source - } - } - if cfg.SubjectTransform != nil { - transform := *cfg.SubjectTransform - clone.SubjectTransform = &transform - } - if cfg.RePublish != nil { - rePublish := *cfg.RePublish - clone.RePublish = &rePublish - } - if cfg.Metadata != nil { - clone.Metadata = make(map[string]string, len(cfg.Metadata)) - for k, v := range cfg.Metadata { - clone.Metadata[k] = v - } - } - return &clone -} - -type StreamConsumerLimits struct { - InactiveThreshold time.Duration `json:"inactive_threshold,omitempty"` - MaxAckPending int `json:"max_ack_pending,omitempty"` -} - -// SubjectTransformConfig is for applying a subject transform (to matching messages) before doing anything else when a new message is received -type SubjectTransformConfig struct { - Source string `json:"src"` - Destination string `json:"dest"` -} - -// RePublish is for republishing messages once committed to a stream. -type RePublish struct { - Source string `json:"src,omitempty"` - Destination string `json:"dest"` - HeadersOnly bool `json:"headers_only,omitempty"` -} - -// JSPubAckResponse is a formal response to a publish operation. -type JSPubAckResponse struct { - Error *ApiError `json:"error,omitempty"` - *PubAck -} - -// ToError checks if the response has a error and if it does converts it to an error -// avoiding the pitfalls described by https://yourbasic.org/golang/gotcha-why-nil-error-not-equal-nil/ -func (r *JSPubAckResponse) ToError() error { - if r.Error == nil { - return nil - } - return r.Error -} - -// PubAck is the detail you get back from a publish to a stream that was successful. -// e.g. +OK {"stream": "Orders", "seq": 22} -type PubAck struct { - Stream string `json:"stream"` - Sequence uint64 `json:"seq"` - Domain string `json:"domain,omitempty"` - Duplicate bool `json:"duplicate,omitempty"` -} - -// StreamInfo shows config and current state for this stream. -type StreamInfo struct { - Config StreamConfig `json:"config"` - Created time.Time `json:"created"` - State StreamState `json:"state"` - Domain string `json:"domain,omitempty"` - Cluster *ClusterInfo `json:"cluster,omitempty"` - Mirror *StreamSourceInfo `json:"mirror,omitempty"` - Sources []*StreamSourceInfo `json:"sources,omitempty"` - Alternates []StreamAlternate `json:"alternates,omitempty"` - // TimeStamp indicates when the info was gathered - TimeStamp time.Time `json:"ts"` -} - -type StreamAlternate struct { - Name string `json:"name"` - Domain string `json:"domain,omitempty"` - Cluster string `json:"cluster"` -} - -// ClusterInfo shows information about the underlying set of servers -// that make up the stream or consumer. -type ClusterInfo struct { - Name string `json:"name,omitempty"` - RaftGroup string `json:"raft_group,omitempty"` - Leader string `json:"leader,omitempty"` - Replicas []*PeerInfo `json:"replicas,omitempty"` -} - -// PeerInfo shows information about all the peers in the cluster that -// are supporting the stream or consumer. -type PeerInfo struct { - Name string `json:"name"` - Current bool `json:"current"` - Offline bool `json:"offline,omitempty"` - Active time.Duration `json:"active"` - Lag uint64 `json:"lag,omitempty"` - Peer string `json:"peer"` - // For migrations. - cluster string -} - -// StreamSourceInfo shows information about an upstream stream source. -type StreamSourceInfo struct { - Name string `json:"name"` - External *ExternalStream `json:"external,omitempty"` - Lag uint64 `json:"lag"` - Active time.Duration `json:"active"` - Error *ApiError `json:"error,omitempty"` - FilterSubject string `json:"filter_subject,omitempty"` - SubjectTransforms []SubjectTransformConfig `json:"subject_transforms,omitempty"` -} - -// StreamSource dictates how streams can source from other streams. -type StreamSource struct { - Name string `json:"name"` - OptStartSeq uint64 `json:"opt_start_seq,omitempty"` - OptStartTime *time.Time `json:"opt_start_time,omitempty"` - FilterSubject string `json:"filter_subject,omitempty"` - SubjectTransforms []SubjectTransformConfig `json:"subject_transforms,omitempty"` - External *ExternalStream `json:"external,omitempty"` - - // Internal - iname string // For indexing when stream names are the same for multiple sources. -} - -// ExternalStream allows you to qualify access to a stream source in another account. -type ExternalStream struct { - ApiPrefix string `json:"api"` - DeliverPrefix string `json:"deliver"` -} - -// For managing stream ingest. -const ( - streamDefaultMaxQueueMsgs = 10_000 - streamDefaultMaxQueueBytes = 1024 * 1024 * 128 -) - -// Stream is a jetstream stream of messages. When we receive a message internally destined -// for a Stream we will direct link from the client to this structure. -type stream struct { - mu sync.RWMutex // Read/write lock for the stream. - js *jetStream // The internal *jetStream for the account. - jsa *jsAccount // The JetStream account-level information. - acc *Account // The account this stream is defined in. - srv *Server // The server we are running in. - client *client // The internal JetStream client. - sysc *client // The internal JetStream system client. - - // The current last subscription ID for the subscriptions through `client`. - // Those subscriptions are for the subjects filters being listened to and captured by the stream. - sid atomic.Uint64 - - pubAck []byte // The template (prefix) to generate the pubAck responses for this stream quickly. - outq *jsOutQ // Queue of *jsPubMsg for sending messages. - msgs *ipQueue[*inMsg] // Intra-process queue for the ingress of messages. - gets *ipQueue[*directGetReq] // Intra-process queue for the direct get requests. - store StreamStore // The storage for this stream. - ackq *ipQueue[uint64] // Intra-process queue for acks. - lseq uint64 // The sequence number of the last message stored in the stream. - lmsgId string // The de-duplication message ID of the last message stored in the stream. - consumers map[string]*consumer // The consumers for this stream. - numFilter int // The number of filtered consumers. - cfg StreamConfig // The stream's config. - cfgMu sync.RWMutex // Config mutex used to solve some races with consumer code - created time.Time // Time the stream was created. - stype StorageType // The storage type. - tier string // The tier is the number of replicas for the stream (e.g. "R1" or "R3"). - ddmap map[string]*ddentry // The dedupe map. - ddarr []*ddentry // The dedupe array. - ddindex int // The dedupe index. - ddtmr *time.Timer // The dedupe timer. - qch chan struct{} // The quit channel. - mqch chan struct{} // The monitor's quit channel. - active bool // Indicates that there are active internal subscriptions (for the subject filters) - // and/or mirror/sources consumers are scheduled to be established or already started. - ddloaded bool // set to true when the deduplication structures are been built. - closed atomic.Bool // Set to true when stop() is called on the stream. - - // Mirror - mirror *sourceInfo - - // Sources - sources map[string]*sourceInfo - sourceSetupSchedules map[string]*time.Timer - sourcesConsumerSetup *time.Timer - smsgs *ipQueue[*inMsg] // Intra-process queue for all incoming sourced messages. - - // Indicates we have direct consumers. - directs int - - // For input subject transform. - itr *subjectTransform - - // For republishing. - tr *subjectTransform - - // For processing consumers without main stream lock. - clsMu sync.RWMutex - cList []*consumer // Consumer list. - sch chan struct{} // Channel to signal consumers. - sigq *ipQueue[*cMsg] // Intra-process queue for the messages to signal to the consumers. - csl *gsl.GenericSublist[*consumer] // Consumer subscription list. - - // Leader will store seq/msgTrace in clustering mode. Used in applyStreamEntries - // to know if trace event should be sent after processing. - mt map[uint64]*msgTrace - - // For non limits policy streams when they process an ack before the actual msg. - // Can happen in stretch clusters, multi-cloud, or during catchup for a restarted server. - preAcks map[uint64]map[*consumer]struct{} - - // TODO(dlc) - Hide everything below behind two pointers. - // Clustered mode. - sa *streamAssignment // What the meta controller uses to assign streams to peers. - node RaftNode // Our RAFT node for the stream's group. - catchup atomic.Bool // Used to signal we are in catchup mode. - catchups map[string]uint64 // The number of messages that need to be caught per peer. - syncSub *subscription // Internal subscription for sync messages (on "$JSC.SYNC"). - infoSub *subscription // Internal subscription for stream info requests. - clMu sync.Mutex // The mutex for clseq and clfs. - clseq uint64 // The current last seq being proposed to the NRG layer. - clfs uint64 // The count (offset) of the number of failed NRG sequences used to compute clseq. - inflight map[uint64]uint64 // Inflight message sizes per clseq. - lqsent time.Time // The time at which the last lost quorum advisory was sent. Used to rate limit. - uch chan struct{} // The channel to signal updates to the monitor routine. - inMonitor bool // True if the monitor routine has been started. - - expectedPerSubjectSequence map[uint64]string // Inflight 'expected per subject' subjects per clseq. - expectedPerSubjectInProcess map[string]struct{} // Current 'expected per subject' subjects in process. - - // Direct get subscription. - directSub *subscription - lastBySub *subscription - - monitorWg sync.WaitGroup // Wait group for the monitor routine. -} - -type sourceInfo struct { - name string // The name of the stream being sourced. - iname string // The unique index name of this particular source. - cname string // The name of the current consumer for this source. - sub *subscription // The subscription to the consumer. - - // (mirrors only) The subscription to the direct get request subject for - // the source stream's name on the `_sys_` queue group. - dsub *subscription - - // (mirrors only) The subscription to the direct get last per subject request subject for - // the source stream's name on the `_sys_` queue group. - lbsub *subscription - - msgs *ipQueue[*inMsg] // Intra-process queue for incoming messages. - sseq uint64 // Last stream message sequence number seen from the source. - dseq uint64 // Last delivery (i.e. consumer's) sequence number. - lag uint64 // 0 or number of messages pending (as last reported by the consumer) - 1. - err *ApiError // The API error that caused the last consumer setup to fail. - fails int // The number of times trying to setup the consumer failed. - last atomic.Int64 // Time the consumer was created or of last message it received. - lreq time.Time // The last time setupMirrorConsumer/setupSourceConsumer was called. - qch chan struct{} // Quit channel. - sip bool // Setup in progress. - wg sync.WaitGroup // WaitGroup for the consumer's go routine. - sf string // The subject filter. - sfs []string // The subject filters. - trs []*subjectTransform // The subject transforms. -} - -// For mirrors and direct get -const ( - dgetGroup = sysGroup - dgetCaughtUpThresh = 10 -) - -// Headers for published messages. -const ( - JSMsgId = "Nats-Msg-Id" - JSExpectedStream = "Nats-Expected-Stream" - JSExpectedLastSeq = "Nats-Expected-Last-Sequence" - JSExpectedLastSubjSeq = "Nats-Expected-Last-Subject-Sequence" - JSExpectedLastSubjSeqSubj = "Nats-Expected-Last-Subject-Sequence-Subject" - JSExpectedLastMsgId = "Nats-Expected-Last-Msg-Id" - JSStreamSource = "Nats-Stream-Source" - JSLastConsumerSeq = "Nats-Last-Consumer" - JSLastStreamSeq = "Nats-Last-Stream" - JSConsumerStalled = "Nats-Consumer-Stalled" - JSMsgRollup = "Nats-Rollup" - JSMsgSize = "Nats-Msg-Size" - JSResponseType = "Nats-Response-Type" - JSMessageTTL = "Nats-TTL" - JSMarkerReason = "Nats-Marker-Reason" -) - -// Headers for republished messages and direct gets. -const ( - JSStream = "Nats-Stream" - JSSequence = "Nats-Sequence" - JSTimeStamp = "Nats-Time-Stamp" - JSSubject = "Nats-Subject" - JSLastSequence = "Nats-Last-Sequence" - JSNumPending = "Nats-Num-Pending" - JSUpToSequence = "Nats-UpTo-Sequence" -) - -// Rollups, can be subject only or all messages. -const ( - JSMsgRollupSubject = "sub" - JSMsgRollupAll = "all" -) - -// Applied limits in the Nats-Applied-Limit header. -const ( - JSMarkerReasonMaxAge = "MaxAge" - JSMarkerReasonPurge = "Purge" - JSMarkerReasonRemove = "Remove" -) - -const ( - jsCreateResponse = "create" -) - -// Dedupe entry -type ddentry struct { - id string // The unique message ID provided by the client. - seq uint64 // The sequence number of the message. - ts int64 // The timestamp of the message. -} - -// Replicas Range -const StreamMaxReplicas = 5 - -// AddStream adds a stream for the given account. -func (a *Account) addStream(config *StreamConfig) (*stream, error) { - return a.addStreamWithAssignment(config, nil, nil, false) -} - -// AddStreamWithStore adds a stream for the given account with custome store config options. -func (a *Account) addStreamWithStore(config *StreamConfig, fsConfig *FileStoreConfig) (*stream, error) { - return a.addStreamWithAssignment(config, fsConfig, nil, false) -} - -func (a *Account) addStreamPedantic(config *StreamConfig, pedantic bool) (*stream, error) { - return a.addStreamWithAssignment(config, nil, nil, pedantic) -} - -func (a *Account) addStreamWithAssignment(config *StreamConfig, fsConfig *FileStoreConfig, sa *streamAssignment, pedantic bool) (*stream, error) { - s, jsa, err := a.checkForJetStream() - if err != nil { - return nil, err - } - - // If we do not have the stream currently assigned to us in cluster mode we will proceed but warn. - // This can happen on startup with restored state where on meta replay we still do not have - // the assignment. Running in single server mode this always returns true. - if !jsa.streamAssigned(config.Name) { - s.Debugf("Stream '%s > %s' does not seem to be assigned to this server", a.Name, config.Name) - } - - // Sensible defaults. - cfg, apiErr := s.checkStreamCfg(config, a, pedantic) - if apiErr != nil { - return nil, apiErr - } - - singleServerMode := !s.JetStreamIsClustered() && s.standAloneMode() - if singleServerMode && cfg.Replicas > 1 { - return nil, ApiErrors[JSStreamReplicasNotSupportedErr] - } - - // Make sure we are ok when these are done in parallel. - // We used to call Add(1) in the "else" clause of the "if loaded" - // statement. This caused a data race because it was possible - // that one go routine stores (with count==0) and another routine - // gets "loaded==true" and calls wg.Wait() while the other routine - // then calls wg.Add(1). It also could mean that two routines execute - // the rest of the code concurrently. - swg := &sync.WaitGroup{} - swg.Add(1) - v, loaded := jsa.inflight.LoadOrStore(cfg.Name, swg) - wg := v.(*sync.WaitGroup) - if loaded { - wg.Wait() - // This waitgroup is "thrown away" (since there was an existing one). - swg.Done() - } else { - defer func() { - jsa.inflight.Delete(cfg.Name) - wg.Done() - }() - } - - js, isClustered := jsa.jetStreamAndClustered() - jsa.mu.Lock() - if mset, ok := jsa.streams[cfg.Name]; ok { - jsa.mu.Unlock() - // Check to see if configs are same. - ocfg := mset.config() - - // set the index name on cfg since it would not contain a value for iname while the return from mset.config() does to ensure the DeepEqual works - for _, s := range cfg.Sources { - s.setIndexName() - } - - if reflect.DeepEqual(ocfg, cfg) { - if sa != nil { - mset.setStreamAssignment(sa) - } - return mset, nil - } else { - return nil, ApiErrors[JSStreamNameExistErr] - } - } - jsa.usageMu.RLock() - selected, tier, hasTier := jsa.selectLimits(cfg.Replicas) - jsa.usageMu.RUnlock() - reserved := int64(0) - if !isClustered { - reserved = jsa.tieredReservation(tier, &cfg) - } - jsa.mu.Unlock() - - if !hasTier { - return nil, NewJSNoLimitsError() - } - js.mu.RLock() - if isClustered { - _, reserved = tieredStreamAndReservationCount(js.cluster.streams[a.Name], tier, &cfg) - } - if err := js.checkAllLimits(&selected, &cfg, reserved, 0); err != nil { - js.mu.RUnlock() - return nil, err - } - js.mu.RUnlock() - jsa.mu.Lock() - // Check for template ownership if present. - if cfg.Template != _EMPTY_ && jsa.account != nil { - if !jsa.checkTemplateOwnership(cfg.Template, cfg.Name) { - jsa.mu.Unlock() - return nil, fmt.Errorf("stream not owned by template") - } - } - - // If mirror, check if the transforms (if any) are valid. - if cfg.Mirror != nil { - if len(cfg.Mirror.SubjectTransforms) == 0 { - if cfg.Mirror.FilterSubject != _EMPTY_ && !IsValidSubject(cfg.Mirror.FilterSubject) { - jsa.mu.Unlock() - return nil, fmt.Errorf("subject filter '%s' for the mirror %w", cfg.Mirror.FilterSubject, ErrBadSubject) - } - } else { - for _, st := range cfg.Mirror.SubjectTransforms { - if st.Source != _EMPTY_ && !IsValidSubject(st.Source) { - jsa.mu.Unlock() - return nil, fmt.Errorf("invalid subject transform source '%s' for the mirror: %w", st.Source, ErrBadSubject) - } - // check the transform, if any, is valid - if st.Destination != _EMPTY_ { - if _, err = NewSubjectTransform(st.Source, st.Destination); err != nil { - jsa.mu.Unlock() - return nil, fmt.Errorf("subject transform from '%s' to '%s' for the mirror: %w", st.Source, st.Destination, err) - } - } - } - } - } - - // Setup our internal indexed names here for sources and check if the transforms (if any) are valid. - for _, ssi := range cfg.Sources { - if len(ssi.SubjectTransforms) == 0 { - // check the filter, if any, is valid - if ssi.FilterSubject != _EMPTY_ && !IsValidSubject(ssi.FilterSubject) { - jsa.mu.Unlock() - return nil, fmt.Errorf("subject filter '%s' for the source: %w", ssi.FilterSubject, ErrBadSubject) - } - } else { - for _, st := range ssi.SubjectTransforms { - if st.Source != _EMPTY_ && !IsValidSubject(st.Source) { - jsa.mu.Unlock() - return nil, fmt.Errorf("subject filter '%s' for the source: %w", st.Source, ErrBadSubject) - } - // check the transform, if any, is valid - if st.Destination != _EMPTY_ { - if _, err = NewSubjectTransform(st.Source, st.Destination); err != nil { - jsa.mu.Unlock() - return nil, fmt.Errorf("subject transform from '%s' to '%s' for the source: %w", st.Source, st.Destination, err) - } - } - } - } - } - - // Check for overlapping subjects with other streams. - // These are not allowed for now. - if jsa.subjectsOverlap(cfg.Subjects, nil) { - jsa.mu.Unlock() - return nil, NewJSStreamSubjectOverlapError() - } - - if !hasTier { - jsa.mu.Unlock() - return nil, fmt.Errorf("no applicable tier found") - } - - // Setup the internal clients. - c := s.createInternalJetStreamClient() - ic := s.createInternalJetStreamClient() - - // Work out the stream ingest limits. - mlen := s.opts.StreamMaxBufferedMsgs - msz := uint64(s.opts.StreamMaxBufferedSize) - if mlen == 0 { - mlen = streamDefaultMaxQueueMsgs - } - if msz == 0 { - msz = streamDefaultMaxQueueBytes - } - - qpfx := fmt.Sprintf("[ACC:%s] stream '%s' ", a.Name, config.Name) - mset := &stream{ - acc: a, - jsa: jsa, - cfg: cfg, - js: js, - srv: s, - client: c, - sysc: ic, - tier: tier, - stype: cfg.Storage, - consumers: make(map[string]*consumer), - msgs: newIPQueue[*inMsg](s, qpfx+"messages", - ipqSizeCalculation(func(msg *inMsg) uint64 { - return uint64(len(msg.hdr) + len(msg.msg) + len(msg.rply) + len(msg.subj)) - }), - ipqLimitByLen[*inMsg](mlen), - ipqLimitBySize[*inMsg](msz), - ), - gets: newIPQueue[*directGetReq](s, qpfx+"direct gets"), - qch: make(chan struct{}), - mqch: make(chan struct{}), - uch: make(chan struct{}, 4), - sch: make(chan struct{}, 1), - } - - // Start our signaling routine to process consumers. - mset.sigq = newIPQueue[*cMsg](s, qpfx+"obs") // of *cMsg - go mset.signalConsumersLoop() - - // For no-ack consumers when we are interest retention. - if cfg.Retention != LimitsPolicy { - mset.ackq = newIPQueue[uint64](s, qpfx+"acks") - } - - // Check for input subject transform - if cfg.SubjectTransform != nil { - tr, err := NewSubjectTransform(cfg.SubjectTransform.Source, cfg.SubjectTransform.Destination) - if err != nil { - jsa.mu.Unlock() - return nil, fmt.Errorf("stream subject transform from '%s' to '%s': %w", cfg.SubjectTransform.Source, cfg.SubjectTransform.Destination, err) - } - mset.itr = tr - } - - // Check for RePublish. - if cfg.RePublish != nil { - tr, err := NewSubjectTransform(cfg.RePublish.Source, cfg.RePublish.Destination) - if err != nil { - jsa.mu.Unlock() - return nil, fmt.Errorf("stream republish transform from '%s' to '%s': %w", cfg.RePublish.Source, cfg.RePublish.Destination, err) - } - // Assign our transform for republishing. - mset.tr = tr - } - storeDir := filepath.Join(jsa.storeDir, streamsDir, cfg.Name) - jsa.mu.Unlock() - - // Bind to the user account. - c.registerWithAccount(a) - // Bind to the system account. - ic.registerWithAccount(s.SystemAccount()) - - // Create the appropriate storage - fsCfg := fsConfig - if fsCfg == nil { - fsCfg = &FileStoreConfig{} - // If we are file based and not explicitly configured - // we may be able to auto-tune based on max msgs or bytes. - if cfg.Storage == FileStorage { - mset.autoTuneFileStorageBlockSize(fsCfg) - } - } - fsCfg.StoreDir = storeDir - fsCfg.AsyncFlush = false - // Grab configured sync interval. - fsCfg.SyncInterval = s.getOpts().SyncInterval - fsCfg.SyncAlways = s.getOpts().SyncAlways - fsCfg.Compression = config.Compression - - if err := mset.setupStore(fsCfg); err != nil { - mset.stop(true, false) - return nil, NewJSStreamStoreFailedError(err) - } - - // Create our pubAck template here. Better than json marshal each time on success. - if domain := s.getOpts().JetStreamDomain; domain != _EMPTY_ { - mset.pubAck = []byte(fmt.Sprintf("{%q:%q, %q:%q, %q:", "stream", cfg.Name, "domain", domain, "seq")) - } else { - mset.pubAck = []byte(fmt.Sprintf("{%q:%q, %q:", "stream", cfg.Name, "seq")) - } - end := len(mset.pubAck) - mset.pubAck = mset.pubAck[:end:end] - - // Set our known last sequence. - var state StreamState - mset.store.FastState(&state) - - // Possible race with consumer.setLeader during recovery. - mset.mu.Lock() - mset.lseq = state.LastSeq - mset.mu.Unlock() - - // If no msgs (new stream), set dedupe state loaded to true. - if state.Msgs == 0 { - mset.ddloaded = true - } - - // Set our stream assignment if in clustered mode. - reserveResources := true - if sa != nil { - mset.setStreamAssignment(sa) - - // If the stream is resetting we must not double-account resources, they were already accounted for. - js.mu.Lock() - if sa.resetting { - reserveResources, sa.resetting = false, false - } - js.mu.Unlock() - } - - // Setup our internal send go routine. - mset.setupSendCapabilities() - - // Reserve resources if MaxBytes present. - if reserveResources { - mset.js.reserveStreamResources(&mset.cfg) - } - - // Call directly to set leader if not in clustered mode. - // This can be called though before we actually setup clustering, so check both. - if singleServerMode { - if err := mset.setLeader(true); err != nil { - mset.stop(true, false) - return nil, err - } - } - - // This is always true in single server mode. - if mset.IsLeader() { - // Send advisory. - var suppress bool - if !s.standAloneMode() && sa == nil { - if cfg.Replicas > 1 { - suppress = true - } - } else if sa != nil { - suppress = sa.responded - } - if !suppress { - mset.sendCreateAdvisory() - } - } - - // Register with our account last. - jsa.mu.Lock() - jsa.streams[cfg.Name] = mset - jsa.mu.Unlock() - - return mset, nil -} - -// Composes the index name. Contains the stream name, subject filter, and transform destination -// when the stream is external we will use the api prefix as part of the index name -// (as the same stream name could be used in multiple JS domains) -func (ssi *StreamSource) composeIName() string { - var iName = ssi.Name - - if ssi.External != nil { - iName = iName + ":" + getHash(ssi.External.ApiPrefix) - } - - source := ssi.FilterSubject - destination := fwcs - - if len(ssi.SubjectTransforms) == 0 { - // normalize filter and destination in case they are empty - if source == _EMPTY_ { - source = fwcs - } - if destination == _EMPTY_ { - destination = fwcs - } - } else { - var sources, destinations []string - - for _, tr := range ssi.SubjectTransforms { - trsrc, trdest := tr.Source, tr.Destination - if trsrc == _EMPTY_ { - trsrc = fwcs - } - if trdest == _EMPTY_ { - trdest = fwcs - } - sources = append(sources, trsrc) - destinations = append(destinations, trdest) - } - source = strings.Join(sources, "\f") - destination = strings.Join(destinations, "\f") - } - - return strings.Join([]string{iName, source, destination}, " ") -} - -// Sets the index name. -func (ssi *StreamSource) setIndexName() { - ssi.iname = ssi.composeIName() -} - -func (mset *stream) streamAssignment() *streamAssignment { - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.sa -} - -func (mset *stream) setStreamAssignment(sa *streamAssignment) { - var node RaftNode - var peers []string - - mset.mu.RLock() - js := mset.js - mset.mu.RUnlock() - - if js != nil { - js.mu.RLock() - if sa.Group != nil { - node = sa.Group.node - peers = sa.Group.Peers - } - js.mu.RUnlock() - } - - mset.mu.Lock() - defer mset.mu.Unlock() - - mset.sa = sa - if sa == nil { - return - } - - // Set our node. - mset.node = node - if mset.node != nil { - mset.node.UpdateKnownPeers(peers) - } - - // Setup our info sub here as well for all stream members. This is now by design. - if mset.infoSub == nil { - isubj := fmt.Sprintf(clusterStreamInfoT, mset.jsa.acc(), mset.cfg.Name) - // Note below the way we subscribe here is so that we can send requests to ourselves. - mset.infoSub, _ = mset.srv.systemSubscribe(isubj, _EMPTY_, false, mset.sysc, mset.handleClusterStreamInfoRequest) - } - - // Trigger update chan. - select { - case mset.uch <- struct{}{}: - default: - } -} - -func (mset *stream) monitorQuitC() <-chan struct{} { - if mset == nil { - return nil - } - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.mqch -} - -func (mset *stream) updateC() <-chan struct{} { - if mset == nil { - return nil - } - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.uch -} - -// IsLeader will return if we are the current leader. -func (mset *stream) IsLeader() bool { - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.isLeader() -} - -// Lock should be held. -func (mset *stream) isLeader() bool { - if mset.isClustered() { - return mset.node.Leader() - } - return true -} - -// isLeaderNodeState should NOT be used normally, use isLeader instead. -// Returns whether the node thinks it is the leader, regardless of whether applies are up-to-date yet -// (unlike isLeader, which requires applies to be caught up). -// May be used to respond to clients after a leader change, when applying entries from a former leader. -// Lock should be held. -func (mset *stream) isLeaderNodeState() bool { - if mset.isClustered() { - return mset.node.State() == Leader - } - return true -} - -// TODO(dlc) - Check to see if we can accept being the leader or we should step down. -func (mset *stream) setLeader(isLeader bool) error { - mset.mu.Lock() - // If we are here we have a change in leader status. - if isLeader { - // Make sure we are listening for sync requests. - // TODO(dlc) - Original design was that all in sync members of the group would do DQ. - if mset.isClustered() { - mset.startClusterSubs() - } - - // Setup subscriptions if we were not already the leader. - if err := mset.subscribeToStream(); err != nil { - if mset.isClustered() { - // Stepdown since we have an error. - mset.node.StepDown() - } - mset.mu.Unlock() - return err - } - } else { - // cancel timer to create the source consumers if not fired yet - if mset.sourcesConsumerSetup != nil { - mset.sourcesConsumerSetup.Stop() - mset.sourcesConsumerSetup = nil - } else { - // Stop any source consumers - mset.stopSourceConsumers() - } - - // Stop responding to sync requests. - mset.stopClusterSubs() - // Unsubscribe from direct stream. - mset.unsubscribeToStream(false) - // Clear catchup state - mset.clearAllCatchupPeers() - } - mset.mu.Unlock() - - // If we are interest based make sure to check consumers. - // This is to make sure we process any outstanding acks. - mset.checkInterestState() - - return nil -} - -// Lock should be held. -func (mset *stream) startClusterSubs() { - if mset.syncSub == nil { - mset.syncSub, _ = mset.srv.systemSubscribe(mset.sa.Sync, _EMPTY_, false, mset.sysc, mset.handleClusterSyncRequest) - } -} - -// Lock should be held. -func (mset *stream) stopClusterSubs() { - if mset.syncSub != nil { - mset.srv.sysUnsubscribe(mset.syncSub) - mset.syncSub = nil - } -} - -// account gets the account for this stream. -func (mset *stream) account() *Account { - mset.mu.RLock() - jsa := mset.jsa - mset.mu.RUnlock() - if jsa == nil { - return nil - } - return jsa.acc() -} - -// Helper to determine the max msg size for this stream if file based. -func (mset *stream) maxMsgSize() uint64 { - maxMsgSize := mset.cfg.MaxMsgSize - if maxMsgSize <= 0 { - // Pull from the account. - if mset.jsa != nil { - if acc := mset.jsa.acc(); acc != nil { - acc.mu.RLock() - maxMsgSize = acc.mpay - acc.mu.RUnlock() - } - } - // If all else fails use default. - if maxMsgSize <= 0 { - maxMsgSize = MAX_PAYLOAD_SIZE - } - } - // Now determine an estimation for the subjects etc. - maxSubject := -1 - for _, subj := range mset.cfg.Subjects { - if subjectIsLiteral(subj) { - if len(subj) > maxSubject { - maxSubject = len(subj) - } - } - } - if maxSubject < 0 { - const defaultMaxSubject = 256 - maxSubject = defaultMaxSubject - } - // filestore will add in estimates for record headers, etc. - return fileStoreMsgSizeEstimate(maxSubject, int(maxMsgSize)) -} - -// If we are file based and the file storage config was not explicitly set -// we can autotune block sizes to better match. Our target will be to store 125% -// of the theoretical limit. We will round up to nearest 100 bytes as well. -func (mset *stream) autoTuneFileStorageBlockSize(fsCfg *FileStoreConfig) { - var totalEstSize uint64 - - // MaxBytes will take precedence for now. - if mset.cfg.MaxBytes > 0 { - totalEstSize = uint64(mset.cfg.MaxBytes) - } else if mset.cfg.MaxMsgs > 0 { - // Determine max message size to estimate. - totalEstSize = mset.maxMsgSize() * uint64(mset.cfg.MaxMsgs) - } else if mset.cfg.MaxMsgsPer > 0 { - fsCfg.BlockSize = uint64(defaultKVBlockSize) - return - } else { - // If nothing set will let underlying filestore determine blkSize. - return - } - - blkSize := (totalEstSize / 4) + 1 // (25% overhead) - // Round up to nearest 100 - if m := blkSize % 100; m != 0 { - blkSize += 100 - m - } - if blkSize <= FileStoreMinBlkSize { - blkSize = FileStoreMinBlkSize - } else if blkSize >= FileStoreMaxBlkSize { - blkSize = FileStoreMaxBlkSize - } else { - blkSize = defaultMediumBlockSize - } - fsCfg.BlockSize = uint64(blkSize) -} - -// rebuildDedupe will rebuild any dedupe structures needed after recovery of a stream. -// Will be called lazily to avoid penalizing startup times. -// TODO(dlc) - Might be good to know if this should be checked at all for streams with no -// headers and msgId in them. Would need signaling from the storage layer. -// Lock should be held. -func (mset *stream) rebuildDedupe() { - if mset.ddloaded { - return - } - - mset.ddloaded = true - - // We have some messages. Lookup starting sequence by duplicate time window. - sseq := mset.store.GetSeqFromTime(time.Now().Add(-mset.cfg.Duplicates)) - if sseq == 0 { - return - } - - var smv StoreMsg - var state StreamState - mset.store.FastState(&state) - - for seq := sseq; seq <= state.LastSeq; seq++ { - sm, err := mset.store.LoadMsg(seq, &smv) - if err != nil { - continue - } - var msgId string - if len(sm.hdr) > 0 { - if msgId = getMsgId(sm.hdr); msgId != _EMPTY_ { - mset.storeMsgIdLocked(&ddentry{msgId, sm.seq, sm.ts}) - } - } - if seq == state.LastSeq { - mset.lmsgId = msgId - } - } -} - -func (mset *stream) lastSeqAndCLFS() (uint64, uint64) { - return mset.lastSeq(), mset.getCLFS() -} - -func (mset *stream) getCLFS() uint64 { - if mset == nil { - return 0 - } - mset.clMu.Lock() - defer mset.clMu.Unlock() - return mset.clfs -} - -func (mset *stream) setCLFS(clfs uint64) { - mset.clMu.Lock() - mset.clfs = clfs - mset.clMu.Unlock() -} - -func (mset *stream) lastSeq() uint64 { - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.lseq -} - -// Set last seq. -// Write lock should be held. -func (mset *stream) setLastSeq(lseq uint64) { - mset.lseq = lseq -} - -func (mset *stream) sendCreateAdvisory() { - mset.mu.RLock() - name := mset.cfg.Name - template := mset.cfg.Template - outq := mset.outq - srv := mset.srv - mset.mu.RUnlock() - - if outq == nil { - return - } - - // finally send an event that this stream was created - m := JSStreamActionAdvisory{ - TypedEvent: TypedEvent{ - Type: JSStreamActionAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Stream: name, - Action: CreateEvent, - Template: template, - Domain: srv.getOpts().JetStreamDomain, - } - - j, err := json.Marshal(m) - if err != nil { - return - } - - subj := JSAdvisoryStreamCreatedPre + "." + name - outq.sendMsg(subj, j) -} - -func (mset *stream) sendDeleteAdvisoryLocked() { - if mset.outq == nil { - return - } - - m := JSStreamActionAdvisory{ - TypedEvent: TypedEvent{ - Type: JSStreamActionAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Stream: mset.cfg.Name, - Action: DeleteEvent, - Template: mset.cfg.Template, - Domain: mset.srv.getOpts().JetStreamDomain, - } - - j, err := json.Marshal(m) - if err == nil { - subj := JSAdvisoryStreamDeletedPre + "." + mset.cfg.Name - mset.outq.sendMsg(subj, j) - } -} - -func (mset *stream) sendUpdateAdvisoryLocked() { - if mset.outq == nil { - return - } - - m := JSStreamActionAdvisory{ - TypedEvent: TypedEvent{ - Type: JSStreamActionAdvisoryType, - ID: nuid.Next(), - Time: time.Now().UTC(), - }, - Stream: mset.cfg.Name, - Action: ModifyEvent, - Domain: mset.srv.getOpts().JetStreamDomain, - } - - j, err := json.Marshal(m) - if err == nil { - subj := JSAdvisoryStreamUpdatedPre + "." + mset.cfg.Name - mset.outq.sendMsg(subj, j) - } -} - -// Created returns created time. -func (mset *stream) createdTime() time.Time { - mset.mu.RLock() - created := mset.created - mset.mu.RUnlock() - return created -} - -// Internal to allow creation time to be restored. -func (mset *stream) setCreatedTime(created time.Time) { - mset.mu.Lock() - mset.created = created - mset.mu.Unlock() -} - -// subjectsOverlap to see if these subjects overlap with existing subjects. -// Use only for non-clustered JetStream -// RLock minimum should be held. -func (jsa *jsAccount) subjectsOverlap(subjects []string, self *stream) bool { - for _, mset := range jsa.streams { - if self != nil && mset == self { - continue - } - for _, subj := range mset.cfg.Subjects { - for _, tsubj := range subjects { - if SubjectsCollide(tsubj, subj) { - return true - } - } - } - } - return false -} - -// StreamDefaultDuplicatesWindow default duplicates window. -const StreamDefaultDuplicatesWindow = 2 * time.Minute - -func (s *Server) checkStreamCfg(config *StreamConfig, acc *Account, pedantic bool) (StreamConfig, *ApiError) { - lim := &s.getOpts().JetStreamLimits - - if config == nil { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration invalid")) - } - if !isValidName(config.Name) { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("stream name is required and can not contain '.', '*', '>'")) - } - if len(config.Name) > JSMaxNameLen { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("stream name is too long, maximum allowed is %d", JSMaxNameLen)) - } - if len(config.Description) > JSMaxDescriptionLen { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("stream description is too long, maximum allowed is %d", JSMaxDescriptionLen)) - } - - var metadataLen int - for k, v := range config.Metadata { - metadataLen += len(k) + len(v) - } - if metadataLen > JSMaxMetadataLen { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("stream metadata exceeds maximum size of %d bytes", JSMaxMetadataLen)) - } - - cfg := *config - - // Make file the default. - if cfg.Storage == 0 { - cfg.Storage = FileStorage - } - if cfg.Replicas == 0 { - cfg.Replicas = 1 - } - if cfg.Replicas > StreamMaxReplicas { - return cfg, NewJSStreamInvalidConfigError(fmt.Errorf("maximum replicas is %d", StreamMaxReplicas)) - } - if cfg.Replicas < 0 { - return cfg, NewJSReplicasCountCannotBeNegativeError() - } - if cfg.MaxMsgs == 0 { - cfg.MaxMsgs = -1 - } - if cfg.MaxMsgsPer == 0 { - cfg.MaxMsgsPer = -1 - } - if cfg.MaxBytes == 0 { - cfg.MaxBytes = -1 - } - if cfg.MaxMsgSize == 0 { - cfg.MaxMsgSize = -1 - } - if cfg.MaxConsumers == 0 { - cfg.MaxConsumers = -1 - } - if cfg.Duplicates == 0 && cfg.Mirror == nil { - maxWindow := StreamDefaultDuplicatesWindow - if lim.Duplicates > 0 && maxWindow > lim.Duplicates { - if pedantic { - return StreamConfig{}, NewJSPedanticError(fmt.Errorf("pedantic mode: duplicate window limits are higher than current limits")) - } - maxWindow = lim.Duplicates - } - if cfg.MaxAge != 0 && cfg.MaxAge < maxWindow { - if pedantic { - return StreamConfig{}, NewJSPedanticError(fmt.Errorf("pedantic mode: duplicate window cannot be bigger than max age")) - } - cfg.Duplicates = cfg.MaxAge - } else { - cfg.Duplicates = maxWindow - } - } - if cfg.MaxAge > 0 && cfg.MaxAge < 100*time.Millisecond { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("max age needs to be >= 100ms")) - } - if cfg.Duplicates < 0 { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("duplicates window can not be negative")) - } - // Check that duplicates is not larger then age if set. - if cfg.MaxAge != 0 && cfg.Duplicates > cfg.MaxAge { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("duplicates window can not be larger then max age")) - } - if lim.Duplicates > 0 && cfg.Duplicates > lim.Duplicates { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("duplicates window can not be larger then server limit of %v", - lim.Duplicates.String())) - } - if cfg.Duplicates > 0 && cfg.Duplicates < 100*time.Millisecond { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("duplicates window needs to be >= 100ms")) - } - - if cfg.DenyPurge && cfg.AllowRollup { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("roll-ups require the purge permission")) - } - - // Check for new discard new per subject, we require the discard policy to also be new. - if cfg.DiscardNewPer { - if cfg.Discard != DiscardNew { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("discard new per subject requires discard new policy to be set")) - } - if cfg.MaxMsgsPer <= 0 { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("discard new per subject requires max msgs per subject > 0")) - } - } - - if cfg.SubjectDeleteMarkerTTL > 0 { - if !cfg.AllowMsgTTL { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("subject marker delete cannot be set if message TTLs are disabled")) - } - if cfg.SubjectDeleteMarkerTTL < time.Second { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("subject marker delete TTL must be at least 1 second")) - } - } else if cfg.SubjectDeleteMarkerTTL < 0 { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("subject marker delete TTL must not be negative")) - } - - getStream := func(streamName string) (bool, StreamConfig) { - var exists bool - var cfg StreamConfig - if s.JetStreamIsClustered() { - if js, _ := s.getJetStreamCluster(); js != nil { - js.mu.RLock() - if sa := js.streamAssignment(acc.Name, streamName); sa != nil { - cfg = *sa.Config.clone() - exists = true - } - js.mu.RUnlock() - } - } else if mset, err := acc.lookupStream(streamName); err == nil { - cfg = mset.cfg - exists = true - } - return exists, cfg - } - - hasStream := func(streamName string) (bool, int32, []string) { - exists, cfg := getStream(streamName) - return exists, cfg.MaxMsgSize, cfg.Subjects - } - - var streamSubs []string - var deliveryPrefixes []string - var apiPrefixes []string - - // Do some pre-checking for mirror config to avoid cycles in clustered mode. - if cfg.Mirror != nil { - if cfg.FirstSeq > 0 { - return StreamConfig{}, NewJSMirrorWithFirstSeqError() - } - if len(cfg.Subjects) > 0 { - return StreamConfig{}, NewJSMirrorWithSubjectsError() - } - if len(cfg.Sources) > 0 { - return StreamConfig{}, NewJSMirrorWithSourcesError() - } - if cfg.Mirror.FilterSubject != _EMPTY_ && len(cfg.Mirror.SubjectTransforms) != 0 { - return StreamConfig{}, NewJSMirrorMultipleFiltersNotAllowedError() - } - if cfg.SubjectDeleteMarkerTTL > 0 { - // Delete markers cannot be configured on a mirror as it would result in new - // tombstones which would use up sequence numbers, diverging from the origin - // stream. - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("subject delete markers forbidden on mirrors")) - } - // Check subject filters overlap. - for outer, tr := range cfg.Mirror.SubjectTransforms { - if tr.Source != _EMPTY_ && !IsValidSubject(tr.Source) { - return StreamConfig{}, NewJSMirrorInvalidSubjectFilterError(fmt.Errorf("%w %s", ErrBadSubject, tr.Source)) - } - - err := ValidateMapping(tr.Source, tr.Destination) - if err != nil { - return StreamConfig{}, NewJSMirrorInvalidTransformDestinationError(err) - } - - for inner, innertr := range cfg.Mirror.SubjectTransforms { - if inner != outer && SubjectsCollide(tr.Source, innertr.Source) { - return StreamConfig{}, NewJSMirrorOverlappingSubjectFiltersError() - } - } - } - // Do not perform checks if External is provided, as it could lead to - // checking against itself (if sourced stream name is the same on different JetStream) - if cfg.Mirror.External == nil { - if !isValidName(cfg.Mirror.Name) { - return StreamConfig{}, NewJSMirrorInvalidStreamNameError() - } - // We do not require other stream to exist anymore, but if we can see it check payloads. - exists, maxMsgSize, subs := hasStream(cfg.Mirror.Name) - if len(subs) > 0 { - streamSubs = append(streamSubs, subs...) - } - if exists { - if cfg.MaxMsgSize > 0 && maxMsgSize > 0 && cfg.MaxMsgSize < maxMsgSize { - return StreamConfig{}, NewJSMirrorMaxMessageSizeTooBigError() - } - } - // Determine if we are inheriting direct gets. - if exists, ocfg := getStream(cfg.Mirror.Name); exists { - if pedantic && cfg.MirrorDirect != ocfg.AllowDirect { - return StreamConfig{}, NewJSPedanticError(fmt.Errorf("origin stream has direct get set, mirror has it disabled")) - } - cfg.MirrorDirect = ocfg.AllowDirect - } else if js := s.getJetStream(); js != nil && js.isClustered() { - // Could not find it here. If we are clustered we can look it up. - js.mu.RLock() - if cc := js.cluster; cc != nil { - if as := cc.streams[acc.Name]; as != nil { - if sa := as[cfg.Mirror.Name]; sa != nil { - if pedantic && cfg.MirrorDirect != sa.Config.AllowDirect { - js.mu.RUnlock() - return StreamConfig{}, NewJSPedanticError(fmt.Errorf("origin stream has direct get set, mirror has it disabled")) - } - cfg.MirrorDirect = sa.Config.AllowDirect - } - } - } - js.mu.RUnlock() - } - } else { - if cfg.Mirror.External.DeliverPrefix != _EMPTY_ { - deliveryPrefixes = append(deliveryPrefixes, cfg.Mirror.External.DeliverPrefix) - } - - if cfg.Mirror.External.ApiPrefix != _EMPTY_ { - apiPrefixes = append(apiPrefixes, cfg.Mirror.External.ApiPrefix) - } - } - } - - // check for duplicates - var iNames = make(map[string]struct{}) - for _, src := range cfg.Sources { - if !isValidName(src.Name) { - return StreamConfig{}, NewJSSourceInvalidStreamNameError() - } - if _, ok := iNames[src.composeIName()]; !ok { - iNames[src.composeIName()] = struct{}{} - } else { - return StreamConfig{}, NewJSSourceDuplicateDetectedError() - } - // Do not perform checks if External is provided, as it could lead to - // checking against itself (if sourced stream name is the same on different JetStream) - if src.External == nil { - exists, maxMsgSize, subs := hasStream(src.Name) - if len(subs) > 0 { - streamSubs = append(streamSubs, subs...) - } - if exists { - if cfg.MaxMsgSize > 0 && maxMsgSize > 0 && cfg.MaxMsgSize < maxMsgSize { - return StreamConfig{}, NewJSSourceMaxMessageSizeTooBigError() - } - } - - if src.FilterSubject != _EMPTY_ && len(src.SubjectTransforms) != 0 { - return StreamConfig{}, NewJSSourceMultipleFiltersNotAllowedError() - } - - for _, tr := range src.SubjectTransforms { - if tr.Source != _EMPTY_ && !IsValidSubject(tr.Source) { - return StreamConfig{}, NewJSSourceInvalidSubjectFilterError(fmt.Errorf("%w %s", ErrBadSubject, tr.Source)) - } - - err := ValidateMapping(tr.Source, tr.Destination) - if err != nil { - return StreamConfig{}, NewJSSourceInvalidTransformDestinationError(err) - } - } - - // Check subject filters overlap. - for outer, tr := range src.SubjectTransforms { - for inner, innertr := range src.SubjectTransforms { - if inner != outer && subjectIsSubsetMatch(tr.Source, innertr.Source) { - return StreamConfig{}, NewJSSourceOverlappingSubjectFiltersError() - } - } - } - continue - } else { - if src.External.DeliverPrefix != _EMPTY_ { - deliveryPrefixes = append(deliveryPrefixes, src.External.DeliverPrefix) - } - if src.External.ApiPrefix != _EMPTY_ { - apiPrefixes = append(apiPrefixes, src.External.ApiPrefix) - } - } - } - - // check prefix overlap with subjects - for _, pfx := range deliveryPrefixes { - if !IsValidPublishSubject(pfx) { - return StreamConfig{}, NewJSStreamInvalidExternalDeliverySubjError(pfx) - } - for _, sub := range streamSubs { - if SubjectsCollide(sub, fmt.Sprintf("%s.%s", pfx, sub)) { - return StreamConfig{}, NewJSStreamExternalDelPrefixOverlapsError(pfx, sub) - } - } - } - // check if api prefixes overlap - for _, apiPfx := range apiPrefixes { - if !IsValidPublishSubject(apiPfx) { - return StreamConfig{}, NewJSStreamInvalidConfigError( - fmt.Errorf("stream external api prefix %q must be a valid subject without wildcards", apiPfx)) - } - if SubjectsCollide(apiPfx, JSApiPrefix) { - return StreamConfig{}, NewJSStreamExternalApiOverlapError(apiPfx, JSApiPrefix) - } - } - - // cycle check for source cycle - toVisit := []*StreamConfig{&cfg} - visited := make(map[string]struct{}) - overlaps := func(subjects []string, filter string) bool { - if filter == _EMPTY_ { - return true - } - for _, subject := range subjects { - if SubjectsCollide(subject, filter) { - return true - } - } - return false - } - - for len(toVisit) > 0 { - cfg := toVisit[0] - toVisit = toVisit[1:] - visited[cfg.Name] = struct{}{} - for _, src := range cfg.Sources { - if src.External != nil { - continue - } - // We can detect a cycle between streams, but let's double check that the - // subjects actually form a cycle. - if _, ok := visited[src.Name]; ok { - if overlaps(cfg.Subjects, src.FilterSubject) { - return StreamConfig{}, NewJSStreamInvalidConfigError(errors.New("detected cycle")) - } - } else if exists, cfg := getStream(src.Name); exists { - toVisit = append(toVisit, &cfg) - } - } - // Avoid cycles hiding behind mirrors - if m := cfg.Mirror; m != nil { - if m.External == nil { - if _, ok := visited[m.Name]; ok { - return StreamConfig{}, NewJSStreamInvalidConfigError(errors.New("detected cycle")) - } - if exists, cfg := getStream(m.Name); exists { - toVisit = append(toVisit, &cfg) - } - } - } - } - - if len(cfg.Subjects) == 0 { - if cfg.Mirror == nil && len(cfg.Sources) == 0 { - cfg.Subjects = append(cfg.Subjects, cfg.Name) - } - } else { - if cfg.Mirror != nil { - return StreamConfig{}, NewJSMirrorWithSubjectsError() - } - - // Check for literal duplication of subject interest in config - // and no overlap with any JS or SYS API subject space. - dset := make(map[string]struct{}, len(cfg.Subjects)) - for _, subj := range cfg.Subjects { - // Make sure the subject is valid. Check this first. - if !IsValidSubject(subj) { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("invalid subject")) - } - if _, ok := dset[subj]; ok { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("duplicate subjects detected")) - } - // Check for trying to capture everything. - if subj == fwcs { - if !cfg.NoAck { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("capturing all subjects requires no-ack to be true")) - } - // Capturing everything also will require R1. - if cfg.Replicas != 1 { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("capturing all subjects requires replicas of 1")) - } - } - // Also check to make sure we do not overlap with our $JS API subjects. - if !cfg.NoAck && (subjectIsSubsetMatch(subj, "$JS.>") || subjectIsSubsetMatch(subj, "$JSC.>")) { - // We allow an exception for $JS.EVENT.> since these could have been created in the past. - if !subjectIsSubsetMatch(subj, "$JS.EVENT.>") { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("subjects that overlap with jetstream api require no-ack to be true")) - } - } - // And the $SYS subjects. - if !cfg.NoAck && subjectIsSubsetMatch(subj, "$SYS.>") { - if !subjectIsSubsetMatch(subj, "$SYS.ACCOUNT.>") { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("subjects that overlap with system api require no-ack to be true")) - } - } - // Mark for duplicate check. - dset[subj] = struct{}{} - } - } - - if len(cfg.Subjects) == 0 && len(cfg.Sources) == 0 && cfg.Mirror == nil { - return StreamConfig{}, NewJSStreamInvalidConfigError( - fmt.Errorf("stream needs at least one configured subject or be a source/mirror")) - } - - // Check for MaxBytes required and it's limit - if required, limit := acc.maxBytesLimits(&cfg); required && cfg.MaxBytes <= 0 { - return StreamConfig{}, NewJSStreamMaxBytesRequiredError() - } else if limit > 0 && cfg.MaxBytes > limit { - return StreamConfig{}, NewJSStreamMaxStreamBytesExceededError() - } - - // Now check if we have multiple subjects they we do not overlap ourselves - // which would cause duplicate entries (assuming no MsgID). - if len(cfg.Subjects) > 1 { - for _, subj := range cfg.Subjects { - for _, tsubj := range cfg.Subjects { - if tsubj != subj && SubjectsCollide(tsubj, subj) { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("subject %q overlaps with %q", subj, tsubj)) - } - } - } - } - - // If we have a republish directive check if we can create a transform here. - if cfg.RePublish != nil { - // Check to make sure source is a valid subset of the subjects we have. - // Also make sure it does not form a cycle. - // Empty same as all. - if cfg.RePublish.Source == _EMPTY_ { - if pedantic { - return StreamConfig{}, NewJSPedanticError(fmt.Errorf("pedantic mode: republish source can not be empty")) - } - cfg.RePublish.Source = fwcs - } - var formsCycle bool - for _, subj := range cfg.Subjects { - if SubjectsCollide(cfg.RePublish.Destination, subj) { - formsCycle = true - break - } - } - if formsCycle { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration for republish destination forms a cycle")) - } - if _, err := NewSubjectTransform(cfg.RePublish.Source, cfg.RePublish.Destination); err != nil { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration for republish with transform from '%s' to '%s' not valid", cfg.RePublish.Source, cfg.RePublish.Destination)) - } - } - - // Check the subject transform if any - if cfg.SubjectTransform != nil { - if cfg.SubjectTransform.Source != _EMPTY_ && !IsValidSubject(cfg.SubjectTransform.Source) { - return StreamConfig{}, NewJSStreamTransformInvalidSourceError(fmt.Errorf("%w %s", ErrBadSubject, cfg.SubjectTransform.Source)) - } - - err := ValidateMapping(cfg.SubjectTransform.Source, cfg.SubjectTransform.Destination) - if err != nil { - return StreamConfig{}, NewJSStreamTransformInvalidDestinationError(err) - } - } - - // For now don't allow preferred server in placement. - if cfg.Placement != nil && cfg.Placement.Preferred != _EMPTY_ { - return StreamConfig{}, NewJSStreamInvalidConfigError(fmt.Errorf("preferred server not permitted in placement")) - } - - return cfg, nil -} - -// Config returns the stream's configuration. -func (mset *stream) config() StreamConfig { - mset.cfgMu.RLock() - defer mset.cfgMu.RUnlock() - return mset.cfg -} - -func (mset *stream) fileStoreConfig() (FileStoreConfig, error) { - mset.mu.Lock() - defer mset.mu.Unlock() - fs, ok := mset.store.(*fileStore) - if !ok { - return FileStoreConfig{}, ErrStoreWrongType - } - return fs.fileStoreConfig(), nil -} - -// Do not hold jsAccount or jetStream lock -func (jsa *jsAccount) configUpdateCheck(old, new *StreamConfig, s *Server, pedantic bool) (*StreamConfig, error) { - cfg, apiErr := s.checkStreamCfg(new, jsa.acc(), pedantic) - if apiErr != nil { - return nil, apiErr - } - - // Name must match. - if cfg.Name != old.Name { - return nil, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration name must match original")) - } - // Can't change MaxConsumers for now. - if cfg.MaxConsumers != old.MaxConsumers { - return nil, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration update can not change MaxConsumers")) - } - // Can't change storage types. - if cfg.Storage != old.Storage { - return nil, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration update can not change storage type")) - } - // Can only change retention from limits to interest or back, not to/from work queue for now. - if cfg.Retention != old.Retention { - if old.Retention == WorkQueuePolicy || cfg.Retention == WorkQueuePolicy { - return nil, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration update can not change retention policy to/from workqueue")) - } - } - // Can not have a template owner for now. - if old.Template != _EMPTY_ { - return nil, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration update not allowed on template owned stream")) - } - if cfg.Template != _EMPTY_ { - return nil, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration update can not be owned by a template")) - } - // Can not change from true to false. - if !cfg.Sealed && old.Sealed { - return nil, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration update can not unseal a sealed stream")) - } - // Can not change from true to false. - if !cfg.DenyDelete && old.DenyDelete { - return nil, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration update can not cancel deny message deletes")) - } - // Can not change from true to false. - if !cfg.DenyPurge && old.DenyPurge { - return nil, NewJSStreamInvalidConfigError(fmt.Errorf("stream configuration update can not cancel deny purge")) - } - // Check for mirror changes which are not allowed. - if !reflect.DeepEqual(cfg.Mirror, old.Mirror) { - return nil, NewJSStreamMirrorNotUpdatableError() - } - - // Check on new discard new per subject. - if cfg.DiscardNewPer { - if cfg.Discard != DiscardNew { - return nil, NewJSStreamInvalidConfigError(fmt.Errorf("discard new per subject requires discard new policy to be set")) - } - if cfg.MaxMsgsPer <= 0 { - return nil, NewJSStreamInvalidConfigError(fmt.Errorf("discard new per subject requires max msgs per subject > 0")) - } - } - - // Check on the allowed message TTL status. - if cfg.AllowMsgTTL != old.AllowMsgTTL { - return nil, NewJSStreamInvalidConfigError(fmt.Errorf("message TTL status can not be changed after stream creation")) - } - - // Do some adjustments for being sealed. - // Pedantic mode will allow those changes to be made, as they are determinictic and important to get a sealed stream. - if cfg.Sealed { - cfg.MaxAge = 0 - cfg.Discard = DiscardNew - cfg.DenyDelete, cfg.DenyPurge = true, true - cfg.AllowRollup = false - } - - // Check limits. We need some extra handling to allow updating MaxBytes. - - // First, let's calculate the difference between the new and old MaxBytes. - maxBytesDiff := cfg.MaxBytes - old.MaxBytes - if maxBytesDiff < 0 { - // If we're updating to a lower MaxBytes (maxBytesDiff is negative), - // then set to zero so checkBytesLimits doesn't set addBytes to 1. - maxBytesDiff = 0 - } - // If maxBytesDiff == 0, then that means MaxBytes didn't change. - // If maxBytesDiff > 0, then we want to reserve additional bytes. - - // Save the user configured MaxBytes. - newMaxBytes := cfg.MaxBytes - maxBytesOffset := int64(0) - - // We temporarily set cfg.MaxBytes to maxBytesDiff because checkAllLimits - // adds cfg.MaxBytes to the current reserved limit and checks if we've gone - // over. However, we don't want an addition cfg.MaxBytes, we only want to - // reserve the difference between the new and the old values. - cfg.MaxBytes = maxBytesDiff - - // Check limits. - js, isClustered := jsa.jetStreamAndClustered() - jsa.mu.RLock() - acc := jsa.account - jsa.usageMu.RLock() - selected, tier, hasTier := jsa.selectLimits(cfg.Replicas) - if !hasTier && old.Replicas != cfg.Replicas { - selected, tier, hasTier = jsa.selectLimits(old.Replicas) - } - jsa.usageMu.RUnlock() - reserved := int64(0) - if !isClustered { - reserved = jsa.tieredReservation(tier, &cfg) - } - jsa.mu.RUnlock() - if !hasTier { - return nil, NewJSNoLimitsError() - } - js.mu.RLock() - defer js.mu.RUnlock() - if isClustered { - _, reserved = tieredStreamAndReservationCount(js.cluster.streams[acc.Name], tier, &cfg) - } - // reservation does not account for this stream, hence add the old value - if tier == _EMPTY_ && old.Replicas > 1 { - reserved += old.MaxBytes * int64(old.Replicas) - } else { - reserved += old.MaxBytes - } - if err := js.checkAllLimits(&selected, &cfg, reserved, maxBytesOffset); err != nil { - return nil, err - } - // Restore the user configured MaxBytes. - cfg.MaxBytes = newMaxBytes - return &cfg, nil -} - -// Update will allow certain configuration properties of an existing stream to be updated. -func (mset *stream) update(config *StreamConfig) error { - return mset.updateWithAdvisory(config, true, false) -} - -func (mset *stream) updatePedantic(config *StreamConfig, pedantic bool) error { - return mset.updateWithAdvisory(config, true, pedantic) -} - -// Update will allow certain configuration properties of an existing stream to be updated. -func (mset *stream) updateWithAdvisory(config *StreamConfig, sendAdvisory bool, pedantic bool) error { - _, jsa, err := mset.acc.checkForJetStream() - if err != nil { - return err - } - - mset.mu.RLock() - ocfg := mset.cfg - s := mset.srv - mset.mu.RUnlock() - - cfg, err := mset.jsa.configUpdateCheck(&ocfg, config, s, pedantic) - if err != nil { - return NewJSStreamInvalidConfigError(err, Unless(err)) - } - - // In the event that some of the stream-level limits have changed, yell appropriately - // if any of the consumers exceed that limit. - updateLimits := ocfg.ConsumerLimits.InactiveThreshold != cfg.ConsumerLimits.InactiveThreshold || - ocfg.ConsumerLimits.MaxAckPending != cfg.ConsumerLimits.MaxAckPending - if updateLimits { - var errorConsumers []string - consumers := map[string]*ConsumerConfig{} - if mset.js.isClustered() { - for _, c := range mset.sa.consumers { - consumers[c.Name] = c.Config - } - } else { - for _, c := range mset.consumers { - consumers[c.name] = &c.cfg - } - } - for name, ccfg := range consumers { - if ccfg.InactiveThreshold > cfg.ConsumerLimits.InactiveThreshold || - ccfg.MaxAckPending > cfg.ConsumerLimits.MaxAckPending { - errorConsumers = append(errorConsumers, name) - } - } - if len(errorConsumers) > 0 { - // TODO(nat): Return a parsable error so that we can surface something - // sensible through the JS API. - return fmt.Errorf("change to limits violates consumers: %s", strings.Join(errorConsumers, ", ")) - } - } - - jsa.mu.RLock() - if jsa.subjectsOverlap(cfg.Subjects, mset) { - jsa.mu.RUnlock() - return NewJSStreamSubjectOverlapError() - } - jsa.mu.RUnlock() - - mset.mu.Lock() - if mset.isLeader() { - // Now check for subject interest differences. - current := make(map[string]struct{}, len(ocfg.Subjects)) - for _, s := range ocfg.Subjects { - current[s] = struct{}{} - } - // Update config with new values. The store update will enforce any stricter limits. - - // Now walk new subjects. All of these need to be added, but we will check - // the originals first, since if it is in there we can skip, already added. - for _, s := range cfg.Subjects { - if _, ok := current[s]; !ok { - if _, err := mset.subscribeInternal(s, mset.processInboundJetStreamMsg); err != nil { - mset.mu.Unlock() - return err - } - } - delete(current, s) - } - // What is left in current needs to be deleted. - for s := range current { - if err := mset.unsubscribeInternal(s); err != nil { - mset.mu.Unlock() - return err - } - } - - // Check for the Duplicates - if cfg.Duplicates != ocfg.Duplicates && mset.ddtmr != nil { - // Let it fire right away, it will adjust properly on purge. - mset.ddtmr.Reset(time.Microsecond) - } - - // Check for Sources. - if len(cfg.Sources) > 0 || len(ocfg.Sources) > 0 { - currentIName := make(map[string]struct{}) - needsStartingSeqNum := make(map[string]struct{}) - - for _, s := range ocfg.Sources { - currentIName[s.iname] = struct{}{} - } - for _, s := range cfg.Sources { - s.setIndexName() - if _, ok := currentIName[s.iname]; !ok { - // new source - if mset.sources == nil { - mset.sources = make(map[string]*sourceInfo) - } - mset.cfg.Sources = append(mset.cfg.Sources, s) - - var si *sourceInfo - - if len(s.SubjectTransforms) == 0 { - si = &sourceInfo{name: s.Name, iname: s.iname, sf: s.FilterSubject} - } else { - si = &sourceInfo{name: s.Name, iname: s.iname} - si.trs = make([]*subjectTransform, len(s.SubjectTransforms)) - si.sfs = make([]string, len(s.SubjectTransforms)) - for i := range s.SubjectTransforms { - // err can be ignored as already validated in config check - si.sfs[i] = s.SubjectTransforms[i].Source - var err error - si.trs[i], err = NewSubjectTransform(s.SubjectTransforms[i].Source, s.SubjectTransforms[i].Destination) - if err != nil { - mset.mu.Unlock() - return fmt.Errorf("unable to get subject transform for source: %v", err) - } - } - } - - mset.sources[s.iname] = si - needsStartingSeqNum[s.iname] = struct{}{} - } else { - // source already exists - delete(currentIName, s.iname) - } - } - // What is left in currentIName needs to be deleted. - for iName := range currentIName { - mset.cancelSourceConsumer(iName) - delete(mset.sources, iName) - } - neededCopy := make(map[string]struct{}, len(needsStartingSeqNum)) - for iName := range needsStartingSeqNum { - neededCopy[iName] = struct{}{} - } - mset.setStartingSequenceForSources(needsStartingSeqNum) - for iName := range neededCopy { - mset.setupSourceConsumer(iName, mset.sources[iName].sseq+1, time.Time{}) - } - } - } - - // Check for a change in allow direct status. - // These will run on all members, so just update as appropriate here. - // We do make sure we are caught up under monitorStream() during initial startup. - if cfg.AllowDirect != ocfg.AllowDirect { - if cfg.AllowDirect { - mset.subscribeToDirect() - } else { - mset.unsubscribeToDirect() - } - } - - // Check for changes to RePublish. - if cfg.RePublish != nil { - // Empty same as all. - if cfg.RePublish.Source == _EMPTY_ { - cfg.RePublish.Source = fwcs - } - if cfg.RePublish.Destination == _EMPTY_ { - cfg.RePublish.Destination = fwcs - } - tr, err := NewSubjectTransform(cfg.RePublish.Source, cfg.RePublish.Destination) - if err != nil { - mset.mu.Unlock() - return fmt.Errorf("stream configuration for republish from '%s' to '%s': %w", cfg.RePublish.Source, cfg.RePublish.Destination, err) - } - // Assign our transform for republishing. - mset.tr = tr - } else { - mset.tr = nil - } - - // Check for changes to subject transform - if ocfg.SubjectTransform == nil && cfg.SubjectTransform != nil { - tr, err := NewSubjectTransform(cfg.SubjectTransform.Source, cfg.SubjectTransform.Destination) - if err != nil { - mset.mu.Unlock() - return fmt.Errorf("stream configuration for subject transform from '%s' to '%s': %w", cfg.SubjectTransform.Source, cfg.SubjectTransform.Destination, err) - } - mset.itr = tr - } else if ocfg.SubjectTransform != nil && cfg.SubjectTransform != nil && - (ocfg.SubjectTransform.Source != cfg.SubjectTransform.Source || ocfg.SubjectTransform.Destination != cfg.SubjectTransform.Destination) { - tr, err := NewSubjectTransform(cfg.SubjectTransform.Source, cfg.SubjectTransform.Destination) - if err != nil { - mset.mu.Unlock() - return fmt.Errorf("stream configuration for subject transform from '%s' to '%s': %w", cfg.SubjectTransform.Source, cfg.SubjectTransform.Destination, err) - } - mset.itr = tr - } else if ocfg.SubjectTransform != nil && cfg.SubjectTransform == nil { - mset.itr = nil - } - - js := mset.js - - if targetTier := tierName(cfg.Replicas); mset.tier != targetTier { - // In cases such as R1->R3, only one update is needed - jsa.usageMu.RLock() - _, ok := jsa.limits[targetTier] - jsa.usageMu.RUnlock() - if ok { - // error never set - _, reported, _ := mset.store.Utilization() - jsa.updateUsage(mset.tier, mset.stype, -int64(reported)) - jsa.updateUsage(targetTier, mset.stype, int64(reported)) - mset.tier = targetTier - } - // else in case the new tier does not exist (say on move), keep the old tier around - // a subsequent update to an existing tier will then move from existing past tier to existing new tier - } - - if mset.isLeader() && mset.sa != nil && ocfg.Retention != cfg.Retention && cfg.Retention == InterestPolicy { - // Before we can update the retention policy for the consumer, we need - // the replica count of all consumers to match the stream. - for _, c := range mset.sa.consumers { - if c.Config.Replicas > 0 && c.Config.Replicas != cfg.Replicas { - mset.mu.Unlock() - return fmt.Errorf("consumer %q replica count must be %d", c.Name, cfg.Replicas) - } - } - } - - // Now update config and store's version of our config. - // Although we are under the stream write lock, we will also assign the new - // configuration under mset.cfgMu lock. This is so that in places where - // mset.mu cannot be acquired (like many cases in consumer.go where code - // is under the consumer's lock), and the stream's configuration needs to - // be inspected, one can use mset.cfgMu's read lock to do that safely. - mset.cfgMu.Lock() - mset.cfg = *cfg - mset.cfgMu.Unlock() - - // If we're changing retention and haven't errored because of consumer - // replicas by now, whip through and update the consumer retention. - if ocfg.Retention != cfg.Retention && cfg.Retention == InterestPolicy { - toUpdate := make([]*consumer, 0, len(mset.consumers)) - for _, c := range mset.consumers { - toUpdate = append(toUpdate, c) - } - var ss StreamState - mset.store.FastState(&ss) - mset.mu.Unlock() - for _, c := range toUpdate { - c.mu.Lock() - c.retention = cfg.Retention - c.mu.Unlock() - if c.retention == InterestPolicy { - // If we're switching to interest, force a check of the - // interest of existing stream messages. - c.checkStateForInterestStream(&ss) - } - } - mset.mu.Lock() - } - - // If we are the leader never suppress update advisory, simply send. - if mset.isLeader() && sendAdvisory { - mset.sendUpdateAdvisoryLocked() - } - mset.mu.Unlock() - - if js != nil { - maxBytesDiff := cfg.MaxBytes - ocfg.MaxBytes - if maxBytesDiff > 0 { - // Reserve the difference - js.reserveStreamResources(&StreamConfig{ - MaxBytes: maxBytesDiff, - Storage: cfg.Storage, - }) - } else if maxBytesDiff < 0 { - // Release the difference - js.releaseStreamResources(&StreamConfig{ - MaxBytes: -maxBytesDiff, - Storage: ocfg.Storage, - }) - } - } - - mset.store.UpdateConfig(cfg) - - return nil -} - -// Small helper to return the Name field from mset.cfg, protected by -// the mset.cfgMu mutex. This is simply because we have several places -// in consumer.go where we need it. -func (mset *stream) getCfgName() string { - mset.cfgMu.RLock() - defer mset.cfgMu.RUnlock() - return mset.cfg.Name -} - -// Purge will remove all messages from the stream and underlying store based on the request. -func (mset *stream) purge(preq *JSApiStreamPurgeRequest) (purged uint64, err error) { - mset.mu.RLock() - if mset.closed.Load() { - mset.mu.RUnlock() - return 0, errStreamClosed - } - if mset.cfg.Sealed { - mset.mu.RUnlock() - return 0, errors.New("sealed stream") - } - store, mlseq := mset.store, mset.lseq - mset.mu.RUnlock() - - if preq != nil { - purged, err = mset.store.PurgeEx(preq.Subject, preq.Sequence, preq.Keep, false /*preq.NoMarkers*/) - } else { - purged, err = mset.store.Purge() - } - if err != nil { - return purged, err - } - - // Grab our stream state. - var state StreamState - store.FastState(&state) - fseq, lseq := state.FirstSeq, state.LastSeq - - mset.mu.Lock() - // Check if our last has moved past what our original last sequence was, if so reset. - if lseq > mlseq { - mset.setLastSeq(lseq) - } - - // Clear any pending acks below first seq. - mset.clearAllPreAcksBelowFloor(fseq) - mset.mu.Unlock() - - // Purge consumers. - // Check for filtered purge. - if preq != nil && preq.Subject != _EMPTY_ { - ss := store.FilteredState(fseq, preq.Subject) - fseq = ss.First - } - - mset.clsMu.RLock() - for _, o := range mset.cList { - start := fseq - o.mu.RLock() - // we update consumer sequences if: - // no subject was specified, we can purge all consumers sequences - doPurge := preq == nil || - preq.Subject == _EMPTY_ || - // consumer filter subject is equal to purged subject - // or consumer filter subject is subset of purged subject, - // but not the other way around. - o.isEqualOrSubsetMatch(preq.Subject) - // Check if a consumer has a wider subject space then what we purged - var isWider bool - if !doPurge && preq != nil && o.isFilteredMatch(preq.Subject) { - doPurge, isWider = true, true - start = state.FirstSeq - } - o.mu.RUnlock() - if doPurge { - o.purge(start, lseq, isWider) - } - } - mset.clsMu.RUnlock() - - return purged, nil -} - -// RemoveMsg will remove a message from a stream. -// FIXME(dlc) - Should pick one and be consistent. -func (mset *stream) removeMsg(seq uint64) (bool, error) { - return mset.deleteMsg(seq) -} - -// DeleteMsg will remove a message from a stream. -func (mset *stream) deleteMsg(seq uint64) (bool, error) { - if mset.closed.Load() { - return false, errStreamClosed - } - removed, err := mset.store.RemoveMsg(seq) - if err != nil { - return removed, err - } - mset.mu.Lock() - mset.clearAllPreAcks(seq) - mset.mu.Unlock() - return removed, err -} - -// EraseMsg will securely remove a message and rewrite the data with random data. -func (mset *stream) eraseMsg(seq uint64) (bool, error) { - if mset.closed.Load() { - return false, errStreamClosed - } - removed, err := mset.store.EraseMsg(seq) - if err != nil { - return removed, err - } - mset.mu.Lock() - mset.clearAllPreAcks(seq) - mset.mu.Unlock() - return removed, err -} - -// Are we a mirror? -func (mset *stream) isMirror() bool { - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.cfg.Mirror != nil -} - -func (mset *stream) sourcesInfo() (sis []*StreamSourceInfo) { - mset.mu.RLock() - defer mset.mu.RUnlock() - for _, si := range mset.sources { - sis = append(sis, mset.sourceInfo(si)) - } - return sis -} - -// Lock should be held -func (mset *stream) sourceInfo(si *sourceInfo) *StreamSourceInfo { - if si == nil { - return nil - } - - var ssi = StreamSourceInfo{Name: si.name, Lag: si.lag, Error: si.err, FilterSubject: si.sf} - - trConfigs := make([]SubjectTransformConfig, len(si.sfs)) - for i := range si.sfs { - destination := _EMPTY_ - if si.trs[i] != nil { - destination = si.trs[i].dest - } - trConfigs[i] = SubjectTransformConfig{si.sfs[i], destination} - } - - ssi.SubjectTransforms = trConfigs - - // If we have not heard from the source, set Active to -1. - if last := si.last.Load(); last == 0 { - ssi.Active = -1 - } else { - ssi.Active = time.Since(time.Unix(0, last)) - } - - var ext *ExternalStream - if mset.cfg.Mirror != nil { - ext = mset.cfg.Mirror.External - } else if ss := mset.streamSource(si.iname); ss != nil && ss.External != nil { - ext = ss.External - } - if ext != nil { - ssi.External = &ExternalStream{ - ApiPrefix: ext.ApiPrefix, - DeliverPrefix: ext.DeliverPrefix, - } - } - return &ssi -} - -// Return our source info for our mirror. -func (mset *stream) mirrorInfo() *StreamSourceInfo { - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.sourceInfo(mset.mirror) -} - -const ( - // Our consumer HB interval. - sourceHealthHB = 1 * time.Second - // How often we check and our stalled interval. - sourceHealthCheckInterval = 10 * time.Second -) - -// Will run as a Go routine to process mirror consumer messages. -func (mset *stream) processMirrorMsgs(mirror *sourceInfo, ready *sync.WaitGroup) { - s := mset.srv - defer func() { - mirror.wg.Done() - s.grWG.Done() - }() - - // Grab stream quit channel. - mset.mu.Lock() - msgs, qch, siqch := mirror.msgs, mset.qch, mirror.qch - // Set the last seen as now so that we don't fail at the first check. - mirror.last.Store(time.Now().UnixNano()) - mset.mu.Unlock() - - // Signal the caller that we have captured the above fields. - ready.Done() - - // Make sure we have valid ipq for msgs. - if msgs == nil { - mset.mu.Lock() - mset.cancelMirrorConsumer() - mset.mu.Unlock() - return - } - - t := time.NewTicker(sourceHealthCheckInterval) - defer t.Stop() - - for { - select { - case <-s.quitCh: - return - case <-qch: - return - case <-siqch: - return - case <-msgs.ch: - ims := msgs.pop() - for _, im := range ims { - if !mset.processInboundMirrorMsg(im) { - break - } - im.returnToPool() - } - msgs.recycle(&ims) - case <-t.C: - mset.mu.RLock() - var stalled bool - if mset.mirror != nil { - stalled = time.Since(time.Unix(0, mset.mirror.last.Load())) > sourceHealthCheckInterval - } - isLeader := mset.isLeader() - mset.mu.RUnlock() - // No longer leader. - if !isLeader { - mset.mu.Lock() - mset.cancelMirrorConsumer() - mset.mu.Unlock() - return - } - // We are stalled. - if stalled { - mset.retryMirrorConsumer() - } - } - } -} - -// Checks that the message is from our current direct consumer. We can not depend on sub comparison -// since cross account imports break. -func (si *sourceInfo) isCurrentSub(reply string) bool { - return si.cname != _EMPTY_ && strings.HasPrefix(reply, jsAckPre) && si.cname == tokenAt(reply, 4) -} - -// processInboundMirrorMsg handles processing messages bound for a stream. -func (mset *stream) processInboundMirrorMsg(m *inMsg) bool { - mset.mu.Lock() - if mset.mirror == nil { - mset.mu.Unlock() - return false - } - if !mset.isLeader() { - mset.cancelMirrorConsumer() - mset.mu.Unlock() - return false - } - - isControl := m.isControlMsg() - - // Ignore from old subscriptions. - // The reason we can not just compare subs is that on cross account imports they will not match. - if !mset.mirror.isCurrentSub(m.rply) && !isControl { - mset.mu.Unlock() - return false - } - - // Check for heartbeats and flow control messages. - if isControl { - var needsRetry bool - // Flow controls have reply subjects. - if m.rply != _EMPTY_ { - mset.handleFlowControl(m) - } else { - // For idle heartbeats make sure we did not miss anything and check if we are considered stalled. - if ldseq := parseInt64(getHeader(JSLastConsumerSeq, m.hdr)); ldseq > 0 && uint64(ldseq) != mset.mirror.dseq { - needsRetry = true - } else if fcReply := getHeader(JSConsumerStalled, m.hdr); len(fcReply) > 0 { - // Other side thinks we are stalled, so send flow control reply. - mset.outq.sendMsg(string(fcReply), nil) - } - } - mset.mu.Unlock() - if needsRetry { - mset.retryMirrorConsumer() - } - return !needsRetry - } - - sseq, dseq, dc, ts, pending := replyInfo(m.rply) - - if dc > 1 { - mset.mu.Unlock() - return false - } - - // Mirror info tracking. - olag, osseq, odseq := mset.mirror.lag, mset.mirror.sseq, mset.mirror.dseq - if sseq == mset.mirror.sseq+1 { - mset.mirror.dseq = dseq - mset.mirror.sseq++ - } else if sseq <= mset.mirror.sseq { - // Ignore older messages. - mset.mu.Unlock() - return true - } else if mset.mirror.cname == _EMPTY_ { - mset.mirror.cname = tokenAt(m.rply, 4) - mset.mirror.dseq, mset.mirror.sseq = dseq, sseq - } else { - // If the deliver sequence matches then the upstream stream has expired or deleted messages. - if dseq == mset.mirror.dseq+1 { - mset.skipMsgs(mset.mirror.sseq+1, sseq-1) - mset.mirror.dseq++ - mset.mirror.sseq = sseq - } else { - mset.mu.Unlock() - mset.retryMirrorConsumer() - return false - } - } - - if pending == 0 { - mset.mirror.lag = 0 - } else { - mset.mirror.lag = pending - 1 - } - - // Check if we allow mirror direct here. If so check they we have mostly caught up. - // The reason we do not require 0 is if the source is active we may always be slightly behind. - if mset.cfg.MirrorDirect && mset.mirror.dsub == nil && pending < dgetCaughtUpThresh { - if err := mset.subscribeToMirrorDirect(); err != nil { - // Disable since we had problems above. - mset.cfg.MirrorDirect = false - } - } - - // Do the subject transform if there's one - if len(mset.mirror.trs) > 0 { - for _, tr := range mset.mirror.trs { - if tr == nil { - continue - } else { - tsubj, err := tr.Match(m.subj) - if err == nil { - m.subj = tsubj - break - } - } - } - } - - s, js, stype := mset.srv, mset.js, mset.cfg.Storage - node := mset.node - mset.mu.Unlock() - - var err error - if node != nil { - if js.limitsExceeded(stype) { - s.resourcesExceededError() - err = ApiErrors[JSInsufficientResourcesErr] - } else { - err = node.Propose(encodeStreamMsg(m.subj, _EMPTY_, m.hdr, m.msg, sseq-1, ts, true)) - } - } else { - err = mset.processJetStreamMsg(m.subj, _EMPTY_, m.hdr, m.msg, sseq-1, ts, nil, true) - } - if err != nil { - if strings.Contains(err.Error(), "no space left") { - s.Errorf("JetStream out of space, will be DISABLED") - s.DisableJetStream() - return false - } - if err != errLastSeqMismatch { - mset.mu.RLock() - accName, sname := mset.acc.Name, mset.cfg.Name - mset.mu.RUnlock() - s.RateLimitWarnf("Error processing inbound mirror message for '%s' > '%s': %v", - accName, sname, err) - } else { - // We may have missed messages, restart. - if sseq <= mset.lastSeq() { - mset.mu.Lock() - mset.mirror.lag = olag - mset.mirror.sseq = osseq - mset.mirror.dseq = odseq - mset.mu.Unlock() - return false - } else { - mset.mu.Lock() - mset.mirror.dseq = odseq - mset.mirror.sseq = osseq - mset.mu.Unlock() - mset.retryMirrorConsumer() - } - } - } - return err == nil -} - -func (mset *stream) setMirrorErr(err *ApiError) { - mset.mu.Lock() - if mset.mirror != nil { - mset.mirror.err = err - } - mset.mu.Unlock() -} - -// Cancels a mirror consumer. -// -// Lock held on entry -func (mset *stream) cancelMirrorConsumer() { - if mset.mirror == nil { - return - } - mset.cancelSourceInfo(mset.mirror) -} - -// Similar to setupMirrorConsumer except that it will print a debug statement -// indicating that there is a retry. -// -// Lock is acquired in this function -func (mset *stream) retryMirrorConsumer() error { - mset.mu.Lock() - defer mset.mu.Unlock() - mset.srv.Debugf("Retrying mirror consumer for '%s > %s'", mset.acc.Name, mset.cfg.Name) - mset.cancelMirrorConsumer() - return mset.setupMirrorConsumer() -} - -// Lock should be held. -func (mset *stream) skipMsgs(start, end uint64) { - node, store := mset.node, mset.store - // If we are not clustered we can short circuit now with store.SkipMsgs - if node == nil { - store.SkipMsgs(start, end-start+1) - mset.lseq = end - return - } - - // FIXME (dlc) - We should allow proposals of DeleteRange, but would need to make sure all peers support. - // With syncRequest was easy to add bool into request. - var entries []*Entry - for seq := start; seq <= end; seq++ { - entries = append(entries, newEntry(EntryNormal, encodeStreamMsg(_EMPTY_, _EMPTY_, nil, nil, seq-1, 0, false))) - // So a single message does not get too big. - if len(entries) > 10_000 { - node.ProposeMulti(entries) - // We need to re-create `entries` because there is a reference - // to it in the node's pae map. - entries = entries[:0] - } - } - // Send all at once. - if len(entries) > 0 { - node.ProposeMulti(entries) - } -} - -const ( - // Base retry backoff duration. - retryBackOff = 5 * time.Second - // Maximum amount we will wait. - retryMaximum = 2 * time.Minute -) - -// Calculate our backoff based on number of failures. -func calculateRetryBackoff(fails int) time.Duration { - backoff := time.Duration(retryBackOff) * time.Duration(fails*2) - if backoff > retryMaximum { - backoff = retryMaximum - } - return backoff -} - -// This will schedule a call to setupMirrorConsumer, taking into account the last -// time it was retried and determine the soonest setupMirrorConsumer can be called -// without tripping the sourceConsumerRetryThreshold. We will also take into account -// number of failures and will back off our retries. -// The mset.mirror pointer has been verified to be not nil by the caller. -// -// Lock held on entry -func (mset *stream) scheduleSetupMirrorConsumerRetry() { - // We are trying to figure out how soon we can retry. setupMirrorConsumer will reject - // a retry if last was done less than "sourceConsumerRetryThreshold" ago. - next := sourceConsumerRetryThreshold - time.Since(mset.mirror.lreq) - if next < 0 { - // It means that we have passed the threshold and so we are ready to go. - next = 0 - } - // Take into account failures here. - next += calculateRetryBackoff(mset.mirror.fails) - - // Add some jitter. - next += time.Duration(rand.Intn(int(100*time.Millisecond))) + 100*time.Millisecond - - time.AfterFunc(next, func() { - mset.mu.Lock() - mset.setupMirrorConsumer() - mset.mu.Unlock() - }) -} - -// How long we wait for a response from a consumer create request for a source or mirror. -var srcConsumerWaitTime = 30 * time.Second - -// Setup our mirror consumer. -// Lock should be held. -func (mset *stream) setupMirrorConsumer() error { - if mset.closed.Load() { - return errStreamClosed - } - if mset.outq == nil { - return errors.New("outq required") - } - // We use to prevent update of a mirror configuration in cluster - // mode but not in standalone. This is now fixed. However, without - // rejecting the update, it could be that if the source stream was - // removed and then later the mirrored stream config changed to - // remove mirror configuration, this function would panic when - // accessing mset.cfg.Mirror fields. Adding this protection in case - // we allow in the future the mirror config to be changed (removed). - if mset.cfg.Mirror == nil { - return errors.New("invalid mirror configuration") - } - - // If this is the first time - if mset.mirror == nil { - mset.mirror = &sourceInfo{name: mset.cfg.Mirror.Name} - } else { - mset.cancelSourceInfo(mset.mirror) - mset.mirror.sseq = mset.lseq - } - - // If we are no longer the leader stop trying. - if !mset.isLeader() { - return nil - } - - mirror := mset.mirror - - // We want to throttle here in terms of how fast we request new consumers, - // or if the previous is still in progress. - if last := time.Since(mirror.lreq); last < sourceConsumerRetryThreshold || mirror.sip { - mset.scheduleSetupMirrorConsumerRetry() - return nil - } - mirror.lreq = time.Now() - - // Determine subjects etc. - var deliverSubject string - ext := mset.cfg.Mirror.External - - if ext != nil && ext.DeliverPrefix != _EMPTY_ { - deliverSubject = strings.ReplaceAll(ext.DeliverPrefix+syncSubject(".M"), "..", ".") - } else { - deliverSubject = syncSubject("$JS.M") - } - - // Now send off request to create/update our consumer. This will be all API based even in single server mode. - // We calculate durable names apriori so we do not need to save them off. - - var state StreamState - mset.store.FastState(&state) - - req := &CreateConsumerRequest{ - Stream: mset.cfg.Mirror.Name, - Config: ConsumerConfig{ - DeliverSubject: deliverSubject, - DeliverPolicy: DeliverByStartSequence, - OptStartSeq: state.LastSeq + 1, - AckPolicy: AckNone, - AckWait: 22 * time.Hour, - MaxDeliver: 1, - Heartbeat: sourceHealthHB, - FlowControl: true, - Direct: true, - InactiveThreshold: sourceHealthCheckInterval, - }, - } - - // Only use start optionals on first time. - if state.Msgs == 0 && state.FirstSeq == 0 { - req.Config.OptStartSeq = 0 - if mset.cfg.Mirror.OptStartSeq > 0 { - req.Config.OptStartSeq = mset.cfg.Mirror.OptStartSeq - } else if mset.cfg.Mirror.OptStartTime != nil { - req.Config.OptStartTime = mset.cfg.Mirror.OptStartTime - req.Config.DeliverPolicy = DeliverByStartTime - } - } - if req.Config.OptStartSeq == 0 && req.Config.OptStartTime == nil { - // If starting out and lastSeq is 0. - req.Config.DeliverPolicy = DeliverAll - } - - // Filters - if mset.cfg.Mirror.FilterSubject != _EMPTY_ { - req.Config.FilterSubject = mset.cfg.Mirror.FilterSubject - mirror.sf = mset.cfg.Mirror.FilterSubject - } - - if lst := len(mset.cfg.Mirror.SubjectTransforms); lst > 0 { - sfs := make([]string, lst) - trs := make([]*subjectTransform, lst) - - for i, tr := range mset.cfg.Mirror.SubjectTransforms { - // will not fail as already checked before that the transform will work - subjectTransform, err := NewSubjectTransform(tr.Source, tr.Destination) - if err != nil { - mset.srv.Errorf("Unable to get transform for mirror consumer: %v", err) - } - sfs[i] = tr.Source - trs[i] = subjectTransform - } - mirror.sfs = sfs - mirror.trs = trs - req.Config.FilterSubjects = sfs - } - - respCh := make(chan *JSApiConsumerCreateResponse, 1) - reply := infoReplySubject() - crSub, err := mset.subscribeInternal(reply, func(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - mset.unsubscribe(sub) - _, msg := c.msgParts(rmsg) - - var ccr JSApiConsumerCreateResponse - if err := json.Unmarshal(msg, &ccr); err != nil { - c.Warnf("JetStream bad mirror consumer create response: %q", msg) - mset.setMirrorErr(ApiErrors[JSInvalidJSONErr]) - return - } - select { - case respCh <- &ccr: - default: - } - }) - if err != nil { - mirror.err = NewJSMirrorConsumerSetupFailedError(err, Unless(err)) - mset.scheduleSetupMirrorConsumerRetry() - return nil - } - - b, _ := json.Marshal(req) - - var subject string - if req.Config.FilterSubject != _EMPTY_ { - req.Config.Name = fmt.Sprintf("mirror-%s", createConsumerName()) - subject = fmt.Sprintf(JSApiConsumerCreateExT, mset.cfg.Mirror.Name, req.Config.Name, req.Config.FilterSubject) - } else { - subject = fmt.Sprintf(JSApiConsumerCreateT, mset.cfg.Mirror.Name) - } - if ext != nil { - subject = strings.Replace(subject, JSApiPrefix, ext.ApiPrefix, 1) - subject = strings.ReplaceAll(subject, "..", ".") - } - - // Reset - mirror.msgs = nil - mirror.err = nil - mirror.sip = true - - // Send the consumer create request - mset.outq.send(newJSPubMsg(subject, _EMPTY_, reply, nil, b, nil, 0)) - - go func() { - - var retry bool - defer func() { - mset.mu.Lock() - // Check that this is still valid and if so, clear the "setup in progress" flag. - if mset.mirror != nil { - mset.mirror.sip = false - // If we need to retry, schedule now - if retry { - mset.mirror.fails++ - // Cancel here since we can not do anything with this consumer at this point. - mset.cancelSourceInfo(mset.mirror) - mset.scheduleSetupMirrorConsumerRetry() - } else { - // Clear on success. - mset.mirror.fails = 0 - } - } - mset.mu.Unlock() - }() - - // Wait for previous processMirrorMsgs go routine to be completely done. - // If none is running, this will not block. - mirror.wg.Wait() - - select { - case ccr := <-respCh: - mset.mu.Lock() - // Mirror config has been removed. - if mset.mirror == nil { - mset.mu.Unlock() - return - } - ready := sync.WaitGroup{} - mirror := mset.mirror - mirror.err = nil - if ccr.Error != nil || ccr.ConsumerInfo == nil { - mset.srv.Warnf("JetStream error response for create mirror consumer: %+v", ccr.Error) - mirror.err = ccr.Error - // Let's retry as soon as possible, but we are gated by sourceConsumerRetryThreshold - retry = true - mset.mu.Unlock() - return - } else { - // Setup actual subscription to process messages from our source. - qname := fmt.Sprintf("[ACC:%s] stream mirror '%s' of '%s' msgs", mset.acc.Name, mset.cfg.Name, mset.cfg.Mirror.Name) - // Create a new queue each time - mirror.msgs = newIPQueue[*inMsg](mset.srv, qname) - msgs := mirror.msgs - sub, err := mset.subscribeInternal(deliverSubject, func(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - hdr, msg := c.msgParts(copyBytes(rmsg)) // Need to copy. - mset.queueInbound(msgs, subject, reply, hdr, msg, nil, nil) - mirror.last.Store(time.Now().UnixNano()) - }) - if err != nil { - mirror.err = NewJSMirrorConsumerSetupFailedError(err, Unless(err)) - retry = true - mset.mu.Unlock() - return - } - // Save our sub. - mirror.sub = sub - - // When an upstream stream expires messages or in general has messages that we want - // that are no longer available we need to adjust here. - var state StreamState - mset.store.FastState(&state) - - // Check if we need to skip messages. - if state.LastSeq != ccr.ConsumerInfo.Delivered.Stream { - // Check to see if delivered is past our last and we have no msgs. This will help the - // case when mirroring a stream that has a very high starting sequence number. - if state.Msgs == 0 && ccr.ConsumerInfo.Delivered.Stream > state.LastSeq { - mset.store.PurgeEx(_EMPTY_, ccr.ConsumerInfo.Delivered.Stream+1, 0, true) - mset.lseq = ccr.ConsumerInfo.Delivered.Stream - } else { - mset.skipMsgs(state.LastSeq+1, ccr.ConsumerInfo.Delivered.Stream) - } - } - - // Capture consumer name. - mirror.cname = ccr.ConsumerInfo.Name - mirror.dseq = 0 - mirror.sseq = ccr.ConsumerInfo.Delivered.Stream - mirror.qch = make(chan struct{}) - mirror.wg.Add(1) - ready.Add(1) - if !mset.srv.startGoRoutine( - func() { mset.processMirrorMsgs(mirror, &ready) }, - pprofLabels{ - "type": "mirror", - "account": mset.acc.Name, - "stream": mset.cfg.Name, - "consumer": mirror.cname, - }, - ) { - ready.Done() - } - } - mset.mu.Unlock() - ready.Wait() - case <-time.After(srcConsumerWaitTime): - mset.unsubscribe(crSub) - // We already waited 30 seconds, let's retry now. - retry = true - } - }() - - return nil -} - -func (mset *stream) streamSource(iname string) *StreamSource { - for _, ssi := range mset.cfg.Sources { - if ssi.iname == iname { - return ssi - } - } - return nil -} - -// Lock should be held. -func (mset *stream) retrySourceConsumerAtSeq(iName string, seq uint64) { - s := mset.srv - - s.Debugf("Retrying source consumer for '%s > %s'", mset.acc.Name, mset.cfg.Name) - - // setupSourceConsumer will check that the source is still configured. - mset.setupSourceConsumer(iName, seq, time.Time{}) -} - -// Lock should be held. -func (mset *stream) cancelSourceConsumer(iname string) { - if si := mset.sources[iname]; si != nil { - mset.cancelSourceInfo(si) - si.sseq, si.dseq = 0, 0 - } -} - -// The `si` has been verified to be not nil. The sourceInfo's sub will -// be unsubscribed and set to nil (if not already done) and the -// cname will be reset. The message processing's go routine quit channel -// will be closed if still opened. -// -// Lock should be held -func (mset *stream) cancelSourceInfo(si *sourceInfo) { - if si.sub != nil { - mset.unsubscribe(si.sub) - si.sub = nil - } - // In case we had a mirror direct subscription. - if si.dsub != nil { - mset.unsubscribe(si.dsub) - si.dsub = nil - } - mset.removeInternalConsumer(si) - if si.qch != nil { - close(si.qch) - si.qch = nil - } - if si.msgs != nil { - si.msgs.drain() - si.msgs.unregister() - } - // If we have a schedule setup go ahead and delete that. - if t := mset.sourceSetupSchedules[si.iname]; t != nil { - t.Stop() - delete(mset.sourceSetupSchedules, si.iname) - } -} - -const sourceConsumerRetryThreshold = 2 * time.Second - -// This is the main function to call when needing to setup a new consumer for the source. -// It actually only does the scheduling of the execution of trySetupSourceConsumer in order to implement retry backoff -// and throttle the number of requests. -// Lock should be held. -func (mset *stream) setupSourceConsumer(iname string, seq uint64, startTime time.Time) { - if mset.sourceSetupSchedules == nil { - mset.sourceSetupSchedules = map[string]*time.Timer{} - } - - if _, ok := mset.sourceSetupSchedules[iname]; ok { - // If there is already a timer scheduled, we don't need to do anything. - return - } - - si := mset.sources[iname] - if si == nil || si.sip { // if sourceInfo was removed or setup is in progress, nothing to do - return - } - - // First calculate the delay until the next time we can - var scheduleDelay time.Duration - - if !si.lreq.IsZero() { // it's not the very first time we are called, compute the delay - // We want to throttle here in terms of how fast we request new consumers - if sinceLast := time.Since(si.lreq); sinceLast < sourceConsumerRetryThreshold { - scheduleDelay = sourceConsumerRetryThreshold - sinceLast - } - // Is it a retry? If so, add a backoff - if si.fails > 0 { - scheduleDelay += calculateRetryBackoff(si.fails) - } - } - - // Always add some jitter - scheduleDelay += time.Duration(rand.Intn(int(100*time.Millisecond))) + 100*time.Millisecond - - // Schedule the call to trySetupSourceConsumer - mset.sourceSetupSchedules[iname] = time.AfterFunc(scheduleDelay, func() { - mset.mu.Lock() - defer mset.mu.Unlock() - - delete(mset.sourceSetupSchedules, iname) - mset.trySetupSourceConsumer(iname, seq, startTime) - }) -} - -// This is where we will actually try to create a new consumer for the source -// Lock should be held. -func (mset *stream) trySetupSourceConsumer(iname string, seq uint64, startTime time.Time) { - // Ignore if closed or not leader. - if mset.closed.Load() || !mset.isLeader() { - return - } - - si := mset.sources[iname] - if si == nil { - return - } - - // Cancel previous instance if applicable - mset.cancelSourceInfo(si) - - ssi := mset.streamSource(iname) - if ssi == nil { - return - } - - si.lreq = time.Now() - - // Determine subjects etc. - var deliverSubject string - ext := ssi.External - - if ext != nil && ext.DeliverPrefix != _EMPTY_ { - deliverSubject = strings.ReplaceAll(ext.DeliverPrefix+syncSubject(".S"), "..", ".") - } else { - deliverSubject = syncSubject("$JS.S") - } - - req := &CreateConsumerRequest{ - Stream: si.name, - Config: ConsumerConfig{ - DeliverSubject: deliverSubject, - AckPolicy: AckNone, - AckWait: 22 * time.Hour, - MaxDeliver: 1, - Heartbeat: sourceHealthHB, - FlowControl: true, - Direct: true, - InactiveThreshold: sourceHealthCheckInterval, - }, - } - - // If starting, check any configs. - if !startTime.IsZero() && seq > 1 { - req.Config.OptStartTime = &startTime - req.Config.DeliverPolicy = DeliverByStartTime - } else if seq <= 1 { - if ssi.OptStartSeq > 0 { - req.Config.OptStartSeq = ssi.OptStartSeq - req.Config.DeliverPolicy = DeliverByStartSequence - } else { - // We have not recovered state so check that configured time is less that our first seq time. - var state StreamState - mset.store.FastState(&state) - if ssi.OptStartTime != nil { - if !state.LastTime.IsZero() && ssi.OptStartTime.Before(state.LastTime) { - req.Config.OptStartTime = &state.LastTime - } else { - req.Config.OptStartTime = ssi.OptStartTime - } - req.Config.DeliverPolicy = DeliverByStartTime - } else if state.FirstSeq > 1 && !state.LastTime.IsZero() { - req.Config.OptStartTime = &state.LastTime - req.Config.DeliverPolicy = DeliverByStartTime - } - } - - } else { - req.Config.OptStartSeq = seq - req.Config.DeliverPolicy = DeliverByStartSequence - } - // Filters - if ssi.FilterSubject != _EMPTY_ { - req.Config.FilterSubject = ssi.FilterSubject - } - - var filterSubjects []string - for _, tr := range ssi.SubjectTransforms { - filterSubjects = append(filterSubjects, tr.Source) - } - req.Config.FilterSubjects = filterSubjects - - respCh := make(chan *JSApiConsumerCreateResponse, 1) - reply := infoReplySubject() - crSub, err := mset.subscribeInternal(reply, func(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - mset.unsubscribe(sub) - _, msg := c.msgParts(rmsg) - var ccr JSApiConsumerCreateResponse - if err := json.Unmarshal(msg, &ccr); err != nil { - c.Warnf("JetStream bad source consumer create response: %q", msg) - return - } - select { - case respCh <- &ccr: - default: - } - }) - if err != nil { - si.err = NewJSSourceConsumerSetupFailedError(err, Unless(err)) - mset.setupSourceConsumer(iname, seq, startTime) - return - } - - var subject string - if req.Config.FilterSubject != _EMPTY_ { - req.Config.Name = fmt.Sprintf("src-%s", createConsumerName()) - subject = fmt.Sprintf(JSApiConsumerCreateExT, si.name, req.Config.Name, req.Config.FilterSubject) - } else if len(req.Config.FilterSubjects) == 1 { - req.Config.Name = fmt.Sprintf("src-%s", createConsumerName()) - // It is necessary to switch to using FilterSubject here as the extended consumer - // create API checks for it, so as to not accidentally allow multiple filtered subjects. - req.Config.FilterSubject = req.Config.FilterSubjects[0] - req.Config.FilterSubjects = nil - subject = fmt.Sprintf(JSApiConsumerCreateExT, si.name, req.Config.Name, req.Config.FilterSubject) - } else { - subject = fmt.Sprintf(JSApiConsumerCreateT, si.name) - } - if ext != nil { - subject = strings.Replace(subject, JSApiPrefix, ext.ApiPrefix, 1) - subject = strings.ReplaceAll(subject, "..", ".") - } - - // Marshal request. - b, _ := json.Marshal(req) - - // Reset - si.msgs = nil - si.err = nil - si.sip = true - - // Send the consumer create request - mset.outq.send(newJSPubMsg(subject, _EMPTY_, reply, nil, b, nil, 0)) - - go func() { - - var retry bool - defer func() { - mset.mu.Lock() - // Check that this is still valid and if so, clear the "setup in progress" flag. - if si := mset.sources[iname]; si != nil { - si.sip = false - // If we need to retry, schedule now - if retry { - si.fails++ - // Cancel here since we can not do anything with this consumer at this point. - mset.cancelSourceInfo(si) - mset.setupSourceConsumer(iname, seq, startTime) - } else { - // Clear on success. - si.fails = 0 - } - } - mset.mu.Unlock() - }() - - select { - case ccr := <-respCh: - mset.mu.Lock() - // Check that it has not been removed or canceled (si.sub would be nil) - if si := mset.sources[iname]; si != nil { - si.err = nil - if ccr.Error != nil || ccr.ConsumerInfo == nil { - // Note: this warning can happen a few times when starting up the server when sourcing streams are - // defined, this is normal as the streams are re-created in no particular order and it is possible - // that a stream sourcing another could come up before all of its sources have been recreated. - mset.srv.Warnf("JetStream error response for stream %s create source consumer %s: %+v", mset.cfg.Name, si.name, ccr.Error) - si.err = ccr.Error - // Let's retry as soon as possible, but we are gated by sourceConsumerRetryThreshold - retry = true - mset.mu.Unlock() - return - } else { - // Check if our shared msg queue and go routine is running or not. - if mset.smsgs == nil { - qname := fmt.Sprintf("[ACC:%s] stream sources '%s' msgs", mset.acc.Name, mset.cfg.Name) - mset.smsgs = newIPQueue[*inMsg](mset.srv, qname) - mset.srv.startGoRoutine(func() { mset.processAllSourceMsgs() }, - pprofLabels{ - "type": "source", - "account": mset.acc.Name, - "stream": mset.cfg.Name, - }, - ) - } - - // Setup actual subscription to process messages from our source. - if si.sseq != ccr.ConsumerInfo.Delivered.Stream { - si.sseq = ccr.ConsumerInfo.Delivered.Stream + 1 - } - // Capture consumer name. - si.cname = ccr.ConsumerInfo.Name - - // Do not set si.sseq to seq here. si.sseq will be set in processInboundSourceMsg - si.dseq = 0 - si.qch = make(chan struct{}) - // Set the last seen as now so that we don't fail at the first check. - si.last.Store(time.Now().UnixNano()) - - msgs := mset.smsgs - sub, err := mset.subscribeInternal(deliverSubject, func(sub *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - hdr, msg := c.msgParts(copyBytes(rmsg)) // Need to copy. - mset.queueInbound(msgs, subject, reply, hdr, msg, si, nil) - si.last.Store(time.Now().UnixNano()) - }) - if err != nil { - si.err = NewJSSourceConsumerSetupFailedError(err, Unless(err)) - retry = true - mset.mu.Unlock() - return - } - // Save our sub. - si.sub = sub - } - } - mset.mu.Unlock() - case <-time.After(srcConsumerWaitTime): - mset.unsubscribe(crSub) - // We already waited 30 seconds, let's retry now. - retry = true - } - }() -} - -// This will process all inbound source msgs. -// We mux them into one go routine to avoid lock contention and high cpu and thread thrashing. -// TODO(dlc) make this more then one and pin sources to one of a group. -func (mset *stream) processAllSourceMsgs() { - s := mset.srv - defer s.grWG.Done() - - mset.mu.RLock() - msgs, qch := mset.smsgs, mset.qch - mset.mu.RUnlock() - - t := time.NewTicker(sourceHealthCheckInterval) - defer t.Stop() - - // When we detect we are no longer leader, we will cleanup. - // Should always return right after this is called. - cleanUp := func() { - mset.mu.Lock() - defer mset.mu.Unlock() - for _, si := range mset.sources { - mset.cancelSourceConsumer(si.iname) - } - mset.smsgs.drain() - mset.smsgs.unregister() - mset.smsgs = nil - } - - for { - select { - case <-s.quitCh: - return - case <-qch: - return - case <-msgs.ch: - ims := msgs.pop() - for _, im := range ims { - if !mset.processInboundSourceMsg(im.si, im) { - // If we are no longer leader bail. - if !mset.IsLeader() { - msgs.recycle(&ims) - cleanUp() - return - } - break - } - im.returnToPool() - } - msgs.recycle(&ims) - case <-t.C: - // If we are no longer leader bail. - if !mset.IsLeader() { - cleanUp() - return - } - - // Check health of all sources. - var stalled []*sourceInfo - mset.mu.RLock() - for _, si := range mset.sources { - if time.Since(time.Unix(0, si.last.Load())) > sourceHealthCheckInterval { - stalled = append(stalled, si) - } - } - numSources := len(mset.sources) - mset.mu.RUnlock() - - // This can happen on an update when no longer have sources. - if numSources == 0 { - cleanUp() - return - } - - // We do not want to block here so do in separate Go routine. - if len(stalled) > 0 { - go func() { - mset.mu.Lock() - defer mset.mu.Unlock() - for _, si := range stalled { - mset.setupSourceConsumer(si.iname, si.sseq+1, time.Time{}) - si.last.Store(time.Now().UnixNano()) - } - }() - } - } - } -} - -// isControlMsg determines if this is a control message. -func (m *inMsg) isControlMsg() bool { - return len(m.msg) == 0 && len(m.hdr) > 0 && bytes.HasPrefix(m.hdr, []byte("NATS/1.0 100 ")) -} - -// Sends a reply to a flow control request. -func (mset *stream) sendFlowControlReply(reply string) { - mset.mu.RLock() - if mset.isLeader() && mset.outq != nil { - mset.outq.sendMsg(reply, nil) - } - mset.mu.RUnlock() -} - -// handleFlowControl will properly handle flow control messages for both R==1 and R>1. -// Lock should be held. -func (mset *stream) handleFlowControl(m *inMsg) { - // If we are clustered we will send the flow control message through the replication stack. - if mset.isClustered() { - mset.node.Propose(encodeStreamMsg(_EMPTY_, m.rply, m.hdr, nil, 0, 0, false)) - } else { - mset.outq.sendMsg(m.rply, nil) - } -} - -// processInboundSourceMsg handles processing other stream messages bound for this stream. -func (mset *stream) processInboundSourceMsg(si *sourceInfo, m *inMsg) bool { - mset.mu.Lock() - // If we are no longer the leader cancel this subscriber. - if !mset.isLeader() { - mset.cancelSourceConsumer(si.iname) - mset.mu.Unlock() - return false - } - - isControl := m.isControlMsg() - - // Ignore from old subscriptions. - if !si.isCurrentSub(m.rply) && !isControl { - mset.mu.Unlock() - return false - } - - // Check for heartbeats and flow control messages. - if isControl { - var needsRetry bool - // Flow controls have reply subjects. - if m.rply != _EMPTY_ { - mset.handleFlowControl(m) - } else { - // For idle heartbeats make sure we did not miss anything. - if ldseq := parseInt64(getHeader(JSLastConsumerSeq, m.hdr)); ldseq > 0 && uint64(ldseq) != si.dseq { - needsRetry = true - mset.retrySourceConsumerAtSeq(si.iname, si.sseq+1) - } else if fcReply := getHeader(JSConsumerStalled, m.hdr); len(fcReply) > 0 { - // Other side thinks we are stalled, so send flow control reply. - mset.outq.sendMsg(string(fcReply), nil) - } - } - mset.mu.Unlock() - return !needsRetry - } - - sseq, dseq, dc, _, pending := replyInfo(m.rply) - - if dc > 1 { - mset.mu.Unlock() - return false - } - - // Tracking is done here. - if dseq == si.dseq+1 { - si.dseq++ - si.sseq = sseq - } else if dseq > si.dseq { - if si.cname == _EMPTY_ { - si.cname = tokenAt(m.rply, 4) - si.dseq, si.sseq = dseq, sseq - } else { - mset.retrySourceConsumerAtSeq(si.iname, si.sseq+1) - mset.mu.Unlock() - return false - } - } else { - mset.mu.Unlock() - return false - } - - if pending == 0 { - si.lag = 0 - } else { - si.lag = pending - 1 - } - node := mset.node - mset.mu.Unlock() - - hdr, msg := m.hdr, m.msg - - // If we are daisy chained here make sure to remove the original one. - if len(hdr) > 0 { - hdr = removeHeaderIfPresent(hdr, JSStreamSource) - // Remove any Nats-Expected- headers as we don't want to validate them. - hdr = removeHeaderIfPrefixPresent(hdr, "Nats-Expected-") - } - // Hold onto the origin reply which has all the metadata. - hdr = genHeader(hdr, JSStreamSource, si.genSourceHeader(m.rply)) - - // Do the subject transform for the source if there's one - if len(si.trs) > 0 { - for _, tr := range si.trs { - if tr == nil { - continue - } else { - tsubj, err := tr.Match(m.subj) - if err == nil { - m.subj = tsubj - break - } - } - } - } - - var err error - // If we are clustered we need to propose this message to the underlying raft group. - if node != nil { - err = mset.processClusteredInboundMsg(m.subj, _EMPTY_, hdr, msg, nil, true) - } else { - err = mset.processJetStreamMsg(m.subj, _EMPTY_, hdr, msg, 0, 0, nil, true) - } - - if err != nil { - s := mset.srv - if strings.Contains(err.Error(), "no space left") { - s.Errorf("JetStream out of space, will be DISABLED") - s.DisableJetStream() - } else { - mset.mu.RLock() - accName, sname, iName := mset.acc.Name, mset.cfg.Name, si.iname - mset.mu.RUnlock() - - // Can happen temporarily all the time during normal operations when the sourcing stream - // is working queue/interest with a limit and discard new. - // TODO - Improve sourcing to WQ with limit and new to use flow control rather than re-creating the consumer. - if errors.Is(err, ErrMaxMsgs) || errors.Is(err, ErrMaxBytes) { - // Do not need to do a full retry that includes finding the last sequence in the stream - // for that source. Just re-create starting with the seq we couldn't store instead. - mset.mu.Lock() - mset.retrySourceConsumerAtSeq(iName, si.sseq) - mset.mu.Unlock() - } else { - // Log some warning for errors other than errLastSeqMismatch or errMaxMsgs. - if !errors.Is(err, errLastSeqMismatch) { - s.RateLimitWarnf("Error processing inbound source %q for '%s' > '%s': %v", - iName, accName, sname, err) - } - // Retry in all type of errors if we are still leader. - if mset.isLeader() { - // This will make sure the source is still in mset.sources map, - // find the last sequence and then call setupSourceConsumer. - iNameMap := map[string]struct{}{iName: {}} - mset.setStartingSequenceForSources(iNameMap) - mset.mu.Lock() - mset.retrySourceConsumerAtSeq(iName, si.sseq+1) - mset.mu.Unlock() - } - } - } - return false - } - - return true -} - -// Generate a new (2.10) style source header (stream name, sequence number, source filter, source destination transform). -func (si *sourceInfo) genSourceHeader(reply string) string { - var b strings.Builder - iNameParts := strings.Split(si.iname, " ") - - b.WriteString(iNameParts[0]) - b.WriteByte(' ') - // Grab sequence as text here from reply subject. - var tsa [expectedNumReplyTokens]string - start, tokens := 0, tsa[:0] - for i := 0; i < len(reply); i++ { - if reply[i] == btsep { - tokens, start = append(tokens, reply[start:i]), i+1 - } - } - tokens = append(tokens, reply[start:]) - seq := "1" // Default - if len(tokens) == expectedNumReplyTokens && tokens[0] == "$JS" && tokens[1] == "ACK" { - seq = tokens[5] - } - b.WriteString(seq) - - b.WriteByte(' ') - b.WriteString(iNameParts[1]) - b.WriteByte(' ') - b.WriteString(iNameParts[2]) - return b.String() -} - -// Original version of header that stored ack reply direct. -func streamAndSeqFromAckReply(reply string) (string, string, uint64) { - tsa := [expectedNumReplyTokens]string{} - start, tokens := 0, tsa[:0] - for i := 0; i < len(reply); i++ { - if reply[i] == btsep { - tokens, start = append(tokens, reply[start:i]), i+1 - } - } - tokens = append(tokens, reply[start:]) - if len(tokens) != expectedNumReplyTokens || tokens[0] != "$JS" || tokens[1] != "ACK" { - return _EMPTY_, _EMPTY_, 0 - } - return tokens[2], _EMPTY_, uint64(parseAckReplyNum(tokens[5])) -} - -// Extract the stream name, the source index name and the message sequence number from the source header. -// Uses the filter and transform arguments to provide backwards compatibility -func streamAndSeq(shdr string) (string, string, uint64) { - if strings.HasPrefix(shdr, jsAckPre) { - return streamAndSeqFromAckReply(shdr) - } - // New version which is stream index name sequence - fields := strings.Split(shdr, " ") - nFields := len(fields) - - if nFields != 2 && nFields <= 3 { - return _EMPTY_, _EMPTY_, 0 - } - - if nFields >= 4 { - return fields[0], strings.Join([]string{fields[0], fields[2], fields[3]}, " "), uint64(parseAckReplyNum(fields[1])) - } else { - return fields[0], _EMPTY_, uint64(parseAckReplyNum(fields[1])) - } - -} - -// Lock should be held. -func (mset *stream) setStartingSequenceForSources(iNames map[string]struct{}) { - var state StreamState - mset.store.FastState(&state) - - // Do not reset sseq here so we can remember when purge/expiration happens. - if state.Msgs == 0 { - for iName := range iNames { - si := mset.sources[iName] - if si == nil { - continue - } else { - si.dseq = 0 - } - } - return - } - - var smv StoreMsg - for seq := state.LastSeq; seq >= state.FirstSeq; { - sm, err := mset.store.LoadPrevMsg(seq, &smv) - if err == ErrStoreEOF || err != nil { - break - } - seq = sm.seq - 1 - if len(sm.hdr) == 0 { - continue - } - - ss := getHeader(JSStreamSource, sm.hdr) - if len(ss) == 0 { - continue - } - streamName, indexName, sseq := streamAndSeq(bytesToString(ss)) - - if _, ok := iNames[indexName]; ok { - si := mset.sources[indexName] - si.sseq = sseq - si.dseq = 0 - delete(iNames, indexName) - } else if indexName == _EMPTY_ && streamName != _EMPTY_ { - for iName := range iNames { - // TODO streamSource is a linear walk, to optimize later - if si := mset.sources[iName]; si != nil && streamName == si.name || - (mset.streamSource(iName).External != nil && streamName == si.name+":"+getHash(mset.streamSource(iName).External.ApiPrefix)) { - si.sseq = sseq - si.dseq = 0 - delete(iNames, iName) - break - } - } - } - if len(iNames) == 0 { - break - } - } -} - -// Resets the SourceInfo for all the sources -// lock should be held. -func (mset *stream) resetSourceInfo() { - // Reset if needed. - mset.stopSourceConsumers() - mset.sources = make(map[string]*sourceInfo) - - for _, ssi := range mset.cfg.Sources { - if ssi.iname == _EMPTY_ { - ssi.setIndexName() - } - - var si *sourceInfo - - if len(ssi.SubjectTransforms) == 0 { - si = &sourceInfo{name: ssi.Name, iname: ssi.iname, sf: ssi.FilterSubject} - } else { - sfs := make([]string, len(ssi.SubjectTransforms)) - trs := make([]*subjectTransform, len(ssi.SubjectTransforms)) - for i, str := range ssi.SubjectTransforms { - tr, err := NewSubjectTransform(str.Source, str.Destination) - if err != nil { - mset.srv.Errorf("Unable to get subject transform for source: %v", err) - } - sfs[i] = str.Source - trs[i] = tr - } - si = &sourceInfo{name: ssi.Name, iname: ssi.iname, sfs: sfs, trs: trs} - } - mset.sources[ssi.iname] = si - } -} - -// This will do a reverse scan on startup or leader election -// searching for the starting sequence number. -// This can be slow in degenerative cases. -// Lock should be held. -func (mset *stream) startingSequenceForSources() { - if len(mset.cfg.Sources) == 0 { - return - } - - // Always reset here. - mset.resetSourceInfo() - - var state StreamState - mset.store.FastState(&state) - - // Bail if no messages, meaning no context. - if state.Msgs == 0 { - return - } - - // For short circuiting return. - expected := len(mset.cfg.Sources) - seqs := make(map[string]uint64) - - // Stamp our si seq records on the way out. - defer func() { - for sname, seq := range seqs { - // Ignore if not set. - if seq == 0 { - continue - } - if si := mset.sources[sname]; si != nil { - si.sseq = seq - si.dseq = 0 - } - } - }() - - update := func(iName string, seq uint64) { - // Only update active in case we have older ones in here that got configured out. - if si := mset.sources[iName]; si != nil { - if _, ok := seqs[iName]; !ok { - seqs[iName] = seq - } - } - } - - var smv StoreMsg - for seq := state.LastSeq; ; { - sm, err := mset.store.LoadPrevMsg(seq, &smv) - if err == ErrStoreEOF || err != nil { - break - } - seq = sm.seq - 1 - if len(sm.hdr) == 0 { - continue - } - ss := getHeader(JSStreamSource, sm.hdr) - if len(ss) == 0 { - continue - } - - streamName, iName, sseq := streamAndSeq(bytesToString(ss)) - if iName == _EMPTY_ { // Pre-2.10 message header means it's a match for any source using that stream name - for _, ssi := range mset.cfg.Sources { - if streamName == ssi.Name || (ssi.External != nil && streamName == ssi.Name+":"+getHash(ssi.External.ApiPrefix)) { - update(ssi.iname, sseq) - } - } - } else { - update(iName, sseq) - } - if len(seqs) == expected { - return - } - } -} - -// Setup our source consumers. -// Lock should be held. -func (mset *stream) setupSourceConsumers() error { - if mset.outq == nil { - return errors.New("outq required") - } - // Reset if needed. - for _, si := range mset.sources { - if si.sub != nil { - mset.cancelSourceConsumer(si.iname) - } - } - - // If we are no longer the leader, give up - if !mset.isLeader() { - return nil - } - - mset.startingSequenceForSources() - - // Setup our consumers at the proper starting position. - for _, ssi := range mset.cfg.Sources { - if si := mset.sources[ssi.iname]; si != nil { - mset.setupSourceConsumer(ssi.iname, si.sseq+1, time.Time{}) - } - } - - return nil -} - -// Will create internal subscriptions for the stream. -// Lock should be held. -func (mset *stream) subscribeToStream() error { - if mset.active { - return nil - } - for _, subject := range mset.cfg.Subjects { - if _, err := mset.subscribeInternal(subject, mset.processInboundJetStreamMsg); err != nil { - return err - } - } - // Check if we need to setup mirroring. - if mset.cfg.Mirror != nil { - // setup the initial mirror sourceInfo - mset.mirror = &sourceInfo{name: mset.cfg.Mirror.Name} - sfs := make([]string, len(mset.cfg.Mirror.SubjectTransforms)) - trs := make([]*subjectTransform, len(mset.cfg.Mirror.SubjectTransforms)) - - for i, tr := range mset.cfg.Mirror.SubjectTransforms { - // will not fail as already checked before that the transform will work - subjectTransform, err := NewSubjectTransform(tr.Source, tr.Destination) - if err != nil { - mset.srv.Errorf("Unable to get transform for mirror consumer: %v", err) - } - - sfs[i] = tr.Source - trs[i] = subjectTransform - } - mset.mirror.sfs = sfs - mset.mirror.trs = trs - // delay the actual mirror consumer creation for after a delay - mset.scheduleSetupMirrorConsumerRetry() - } else if len(mset.cfg.Sources) > 0 && mset.sourcesConsumerSetup == nil { - // Setup the initial source infos for the sources - mset.resetSourceInfo() - // Delay the actual source consumer(s) creation(s) for after a delay if a replicated stream. - // If it's an R1, this is done at startup and we will do inline. - if mset.cfg.Replicas == 1 { - mset.setupSourceConsumers() - } else { - mset.sourcesConsumerSetup = time.AfterFunc(time.Duration(rand.Intn(int(500*time.Millisecond)))+100*time.Millisecond, func() { - mset.mu.Lock() - mset.setupSourceConsumers() - mset.mu.Unlock() - }) - } - } - // Check for direct get access. - // We spin up followers for clustered streams in monitorStream(). - if mset.cfg.AllowDirect { - if err := mset.subscribeToDirect(); err != nil { - return err - } - } - - mset.active = true - return nil -} - -// Lock should be held. -func (mset *stream) subscribeToDirect() error { - // We will make this listen on a queue group by default, which can allow mirrors to participate on opt-in basis. - if mset.directSub == nil { - dsubj := fmt.Sprintf(JSDirectMsgGetT, mset.cfg.Name) - if sub, err := mset.queueSubscribeInternal(dsubj, dgetGroup, mset.processDirectGetRequest); err == nil { - mset.directSub = sub - } else { - return err - } - } - // Now the one that will have subject appended past stream name. - if mset.lastBySub == nil { - dsubj := fmt.Sprintf(JSDirectGetLastBySubjectT, mset.cfg.Name, fwcs) - // We will make this listen on a queue group by default, which can allow mirrors to participate on opt-in basis. - if sub, err := mset.queueSubscribeInternal(dsubj, dgetGroup, mset.processDirectGetLastBySubjectRequest); err == nil { - mset.lastBySub = sub - } else { - return err - } - } - - return nil -} - -// Lock should be held. -func (mset *stream) unsubscribeToDirect() { - if mset.directSub != nil { - mset.unsubscribe(mset.directSub) - mset.directSub = nil - } - if mset.lastBySub != nil { - mset.unsubscribe(mset.lastBySub) - mset.lastBySub = nil - } -} - -// Lock should be held. -func (mset *stream) subscribeToMirrorDirect() error { - if mset.mirror == nil { - return nil - } - - // We will make this listen on a queue group by default, which can allow mirrors to participate on opt-in basis. - if mset.mirror.dsub == nil { - dsubj := fmt.Sprintf(JSDirectMsgGetT, mset.mirror.name) - // We will make this listen on a queue group by default, which can allow mirrors to participate on opt-in basis. - if sub, err := mset.queueSubscribeInternal(dsubj, dgetGroup, mset.processDirectGetRequest); err == nil { - mset.mirror.dsub = sub - } else { - return err - } - } - // Now the one that will have subject appended past stream name. - if mset.mirror.lbsub == nil { - dsubj := fmt.Sprintf(JSDirectGetLastBySubjectT, mset.mirror.name, fwcs) - // We will make this listen on a queue group by default, which can allow mirrors to participate on opt-in basis. - if sub, err := mset.queueSubscribeInternal(dsubj, dgetGroup, mset.processDirectGetLastBySubjectRequest); err == nil { - mset.mirror.lbsub = sub - } else { - return err - } - } - - return nil -} - -// Stop our source consumers. -// Lock should be held. -func (mset *stream) stopSourceConsumers() { - for _, si := range mset.sources { - mset.cancelSourceInfo(si) - } -} - -// Lock should be held. -func (mset *stream) removeInternalConsumer(si *sourceInfo) { - if si == nil || si.cname == _EMPTY_ { - return - } - si.cname = _EMPTY_ -} - -// Will unsubscribe from the stream. -// Lock should be held. -func (mset *stream) unsubscribeToStream(stopping bool) error { - for _, subject := range mset.cfg.Subjects { - mset.unsubscribeInternal(subject) - } - if mset.mirror != nil { - mset.cancelSourceInfo(mset.mirror) - mset.mirror = nil - } - - if len(mset.sources) > 0 { - mset.stopSourceConsumers() - } - - // In case we had a direct get subscriptions. - if stopping { - mset.unsubscribeToDirect() - } - - mset.active = false - return nil -} - -// Lock does NOT need to be held, we set the client on setup and never change it at this point. -func (mset *stream) subscribeInternal(subject string, cb msgHandler) (*subscription, error) { - if mset.closed.Load() { - return nil, errStreamClosed - } - if cb == nil { - return nil, errInvalidMsgHandler - } - c := mset.client - sid := int(mset.sid.Add(1)) - // Now create the subscription - return c.processSub([]byte(subject), nil, []byte(strconv.Itoa(sid)), cb, false) -} - -// Lock does NOT need to be held, we set the client on setup and never change it at this point. -func (mset *stream) queueSubscribeInternal(subject, group string, cb msgHandler) (*subscription, error) { - if mset.closed.Load() { - return nil, errStreamClosed - } - if cb == nil { - return nil, errInvalidMsgHandler - } - c := mset.client - sid := int(mset.sid.Add(1)) - // Now create the subscription - return c.processSub([]byte(subject), []byte(group), []byte(strconv.Itoa(sid)), cb, false) -} - -// This will unsubscribe us from the exact subject given. -// We do not currently track the subs so do not have the sid. -// This should be called only on an update. -// Lock does NOT need to be held, we set the client on setup and never change it at this point. -func (mset *stream) unsubscribeInternal(subject string) error { - if mset.closed.Load() { - return errStreamClosed - } - c := mset.client - var sid []byte - c.mu.Lock() - for _, sub := range c.subs { - if subject == string(sub.subject) { - sid = sub.sid - break - } - } - c.mu.Unlock() - - if sid != nil { - return c.processUnsub(sid) - } - return nil -} - -// Lock should be held. -func (mset *stream) unsubscribe(sub *subscription) { - if sub == nil || mset.closed.Load() { - return - } - mset.client.processUnsub(sub.sid) -} - -func (mset *stream) setupStore(fsCfg *FileStoreConfig) error { - mset.mu.Lock() - mset.created = time.Now().UTC() - - switch mset.cfg.Storage { - case MemoryStorage: - ms, err := newMemStore(&mset.cfg) - if err != nil { - mset.mu.Unlock() - return err - } - mset.store = ms - case FileStorage: - s := mset.srv - prf := s.jsKeyGen(s.getOpts().JetStreamKey, mset.acc.Name) - if prf != nil { - // We are encrypted here, fill in correct cipher selection. - fsCfg.Cipher = s.getOpts().JetStreamCipher - } - oldprf := s.jsKeyGen(s.getOpts().JetStreamOldKey, mset.acc.Name) - cfg := *fsCfg - cfg.srv = s - fs, err := newFileStoreWithCreated(cfg, mset.cfg, mset.created, prf, oldprf) - if err != nil { - mset.mu.Unlock() - return err - } - mset.store = fs - } - // This will fire the callback but we do not require the lock since md will be 0 here. - mset.store.RegisterStorageUpdates(mset.storeUpdates) - mset.store.RegisterSubjectDeleteMarkerUpdates(func(im *inMsg) { - if mset.IsClustered() { - if mset.IsLeader() { - mset.processClusteredInboundMsg(im.subj, im.rply, im.hdr, im.msg, im.mt, false) - } - } else { - mset.processJetStreamMsg(im.subj, im.rply, im.hdr, im.msg, 0, 0, im.mt, false) - } - }) - mset.mu.Unlock() - - return nil -} - -// Called for any updates to the underlying stream. We pass through the bytes to the -// jetstream account. We do local processing for stream pending for consumers, but only -// for removals. -// Lock should not be held. -func (mset *stream) storeUpdates(md, bd int64, seq uint64, subj string) { - // If we have a single negative update then we will process our consumers for stream pending. - // Purge and Store handled separately inside individual calls. - if md == -1 && seq > 0 && subj != _EMPTY_ { - // We use our consumer list mutex here instead of the main stream lock since it may be held already. - mset.clsMu.RLock() - if mset.csl != nil { - mset.csl.Match(subj, func(o *consumer) { - o.decStreamPending(seq, subj) - }) - } else { - for _, o := range mset.cList { - o.decStreamPending(seq, subj) - } - } - mset.clsMu.RUnlock() - } else if md < 0 { - // Batch decrements we need to force consumers to re-calculate num pending. - mset.clsMu.RLock() - for _, o := range mset.cList { - o.streamNumPendingLocked() - } - mset.clsMu.RUnlock() - } - - if mset.jsa != nil { - mset.jsa.updateUsage(mset.tier, mset.stype, bd) - } -} - -// NumMsgIds returns the number of message ids being tracked for duplicate suppression. -func (mset *stream) numMsgIds() int { - mset.mu.Lock() - defer mset.mu.Unlock() - if !mset.ddloaded { - mset.rebuildDedupe() - } - return len(mset.ddmap) -} - -// checkMsgId will process and check for duplicates. -// Lock should be held. -func (mset *stream) checkMsgId(id string) *ddentry { - if !mset.ddloaded { - mset.rebuildDedupe() - } - if id == _EMPTY_ || len(mset.ddmap) == 0 { - return nil - } - return mset.ddmap[id] -} - -// Will purge the entries that are past the window. -// Should be called from a timer. -func (mset *stream) purgeMsgIds() { - mset.mu.Lock() - defer mset.mu.Unlock() - - now := time.Now().UnixNano() - tmrNext := mset.cfg.Duplicates - window := int64(tmrNext) - - for i, dde := range mset.ddarr[mset.ddindex:] { - if now-dde.ts >= window { - delete(mset.ddmap, dde.id) - } else { - mset.ddindex += i - // Check if we should garbage collect here if we are 1/3 total size. - if cap(mset.ddarr) > 3*(len(mset.ddarr)-mset.ddindex) { - mset.ddarr = append([]*ddentry(nil), mset.ddarr[mset.ddindex:]...) - mset.ddindex = 0 - } - tmrNext = time.Duration(window - (now - dde.ts)) - break - } - } - if len(mset.ddmap) > 0 { - // Make sure to not fire too quick - const minFire = 50 * time.Millisecond - if tmrNext < minFire { - tmrNext = minFire - } - if mset.ddtmr != nil { - mset.ddtmr.Reset(tmrNext) - } else { - mset.ddtmr = time.AfterFunc(tmrNext, mset.purgeMsgIds) - } - } else { - if mset.ddtmr != nil { - mset.ddtmr.Stop() - mset.ddtmr = nil - } - mset.ddmap = nil - mset.ddarr = nil - mset.ddindex = 0 - } -} - -// storeMsgIdLocked will store the message id for duplicate detection. -// Lock should be held. -func (mset *stream) storeMsgIdLocked(dde *ddentry) { - if mset.ddmap == nil { - mset.ddmap = make(map[string]*ddentry) - } - mset.ddmap[dde.id] = dde - mset.ddarr = append(mset.ddarr, dde) - if mset.ddtmr == nil { - mset.ddtmr = time.AfterFunc(mset.cfg.Duplicates, mset.purgeMsgIds) - } -} - -// Fast lookup of msgId. -func getMsgId(hdr []byte) string { - return string(getHeader(JSMsgId, hdr)) -} - -// Fast lookup of expected last msgId. -func getExpectedLastMsgId(hdr []byte) string { - return string(getHeader(JSExpectedLastMsgId, hdr)) -} - -// Fast lookup of expected stream. -func getExpectedStream(hdr []byte) string { - return string(getHeader(JSExpectedStream, hdr)) -} - -// Fast lookup of expected stream. -func getExpectedLastSeq(hdr []byte) (uint64, bool) { - bseq := getHeader(JSExpectedLastSeq, hdr) - if len(bseq) == 0 { - return 0, false - } - return uint64(parseInt64(bseq)), true -} - -// Fast lookup of rollups. -func getRollup(hdr []byte) string { - r := getHeader(JSMsgRollup, hdr) - if len(r) == 0 { - return _EMPTY_ - } - return strings.ToLower(string(r)) -} - -// Fast lookup of expected stream sequence per subject. -func getExpectedLastSeqPerSubject(hdr []byte) (uint64, bool) { - bseq := getHeader(JSExpectedLastSubjSeq, hdr) - if len(bseq) == 0 { - return 0, false - } - return uint64(parseInt64(bseq)), true -} - -// Fast lookup of expected subject for the expected stream sequence per subject. -func getExpectedLastSeqPerSubjectForSubject(hdr []byte) string { - return string(getHeader(JSExpectedLastSubjSeqSubj, hdr)) -} - -// Fast lookup of the message TTL from headers: -// - Positive return value: duration in seconds. -// - Zero return value: no TTL or parse error. -// - Negative return value: never expires. -func getMessageTTL(hdr []byte) (int64, error) { - ttl := getHeader(JSMessageTTL, hdr) - if len(ttl) == 0 { - return 0, nil - } - return parseMessageTTL(bytesToString(ttl)) -} - -// - Positive return value: duration in seconds. -// - Zero return value: no TTL or parse error. -// - Negative return value: never expires. -func parseMessageTTL(ttl string) (int64, error) { - if strings.ToLower(ttl) == "never" { - return -1, nil - } - dur, err := time.ParseDuration(ttl) - if err == nil { - if dur < time.Second { - return 0, NewJSMessageTTLInvalidError() - } - return int64(dur.Seconds()), nil - } - t := parseInt64(stringToBytes(ttl)) - if t < 0 { - // This probably means a parse failure, hence why - // we have a special case "never" for returning -1. - // Otherwise we can't know if it's a genuine TTL - // that says never expire or if it's a parse error. - return 0, NewJSMessageTTLInvalidError() - } - return t, nil -} - -// Signal if we are clustered. Will acquire rlock. -func (mset *stream) IsClustered() bool { - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.isClustered() -} - -// Lock should be held. -func (mset *stream) isClustered() bool { - return mset.node != nil -} - -// Used if we have to queue things internally to avoid the route/gw path. -type inMsg struct { - subj string - rply string - hdr []byte - msg []byte - si *sourceInfo - mt *msgTrace -} - -var inMsgPool = sync.Pool{ - New: func() any { - return &inMsg{} - }, -} - -func (im *inMsg) returnToPool() { - im.subj, im.rply, im.hdr, im.msg, im.si, im.mt = _EMPTY_, _EMPTY_, nil, nil, nil, nil - inMsgPool.Put(im) -} - -func (mset *stream) queueInbound(ib *ipQueue[*inMsg], subj, rply string, hdr, msg []byte, si *sourceInfo, mt *msgTrace) { - im := inMsgPool.Get().(*inMsg) - im.subj, im.rply, im.hdr, im.msg, im.si, im.mt = subj, rply, hdr, msg, si, mt - if _, err := ib.push(im); err != nil { - im.returnToPool() - mset.srv.RateLimitWarnf("Dropping messages due to excessive stream ingest rate on '%s' > '%s': %s", mset.acc.Name, mset.name(), err) - if rply != _EMPTY_ { - hdr := []byte("NATS/1.0 429 Too Many Requests\r\n\r\n") - b, _ := json.Marshal(&JSPubAckResponse{PubAck: &PubAck{Stream: mset.cfg.Name}, Error: NewJSStreamTooManyRequestsError()}) - mset.outq.send(newJSPubMsg(rply, _EMPTY_, _EMPTY_, hdr, b, nil, 0)) - } - } -} - -var dgPool = sync.Pool{ - New: func() any { - return &directGetReq{} - }, -} - -// For when we need to not inline the request. -type directGetReq struct { - // Copy of this is correct for this. - req JSApiMsgGetRequest - reply string -} - -// processDirectGetRequest handles direct get request for stream messages. -func (mset *stream) processDirectGetRequest(_ *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if len(reply) == 0 { - return - } - _, msg := c.msgParts(rmsg) - if len(msg) == 0 { - hdr := []byte("NATS/1.0 408 Empty Request\r\n\r\n") - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - return - } - var req JSApiMsgGetRequest - err := json.Unmarshal(msg, &req) - if err != nil { - hdr := []byte("NATS/1.0 408 Malformed Request\r\n\r\n") - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - return - } - // Check if nothing set. - if req.Seq == 0 && req.LastFor == _EMPTY_ && req.NextFor == _EMPTY_ && len(req.MultiLastFor) == 0 && req.StartTime == nil { - hdr := []byte("NATS/1.0 408 Empty Request\r\n\r\n") - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - return - } - // Check we don't have conflicting options set. - // We do not allow batch mode for lastFor requests. - if (req.Seq > 0 && req.LastFor != _EMPTY_) || - (req.Seq > 0 && req.StartTime != nil) || - (req.StartTime != nil && req.LastFor != _EMPTY_) || - (req.LastFor != _EMPTY_ && req.NextFor != _EMPTY_) || - (req.LastFor != _EMPTY_ && req.Batch > 0) || - (req.LastFor != _EMPTY_ && len(req.MultiLastFor) > 0) || - (req.NextFor != _EMPTY_ && len(req.MultiLastFor) > 0) || - (req.UpToSeq > 0 && req.UpToTime != nil) { - hdr := []byte("NATS/1.0 408 Bad Request\r\n\r\n") - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - return - } - - inlineOk := c.kind != ROUTER && c.kind != GATEWAY && c.kind != LEAF - if !inlineOk { - dg := dgPool.Get().(*directGetReq) - dg.req, dg.reply = req, reply - mset.gets.push(dg) - } else { - mset.getDirectRequest(&req, reply) - } -} - -// This is for direct get by last subject which is part of the subject itself. -func (mset *stream) processDirectGetLastBySubjectRequest(_ *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - if len(reply) == 0 { - return - } - _, msg := c.msgParts(rmsg) - // This version expects no payload. - if len(msg) != 0 { - hdr := []byte("NATS/1.0 408 Bad Request\r\n\r\n") - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - return - } - // Extract the key. - var key string - for i, n := 0, 0; i < len(subject); i++ { - if subject[i] == btsep { - if n == 4 { - if start := i + 1; start < len(subject) { - key = subject[i+1:] - } - break - } - n++ - } - } - if len(key) == 0 { - hdr := []byte("NATS/1.0 408 Bad Request\r\n\r\n") - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - return - } - - req := JSApiMsgGetRequest{LastFor: key} - - inlineOk := c.kind != ROUTER && c.kind != GATEWAY && c.kind != LEAF - if !inlineOk { - dg := dgPool.Get().(*directGetReq) - dg.req, dg.reply = req, reply - mset.gets.push(dg) - } else { - mset.getDirectRequest(&req, reply) - } -} - -// For direct get batch and multi requests. -const ( - dg = "NATS/1.0\r\nNats-Stream: %s\r\nNats-Subject: %s\r\nNats-Sequence: %d\r\nNats-Time-Stamp: %s\r\n\r\n" - dgb = "NATS/1.0\r\nNats-Stream: %s\r\nNats-Subject: %s\r\nNats-Sequence: %d\r\nNats-Time-Stamp: %s\r\nNats-Num-Pending: %d\r\nNats-Last-Sequence: %d\r\n\r\n" - eob = "NATS/1.0 204 EOB\r\nNats-Num-Pending: %d\r\nNats-Last-Sequence: %d\r\n\r\n" - eobm = "NATS/1.0 204 EOB\r\nNats-Num-Pending: %d\r\nNats-Last-Sequence: %d\r\nNats-UpTo-Sequence: %d\r\n\r\n" -) - -// Handle a multi request. -func (mset *stream) getDirectMulti(req *JSApiMsgGetRequest, reply string) { - // TODO(dlc) - Make configurable? - const maxAllowedResponses = 1024 - - // We hold the lock here to try to avoid changes out from underneath of us. - mset.mu.RLock() - defer mset.mu.RUnlock() - // Grab store and name. - store, name, s := mset.store, mset.cfg.Name, mset.srv - - // Grab MaxBytes - mb := req.MaxBytes - if mb == 0 && s != nil { - // Fill in with the server's MaxPending. - mb = int(s.opts.MaxPending) - } - - upToSeq := req.UpToSeq - // If we have UpToTime set get the proper sequence. - if req.UpToTime != nil { - upToSeq = store.GetSeqFromTime((*req.UpToTime).UTC()) - // We need to back off one since this is used to determine start sequence normally, - // were as here we want it to be the ceiling. - upToSeq-- - } - // If not set, set to the last sequence and remember that for EOB. - if upToSeq == 0 { - var state StreamState - mset.store.FastState(&state) - upToSeq = state.LastSeq - } - - seqs, err := store.MultiLastSeqs(req.MultiLastFor, upToSeq, maxAllowedResponses) - if err != nil { - var hdr []byte - if err == ErrTooManyResults { - hdr = []byte("NATS/1.0 413 Too Many Results\r\n\r\n") - } else { - hdr = []byte(fmt.Sprintf("NATS/1.0 500 %v\r\n\r\n", err)) - } - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - return - } - if len(seqs) == 0 { - hdr := []byte("NATS/1.0 404 No Results\r\n\r\n") - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - return - } - - np, lseq, sentBytes, sent := uint64(len(seqs)-1), uint64(0), 0, 0 - for _, seq := range seqs { - if seq < req.Seq { - if np > 0 { - np-- - } - continue - } - var svp StoreMsg - sm, err := store.LoadMsg(seq, &svp) - if err != nil { - hdr := []byte("NATS/1.0 404 Message Not Found\r\n\r\n") - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - return - } - - hdr := sm.hdr - ts := time.Unix(0, sm.ts).UTC() - - if len(hdr) == 0 { - hdr = fmt.Appendf(nil, dgb, name, sm.subj, sm.seq, ts.Format(time.RFC3339Nano), np, lseq) - } else { - hdr = copyBytes(hdr) - hdr = genHeader(hdr, JSStream, name) - hdr = genHeader(hdr, JSSubject, sm.subj) - hdr = genHeader(hdr, JSSequence, strconv.FormatUint(sm.seq, 10)) - hdr = genHeader(hdr, JSTimeStamp, ts.Format(time.RFC3339Nano)) - hdr = genHeader(hdr, JSNumPending, strconv.FormatUint(np, 10)) - hdr = genHeader(hdr, JSLastSequence, strconv.FormatUint(lseq, 10)) - } - // Decrement num pending. This is optimization and we do not continue to look it up for these operations. - if np > 0 { - np-- - } - // Track our lseq - lseq = sm.seq - // Send out our message. - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, sm.msg, nil, 0)) - // Check if we have exceeded max bytes. - sentBytes += len(sm.subj) + len(sm.hdr) + len(sm.msg) - if sentBytes >= mb { - break - } - sent++ - if req.Batch > 0 && sent >= req.Batch { - break - } - } - - // Send out EOB - hdr := fmt.Appendf(nil, eobm, np, lseq, upToSeq) - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) -} - -// Do actual work on a direct msg request. -// This could be called in a Go routine if we are inline for a non-client connection. -func (mset *stream) getDirectRequest(req *JSApiMsgGetRequest, reply string) { - // Handle multi in separate function. - if len(req.MultiLastFor) > 0 { - mset.getDirectMulti(req, reply) - return - } - - mset.mu.RLock() - store, name, s := mset.store, mset.cfg.Name, mset.srv - mset.mu.RUnlock() - - var seq uint64 - // Lookup start seq if AsOfTime is set. - if req.StartTime != nil { - seq = store.GetSeqFromTime(*req.StartTime) - } else { - seq = req.Seq - } - - wc := subjectHasWildcard(req.NextFor) - // For tracking num pending if we are batch. - var np, lseq, validThrough uint64 - var isBatchRequest bool - batch := req.Batch - if batch == 0 { - batch = 1 - } else { - // This is a batch request, capture initial numPending. - isBatchRequest = true - np, validThrough = store.NumPending(seq, req.NextFor, false) - } - - // Grab MaxBytes - mb := req.MaxBytes - if mb == 0 && s != nil { - // Fill in with the server's MaxPending. - mb = int(s.opts.MaxPending) - } - // Track what we have sent. - var sentBytes int - - // Loop over batch, which defaults to 1. - for i := 0; i < batch; i++ { - var ( - svp StoreMsg - sm *StoreMsg - err error - ) - if seq > 0 && req.NextFor == _EMPTY_ { - // Only do direct lookup for first in a batch. - if i == 0 { - sm, err = store.LoadMsg(seq, &svp) - } else { - // We want to use load next with fwcs to step over deleted msgs. - sm, seq, err = store.LoadNextMsg(fwcs, true, seq, &svp) - } - // Bump for next loop if applicable. - seq++ - } else if req.NextFor != _EMPTY_ { - sm, seq, err = store.LoadNextMsg(req.NextFor, wc, seq, &svp) - seq++ - } else { - // Batch is not applicable here, this is checked before we get here. - sm, err = store.LoadLastMsg(req.LastFor, &svp) - } - if err != nil { - // For batches, if we stop early we want to do EOB logic below. - if batch > 1 && i > 0 { - break - } - hdr := []byte("NATS/1.0 404 Message Not Found\r\n\r\n") - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - return - } - - hdr := sm.hdr - ts := time.Unix(0, sm.ts).UTC() - - if isBatchRequest { - if len(hdr) == 0 { - hdr = fmt.Appendf(nil, dgb, name, sm.subj, sm.seq, ts.Format(time.RFC3339Nano), np, lseq) - } else { - hdr = copyBytes(hdr) - hdr = genHeader(hdr, JSStream, name) - hdr = genHeader(hdr, JSSubject, sm.subj) - hdr = genHeader(hdr, JSSequence, strconv.FormatUint(sm.seq, 10)) - hdr = genHeader(hdr, JSTimeStamp, ts.Format(time.RFC3339Nano)) - hdr = genHeader(hdr, JSNumPending, strconv.FormatUint(np, 10)) - hdr = genHeader(hdr, JSLastSequence, strconv.FormatUint(lseq, 10)) - } - // Decrement num pending. This is optimization and we do not continue to look it up for these operations. - np-- - } else { - if len(hdr) == 0 { - hdr = fmt.Appendf(nil, dg, name, sm.subj, sm.seq, ts.Format(time.RFC3339Nano)) - } else { - hdr = copyBytes(hdr) - hdr = genHeader(hdr, JSStream, name) - hdr = genHeader(hdr, JSSubject, sm.subj) - hdr = genHeader(hdr, JSSequence, strconv.FormatUint(sm.seq, 10)) - hdr = genHeader(hdr, JSTimeStamp, ts.Format(time.RFC3339Nano)) - } - } - // Track our lseq - lseq = sm.seq - // Send out our message. - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, sm.msg, nil, 0)) - // Check if we have exceeded max bytes. - sentBytes += len(sm.subj) + len(sm.hdr) + len(sm.msg) - if sentBytes >= mb { - break - } - } - - // If batch was requested send EOB. - if isBatchRequest { - // Update if the stream's lasts sequence has moved past our validThrough. - if mset.lastSeq() > validThrough { - np, _ = store.NumPending(seq, req.NextFor, false) - } - hdr := fmt.Appendf(nil, eob, np, lseq) - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, hdr, nil, nil, 0)) - } -} - -// processInboundJetStreamMsg handles processing messages bound for a stream. -func (mset *stream) processInboundJetStreamMsg(_ *subscription, c *client, _ *Account, subject, reply string, rmsg []byte) { - hdr, msg := c.msgParts(copyBytes(rmsg)) // Need to copy. - if mt, traceOnly := c.isMsgTraceEnabled(); mt != nil { - // If message is delivered, we need to disable the message trace headers - // to prevent a trace event to be generated when a stored message - // is delivered to a consumer and routed. - if !traceOnly { - disableTraceHeaders(c, hdr) - } - // This will add the jetstream event while in the client read loop. - // Since the event will be updated in a different go routine, the - // tracing object will have a separate reference to the JS trace - // object. - mt.addJetStreamEvent(mset.name()) - } - mset.queueInbound(mset.msgs, subject, reply, hdr, msg, nil, c.pa.trace) -} - -var ( - errLastSeqMismatch = errors.New("last sequence mismatch") - errMsgIdDuplicate = errors.New("msgid is duplicate") - errStreamClosed = errors.New("stream closed") - errInvalidMsgHandler = errors.New("undefined message handler") - errStreamMismatch = errors.New("expected stream does not match") - errMsgTTLDisabled = errors.New("message TTL disabled") -) - -// processJetStreamMsg is where we try to actually process the stream msg. -func (mset *stream) processJetStreamMsg(subject, reply string, hdr, msg []byte, lseq uint64, ts int64, mt *msgTrace, sourced bool) (retErr error) { - if mt != nil { - // Only the leader/standalone will have mt!=nil. On exit, send the - // message trace event. - defer func() { - mt.sendEventFromJetStream(retErr) - }() - } - - if mset.closed.Load() { - return errStreamClosed - } - - mset.mu.Lock() - s, store := mset.srv, mset.store - - traceOnly := mt.traceOnly() - bumpCLFS := func() { - // Do not bump if tracing and not doing message delivery. - if traceOnly { - return - } - mset.clMu.Lock() - mset.clfs++ - mset.clMu.Unlock() - } - - // Apply the input subject transform if any - if mset.itr != nil { - ts, err := mset.itr.Match(subject) - if err == nil { - // no filtering: if the subject doesn't map the source of the transform, don't change it - subject = ts - } - } - - var accName string - if mset.acc != nil { - accName = mset.acc.Name - } - - js, jsa, doAck := mset.js, mset.jsa, !mset.cfg.NoAck - name, stype := mset.cfg.Name, mset.cfg.Storage - maxMsgSize := int(mset.cfg.MaxMsgSize) - numConsumers := len(mset.consumers) - interestRetention := mset.cfg.Retention == InterestPolicy - // Snapshot if we are the leader and if we can respond. - isLeader, isSealed := mset.isLeaderNodeState(), mset.cfg.Sealed - canRespond := doAck && len(reply) > 0 && isLeader - - var resp = &JSPubAckResponse{} - - // Bail here if sealed. - if isSealed { - outq := mset.outq - mset.mu.Unlock() - bumpCLFS() - if canRespond && outq != nil { - resp.PubAck = &PubAck{Stream: name} - resp.Error = ApiErrors[JSStreamSealedErr] - b, _ := json.Marshal(resp) - outq.sendMsg(reply, b) - } - return ApiErrors[JSStreamSealedErr] - } - - var buf [256]byte - pubAck := append(buf[:0], mset.pubAck...) - - // If this is a non-clustered msg and we are not considered active, meaning no active subscription, do not process. - if lseq == 0 && ts == 0 && !mset.active { - mset.mu.Unlock() - return nil - } - - // For clustering the lower layers will pass our expected lseq. If it is present check for that here. - if lseq > 0 && lseq != (mset.lseq+mset.clfs) { - isMisMatch := true - // We may be able to recover here if we have no state whatsoever, or we are a mirror. - // See if we have to adjust our starting sequence. - if mset.lseq == 0 || mset.cfg.Mirror != nil { - var state StreamState - mset.store.FastState(&state) - if state.FirstSeq == 0 { - mset.store.Compact(lseq + 1) - mset.lseq = lseq - isMisMatch = false - } - } - // Really is a mismatch. - if isMisMatch { - outq := mset.outq - mset.mu.Unlock() - if canRespond && outq != nil { - resp.PubAck = &PubAck{Stream: name} - resp.Error = ApiErrors[JSStreamSequenceNotMatchErr] - b, _ := json.Marshal(resp) - outq.sendMsg(reply, b) - } - return errLastSeqMismatch - } - } - - // If we have received this message across an account we may have request information attached. - // For now remove. TODO(dlc) - Should this be opt-in or opt-out? - if len(hdr) > 0 { - hdr = removeHeaderIfPresent(hdr, ClientInfoHdr) - } - - // Process additional msg headers if still present. - var msgId string - var rollupSub, rollupAll bool - isClustered := mset.isClustered() - - if len(hdr) > 0 { - outq := mset.outq - - // Certain checks have already been performed if in clustered mode, so only check if not. - // Note, for cluster mode but with message tracing (without message delivery), we need - // to do this check here since it was not done in processClusteredInboundMsg(). - if !isClustered || traceOnly { - // Expected stream. - if sname := getExpectedStream(hdr); sname != _EMPTY_ && sname != name { - mset.mu.Unlock() - bumpCLFS() - if canRespond { - resp.PubAck = &PubAck{Stream: name} - resp.Error = NewJSStreamNotMatchError() - b, _ := json.Marshal(resp) - outq.sendMsg(reply, b) - } - return errStreamMismatch - } - } - - // TTL'd messages are rejected entirely if TTLs are not enabled on the stream. - // Shouldn't happen in clustered mode since we should have already caught this - // in processClusteredInboundMsg, but needed here for non-clustered etc. - if ttl, _ := getMessageTTL(hdr); !sourced && ttl != 0 && !mset.cfg.AllowMsgTTL { - mset.mu.Unlock() - bumpCLFS() - if canRespond { - resp.PubAck = &PubAck{Stream: name} - resp.Error = NewJSMessageTTLDisabledError() - b, _ := json.Marshal(resp) - outq.sendMsg(reply, b) - } - return errMsgTTLDisabled - } - - // Dedupe detection. This is done at the cluster level for dedupe detectiom above the - // lower layers. But we still need to pull out the msgId. - if msgId = getMsgId(hdr); msgId != _EMPTY_ { - // Do real check only if not clustered or traceOnly flag is set. - if !isClustered || traceOnly { - if dde := mset.checkMsgId(msgId); dde != nil { - mset.mu.Unlock() - bumpCLFS() - if canRespond { - response := append(pubAck, strconv.FormatUint(dde.seq, 10)...) - response = append(response, ",\"duplicate\": true}"...) - outq.sendMsg(reply, response) - } - return errMsgIdDuplicate - } - } - } - - // Expected last sequence per subject. - if seq, exists := getExpectedLastSeqPerSubject(hdr); exists { - // Allow override of the subject used for the check. - seqSubj := subject - if optSubj := getExpectedLastSeqPerSubjectForSubject(hdr); optSubj != _EMPTY_ { - seqSubj = optSubj - } - - // TODO(dlc) - We could make a new store func that does this all in one. - var smv StoreMsg - var fseq uint64 - sm, err := store.LoadLastMsg(seqSubj, &smv) - if sm != nil { - fseq = sm.seq - } - if err == ErrStoreMsgNotFound { - if seq == 0 { - fseq, err = 0, nil - } else if mset.isClustered() { - // Do not bump clfs in case message was not found and could have been deleted. - var ss StreamState - store.FastState(&ss) - if seq <= ss.LastSeq { - fseq, err = seq, nil - } - } - } - if err != nil || fseq != seq { - mset.mu.Unlock() - bumpCLFS() - if canRespond { - resp.PubAck = &PubAck{Stream: name} - resp.Error = NewJSStreamWrongLastSequenceError(fseq) - b, _ := json.Marshal(resp) - outq.sendMsg(reply, b) - } - return fmt.Errorf("last sequence by subject mismatch: %d vs %d", seq, fseq) - } - } - - // Expected last sequence. - if seq, exists := getExpectedLastSeq(hdr); exists && seq != mset.lseq { - mlseq := mset.lseq - mset.mu.Unlock() - bumpCLFS() - if canRespond { - resp.PubAck = &PubAck{Stream: name} - resp.Error = NewJSStreamWrongLastSequenceError(mlseq) - b, _ := json.Marshal(resp) - outq.sendMsg(reply, b) - } - return fmt.Errorf("last sequence mismatch: %d vs %d", seq, mlseq) - } - // Expected last msgId. - if lmsgId := getExpectedLastMsgId(hdr); lmsgId != _EMPTY_ { - if mset.lmsgId == _EMPTY_ && !mset.ddloaded { - mset.rebuildDedupe() - } - if lmsgId != mset.lmsgId { - last := mset.lmsgId - mset.mu.Unlock() - bumpCLFS() - if canRespond { - resp.PubAck = &PubAck{Stream: name} - resp.Error = NewJSStreamWrongLastMsgIDError(last) - b, _ := json.Marshal(resp) - outq.sendMsg(reply, b) - } - return fmt.Errorf("last msgid mismatch: %q vs %q", lmsgId, last) - } - } - // Check for any rollups. - if rollup := getRollup(hdr); rollup != _EMPTY_ { - if !mset.cfg.AllowRollup || mset.cfg.DenyPurge { - mset.mu.Unlock() - bumpCLFS() - if canRespond { - resp.PubAck = &PubAck{Stream: name} - resp.Error = NewJSStreamRollupFailedError(errors.New("rollup not permitted")) - b, _ := json.Marshal(resp) - outq.sendMsg(reply, b) - } - return errors.New("rollup not permitted") - } - switch rollup { - case JSMsgRollupSubject: - rollupSub = true - case JSMsgRollupAll: - rollupAll = true - default: - mset.mu.Unlock() - bumpCLFS() - err := fmt.Errorf("rollup value invalid: %q", rollup) - if canRespond { - resp.PubAck = &PubAck{Stream: name} - resp.Error = NewJSStreamRollupFailedError(err) - b, _ := json.Marshal(resp) - outq.sendMsg(reply, b) - } - return err - } - } - } - - // Response Ack. - var ( - response []byte - seq uint64 - err error - ) - - // Check to see if we are over the max msg size. - if maxMsgSize >= 0 && (len(hdr)+len(msg)) > maxMsgSize { - mset.mu.Unlock() - bumpCLFS() - if canRespond { - resp.PubAck = &PubAck{Stream: name} - resp.Error = NewJSStreamMessageExceedsMaximumError() - response, _ = json.Marshal(resp) - mset.outq.sendMsg(reply, response) - } - return ErrMaxPayload - } - - if len(hdr) > math.MaxUint16 { - mset.mu.Unlock() - bumpCLFS() - if canRespond { - resp.PubAck = &PubAck{Stream: name} - resp.Error = NewJSStreamHeaderExceedsMaximumError() - response, _ = json.Marshal(resp) - mset.outq.sendMsg(reply, response) - } - return ErrMaxPayload - } - - // Check to see if we have exceeded our limits. - if js.limitsExceeded(stype) { - s.resourcesExceededError() - mset.mu.Unlock() - bumpCLFS() - if canRespond { - resp.PubAck = &PubAck{Stream: name} - resp.Error = NewJSInsufficientResourcesError() - response, _ = json.Marshal(resp) - mset.outq.sendMsg(reply, response) - } - // Stepdown regardless. - if node := mset.raftNode(); node != nil { - node.StepDown() - } - return NewJSInsufficientResourcesError() - } - - var noInterest bool - - // If we are interest based retention and have no consumers then we can skip. - if interestRetention { - mset.clsMu.RLock() - noInterest = numConsumers == 0 || mset.csl == nil || !mset.csl.HasInterest(subject) - mset.clsMu.RUnlock() - } - - // Grab timestamp if not already set. - if ts == 0 && lseq > 0 { - ts = time.Now().UnixNano() - } - - mt.updateJetStreamEvent(subject, noInterest) - if traceOnly { - mset.mu.Unlock() - return nil - } - - // Skip msg here. - if noInterest { - mset.lseq = store.SkipMsg() - mset.lmsgId = msgId - // If we have a msgId make sure to save. - if msgId != _EMPTY_ { - mset.storeMsgIdLocked(&ddentry{msgId, mset.lseq, ts}) - } - if canRespond { - response = append(pubAck, strconv.FormatUint(mset.lseq, 10)...) - response = append(response, '}') - mset.outq.sendMsg(reply, response) - } - mset.mu.Unlock() - return nil - } - - // If here we will attempt to store the message. - // Assume this will succeed. - olmsgId := mset.lmsgId - mset.lmsgId = msgId - clfs := mset.clfs - mset.lseq++ - tierName := mset.tier - - // Republish state if needed. - var tsubj string - var tlseq uint64 - var thdrsOnly bool - if mset.tr != nil { - tsubj, _ = mset.tr.Match(subject) - if mset.cfg.RePublish != nil { - thdrsOnly = mset.cfg.RePublish.HeadersOnly - } - } - republish := tsubj != _EMPTY_ && isLeader - - // If we are republishing grab last sequence for this exact subject. Aids in gap detection for lightweight clients. - if republish { - var smv StoreMsg - if sm, _ := store.LoadLastMsg(subject, &smv); sm != nil { - tlseq = sm.seq - } - } - - // If clustered this was already checked and we do not want to check here and possibly introduce skew. - if !isClustered { - if exceeded, err := jsa.wouldExceedLimits(stype, tierName, mset.cfg.Replicas, subject, hdr, msg); exceeded { - if err == nil { - err = NewJSAccountResourcesExceededError() - } - s.RateLimitWarnf("JetStream resource limits exceeded for account: %q", accName) - if canRespond { - resp.PubAck = &PubAck{Stream: name} - resp.Error = err - response, _ = json.Marshal(resp) - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, response, nil, 0)) - } - mset.mu.Unlock() - return err - } - } - - // Find the message TTL if any. - ttl, err := getMessageTTL(hdr) - if err != nil { - if canRespond { - resp.PubAck = &PubAck{Stream: name} - resp.Error = NewJSMessageTTLInvalidError() - response, _ = json.Marshal(resp) - mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, response, nil, 0)) - } - mset.mu.Unlock() - return err - } - - // Store actual msg. - if lseq == 0 && ts == 0 { - seq, ts, err = store.StoreMsg(subject, hdr, msg, ttl) - } else { - // Make sure to take into account any message assignments that we had to skip (clfs). - seq = lseq + 1 - clfs - // Check for preAcks and the need to clear it. - if mset.hasAllPreAcks(seq, subject) { - mset.clearAllPreAcks(seq) - } - err = store.StoreRawMsg(subject, hdr, msg, seq, ts, ttl) - } - - if err != nil { - if isPermissionError(err) { - mset.mu.Unlock() - // messages in block cache could be lost in the worst case. - // In the clustered mode it is very highly unlikely as a result of replication. - mset.srv.DisableJetStream() - mset.srv.Warnf("Filesystem permission denied while writing msg, disabling JetStream: %v", err) - return err - } - // If we did not succeed put those values back and increment clfs in case we are clustered. - var state StreamState - mset.store.FastState(&state) - mset.lseq = state.LastSeq - mset.lmsgId = olmsgId - mset.mu.Unlock() - bumpCLFS() - - switch err { - case ErrMaxMsgs, ErrMaxBytes, ErrMaxMsgsPerSubject, ErrMsgTooLarge: - s.RateLimitDebugf("JetStream failed to store a msg on stream '%s > %s': %v", accName, name, err) - case ErrStoreClosed: - default: - s.Errorf("JetStream failed to store a msg on stream '%s > %s': %v", accName, name, err) - } - - if canRespond { - resp.PubAck = &PubAck{Stream: name} - resp.Error = NewJSStreamStoreFailedError(err, Unless(err)) - response, _ = json.Marshal(resp) - mset.outq.sendMsg(reply, response) - } - return err - } - - // If we have a msgId make sure to save. - // This will replace our estimate from the cluster layer if we are clustered. - if msgId != _EMPTY_ { - if isClustered && isLeader && mset.ddmap != nil { - if dde := mset.ddmap[msgId]; dde != nil { - dde.seq, dde.ts = seq, ts - } else { - mset.storeMsgIdLocked(&ddentry{msgId, seq, ts}) - } - } else { - // R1 or not leader.. - mset.storeMsgIdLocked(&ddentry{msgId, seq, ts}) - } - } - - // If here we succeeded in storing the message. - mset.mu.Unlock() - - // No errors, this is the normal path. - if rollupSub { - mset.purge(&JSApiStreamPurgeRequest{Subject: subject, Keep: 1}) - } else if rollupAll { - mset.purge(&JSApiStreamPurgeRequest{Keep: 1}) - } - - // Check for republish. - if republish { - const ht = "NATS/1.0\r\nNats-Stream: %s\r\nNats-Subject: %s\r\nNats-Sequence: %d\r\nNats-Time-Stamp: %s\r\nNats-Last-Sequence: %d\r\n\r\n" - const htho = "NATS/1.0\r\nNats-Stream: %s\r\nNats-Subject: %s\r\nNats-Sequence: %d\r\nNats-Time-Stamp: %s\r\nNats-Last-Sequence: %d\r\nNats-Msg-Size: %d\r\n\r\n" - // When adding to existing headers, will use the fmt.Append version so this skips the headers from above. - const hoff = 10 - - tsStr := time.Unix(0, ts).UTC().Format(time.RFC3339Nano) - var rpMsg []byte - if len(hdr) == 0 { - if !thdrsOnly { - hdr = fmt.Appendf(nil, ht, name, subject, seq, tsStr, tlseq) - rpMsg = copyBytes(msg) - } else { - hdr = fmt.Appendf(nil, htho, name, subject, seq, tsStr, tlseq, len(msg)) - } - } else { - // use hdr[:end:end] to make sure as we add we copy the original hdr. - end := len(hdr) - LEN_CR_LF - if !thdrsOnly { - hdr = fmt.Appendf(hdr[:end:end], ht[hoff:], name, subject, seq, tsStr, tlseq) - rpMsg = copyBytes(msg) - } else { - hdr = fmt.Appendf(hdr[:end:end], htho[hoff:], name, subject, seq, tsStr, tlseq, len(msg)) - } - } - mset.outq.send(newJSPubMsg(tsubj, _EMPTY_, _EMPTY_, hdr, rpMsg, nil, seq)) - } - - // Send response here. - if canRespond { - response = append(pubAck, strconv.FormatUint(seq, 10)...) - response = append(response, '}') - mset.outq.sendMsg(reply, response) - } - - // Signal consumers for new messages. - if numConsumers > 0 { - mset.sigq.push(newCMsg(subject, seq)) - select { - case mset.sch <- struct{}{}: - default: - } - } - - return nil -} - -// Used to signal inbound message to registered consumers. -type cMsg struct { - seq uint64 - subj string -} - -// Pool to recycle consumer bound msgs. -var cMsgPool sync.Pool - -// Used to queue up consumer bound msgs for signaling. -func newCMsg(subj string, seq uint64) *cMsg { - var m *cMsg - cm := cMsgPool.Get() - if cm != nil { - m = cm.(*cMsg) - } else { - m = new(cMsg) - } - m.subj, m.seq = subj, seq - - return m -} - -func (m *cMsg) returnToPool() { - if m == nil { - return - } - m.subj, m.seq = _EMPTY_, 0 - cMsgPool.Put(m) -} - -// Go routine to signal consumers. -// Offloaded from stream msg processing. -func (mset *stream) signalConsumersLoop() { - mset.mu.RLock() - s, qch, sch, msgs := mset.srv, mset.qch, mset.sch, mset.sigq - mset.mu.RUnlock() - - for { - select { - case <-s.quitCh: - return - case <-qch: - return - case <-sch: - cms := msgs.pop() - for _, m := range cms { - seq, subj := m.seq, m.subj - m.returnToPool() - // Signal all appropriate consumers. - mset.signalConsumers(subj, seq) - } - msgs.recycle(&cms) - } - } -} - -// This will update and signal all consumers that match. -func (mset *stream) signalConsumers(subj string, seq uint64) { - mset.clsMu.RLock() - defer mset.clsMu.RUnlock() - csl := mset.csl - if csl == nil { - return - } - csl.Match(subj, func(o *consumer) { - o.processStreamSignal(seq) - }) -} - -// Internal message for use by jetstream subsystem. -type jsPubMsg struct { - dsubj string // Subject to send to, e.g. _INBOX.xxx - reply string - StoreMsg - o *consumer -} - -var jsPubMsgPool sync.Pool - -func newJSPubMsg(dsubj, subj, reply string, hdr, msg []byte, o *consumer, seq uint64) *jsPubMsg { - var m *jsPubMsg - var buf []byte - pm := jsPubMsgPool.Get() - if pm != nil { - m = pm.(*jsPubMsg) - buf = m.buf[:0] - if hdr != nil { - hdr = append(m.hdr[:0], hdr...) - } - } else { - m = new(jsPubMsg) - } - // When getting something from a pool it is critical that all fields are - // initialized. Doing this way guarantees that if someone adds a field to - // the structure, the compiler will fail the build if this line is not updated. - (*m) = jsPubMsg{dsubj, reply, StoreMsg{subj, hdr, msg, buf, seq, 0}, o} - - return m -} - -// Gets a jsPubMsg from the pool. -func getJSPubMsgFromPool() *jsPubMsg { - pm := jsPubMsgPool.Get() - if pm != nil { - return pm.(*jsPubMsg) - } - return new(jsPubMsg) -} - -func (pm *jsPubMsg) returnToPool() { - if pm == nil { - return - } - pm.subj, pm.dsubj, pm.reply, pm.hdr, pm.msg, pm.o = _EMPTY_, _EMPTY_, _EMPTY_, nil, nil, nil - if len(pm.buf) > 0 { - pm.buf = pm.buf[:0] - } - if len(pm.hdr) > 0 { - pm.hdr = pm.hdr[:0] - } - jsPubMsgPool.Put(pm) -} - -func (pm *jsPubMsg) size() int { - if pm == nil { - return 0 - } - return len(pm.dsubj) + len(pm.reply) + len(pm.hdr) + len(pm.msg) -} - -// Queue of *jsPubMsg for sending internal system messages. -type jsOutQ struct { - *ipQueue[*jsPubMsg] -} - -func (q *jsOutQ) sendMsg(subj string, msg []byte) { - if q != nil { - q.send(newJSPubMsg(subj, _EMPTY_, _EMPTY_, nil, msg, nil, 0)) - } -} - -func (q *jsOutQ) send(msg *jsPubMsg) { - if q == nil || msg == nil { - return - } - q.push(msg) -} - -func (q *jsOutQ) unregister() { - if q == nil { - return - } - q.ipQueue.unregister() -} - -// StoredMsg is for raw access to messages in a stream. -type StoredMsg struct { - Subject string `json:"subject"` - Sequence uint64 `json:"seq"` - Header []byte `json:"hdrs,omitempty"` - Data []byte `json:"data,omitempty"` - Time time.Time `json:"time"` -} - -// This is similar to system semantics but did not want to overload the single system sendq, -// or require system account when doing simple setup with jetstream. -func (mset *stream) setupSendCapabilities() { - mset.mu.Lock() - defer mset.mu.Unlock() - if mset.outq != nil { - return - } - qname := fmt.Sprintf("[ACC:%s] stream '%s' sendQ", mset.acc.Name, mset.cfg.Name) - mset.outq = &jsOutQ{newIPQueue[*jsPubMsg](mset.srv, qname)} - go mset.internalLoop() -} - -// Returns the associated account name. -func (mset *stream) accName() string { - if mset == nil { - return _EMPTY_ - } - mset.mu.RLock() - acc := mset.acc - mset.mu.RUnlock() - return acc.Name -} - -// Name returns the stream name. -func (mset *stream) name() string { - if mset == nil { - return _EMPTY_ - } - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.cfg.Name -} - -func (mset *stream) internalLoop() { - mset.mu.RLock() - setGoRoutineLabels(pprofLabels{ - "account": mset.acc.Name, - "stream": mset.cfg.Name, - }) - s := mset.srv - c := s.createInternalJetStreamClient() - c.registerWithAccount(mset.acc) - defer c.closeConnection(ClientClosed) - outq, qch, msgs, gets := mset.outq, mset.qch, mset.msgs, mset.gets - - // For the ack msgs queue for interest retention. - var ( - amch chan struct{} - ackq *ipQueue[uint64] - ) - if mset.ackq != nil { - ackq, amch = mset.ackq, mset.ackq.ch - } - mset.mu.RUnlock() - - // Raw scratch buffer. - // This should be rarely used now so can be smaller. - var _r [1024]byte - - // To optimize for not converting a string to a []byte slice. - var ( - subj [256]byte - dsubj [256]byte - rply [256]byte - szb [10]byte - hdb [10]byte - ) - - for { - select { - case <-outq.ch: - pms := outq.pop() - for _, pm := range pms { - c.pa.subject = append(dsubj[:0], pm.dsubj...) - c.pa.deliver = append(subj[:0], pm.subj...) - c.pa.size = len(pm.msg) + len(pm.hdr) - c.pa.szb = append(szb[:0], strconv.Itoa(c.pa.size)...) - if len(pm.reply) > 0 { - c.pa.reply = append(rply[:0], pm.reply...) - } else { - c.pa.reply = nil - } - - // If we have an underlying buf that is the wire contents for hdr + msg, else construct on the fly. - var msg []byte - if len(pm.buf) > 0 { - msg = pm.buf - } else { - if len(pm.hdr) > 0 { - msg = pm.hdr - if len(pm.msg) > 0 { - msg = _r[:0] - msg = append(msg, pm.hdr...) - msg = append(msg, pm.msg...) - } - } else if len(pm.msg) > 0 { - // We own this now from a low level buffer perspective so can use directly here. - msg = pm.msg - } - } - - if len(pm.hdr) > 0 { - c.pa.hdr = len(pm.hdr) - c.pa.hdb = []byte(strconv.Itoa(c.pa.hdr)) - c.pa.hdb = append(hdb[:0], strconv.Itoa(c.pa.hdr)...) - } else { - c.pa.hdr = -1 - c.pa.hdb = nil - } - - msg = append(msg, _CRLF_...) - - didDeliver, _ := c.processInboundClientMsg(msg) - c.pa.szb, c.pa.subject, c.pa.deliver = nil, nil, nil - - // Check to see if this is a delivery for a consumer and - // we failed to deliver the message. If so alert the consumer. - if pm.o != nil && pm.seq > 0 && !didDeliver { - pm.o.didNotDeliver(pm.seq, pm.dsubj) - } - pm.returnToPool() - } - // TODO: Move in the for-loop? - c.flushClients(0) - outq.recycle(&pms) - case <-msgs.ch: - // This can possibly change now so needs to be checked here. - isClustered := mset.IsClustered() - ims := msgs.pop() - for _, im := range ims { - // If we are clustered we need to propose this message to the underlying raft group. - if isClustered { - mset.processClusteredInboundMsg(im.subj, im.rply, im.hdr, im.msg, im.mt, false) - } else { - mset.processJetStreamMsg(im.subj, im.rply, im.hdr, im.msg, 0, 0, im.mt, false) - } - im.returnToPool() - } - msgs.recycle(&ims) - case <-gets.ch: - dgs := gets.pop() - for _, dg := range dgs { - mset.getDirectRequest(&dg.req, dg.reply) - dgPool.Put(dg) - } - gets.recycle(&dgs) - - case <-amch: - seqs := ackq.pop() - for _, seq := range seqs { - mset.ackMsg(nil, seq) - } - ackq.recycle(&seqs) - case <-qch: - return - case <-s.quitCh: - return - } - } -} - -// Used to break consumers out of their monitorConsumer go routines. -func (mset *stream) resetAndWaitOnConsumers() { - mset.mu.RLock() - consumers := make([]*consumer, 0, len(mset.consumers)) - for _, o := range mset.consumers { - consumers = append(consumers, o) - } - mset.mu.RUnlock() - - for _, o := range consumers { - if node := o.raftNode(); node != nil { - node.StepDown() - node.Delete() - } - if o.isMonitorRunning() { - o.monitorWg.Wait() - } - } -} - -// Internal function to delete a stream. -func (mset *stream) delete() error { - if mset == nil { - return nil - } - return mset.stop(true, true) -} - -// Internal function to stop or delete the stream. -func (mset *stream) stop(deleteFlag, advisory bool) error { - mset.mu.RLock() - js, jsa, name := mset.js, mset.jsa, mset.cfg.Name - mset.mu.RUnlock() - - if jsa == nil { - return NewJSNotEnabledForAccountError() - } - - // Remove from our account map first. - jsa.mu.Lock() - delete(jsa.streams, name) - accName := jsa.account.Name - jsa.mu.Unlock() - - // Kick monitor and collect consumers first. - mset.mu.Lock() - - // Mark closed. - mset.closed.Store(true) - - // Signal to the monitor loop. - // Can't use qch here. - if mset.mqch != nil { - close(mset.mqch) - mset.mqch = nil - } - - // Stop responding to sync requests. - mset.stopClusterSubs() - // Unsubscribe from direct stream. - mset.unsubscribeToStream(true) - - // Our info sub if we spun it up. - if mset.infoSub != nil { - mset.srv.sysUnsubscribe(mset.infoSub) - mset.infoSub = nil - } - - // Clean up consumers. - var obs []*consumer - for _, o := range mset.consumers { - obs = append(obs, o) - } - mset.clsMu.Lock() - mset.consumers, mset.cList, mset.csl = nil, nil, nil - mset.clsMu.Unlock() - - // Check if we are a mirror. - if mset.mirror != nil && mset.mirror.sub != nil { - mset.unsubscribe(mset.mirror.sub) - mset.mirror.sub = nil - mset.removeInternalConsumer(mset.mirror) - } - // Now check for sources. - if len(mset.sources) > 0 { - for _, si := range mset.sources { - mset.cancelSourceConsumer(si.iname) - } - } - mset.mu.Unlock() - - isShuttingDown := js.isShuttingDown() - for _, o := range obs { - if !o.isClosed() { - // Third flag says do not broadcast a signal. - // TODO(dlc) - If we have an err here we don't want to stop - // but should we log? - o.stopWithFlags(deleteFlag, deleteFlag, false, advisory) - if !isShuttingDown { - o.monitorWg.Wait() - } - } - } - - mset.mu.Lock() - // Send stream delete advisory after the consumers. - if deleteFlag && advisory { - mset.sendDeleteAdvisoryLocked() - } - - // Quit channel, do this after sending the delete advisory - if mset.qch != nil { - close(mset.qch) - mset.qch = nil - } - - // Cluster cleanup - var sa *streamAssignment - if n := mset.node; n != nil { - if deleteFlag { - n.Delete() - sa = mset.sa - } else { - n.Stop() - } - } - - // Cleanup duplicate timer if running. - if mset.ddtmr != nil { - mset.ddtmr.Stop() - mset.ddtmr = nil - mset.ddmap = nil - mset.ddarr = nil - mset.ddindex = 0 - } - - sysc := mset.sysc - mset.sysc = nil - - if deleteFlag { - // Unregistering ipQueues do not prevent them from push/pop - // just will remove them from the central monitoring map - mset.msgs.unregister() - mset.ackq.unregister() - mset.outq.unregister() - mset.sigq.unregister() - mset.smsgs.unregister() - } - - // Snapshot store. - store := mset.store - c := mset.client - - // Clustered cleanup. - mset.mu.Unlock() - - // Check if the stream assignment has the group node specified. - // We need this cleared for if the stream gets reassigned here. - if sa != nil { - js.mu.Lock() - if sa.Group != nil { - sa.Group.node = nil - } - js.mu.Unlock() - } - - if c != nil { - c.closeConnection(ClientClosed) - } - - if sysc != nil { - sysc.closeConnection(ClientClosed) - } - - if deleteFlag { - if store != nil { - // Ignore errors. - store.Delete() - } - // Release any resources. - js.releaseStreamResources(&mset.cfg) - // cleanup directories after the stream - accDir := filepath.Join(js.config.StoreDir, accName) - // Do cleanup in separate go routine similar to how fs will use purge here.. - go func() { - // no op if not empty - os.Remove(filepath.Join(accDir, streamsDir)) - os.Remove(accDir) - }() - } else if store != nil { - // Ignore errors. - store.Stop() - } - - return nil -} - -func (mset *stream) getMsg(seq uint64) (*StoredMsg, error) { - var smv StoreMsg - sm, err := mset.store.LoadMsg(seq, &smv) - if err != nil { - return nil, err - } - // This only used in tests directly so no need to pool etc. - return &StoredMsg{ - Subject: sm.subj, - Sequence: sm.seq, - Header: sm.hdr, - Data: sm.msg, - Time: time.Unix(0, sm.ts).UTC(), - }, nil -} - -// getConsumers will return a copy of all the current consumers for this stream. -func (mset *stream) getConsumers() []*consumer { - mset.clsMu.RLock() - defer mset.clsMu.RUnlock() - return append([]*consumer(nil), mset.cList...) -} - -// Lock should be held for this one. -func (mset *stream) numPublicConsumers() int { - return len(mset.consumers) - mset.directs -} - -// This returns all consumers that are not DIRECT. -func (mset *stream) getPublicConsumers() []*consumer { - mset.clsMu.RLock() - defer mset.clsMu.RUnlock() - - var obs []*consumer - for _, o := range mset.cList { - if !o.cfg.Direct { - obs = append(obs, o) - } - } - return obs -} - -// 2 minutes plus up to 30s jitter. -const ( - defaultCheckInterestStateT = 2 * time.Minute - defaultCheckInterestStateJ = 30 -) - -var ( - checkInterestStateT = defaultCheckInterestStateT // Interval - checkInterestStateJ = defaultCheckInterestStateJ // Jitter (secs) -) - -// Will check for interest retention and make sure messages -// that have been acked are processed and removed. -// This will check the ack floors of all consumers, and adjust our first sequence accordingly. -func (mset *stream) checkInterestState() { - if mset == nil || !mset.isInterestRetention() { - // If we are limits based nothing to do. - return - } - - var ss StreamState - mset.store.FastState(&ss) - - for _, o := range mset.getConsumers() { - o.checkStateForInterestStream(&ss) - } -} - -func (mset *stream) isInterestRetention() bool { - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.cfg.Retention != LimitsPolicy -} - -// NumConsumers reports on number of active consumers for this stream. -func (mset *stream) numConsumers() int { - mset.mu.RLock() - defer mset.mu.RUnlock() - return len(mset.consumers) -} - -// Lock should be held. -func (mset *stream) setConsumer(o *consumer) { - mset.consumers[o.name] = o - if len(o.subjf) > 0 { - mset.numFilter++ - } - if o.cfg.Direct { - mset.directs++ - } - // Now update consumers list as well - mset.clsMu.Lock() - mset.cList = append(mset.cList, o) - if mset.csl == nil { - mset.csl = gsl.NewSublist[*consumer]() - } - for _, sub := range o.signalSubs() { - mset.csl.Insert(sub, o) - } - mset.clsMu.Unlock() -} - -// Lock should be held. -func (mset *stream) removeConsumer(o *consumer) { - if o.cfg.FilterSubject != _EMPTY_ && mset.numFilter > 0 { - mset.numFilter-- - } - if o.cfg.Direct && mset.directs > 0 { - mset.directs-- - } - if mset.consumers != nil { - delete(mset.consumers, o.name) - // Now update consumers list as well - mset.clsMu.Lock() - for i, ol := range mset.cList { - if ol == o { - mset.cList = append(mset.cList[:i], mset.cList[i+1:]...) - break - } - } - // Always remove from the leader sublist. - if mset.csl != nil { - for _, sub := range o.signalSubs() { - mset.csl.Remove(sub, o) - } - } - mset.clsMu.Unlock() - } -} - -// swapSigSubs will update signal Subs for a new subject filter. -// consumer lock should not be held. -func (mset *stream) swapSigSubs(o *consumer, newFilters []string) { - mset.clsMu.Lock() - o.mu.Lock() - - if o.closed || o.mset == nil { - o.mu.Unlock() - mset.clsMu.Unlock() - return - } - - if o.sigSubs != nil { - if mset.csl != nil { - for _, sub := range o.sigSubs { - mset.csl.Remove(sub, o) - } - } - o.sigSubs = nil - } - - if o.isLeader() { - if mset.csl == nil { - mset.csl = gsl.NewSublist[*consumer]() - } - // If no filters are preset, add fwcs to sublist for that consumer. - if newFilters == nil { - mset.csl.Insert(fwcs, o) - o.sigSubs = append(o.sigSubs, fwcs) - // If there are filters, add their subjects to sublist. - } else { - for _, filter := range newFilters { - mset.csl.Insert(filter, o) - o.sigSubs = append(o.sigSubs, filter) - } - } - } - o.mu.Unlock() - mset.clsMu.Unlock() - - mset.mu.Lock() - defer mset.mu.Unlock() - - if mset.numFilter > 0 && len(o.subjf) > 0 { - mset.numFilter-- - } - if len(newFilters) > 0 { - mset.numFilter++ - } -} - -// lookupConsumer will retrieve a consumer by name. -func (mset *stream) lookupConsumer(name string) *consumer { - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.consumers[name] -} - -func (mset *stream) numDirectConsumers() (num int) { - mset.clsMu.RLock() - defer mset.clsMu.RUnlock() - - // Consumers that are direct are not recorded at the store level. - for _, o := range mset.cList { - o.mu.RLock() - if o.cfg.Direct { - num++ - } - o.mu.RUnlock() - } - return num -} - -// State will return the current state for this stream. -func (mset *stream) state() StreamState { - return mset.stateWithDetail(false) -} - -func (mset *stream) stateWithDetail(details bool) StreamState { - // mset.store does not change once set, so ok to reference here directly. - // We do this elsewhere as well. - store := mset.store - if store == nil { - return StreamState{} - } - - // Currently rely on store for details. - if details { - return store.State() - } - // Here we do the fast version. - var state StreamState - store.FastState(&state) - return state -} - -func (mset *stream) Store() StreamStore { - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.store -} - -// Determines if the new proposed partition is unique amongst all consumers. -// Lock should be held. -func (mset *stream) partitionUnique(name string, partitions []string) bool { - for _, partition := range partitions { - for n, o := range mset.consumers { - // Skip the consumer being checked. - if n == name { - continue - } - if o.subjf == nil { - return false - } - for _, filter := range o.subjf { - if SubjectsCollide(partition, filter.subject) { - return false - } - } - } - } - return true -} - -// Lock should be held. -func (mset *stream) potentialFilteredConsumers() bool { - numSubjects := len(mset.cfg.Subjects) - if len(mset.consumers) == 0 || numSubjects == 0 { - return false - } - if numSubjects > 1 || subjectHasWildcard(mset.cfg.Subjects[0]) { - return true - } - return false -} - -// Check if there is no interest in this sequence number across our consumers. -// The consumer passed is optional if we are processing the ack for that consumer. -// Write lock should be held. -func (mset *stream) noInterest(seq uint64, obs *consumer) bool { - return !mset.checkForInterest(seq, obs) -} - -// Check if there is no interest in this sequence number and subject across our consumers. -// The consumer passed is optional if we are processing the ack for that consumer. -// Write lock should be held. -func (mset *stream) noInterestWithSubject(seq uint64, subj string, obs *consumer) bool { - return !mset.checkForInterestWithSubject(seq, subj, obs) -} - -// Write lock should be held here for the stream to avoid race conditions on state. -func (mset *stream) checkForInterest(seq uint64, obs *consumer) bool { - var subj string - if mset.potentialFilteredConsumers() { - pmsg := getJSPubMsgFromPool() - defer pmsg.returnToPool() - sm, err := mset.store.LoadMsg(seq, &pmsg.StoreMsg) - if err != nil { - if err == ErrStoreEOF { - // Register this as a preAck. - mset.registerPreAck(obs, seq) - return true - } - mset.clearAllPreAcks(seq) - return false - } - subj = sm.subj - } - return mset.checkForInterestWithSubject(seq, subj, obs) -} - -// Checks for interest given a sequence and subject. -func (mset *stream) checkForInterestWithSubject(seq uint64, subj string, obs *consumer) bool { - for _, o := range mset.consumers { - // If this is us or we have a registered preAck for this consumer continue inspecting. - if o == obs || mset.hasPreAck(o, seq) { - continue - } - // Check if we need an ack. - if o.needAck(seq, subj) { - return true - } - } - mset.clearAllPreAcks(seq) - return false -} - -// Check if we have a pre-registered ack for this sequence. -// Write lock should be held. -func (mset *stream) hasPreAck(o *consumer, seq uint64) bool { - if o == nil || len(mset.preAcks) == 0 { - return false - } - consumers := mset.preAcks[seq] - if len(consumers) == 0 { - return false - } - _, found := consumers[o] - return found -} - -// Check if we have all consumers pre-acked for this sequence and subject. -// Write lock should be held. -func (mset *stream) hasAllPreAcks(seq uint64, subj string) bool { - if len(mset.preAcks) == 0 || len(mset.preAcks[seq]) == 0 { - return false - } - // Since these can be filtered and mutually exclusive, - // if we have some preAcks we need to check all interest here. - return mset.noInterestWithSubject(seq, subj, nil) -} - -// Check if we have all consumers pre-acked. -// Write lock should be held. -func (mset *stream) clearAllPreAcks(seq uint64) { - delete(mset.preAcks, seq) -} - -// Clear all preAcks below floor. -// Write lock should be held. -func (mset *stream) clearAllPreAcksBelowFloor(floor uint64) { - for seq := range mset.preAcks { - if seq < floor { - delete(mset.preAcks, seq) - } - } -} - -// This will register an ack for a consumer if it arrives before the actual message. -func (mset *stream) registerPreAckLock(o *consumer, seq uint64) { - mset.mu.Lock() - defer mset.mu.Unlock() - mset.registerPreAck(o, seq) -} - -// This will register an ack for a consumer if it arrives before -// the actual message. -// Write lock should be held. -func (mset *stream) registerPreAck(o *consumer, seq uint64) { - if o == nil { - return - } - if mset.preAcks == nil { - mset.preAcks = make(map[uint64]map[*consumer]struct{}) - } - if mset.preAcks[seq] == nil { - mset.preAcks[seq] = make(map[*consumer]struct{}) - } - mset.preAcks[seq][o] = struct{}{} -} - -// This will clear an ack for a consumer. -// Write lock should be held. -func (mset *stream) clearPreAck(o *consumer, seq uint64) { - if o == nil || len(mset.preAcks) == 0 { - return - } - if consumers := mset.preAcks[seq]; len(consumers) > 0 { - delete(consumers, o) - if len(consumers) == 0 { - delete(mset.preAcks, seq) - } - } -} - -// ackMsg is called into from a consumer when we have a WorkQueue or Interest Retention Policy. -// Returns whether the message at seq was removed as a result of the ACK. -// (Or should be removed in the case of clustered streams, since it requires a message delete proposal) -func (mset *stream) ackMsg(o *consumer, seq uint64) bool { - if seq == 0 { - return false - } - - // Don't make this RLock(). We need to have only 1 running at a time to gauge interest across all consumers. - mset.mu.Lock() - if mset.closed.Load() || mset.cfg.Retention == LimitsPolicy { - mset.mu.Unlock() - return false - } - - store := mset.store - var state StreamState - store.FastState(&state) - - // If this has arrived before we have processed the message itself. - if seq > state.LastSeq { - mset.registerPreAck(o, seq) - mset.mu.Unlock() - // We have not removed the message, but should still signal so we could retry later - // since we potentially need to remove it then. - return true - } - - // Always clear pre-ack if here. - mset.clearPreAck(o, seq) - - // Make sure this sequence is not below our first sequence. - if seq < state.FirstSeq { - mset.mu.Unlock() - return false - } - - var shouldRemove bool - switch mset.cfg.Retention { - case WorkQueuePolicy: - // Normally we just remove a message when its ack'd here but if we have direct consumers - // from sources and/or mirrors we need to make sure they have delivered the msg. - shouldRemove = mset.directs <= 0 || mset.noInterest(seq, o) - case InterestPolicy: - shouldRemove = mset.noInterest(seq, o) - } - - // If nothing else to do. - if !shouldRemove { - mset.mu.Unlock() - return false - } - - if !mset.isClustered() { - mset.mu.Unlock() - // If we are here we should attempt to remove. - if _, err := store.RemoveMsg(seq); err == ErrStoreEOF { - // This should not happen, but being pedantic. - mset.registerPreAckLock(o, seq) - } - return true - } - - // Only propose message deletion to the stream if we're consumer leader, otherwise all followers would also propose. - // We must be the consumer leader, since we know for sure we've stored the message and don't register as pre-ack. - if o != nil && !o.IsLeader() { - mset.mu.Unlock() - // Must still mark as removal if follower. If we become leader later, we must be able to retry the proposal. - return true - } - - md := streamMsgDelete{Seq: seq, NoErase: true, Stream: mset.cfg.Name} - mset.node.ForwardProposal(encodeMsgDelete(&md)) - mset.mu.Unlock() - return true -} - -// Snapshot creates a snapshot for the stream and possibly consumers. -func (mset *stream) snapshot(deadline time.Duration, checkMsgs, includeConsumers bool) (*SnapshotResult, error) { - if mset.closed.Load() { - return nil, errStreamClosed - } - store := mset.store - return store.Snapshot(deadline, checkMsgs, includeConsumers) -} - -const snapsDir = "__snapshots__" - -// RestoreStream will restore a stream from a snapshot. -func (a *Account) RestoreStream(ncfg *StreamConfig, r io.Reader) (*stream, error) { - if ncfg == nil { - return nil, errors.New("nil config on stream restore") - } - - s, jsa, err := a.checkForJetStream() - if err != nil { - return nil, err - } - - cfg, apiErr := s.checkStreamCfg(ncfg, a, false) - if apiErr != nil { - return nil, apiErr - } - - sd := filepath.Join(jsa.storeDir, snapsDir) - if _, err := os.Stat(sd); os.IsNotExist(err) { - if err := os.MkdirAll(sd, defaultDirPerms); err != nil { - return nil, fmt.Errorf("could not create snapshots directory - %v", err) - } - } - sdir, err := os.MkdirTemp(sd, "snap-") - if err != nil { - return nil, err - } - if _, err := os.Stat(sdir); os.IsNotExist(err) { - if err := os.MkdirAll(sdir, defaultDirPerms); err != nil { - return nil, fmt.Errorf("could not create snapshots directory - %v", err) - } - } - defer os.RemoveAll(sdir) - - logAndReturnError := func() error { - a.mu.RLock() - err := fmt.Errorf("unexpected content (account=%s)", a.Name) - if a.srv != nil { - a.srv.Errorf("Stream restore failed due to %v", err) - } - a.mu.RUnlock() - return err - } - sdirCheck := filepath.Clean(sdir) + string(os.PathSeparator) - - tr := tar.NewReader(s2.NewReader(r)) - for { - hdr, err := tr.Next() - if err == io.EOF { - break // End of snapshot - } - if err != nil { - return nil, err - } - if hdr.Typeflag != tar.TypeReg { - return nil, logAndReturnError() - } - fpath := filepath.Join(sdir, filepath.Clean(hdr.Name)) - if !strings.HasPrefix(fpath, sdirCheck) { - return nil, logAndReturnError() - } - os.MkdirAll(filepath.Dir(fpath), defaultDirPerms) - fd, err := os.OpenFile(fpath, os.O_CREATE|os.O_RDWR, 0600) - if err != nil { - return nil, err - } - _, err = io.Copy(fd, tr) - fd.Close() - if err != nil { - return nil, err - } - } - - // Check metadata. - // The cfg passed in will be the new identity for the stream. - var fcfg FileStreamInfo - b, err := os.ReadFile(filepath.Join(sdir, JetStreamMetaFile)) - if err != nil { - return nil, err - } - if err := json.Unmarshal(b, &fcfg); err != nil { - return nil, err - } - - // Check to make sure names match. - if fcfg.Name != cfg.Name { - return nil, errors.New("stream names do not match") - } - - // See if this stream already exists. - if _, err := a.lookupStream(cfg.Name); err == nil { - return nil, NewJSStreamNameExistRestoreFailedError() - } - // Move into the correct place here. - ndir := filepath.Join(jsa.storeDir, streamsDir, cfg.Name) - // Remove old one if for some reason it is still here. - if _, err := os.Stat(ndir); err == nil { - os.RemoveAll(ndir) - } - // Make sure our destination streams directory exists. - if err := os.MkdirAll(filepath.Join(jsa.storeDir, streamsDir), defaultDirPerms); err != nil { - return nil, err - } - // Move into new location. - if err := os.Rename(sdir, ndir); err != nil { - return nil, err - } - - if cfg.Template != _EMPTY_ { - if err := jsa.addStreamNameToTemplate(cfg.Template, cfg.Name); err != nil { - return nil, err - } - } - mset, err := a.addStream(&cfg) - if err != nil { - // Make sure to clean up after ourselves here. - os.RemoveAll(ndir) - return nil, err - } - if !fcfg.Created.IsZero() { - mset.setCreatedTime(fcfg.Created) - } - lseq := mset.lastSeq() - - // Make sure we do an update if the configs have changed. - if !reflect.DeepEqual(fcfg.StreamConfig, cfg) { - if err := mset.update(&cfg); err != nil { - return nil, err - } - } - - // Now do consumers. - odir := filepath.Join(ndir, consumerDir) - ofis, _ := os.ReadDir(odir) - for _, ofi := range ofis { - metafile := filepath.Join(odir, ofi.Name(), JetStreamMetaFile) - metasum := filepath.Join(odir, ofi.Name(), JetStreamMetaFileSum) - if _, err := os.Stat(metafile); os.IsNotExist(err) { - mset.stop(true, false) - return nil, fmt.Errorf("error restoring consumer [%q]: %v", ofi.Name(), err) - } - buf, err := os.ReadFile(metafile) - if err != nil { - mset.stop(true, false) - return nil, fmt.Errorf("error restoring consumer [%q]: %v", ofi.Name(), err) - } - if _, err := os.Stat(metasum); os.IsNotExist(err) { - mset.stop(true, false) - return nil, fmt.Errorf("error restoring consumer [%q]: %v", ofi.Name(), err) - } - var cfg FileConsumerInfo - if err := json.Unmarshal(buf, &cfg); err != nil { - mset.stop(true, false) - return nil, fmt.Errorf("error restoring consumer [%q]: %v", ofi.Name(), err) - } - isEphemeral := !isDurableConsumer(&cfg.ConsumerConfig) - if isEphemeral { - // This is an ephermal consumer and this could fail on restart until - // the consumer can reconnect. We will create it as a durable and switch it. - cfg.ConsumerConfig.Durable = ofi.Name() - } - obs, err := mset.addConsumer(&cfg.ConsumerConfig) - if err != nil { - mset.stop(true, false) - return nil, fmt.Errorf("error restoring consumer [%q]: %v", ofi.Name(), err) - } - if isEphemeral { - obs.switchToEphemeral() - } - if !cfg.Created.IsZero() { - obs.setCreatedTime(cfg.Created) - } - obs.mu.Lock() - err = obs.readStoredState(lseq) - obs.mu.Unlock() - if err != nil { - mset.stop(true, false) - return nil, fmt.Errorf("error restoring consumer [%q]: %v", ofi.Name(), err) - } - } - return mset, nil -} - -// This is to check for dangling messages on interest retention streams. Only called on account enable. -// Issue https://github.com/nats-io/nats-server/issues/3612 -func (mset *stream) checkForOrphanMsgs() { - mset.mu.RLock() - consumers := make([]*consumer, 0, len(mset.consumers)) - for _, o := range mset.consumers { - consumers = append(consumers, o) - } - accName, stream := mset.acc.Name, mset.cfg.Name - - var ss StreamState - mset.store.FastState(&ss) - mset.mu.RUnlock() - - for _, o := range consumers { - if err := o.checkStateForInterestStream(&ss); err == errAckFloorHigherThanLastSeq { - o.mu.RLock() - s, consumer := o.srv, o.name - state, _ := o.store.State() - asflr := state.AckFloor.Stream - o.mu.RUnlock() - // Warn about stream state vs our ack floor. - s.RateLimitWarnf("Detected consumer '%s > %s > %s' ack floor %d is ahead of stream's last sequence %d", - accName, stream, consumer, asflr, ss.LastSeq) - } - } -} - -// Check on startup to make sure that consumers replication matches us. -// Interest retention requires replication matches. -func (mset *stream) checkConsumerReplication() { - mset.mu.RLock() - defer mset.mu.RUnlock() - - if mset.cfg.Retention != InterestPolicy { - return - } - - s, acc := mset.srv, mset.acc - for _, o := range mset.consumers { - o.mu.RLock() - // Consumer replicas 0 can be a legit config for the replicas and we will inherit from the stream - // when this is the case. - if mset.cfg.Replicas != o.cfg.Replicas && o.cfg.Replicas != 0 { - s.Errorf("consumer '%s > %s > %s' MUST match replication (%d vs %d) of stream with interest policy", - acc, mset.cfg.Name, o.cfg.Name, mset.cfg.Replicas, o.cfg.Replicas) - } - o.mu.RUnlock() - } -} - -// Will check if we are running in the monitor already and if not set the appropriate flag. -func (mset *stream) checkInMonitor() bool { - mset.mu.Lock() - defer mset.mu.Unlock() - - if mset.inMonitor { - return true - } - mset.inMonitor = true - return false -} - -// Clear us being in the monitor routine. -func (mset *stream) clearMonitorRunning() { - mset.mu.Lock() - defer mset.mu.Unlock() - mset.inMonitor = false -} - -// Check if our monitor is running. -func (mset *stream) isMonitorRunning() bool { - mset.mu.RLock() - defer mset.mu.RUnlock() - return mset.inMonitor -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/dump.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/dump.go deleted file mode 100644 index 12c62f3b..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/dump.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stree - -import ( - "fmt" - "io" - "strings" -) - -// For dumping out a text representation of a tree. -func (t *SubjectTree[T]) Dump(w io.Writer) { - t.dump(w, t.root, 0) - fmt.Fprintln(w) -} - -// Will dump out a node. -func (t *SubjectTree[T]) dump(w io.Writer, n node, depth int) { - if n == nil { - fmt.Fprintf(w, "EMPTY\n") - return - } - if n.isLeaf() { - leaf := n.(*leaf[T]) - fmt.Fprintf(w, "%s LEAF: Suffix: %q Value: %+v\n", dumpPre(depth), leaf.suffix, leaf.value) - n = nil - } else { - // We are a node type here, grab meta portion. - bn := n.base() - fmt.Fprintf(w, "%s %s Prefix: %q\n", dumpPre(depth), n.kind(), bn.prefix) - depth++ - n.iter(func(n node) bool { - t.dump(w, n, depth) - return true - }) - } -} - -// For individual node/leaf dumps. -func (n *leaf[T]) kind() string { return "LEAF" } -func (n *node4) kind() string { return "NODE4" } -func (n *node10) kind() string { return "NODE10" } -func (n *node16) kind() string { return "NODE16" } -func (n *node48) kind() string { return "NODE48" } -func (n *node256) kind() string { return "NODE256" } - -// Calculates the indendation, etc. -func dumpPre(depth int) string { - if depth == 0 { - return "-- " - } else { - var b strings.Builder - for i := 0; i < depth; i++ { - b.WriteString(" ") - } - b.WriteString("|__ ") - return b.String() - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/leaf.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/leaf.go deleted file mode 100644 index 119837ec..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/leaf.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2023-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stree - -import ( - "bytes" -) - -// Leaf node -// Order of struct fields for best memory alignment (as per govet/fieldalignment) -type leaf[T any] struct { - value T - // This could be the whole subject, but most likely just the suffix portion. - // We will only store the suffix here and assume all prior prefix paths have - // been checked once we arrive at this leafnode. - suffix []byte -} - -func newLeaf[T any](suffix []byte, value T) *leaf[T] { - return &leaf[T]{value, copyBytes(suffix)} -} - -func (n *leaf[T]) isLeaf() bool { return true } -func (n *leaf[T]) base() *meta { return nil } -func (n *leaf[T]) match(subject []byte) bool { return bytes.Equal(subject, n.suffix) } -func (n *leaf[T]) setSuffix(suffix []byte) { n.suffix = copyBytes(suffix) } -func (n *leaf[T]) isFull() bool { return true } -func (n *leaf[T]) matchParts(parts [][]byte) ([][]byte, bool) { return matchParts(parts, n.suffix) } -func (n *leaf[T]) iter(f func(node) bool) {} -func (n *leaf[T]) children() []node { return nil } -func (n *leaf[T]) numChildren() uint16 { return 0 } -func (n *leaf[T]) path() []byte { return n.suffix } - -// Not applicable to leafs and should not be called, so panic if we do. -func (n *leaf[T]) setPrefix(pre []byte) { panic("setPrefix called on leaf") } -func (n *leaf[T]) addChild(_ byte, _ node) { panic("addChild called on leaf") } -func (n *leaf[T]) findChild(_ byte) *node { panic("findChild called on leaf") } -func (n *leaf[T]) grow() node { panic("grow called on leaf") } -func (n *leaf[T]) deleteChild(_ byte) { panic("deleteChild called on leaf") } -func (n *leaf[T]) shrink() node { panic("shrink called on leaf") } diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node.go deleted file mode 100644 index c8edfe3e..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2023-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stree - -// Internal node interface. -type node interface { - isLeaf() bool - base() *meta - setPrefix(pre []byte) - addChild(c byte, n node) - findChild(c byte) *node - deleteChild(c byte) - isFull() bool - grow() node - shrink() node - matchParts(parts [][]byte) ([][]byte, bool) - kind() string - iter(f func(node) bool) - children() []node - numChildren() uint16 - path() []byte -} - -type meta struct { - prefix []byte - size uint16 -} - -func (n *meta) isLeaf() bool { return false } -func (n *meta) base() *meta { return n } - -func (n *meta) setPrefix(pre []byte) { - n.prefix = append([]byte(nil), pre...) -} - -func (n *meta) numChildren() uint16 { return n.size } -func (n *meta) path() []byte { return n.prefix } - -// Will match parts against our prefix. -func (n *meta) matchParts(parts [][]byte) ([][]byte, bool) { - return matchParts(parts, n.prefix) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node10.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node10.go deleted file mode 100644 index 37cd2cc9..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node10.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2023-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stree - -// Node with 10 children -// This node size is for the particular case that a part of the subject is numeric -// in nature, i.e. it only needs to satisfy the range 0-9 without wasting bytes -// Order of struct fields for best memory alignment (as per govet/fieldalignment) -type node10 struct { - child [10]node - meta - key [10]byte -} - -func newNode10(prefix []byte) *node10 { - nn := &node10{} - nn.setPrefix(prefix) - return nn -} - -// Currently we do not keep node10 sorted or use bitfields for traversal so just add to the end. -// TODO(dlc) - We should revisit here with more detailed benchmarks. -func (n *node10) addChild(c byte, nn node) { - if n.size >= 10 { - panic("node10 full!") - } - n.key[n.size] = c - n.child[n.size] = nn - n.size++ -} - -func (n *node10) findChild(c byte) *node { - for i := uint16(0); i < n.size; i++ { - if n.key[i] == c { - return &n.child[i] - } - } - return nil -} - -func (n *node10) isFull() bool { return n.size >= 10 } - -func (n *node10) grow() node { - nn := newNode16(n.prefix) - for i := 0; i < 10; i++ { - nn.addChild(n.key[i], n.child[i]) - } - return nn -} - -// Deletes a child from the node. -func (n *node10) deleteChild(c byte) { - for i, last := uint16(0), n.size-1; i < n.size; i++ { - if n.key[i] == c { - // Unsorted so just swap in last one here, else nil if last. - if i < last { - n.key[i] = n.key[last] - n.child[i] = n.child[last] - n.key[last] = 0 - n.child[last] = nil - } else { - n.key[i] = 0 - n.child[i] = nil - } - n.size-- - return - } - } -} - -// Shrink if needed and return new node, otherwise return nil. -func (n *node10) shrink() node { - if n.size > 4 { - return nil - } - nn := newNode4(nil) - for i := uint16(0); i < n.size; i++ { - nn.addChild(n.key[i], n.child[i]) - } - return nn -} - -// Iterate over all children calling func f. -func (n *node10) iter(f func(node) bool) { - for i := uint16(0); i < n.size; i++ { - if !f(n.child[i]) { - return - } - } -} - -// Return our children as a slice. -func (n *node10) children() []node { - return n.child[:n.size] -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node16.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node16.go deleted file mode 100644 index e2dc9790..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node16.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2023-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stree - -// Node with 16 children -// Order of struct fields for best memory alignment (as per govet/fieldalignment) -type node16 struct { - child [16]node - meta - key [16]byte -} - -func newNode16(prefix []byte) *node16 { - nn := &node16{} - nn.setPrefix(prefix) - return nn -} - -// Currently we do not keep node16 sorted or use bitfields for traversal so just add to the end. -// TODO(dlc) - We should revisit here with more detailed benchmarks. -func (n *node16) addChild(c byte, nn node) { - if n.size >= 16 { - panic("node16 full!") - } - n.key[n.size] = c - n.child[n.size] = nn - n.size++ -} - -func (n *node16) findChild(c byte) *node { - for i := uint16(0); i < n.size; i++ { - if n.key[i] == c { - return &n.child[i] - } - } - return nil -} - -func (n *node16) isFull() bool { return n.size >= 16 } - -func (n *node16) grow() node { - nn := newNode48(n.prefix) - for i := 0; i < 16; i++ { - nn.addChild(n.key[i], n.child[i]) - } - return nn -} - -// Deletes a child from the node. -func (n *node16) deleteChild(c byte) { - for i, last := uint16(0), n.size-1; i < n.size; i++ { - if n.key[i] == c { - // Unsorted so just swap in last one here, else nil if last. - if i < last { - n.key[i] = n.key[last] - n.child[i] = n.child[last] - n.key[last] = 0 - n.child[last] = nil - } else { - n.key[i] = 0 - n.child[i] = nil - } - n.size-- - return - } - } -} - -// Shrink if needed and return new node, otherwise return nil. -func (n *node16) shrink() node { - if n.size > 10 { - return nil - } - nn := newNode10(nil) - for i := uint16(0); i < n.size; i++ { - nn.addChild(n.key[i], n.child[i]) - } - return nn -} - -// Iterate over all children calling func f. -func (n *node16) iter(f func(node) bool) { - for i := uint16(0); i < n.size; i++ { - if !f(n.child[i]) { - return - } - } -} - -// Return our children as a slice. -func (n *node16) children() []node { - return n.child[:n.size] -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node256.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node256.go deleted file mode 100644 index 5d08b148..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node256.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2023-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stree - -// Node with 256 children -// Order of struct fields for best memory alignment (as per govet/fieldalignment) -type node256 struct { - child [256]node - meta -} - -func newNode256(prefix []byte) *node256 { - nn := &node256{} - nn.setPrefix(prefix) - return nn -} - -func (n *node256) addChild(c byte, nn node) { - n.child[c] = nn - n.size++ -} - -func (n *node256) findChild(c byte) *node { - if n.child[c] != nil { - return &n.child[c] - } - return nil -} - -func (n *node256) isFull() bool { return false } -func (n *node256) grow() node { panic("grow can not be called on node256") } - -// Deletes a child from the node. -func (n *node256) deleteChild(c byte) { - if n.child[c] != nil { - n.child[c] = nil - n.size-- - } -} - -// Shrink if needed and return new node, otherwise return nil. -func (n *node256) shrink() node { - if n.size > 48 { - return nil - } - nn := newNode48(nil) - for c, child := range n.child { - if child != nil { - nn.addChild(byte(c), n.child[c]) - } - } - return nn -} - -// Iterate over all children calling func f. -func (n *node256) iter(f func(node) bool) { - for i := 0; i < 256; i++ { - if n.child[i] != nil { - if !f(n.child[i]) { - return - } - } - } -} - -// Return our children as a slice. -func (n *node256) children() []node { - return n.child[:256] -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node4.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node4.go deleted file mode 100644 index 4eddf11b..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node4.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2023-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stree - -// Node with 4 children -// Order of struct fields for best memory alignment (as per govet/fieldalignment) -type node4 struct { - child [4]node - meta - key [4]byte -} - -func newNode4(prefix []byte) *node4 { - nn := &node4{} - nn.setPrefix(prefix) - return nn -} - -// Currently we do not need to keep sorted for traversal so just add to the end. -func (n *node4) addChild(c byte, nn node) { - if n.size >= 4 { - panic("node4 full!") - } - n.key[n.size] = c - n.child[n.size] = nn - n.size++ -} - -func (n *node4) findChild(c byte) *node { - for i := uint16(0); i < n.size; i++ { - if n.key[i] == c { - return &n.child[i] - } - } - return nil -} - -func (n *node4) isFull() bool { return n.size >= 4 } - -func (n *node4) grow() node { - nn := newNode10(n.prefix) - for i := 0; i < 4; i++ { - nn.addChild(n.key[i], n.child[i]) - } - return nn -} - -// Deletes a child from the node. -func (n *node4) deleteChild(c byte) { - for i, last := uint16(0), n.size-1; i < n.size; i++ { - if n.key[i] == c { - // Unsorted so just swap in last one here, else nil if last. - if i < last { - n.key[i] = n.key[last] - n.child[i] = n.child[last] - n.key[last] = 0 - n.child[last] = nil - } else { - n.key[i] = 0 - n.child[i] = nil - } - n.size-- - return - } - } -} - -// Shrink if needed and return new node, otherwise return nil. -func (n *node4) shrink() node { - if n.size == 1 { - return n.child[0] - } - return nil -} - -// Iterate over all children calling func f. -func (n *node4) iter(f func(node) bool) { - for i := uint16(0); i < n.size; i++ { - if !f(n.child[i]) { - return - } - } -} - -// Return our children as a slice. -func (n *node4) children() []node { - return n.child[:n.size] -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node48.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node48.go deleted file mode 100644 index 7099edd5..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/node48.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2023-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stree - -// Node with 48 children -// Memory saving vs node256 comes from the fact that the child array is 16 bytes -// per `node` entry, so node256's 256*16=4096 vs node48's 256+(48*16)=1024 -// Note that `key` is effectively 1-indexed, as 0 means no entry, so offset by 1 -// Order of struct fields for best memory alignment (as per govet/fieldalignment) -type node48 struct { - child [48]node - meta - key [256]byte -} - -func newNode48(prefix []byte) *node48 { - nn := &node48{} - nn.setPrefix(prefix) - return nn -} - -func (n *node48) addChild(c byte, nn node) { - if n.size >= 48 { - panic("node48 full!") - } - n.child[n.size] = nn - n.key[c] = byte(n.size + 1) // 1-indexed - n.size++ -} - -func (n *node48) findChild(c byte) *node { - i := n.key[c] - if i == 0 { - return nil - } - return &n.child[i-1] -} - -func (n *node48) isFull() bool { return n.size >= 48 } - -func (n *node48) grow() node { - nn := newNode256(n.prefix) - for c := 0; c < len(n.key); c++ { - if i := n.key[byte(c)]; i > 0 { - nn.addChild(byte(c), n.child[i-1]) - } - } - return nn -} - -// Deletes a child from the node. -func (n *node48) deleteChild(c byte) { - i := n.key[c] - if i == 0 { - return - } - i-- // Adjust for 1-indexing - last := byte(n.size - 1) - if i < last { - n.child[i] = n.child[last] - for ic := 0; ic < len(n.key); ic++ { - if n.key[byte(ic)] == last+1 { - n.key[byte(ic)] = i + 1 - break - } - } - } - n.child[last] = nil - n.key[c] = 0 - n.size-- -} - -// Shrink if needed and return new node, otherwise return nil. -func (n *node48) shrink() node { - if n.size > 16 { - return nil - } - nn := newNode16(nil) - for c := 0; c < len(n.key); c++ { - if i := n.key[byte(c)]; i > 0 { - nn.addChild(byte(c), n.child[i-1]) - } - } - return nn -} - -// Iterate over all children calling func f. -func (n *node48) iter(f func(node) bool) { - for _, c := range n.child { - if c != nil && !f(c) { - return - } - } -} - -// Return our children as a slice. -func (n *node48) children() []node { - return n.child[:n.size] -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/parts.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/parts.go deleted file mode 100644 index 62236c5a..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/parts.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2023-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stree - -import ( - "bytes" -) - -// genParts will break a filter subject up into parts. -// We need to break this up into chunks based on wildcards, either pwc '*' or fwc '>'. -// We do not care about other tokens per se, just parts that are separated by wildcards with an optional end fwc. -func genParts(filter []byte, parts [][]byte) [][]byte { - var start int - for i, e := 0, len(filter)-1; i < len(filter); i++ { - if filter[i] == tsep { - // See if next token is pwc. Either internal or end pwc. - if i < e && filter[i+1] == pwc && (i+2 <= e && filter[i+2] == tsep || i+1 == e) { - if i > start { - parts = append(parts, filter[start:i+1]) - } - parts = append(parts, filter[i+1:i+2]) - i++ // Skip pwc - if i+2 <= e { - i++ // Skip next tsep from next part too. - } - start = i + 1 - } else if i < e && filter[i+1] == fwc && i+1 == e { - // We have a fwc - if i > start { - parts = append(parts, filter[start:i+1]) - } - parts = append(parts, filter[i+1:i+2]) - i++ // Skip fwc - start = i + 1 - } - } else if filter[i] == pwc || filter[i] == fwc { - // Wildcard must be at the start or preceded by tsep. - if prev := i - 1; prev >= 0 && filter[prev] != tsep { - continue - } - // Wildcard must be at the end or followed by tsep. - if next := i + 1; next == e || next < e && filter[next] != tsep { - continue - } - // We start with a pwc or fwc. - parts = append(parts, filter[i:i+1]) - if i+1 <= e { - i++ // Skip next tsep from next part too. - } - start = i + 1 - } - } - if start < len(filter) { - // Check to see if we need to eat a leading tsep. - if filter[start] == tsep { - start++ - } - parts = append(parts, filter[start:]) - } - return parts -} - -// Match our parts against a fragment, which could be prefix for nodes or a suffix for leafs. -func matchParts(parts [][]byte, frag []byte) ([][]byte, bool) { - lf := len(frag) - if lf == 0 { - return parts, true - } - - var si int - lpi := len(parts) - 1 - - for i, part := range parts { - if si >= lf { - return parts[i:], true - } - lp := len(part) - // Check for pwc or fwc place holders. - if lp == 1 { - if part[0] == pwc { - index := bytes.IndexByte(frag[si:], tsep) - // We are trying to match pwc and did not find our tsep. - // Will need to move to next node from caller. - if index < 0 { - if i == lpi { - return nil, true - } - return parts[i:], true - } - si += index + 1 - continue - } else if part[0] == fwc { - // If we are here we should be good. - return nil, true - } - } - end := min(si+lp, lf) - // If part is bigger then the remaining fragment, adjust to a portion on the part. - if si+lp > end { - // Frag is smaller then part itself. - part = part[:end-si] - } - if !bytes.Equal(part, frag[si:end]) { - return parts, false - } - // If we still have a portion of the fragment left, update and continue. - if end < lf { - si = end - continue - } - // If we matched a partial, do not move past current part - // but update the part to what was consumed. This allows upper layers to continue. - if end < si+lp { - if end >= lf { - parts = append([][]byte{}, parts...) // Create a copy before modifying. - parts[i] = parts[i][lf-si:] - } else { - i++ - } - return parts[i:], true - } - if i == lpi { - return nil, true - } - // If we are here we are not the last part which means we have a wildcard - // gap, so we need to match anything up to next tsep. - si += len(part) - } - return parts, false -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go deleted file mode 100644 index 7cb23a56..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/stree.go +++ /dev/null @@ -1,420 +0,0 @@ -// Copyright 2023-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stree - -import ( - "bytes" - "slices" -) - -// SubjectTree is an adaptive radix trie (ART) for storing subject information on literal subjects. -// Will use dynamic nodes, path compression and lazy expansion. -// The reason this exists is to not only save some memory in our filestore but to greatly optimize matching -// a wildcard subject to certain members, e.g. consumer NumPending calculations. -type SubjectTree[T any] struct { - root node - size int -} - -// NewSubjectTree creates a new SubjectTree with values T. -func NewSubjectTree[T any]() *SubjectTree[T] { - return &SubjectTree[T]{} -} - -// Size returns the number of elements stored. -func (t *SubjectTree[T]) Size() int { - if t == nil { - return 0 - } - return t.size -} - -// Will empty out the tree, or if tree is nil create a new one. -func (t *SubjectTree[T]) Empty() *SubjectTree[T] { - if t == nil { - return NewSubjectTree[T]() - } - t.root, t.size = nil, 0 - return t -} - -// Insert a value into the tree. Will return if the value was updated and if so the old value. -func (t *SubjectTree[T]) Insert(subject []byte, value T) (*T, bool) { - if t == nil { - return nil, false - } - - // Make sure we never insert anything with a noPivot byte. - if bytes.IndexByte(subject, noPivot) >= 0 { - return nil, false - } - - old, updated := t.insert(&t.root, subject, value, 0) - if !updated { - t.size++ - } - return old, updated -} - -// Find will find the value and return it or false if it was not found. -func (t *SubjectTree[T]) Find(subject []byte) (*T, bool) { - if t == nil { - return nil, false - } - - var si int - for n := t.root; n != nil; { - if n.isLeaf() { - if ln := n.(*leaf[T]); ln.match(subject[si:]) { - return &ln.value, true - } - return nil, false - } - // We are a node type here, grab meta portion. - if bn := n.base(); len(bn.prefix) > 0 { - end := min(si+len(bn.prefix), len(subject)) - if !bytes.Equal(subject[si:end], bn.prefix) { - return nil, false - } - // Increment our subject index. - si += len(bn.prefix) - } - if an := n.findChild(pivot(subject, si)); an != nil { - n = *an - } else { - return nil, false - } - } - return nil, false -} - -// Delete will delete the item and return its value, or not found if it did not exist. -func (t *SubjectTree[T]) Delete(subject []byte) (*T, bool) { - if t == nil { - return nil, false - } - - val, deleted := t.delete(&t.root, subject, 0) - if deleted { - t.size-- - } - return val, deleted -} - -// Match will match against a subject that can have wildcards and invoke the callback func for each matched value. -func (t *SubjectTree[T]) Match(filter []byte, cb func(subject []byte, val *T)) { - if t == nil || t.root == nil || len(filter) == 0 || cb == nil { - return - } - // We need to break this up into chunks based on wildcards, either pwc '*' or fwc '>'. - var raw [16][]byte - parts := genParts(filter, raw[:0]) - var _pre [256]byte - t.match(t.root, parts, _pre[:0], cb) -} - -// IterOrdered will walk all entries in the SubjectTree lexographically. The callback can return false to terminate the walk. -func (t *SubjectTree[T]) IterOrdered(cb func(subject []byte, val *T) bool) { - if t == nil || t.root == nil { - return - } - var _pre [256]byte - t.iter(t.root, _pre[:0], true, cb) -} - -// IterFast will walk all entries in the SubjectTree with no guarantees of ordering. The callback can return false to terminate the walk. -func (t *SubjectTree[T]) IterFast(cb func(subject []byte, val *T) bool) { - if t == nil || t.root == nil { - return - } - var _pre [256]byte - t.iter(t.root, _pre[:0], false, cb) -} - -// Internal methods - -// Internal call to insert that can be recursive. -func (t *SubjectTree[T]) insert(np *node, subject []byte, value T, si int) (*T, bool) { - n := *np - if n == nil { - *np = newLeaf(subject, value) - return nil, false - } - if n.isLeaf() { - ln := n.(*leaf[T]) - if ln.match(subject[si:]) { - // Replace with new value. - old := ln.value - ln.value = value - return &old, true - } - // Here we need to split this leaf. - cpi := commonPrefixLen(ln.suffix, subject[si:]) - nn := newNode4(subject[si : si+cpi]) - ln.setSuffix(ln.suffix[cpi:]) - si += cpi - // Make sure we have different pivot, normally this will be the case unless we have overflowing prefixes. - if p := pivot(ln.suffix, 0); cpi > 0 && si < len(subject) && p == subject[si] { - // We need to split the original leaf. Recursively call into insert. - t.insert(np, subject, value, si) - // Now add the update version of *np as a child to the new node4. - nn.addChild(p, *np) - } else { - // Can just add this new leaf as a sibling. - nl := newLeaf(subject[si:], value) - nn.addChild(pivot(nl.suffix, 0), nl) - // Add back original. - nn.addChild(pivot(ln.suffix, 0), ln) - } - *np = nn - return nil, false - } - - // Non-leaf nodes. - bn := n.base() - if len(bn.prefix) > 0 { - cpi := commonPrefixLen(bn.prefix, subject[si:]) - if pli := len(bn.prefix); cpi >= pli { - // Move past this node. We look for an existing child node to recurse into. - // If one does not exist we can create a new leaf node. - si += pli - if nn := n.findChild(pivot(subject, si)); nn != nil { - return t.insert(nn, subject, value, si) - } - if n.isFull() { - n = n.grow() - *np = n - } - n.addChild(pivot(subject, si), newLeaf(subject[si:], value)) - return nil, false - } else { - // We did not match the prefix completely here. - // Calculate new prefix for this node. - prefix := subject[si : si+cpi] - si += len(prefix) - // We will insert a new node4 and attach our current node below after adjusting prefix. - nn := newNode4(prefix) - // Shift the prefix for our original node. - n.setPrefix(bn.prefix[cpi:]) - nn.addChild(pivot(bn.prefix[:], 0), n) - // Add in our new leaf. - nn.addChild(pivot(subject[si:], 0), newLeaf(subject[si:], value)) - // Update our node reference. - *np = nn - } - } else { - if nn := n.findChild(pivot(subject, si)); nn != nil { - return t.insert(nn, subject, value, si) - } - // No prefix and no matched child, so add in new leafnode as needed. - if n.isFull() { - n = n.grow() - *np = n - } - n.addChild(pivot(subject, si), newLeaf(subject[si:], value)) - } - - return nil, false -} - -// internal function to recursively find the leaf to delete. Will do compaction if the item is found and removed. -func (t *SubjectTree[T]) delete(np *node, subject []byte, si int) (*T, bool) { - if t == nil || np == nil || *np == nil || len(subject) == 0 { - return nil, false - } - n := *np - if n.isLeaf() { - ln := n.(*leaf[T]) - if ln.match(subject[si:]) { - *np = nil - return &ln.value, true - } - return nil, false - } - // Not a leaf node. - if bn := n.base(); len(bn.prefix) > 0 { - if !bytes.Equal(subject[si:si+len(bn.prefix)], bn.prefix) { - return nil, false - } - // Increment our subject index. - si += len(bn.prefix) - } - p := pivot(subject, si) - nna := n.findChild(p) - if nna == nil { - return nil, false - } - nn := *nna - if nn.isLeaf() { - ln := nn.(*leaf[T]) - if ln.match(subject[si:]) { - n.deleteChild(p) - - if sn := n.shrink(); sn != nil { - bn := n.base() - // Make sure to set cap so we force an append to copy below. - pre := bn.prefix[:len(bn.prefix):len(bn.prefix)] - // Need to fix up prefixes/suffixes. - if sn.isLeaf() { - ln := sn.(*leaf[T]) - // Make sure to set cap so we force an append to copy. - ln.suffix = append(pre, ln.suffix...) - } else { - // We are a node here, we need to add in the old prefix. - if len(pre) > 0 { - bsn := sn.base() - sn.setPrefix(append(pre, bsn.prefix...)) - } - } - *np = sn - } - - return &ln.value, true - } - return nil, false - } - return t.delete(nna, subject, si) -} - -// Internal function which can be called recursively to match all leaf nodes to a given filter subject which -// once here has been decomposed to parts. These parts only care about wildcards, both pwc and fwc. -func (t *SubjectTree[T]) match(n node, parts [][]byte, pre []byte, cb func(subject []byte, val *T)) { - // Capture if we are sitting on a terminal fwc. - var hasFWC bool - if lp := len(parts); lp > 0 && len(parts[lp-1]) > 0 && parts[lp-1][0] == fwc { - hasFWC = true - } - - for n != nil { - nparts, matched := n.matchParts(parts) - // Check if we did not match. - if !matched { - return - } - // We have matched here. If we are a leaf and have exhausted all parts or he have a FWC fire callback. - if n.isLeaf() { - if len(nparts) == 0 || (hasFWC && len(nparts) == 1) { - ln := n.(*leaf[T]) - cb(append(pre, ln.suffix...), &ln.value) - } - return - } - // We have normal nodes here. - // We need to append our prefix - bn := n.base() - if len(bn.prefix) > 0 { - // Note that this append may reallocate, but it doesn't modify "pre" at the "match" callsite. - pre = append(pre, bn.prefix...) - } - - // Check our remaining parts. - if len(nparts) == 0 && !hasFWC { - // We are a node with no parts left and we are not looking at a fwc. - // We could have a leafnode with no suffix which would be a match. - // We could also have a terminal pwc. Check for those here. - var hasTermPWC bool - if lp := len(parts); lp > 0 && len(parts[lp-1]) == 1 && parts[lp-1][0] == pwc { - // If we are sitting on a terminal pwc, put the pwc back and continue. - nparts = parts[len(parts)-1:] - hasTermPWC = true - } - for _, cn := range n.children() { - if cn == nil { - continue - } - if cn.isLeaf() { - ln := cn.(*leaf[T]) - if len(ln.suffix) == 0 { - cb(append(pre, ln.suffix...), &ln.value) - } else if hasTermPWC && bytes.IndexByte(ln.suffix, tsep) < 0 { - cb(append(pre, ln.suffix...), &ln.value) - } - } else if hasTermPWC { - // We have terminal pwc so call into match again with the child node. - t.match(cn, nparts, pre, cb) - } - } - // Return regardless. - return - } - // If we are sitting on a terminal fwc, put back and continue. - if hasFWC && len(nparts) == 0 { - nparts = parts[len(parts)-1:] - } - - // Here we are a node type with a partial match. - // Check if the first part is a wildcard. - fp := nparts[0] - p := pivot(fp, 0) - // Check if we have a pwc/fwc part here. This will cause us to iterate. - if len(fp) == 1 && (p == pwc || p == fwc) { - // We need to iterate over all children here for the current node - // to see if we match further down. - for _, cn := range n.children() { - if cn != nil { - t.match(cn, nparts, pre, cb) - } - } - return - } - // Here we have normal traversal, so find the next child. - nn := n.findChild(p) - if nn == nil { - return - } - n, parts = *nn, nparts - } -} - -// Interal iter function to walk nodes in lexigraphical order. -func (t *SubjectTree[T]) iter(n node, pre []byte, ordered bool, cb func(subject []byte, val *T) bool) bool { - if n.isLeaf() { - ln := n.(*leaf[T]) - return cb(append(pre, ln.suffix...), &ln.value) - } - // We are normal node here. - bn := n.base() - // Note that this append may reallocate, but it doesn't modify "pre" at the "iter" callsite. - pre = append(pre, bn.prefix...) - // Not everything requires lexicographical sorting, so support a fast path for iterating in - // whatever order the stree has things stored instead. - if !ordered { - for _, cn := range n.children() { - if cn == nil { - continue - } - if !t.iter(cn, pre, false, cb) { - return false - } - } - return true - } - // Collect nodes since unsorted. - var _nodes [256]node - nodes := _nodes[:0] - for _, cn := range n.children() { - if cn != nil { - nodes = append(nodes, cn) - } - } - // Now sort. - slices.SortStableFunc(nodes, func(a, b node) int { return bytes.Compare(a.path(), b.path()) }) - // Now walk the nodes in order and call into next iter. - for i := range nodes { - if !t.iter(nodes[i], pre, true, cb) { - return false - } - } - return true -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/util.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/util.go deleted file mode 100644 index 8cb6224f..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/stree/util.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2023-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stree - -// For subject matching. -const ( - pwc = '*' - fwc = '>' - tsep = '.' -) - -// Determine index of common prefix. No match at all is 0, etc. -func commonPrefixLen(s1, s2 []byte) int { - limit := min(len(s1), len(s2)) - var i int - for ; i < limit; i++ { - if s1[i] != s2[i] { - break - } - } - return i -} - -// Helper to copy bytes. -func copyBytes(src []byte) []byte { - if len(src) == 0 { - return nil - } - dst := make([]byte, len(src)) - copy(dst, src) - return dst -} - -type position interface{ int | uint16 } - -// No pivot available. -const noPivot = byte(127) - -// Can return 127 (DEL) if we have all the subject as prefixes. -// We used to use 0, but when that was in the subject would cause infinite recursion in some situations. -func pivot[N position](subject []byte, pos N) byte { - if int(pos) >= len(subject) { - return noPivot - } - return subject[pos] -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/subject_transform.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/subject_transform.go deleted file mode 100644 index 42cc17e0..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/subject_transform.go +++ /dev/null @@ -1,632 +0,0 @@ -// Copyright 2023-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "fmt" - "hash/fnv" - "regexp" - "strconv" - "strings" -) - -// Subject mapping and transform setups. -var ( - commaSeparatorRegEx = regexp.MustCompile(`,\s*`) - partitionMappingFunctionRegEx = regexp.MustCompile(`{{\s*[pP]artition\s*\((.*)\)\s*}}`) - wildcardMappingFunctionRegEx = regexp.MustCompile(`{{\s*[wW]ildcard\s*\((.*)\)\s*}}`) - splitFromLeftMappingFunctionRegEx = regexp.MustCompile(`{{\s*[sS]plit[fF]rom[lL]eft\s*\((.*)\)\s*}}`) - splitFromRightMappingFunctionRegEx = regexp.MustCompile(`{{\s*[sS]plit[fF]rom[rR]ight\s*\((.*)\)\s*}}`) - sliceFromLeftMappingFunctionRegEx = regexp.MustCompile(`{{\s*[sS]lice[fF]rom[lL]eft\s*\((.*)\)\s*}}`) - sliceFromRightMappingFunctionRegEx = regexp.MustCompile(`{{\s*[sS]lice[fF]rom[rR]ight\s*\((.*)\)\s*}}`) - splitMappingFunctionRegEx = regexp.MustCompile(`{{\s*[sS]plit\s*\((.*)\)\s*}}`) - leftMappingFunctionRegEx = regexp.MustCompile(`{{\s*[lL]eft\s*\((.*)\)\s*}}`) - rightMappingFunctionRegEx = regexp.MustCompile(`{{\s*[rR]ight\s*\((.*)\)\s*}}`) -) - -// Enum for the subject mapping subjectTransform function types -const ( - NoTransform int16 = iota - BadTransform - Partition - Wildcard - SplitFromLeft - SplitFromRight - SliceFromLeft - SliceFromRight - Split - Left - Right -) - -// Transforms for arbitrarily mapping subjects from one to another for maps, tees and filters. -// These can also be used for proper mapping on wildcard exports/imports. -// These will be grouped and caching and locking are assumed to be in the upper layers. -type subjectTransform struct { - src, dest string - dtoks []string // destination tokens - stoks []string // source tokens - dtokmftypes []int16 // destination token mapping function types - dtokmftokindexesargs [][]int // destination token mapping function array of source token index arguments - dtokmfintargs []int32 // destination token mapping function int32 arguments - dtokmfstringargs []string // destination token mapping function string arguments -} - -// SubjectTransformer transforms subjects using mappings -// -// This API is not part of the public API and not subject to SemVer protections -type SubjectTransformer interface { - // TODO(dlc) - We could add in client here to allow for things like foo -> foo.$ACCOUNT - Match(string) (string, error) - TransformSubject(subject string) string - TransformTokenizedSubject(tokens []string) string -} - -func NewSubjectTransformWithStrict(src, dest string, strict bool) (*subjectTransform, error) { - // strict = true for import subject mappings that need to be reversible - // (meaning can only use the Wildcard function and must use all the pwcs that are present in the source) - // No source given is equivalent to the source being ">" - - if dest == _EMPTY_ { - return nil, nil - } - - if src == _EMPTY_ { - src = fwcs - } - - // Both entries need to be valid subjects. - sv, stokens, npwcs, hasFwc := subjectInfo(src) - dv, dtokens, dnpwcs, dHasFwc := subjectInfo(dest) - - // Make sure both are valid, match fwc if present and there are no pwcs in the dest subject. - if !sv || !dv || dnpwcs > 0 || hasFwc != dHasFwc { - return nil, ErrBadSubject - } - - var dtokMappingFunctionTypes []int16 - var dtokMappingFunctionTokenIndexes [][]int - var dtokMappingFunctionIntArgs []int32 - var dtokMappingFunctionStringArgs []string - - // If the src has partial wildcards then the dest needs to have the token place markers. - if npwcs > 0 || hasFwc { - // We need to count to make sure that the dest has token holders for the pwcs. - sti := make(map[int]int) - for i, token := range stokens { - if len(token) == 1 && token[0] == pwc { - sti[len(sti)+1] = i - } - } - - nphs := 0 - for _, token := range dtokens { - tranformType, transformArgWildcardIndexes, transfomArgInt, transformArgString, err := indexPlaceHolders(token) - if err != nil { - return nil, err - } - - if strict { - if tranformType != NoTransform && tranformType != Wildcard { - return nil, &mappingDestinationErr{token, ErrMappingDestinationNotSupportedForImport} - } - } - - if npwcs == 0 { - if tranformType != NoTransform { - return nil, &mappingDestinationErr{token, ErrMappingDestinationIndexOutOfRange} - } - } - - if tranformType == NoTransform { - dtokMappingFunctionTypes = append(dtokMappingFunctionTypes, NoTransform) - dtokMappingFunctionTokenIndexes = append(dtokMappingFunctionTokenIndexes, []int{-1}) - dtokMappingFunctionIntArgs = append(dtokMappingFunctionIntArgs, -1) - dtokMappingFunctionStringArgs = append(dtokMappingFunctionStringArgs, _EMPTY_) - } else { - nphs += len(transformArgWildcardIndexes) - // Now build up our runtime mapping from dest to source tokens. - var stis []int - for _, wildcardIndex := range transformArgWildcardIndexes { - if wildcardIndex > npwcs { - return nil, &mappingDestinationErr{fmt.Sprintf("%s: [%d]", token, wildcardIndex), ErrMappingDestinationIndexOutOfRange} - } - stis = append(stis, sti[wildcardIndex]) - } - dtokMappingFunctionTypes = append(dtokMappingFunctionTypes, tranformType) - dtokMappingFunctionTokenIndexes = append(dtokMappingFunctionTokenIndexes, stis) - dtokMappingFunctionIntArgs = append(dtokMappingFunctionIntArgs, transfomArgInt) - dtokMappingFunctionStringArgs = append(dtokMappingFunctionStringArgs, transformArgString) - - } - } - if strict && nphs < npwcs { - // not all wildcards are being used in the destination - return nil, &mappingDestinationErr{dest, ErrMappingDestinationNotUsingAllWildcards} - } - } else { - // no wildcards used in the source: check that no transform functions are used in the destination - for _, token := range dtokens { - tranformType, _, _, _, err := indexPlaceHolders(token) - if err != nil { - return nil, err - } - - if tranformType != NoTransform { - return nil, &mappingDestinationErr{token, ErrMappingDestinationIndexOutOfRange} - } - } - } - - return &subjectTransform{ - src: src, - dest: dest, - dtoks: dtokens, - stoks: stokens, - dtokmftypes: dtokMappingFunctionTypes, - dtokmftokindexesargs: dtokMappingFunctionTokenIndexes, - dtokmfintargs: dtokMappingFunctionIntArgs, - dtokmfstringargs: dtokMappingFunctionStringArgs, - }, nil -} - -func NewSubjectTransform(src, dest string) (*subjectTransform, error) { - return NewSubjectTransformWithStrict(src, dest, false) -} - -func NewSubjectTransformStrict(src, dest string) (*subjectTransform, error) { - return NewSubjectTransformWithStrict(src, dest, true) -} - -func getMappingFunctionArgs(functionRegEx *regexp.Regexp, token string) []string { - commandStrings := functionRegEx.FindStringSubmatch(token) - if len(commandStrings) > 1 { - return commaSeparatorRegEx.Split(commandStrings[1], -1) - } - return nil -} - -// Helper for mapping functions that take a wildcard index and an integer as arguments -func transformIndexIntArgsHelper(token string, args []string, transformType int16) (int16, []int, int32, string, error) { - if len(args) < 2 { - return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationNotEnoughArgs} - } - if len(args) > 2 { - return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationTooManyArgs} - } - i, err := strconv.Atoi(strings.Trim(args[0], " ")) - if err != nil { - return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationInvalidArg} - } - mappingFunctionIntArg, err := strconv.Atoi(strings.Trim(args[1], " ")) - if err != nil { - return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationInvalidArg} - } - - return transformType, []int{i}, int32(mappingFunctionIntArg), _EMPTY_, nil -} - -// Helper to ingest and index the subjectTransform destination token (e.g. $x or {{}}) in the token -// returns a transformation type, and three function arguments: an array of source subject token indexes, -// and a single number (e.g. number of partitions, or a slice size), and a string (e.g.a split delimiter) -func indexPlaceHolders(token string) (int16, []int, int32, string, error) { - length := len(token) - if length > 1 { - // old $1, $2, etc... mapping format still supported to maintain backwards compatibility - if token[0] == '$' { // simple non-partition mapping - tp, err := strconv.Atoi(token[1:]) - if err != nil { - // other things rely on tokens starting with $ so not an error just leave it as is - return NoTransform, []int{-1}, -1, _EMPTY_, nil - } - return Wildcard, []int{tp}, -1, _EMPTY_, nil - } - - // New 'mustache' style mapping - if length > 4 && token[0] == '{' && token[1] == '{' && token[length-2] == '}' && token[length-1] == '}' { - // wildcard(wildcard token index) (equivalent to $) - args := getMappingFunctionArgs(wildcardMappingFunctionRegEx, token) - if args != nil { - if len(args) == 1 && args[0] == _EMPTY_ { - return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationNotEnoughArgs} - } - if len(args) == 1 { - tokenIndex, err := strconv.Atoi(strings.Trim(args[0], " ")) - if err != nil { - return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationInvalidArg} - } - return Wildcard, []int{tokenIndex}, -1, _EMPTY_, nil - } else { - return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationTooManyArgs} - } - } - - // partition(number of partitions, token1, token2, ...) - args = getMappingFunctionArgs(partitionMappingFunctionRegEx, token) - if args != nil { - if len(args) < 2 { - return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationNotEnoughArgs} - } - if len(args) >= 2 { - mappingFunctionIntArg, err := strconv.Atoi(strings.Trim(args[0], " ")) - if err != nil { - return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationInvalidArg} - } - var numPositions = len(args[1:]) - tokenIndexes := make([]int, numPositions) - for ti, t := range args[1:] { - i, err := strconv.Atoi(strings.Trim(t, " ")) - if err != nil { - return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationInvalidArg} - } - tokenIndexes[ti] = i - } - - return Partition, tokenIndexes, int32(mappingFunctionIntArg), _EMPTY_, nil - } - } - - // SplitFromLeft(token, position) - args = getMappingFunctionArgs(splitFromLeftMappingFunctionRegEx, token) - if args != nil { - return transformIndexIntArgsHelper(token, args, SplitFromLeft) - } - - // SplitFromRight(token, position) - args = getMappingFunctionArgs(splitFromRightMappingFunctionRegEx, token) - if args != nil { - return transformIndexIntArgsHelper(token, args, SplitFromRight) - } - - // SliceFromLeft(token, position) - args = getMappingFunctionArgs(sliceFromLeftMappingFunctionRegEx, token) - if args != nil { - return transformIndexIntArgsHelper(token, args, SliceFromLeft) - } - - // SliceFromRight(token, position) - args = getMappingFunctionArgs(sliceFromRightMappingFunctionRegEx, token) - if args != nil { - return transformIndexIntArgsHelper(token, args, SliceFromRight) - } - - // Right(token, length) - args = getMappingFunctionArgs(rightMappingFunctionRegEx, token) - if args != nil { - return transformIndexIntArgsHelper(token, args, Right) - } - - // Left(token, length) - args = getMappingFunctionArgs(leftMappingFunctionRegEx, token) - if args != nil { - return transformIndexIntArgsHelper(token, args, Left) - } - - // split(token, deliminator) - args = getMappingFunctionArgs(splitMappingFunctionRegEx, token) - if args != nil { - if len(args) < 2 { - return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationNotEnoughArgs} - } - if len(args) > 2 { - return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationTooManyArgs} - } - i, err := strconv.Atoi(strings.Trim(args[0], " ")) - if err != nil { - return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrMappingDestinationInvalidArg} - } - if strings.Contains(args[1], " ") || strings.Contains(args[1], tsep) { - return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token: token, err: ErrMappingDestinationInvalidArg} - } - - return Split, []int{i}, -1, args[1], nil - } - - return BadTransform, []int{}, -1, _EMPTY_, &mappingDestinationErr{token, ErrUnknownMappingDestinationFunction} - } - } - return NoTransform, []int{-1}, -1, _EMPTY_, nil -} - -// Helper function to tokenize subjects with partial wildcards into formal transform destinations. -// e.g. "foo.*.*" -> "foo.$1.$2" -func transformTokenize(subject string) string { - // We need to make the appropriate markers for the wildcards etc. - i := 1 - var nda []string - for _, token := range strings.Split(subject, tsep) { - if token == pwcs { - nda = append(nda, fmt.Sprintf("$%d", i)) - i++ - } else { - nda = append(nda, token) - } - } - return strings.Join(nda, tsep) -} - -// Helper function to go from transform destination to a subject with partial wildcards and ordered list of placeholders -// E.g.: -// -// "bar" -> "bar", [] -// "foo.$2.$1" -> "foo.*.*", ["$2","$1"] -// "foo.{{wildcard(2)}}.{{wildcard(1)}}" -> "foo.*.*", ["{{wildcard(2)}}","{{wildcard(1)}}"] -func transformUntokenize(subject string) (string, []string) { - var phs []string - var nda []string - - for _, token := range strings.Split(subject, tsep) { - if args := getMappingFunctionArgs(wildcardMappingFunctionRegEx, token); (len(token) > 1 && token[0] == '$' && token[1] >= '1' && token[1] <= '9') || (len(args) == 1 && args[0] != _EMPTY_) { - phs = append(phs, token) - nda = append(nda, pwcs) - } else { - nda = append(nda, token) - } - } - return strings.Join(nda, tsep), phs -} - -func tokenizeSubject(subject string) []string { - // Tokenize the subject. - tsa := [32]string{} - tts := tsa[:0] - start := 0 - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - tts = append(tts, subject[start:i]) - start = i + 1 - } - } - tts = append(tts, subject[start:]) - return tts -} - -// Match will take a literal published subject that is associated with a client and will match and subjectTransform -// the subject if possible. -// -// This API is not part of the public API and not subject to SemVer protections -func (tr *subjectTransform) Match(subject string) (string, error) { - // Special case: matches any and no no-op subjectTransform. May not be legal config for some features - // but specific validations made at subjectTransform create time - if (tr.src == fwcs || tr.src == _EMPTY_) && (tr.dest == fwcs || tr.dest == _EMPTY_) { - return subject, nil - } - - tts := tokenizeSubject(subject) - - // TODO(jnm): optimization -> not sure this is actually needed but was there in initial code - if !isValidLiteralSubject(tts) { - return _EMPTY_, ErrBadSubject - } - - if (tr.src == _EMPTY_ || tr.src == fwcs) || isSubsetMatch(tts, tr.src) { - return tr.TransformTokenizedSubject(tts), nil - } - return _EMPTY_, ErrNoTransforms -} - -// TransformSubject transforms a subject -// -// This API is not part of the public API and not subject to SemVer protection -func (tr *subjectTransform) TransformSubject(subject string) string { - return tr.TransformTokenizedSubject(tokenizeSubject(subject)) -} - -func (tr *subjectTransform) getHashPartition(key []byte, numBuckets int) string { - h := fnv.New32a() - _, _ = h.Write(key) - - return strconv.Itoa(int(h.Sum32() % uint32(numBuckets))) -} - -// Do a subjectTransform on the subject to the dest subject. -func (tr *subjectTransform) TransformTokenizedSubject(tokens []string) string { - if len(tr.dtokmftypes) == 0 { - return tr.dest - } - - var b strings.Builder - - // We need to walk destination tokens and create the mapped subject pulling tokens or mapping functions - li := len(tr.dtokmftypes) - 1 - for i, mfType := range tr.dtokmftypes { - if mfType == NoTransform { - // Break if fwc - if len(tr.dtoks[i]) == 1 && tr.dtoks[i][0] == fwc { - break - } - b.WriteString(tr.dtoks[i]) - } else { - switch mfType { - case Partition: - var ( - _buffer [64]byte - keyForHashing = _buffer[:0] - ) - for _, sourceToken := range tr.dtokmftokindexesargs[i] { - keyForHashing = append(keyForHashing, []byte(tokens[sourceToken])...) - } - b.WriteString(tr.getHashPartition(keyForHashing, int(tr.dtokmfintargs[i]))) - case Wildcard: // simple substitution - switch { - case len(tr.dtokmftokindexesargs) < i: - break - case len(tr.dtokmftokindexesargs[i]) < 1: - break - case len(tokens) <= tr.dtokmftokindexesargs[i][0]: - break - default: - b.WriteString(tokens[tr.dtokmftokindexesargs[i][0]]) - } - case SplitFromLeft: - sourceToken := tokens[tr.dtokmftokindexesargs[i][0]] - sourceTokenLen := len(sourceToken) - position := int(tr.dtokmfintargs[i]) - if position > 0 && position < sourceTokenLen { - b.WriteString(sourceToken[:position]) - b.WriteString(tsep) - b.WriteString(sourceToken[position:]) - } else { // too small to split at the requested position: don't split - b.WriteString(sourceToken) - } - case SplitFromRight: - sourceToken := tokens[tr.dtokmftokindexesargs[i][0]] - sourceTokenLen := len(sourceToken) - position := int(tr.dtokmfintargs[i]) - if position > 0 && position < sourceTokenLen { - b.WriteString(sourceToken[:sourceTokenLen-position]) - b.WriteString(tsep) - b.WriteString(sourceToken[sourceTokenLen-position:]) - } else { // too small to split at the requested position: don't split - b.WriteString(sourceToken) - } - case SliceFromLeft: - sourceToken := tokens[tr.dtokmftokindexesargs[i][0]] - sourceTokenLen := len(sourceToken) - sliceSize := int(tr.dtokmfintargs[i]) - if sliceSize > 0 && sliceSize < sourceTokenLen { - for i := 0; i+sliceSize <= sourceTokenLen; i += sliceSize { - if i != 0 { - b.WriteString(tsep) - } - b.WriteString(sourceToken[i : i+sliceSize]) - if i+sliceSize != sourceTokenLen && i+sliceSize+sliceSize > sourceTokenLen { - b.WriteString(tsep) - b.WriteString(sourceToken[i+sliceSize:]) - break - } - } - } else { // too small to slice at the requested size: don't slice - b.WriteString(sourceToken) - } - case SliceFromRight: - sourceToken := tokens[tr.dtokmftokindexesargs[i][0]] - sourceTokenLen := len(sourceToken) - sliceSize := int(tr.dtokmfintargs[i]) - if sliceSize > 0 && sliceSize < sourceTokenLen { - remainder := sourceTokenLen % sliceSize - if remainder > 0 { - b.WriteString(sourceToken[:remainder]) - b.WriteString(tsep) - } - for i := remainder; i+sliceSize <= sourceTokenLen; i += sliceSize { - b.WriteString(sourceToken[i : i+sliceSize]) - if i+sliceSize < sourceTokenLen { - b.WriteString(tsep) - } - } - } else { // too small to slice at the requested size: don't slice - b.WriteString(sourceToken) - } - case Split: - sourceToken := tokens[tr.dtokmftokindexesargs[i][0]] - splits := strings.Split(sourceToken, tr.dtokmfstringargs[i]) - for j, split := range splits { - if split != _EMPTY_ { - b.WriteString(split) - } - if j < len(splits)-1 && splits[j+1] != _EMPTY_ && !(j == 0 && split == _EMPTY_) { - b.WriteString(tsep) - } - } - case Left: - sourceToken := tokens[tr.dtokmftokindexesargs[i][0]] - sourceTokenLen := len(sourceToken) - sliceSize := int(tr.dtokmfintargs[i]) - if sliceSize > 0 && sliceSize < sourceTokenLen { - b.WriteString(sourceToken[0:sliceSize]) - } else { // too small to slice at the requested size: don't slice - b.WriteString(sourceToken) - } - case Right: - sourceToken := tokens[tr.dtokmftokindexesargs[i][0]] - sourceTokenLen := len(sourceToken) - sliceSize := int(tr.dtokmfintargs[i]) - if sliceSize > 0 && sliceSize < sourceTokenLen { - b.WriteString(sourceToken[sourceTokenLen-sliceSize : sourceTokenLen]) - } else { // too small to slice at the requested size: don't slice - b.WriteString(sourceToken) - } - } - } - - if i < li { - b.WriteByte(btsep) - } - } - - // We may have more source tokens available. This happens with ">". - if tr.dtoks[len(tr.dtoks)-1] == fwcs { - for sli, i := len(tokens)-1, len(tr.stoks)-1; i < len(tokens); i++ { - b.WriteString(tokens[i]) - if i < sli { - b.WriteByte(btsep) - } - } - } - return b.String() -} - -// Reverse a subjectTransform. -func (tr *subjectTransform) reverse() *subjectTransform { - if len(tr.dtokmftokindexesargs) == 0 { - rtr, _ := NewSubjectTransformStrict(tr.dest, tr.src) - return rtr - } - // If we are here we need to dynamically get the correct reverse - // of this subjectTransform. - nsrc, phs := transformUntokenize(tr.dest) - var nda []string - for _, token := range tr.stoks { - if token == pwcs { - if len(phs) == 0 { - // TODO(dlc) - Should not happen - return nil - } - nda = append(nda, phs[0]) - phs = phs[1:] - } else { - nda = append(nda, token) - } - } - ndest := strings.Join(nda, tsep) - rtr, _ := NewSubjectTransformStrict(nsrc, ndest) - return rtr -} - -// Will share relevant info regarding the subject. -// Returns valid, tokens, num pwcs, has fwc. -func subjectInfo(subject string) (bool, []string, int, bool) { - if subject == "" { - return false, nil, 0, false - } - npwcs := 0 - sfwc := false - tokens := strings.Split(subject, tsep) - for _, t := range tokens { - if len(t) == 0 || sfwc { - return false, nil, 0, false - } - if len(t) > 1 { - continue - } - switch t[0] { - case fwc: - sfwc = true - case pwc: - npwcs++ - } - } - return true, tokens, npwcs, sfwc -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sublist.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sublist.go deleted file mode 100644 index 004150aa..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sublist.go +++ /dev/null @@ -1,1786 +0,0 @@ -// Copyright 2016-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "errors" - "strings" - "sync" - "sync/atomic" - "unicode/utf8" - - "github.com/nats-io/nats-server/v2/server/stree" -) - -// Sublist is a routing mechanism to handle subject distribution and -// provides a facility to match subjects from published messages to -// interested subscribers. Subscribers can have wildcard subjects to -// match multiple published subjects. - -// Common byte variables for wildcards and token separator. -const ( - pwc = '*' - pwcs = "*" - fwc = '>' - fwcs = ">" - tsep = "." - btsep = '.' -) - -// Sublist related errors -var ( - ErrInvalidSubject = errors.New("sublist: invalid subject") - ErrNotFound = errors.New("sublist: no matches found") - ErrNilChan = errors.New("sublist: nil channel") - ErrAlreadyRegistered = errors.New("sublist: notification already registered") -) - -const ( - // cacheMax is used to bound limit the frontend cache - slCacheMax = 1024 - // If we run a sweeper we will drain to this count. - slCacheSweep = 256 - // plistMin is our lower bounds to create a fast plist for Match. - plistMin = 256 -) - -// SublistResult is a result structure better optimized for queue subs. -type SublistResult struct { - psubs []*subscription - qsubs [][]*subscription // don't make this a map, too expensive to iterate -} - -// A Sublist stores and efficiently retrieves subscriptions. -type Sublist struct { - sync.RWMutex - genid uint64 - matches uint64 - cacheHits uint64 - inserts uint64 - removes uint64 - root *level - cache map[string]*SublistResult - ccSweep int32 - notify *notifyMaps - count uint32 -} - -// notifyMaps holds maps of arrays of channels for notifications -// on a change of interest. -type notifyMaps struct { - insert map[string][]chan<- bool - remove map[string][]chan<- bool -} - -// A node contains subscriptions and a pointer to the next level. -type node struct { - next *level - psubs map[*subscription]struct{} - qsubs map[string]map[*subscription]struct{} - plist []*subscription -} - -// A level represents a group of nodes and special pointers to -// wildcard nodes. -type level struct { - nodes map[string]*node - pwc, fwc *node -} - -// Create a new default node. -func newNode() *node { - return &node{psubs: make(map[*subscription]struct{})} -} - -// Create a new default level. -func newLevel() *level { - return &level{nodes: make(map[string]*node)} -} - -// In general caching is recommended however in some extreme cases where -// interest changes are high, suppressing the cache can help. -// https://github.com/nats-io/nats-server/issues/941 -// FIXME(dlc) - should be more dynamic at some point based on cache thrashing. - -// NewSublist will create a default sublist with caching enabled per the flag. -func NewSublist(enableCache bool) *Sublist { - if enableCache { - return &Sublist{root: newLevel(), cache: make(map[string]*SublistResult)} - } - return &Sublist{root: newLevel()} -} - -// NewSublistWithCache will create a default sublist with caching enabled. -func NewSublistWithCache() *Sublist { - return NewSublist(true) -} - -// NewSublistNoCache will create a default sublist with caching disabled. -func NewSublistNoCache() *Sublist { - return NewSublist(false) -} - -// CacheEnabled returns whether or not caching is enabled for this sublist. -func (s *Sublist) CacheEnabled() bool { - s.RLock() - enabled := s.cache != nil - s.RUnlock() - return enabled -} - -// RegisterNotification will register for notifications when interest for the given -// subject changes. The subject must be a literal publish type subject. -// The notification is true for when the first interest for a subject is inserted, -// and false when all interest in the subject is removed. Note that this interest -// needs to be exact and that wildcards will not trigger the notifications. The sublist -// will not block when trying to send the notification. Its up to the caller to make -// sure the channel send will not block. -func (s *Sublist) RegisterNotification(subject string, notify chan<- bool) error { - return s.registerNotification(subject, _EMPTY_, notify) -} - -func (s *Sublist) RegisterQueueNotification(subject, queue string, notify chan<- bool) error { - return s.registerNotification(subject, queue, notify) -} - -func (s *Sublist) registerNotification(subject, queue string, notify chan<- bool) error { - if subjectHasWildcard(subject) { - return ErrInvalidSubject - } - if notify == nil { - return ErrNilChan - } - - var hasInterest bool - r := s.Match(subject) - - if len(r.psubs)+len(r.qsubs) > 0 { - if queue == _EMPTY_ { - for _, sub := range r.psubs { - if string(sub.subject) == subject { - hasInterest = true - break - } - } - } else { - for _, qsub := range r.qsubs { - qs := qsub[0] - if string(qs.subject) == subject && string(qs.queue) == queue { - hasInterest = true - break - } - } - } - } - - key := keyFromSubjectAndQueue(subject, queue) - var err error - - s.Lock() - if s.notify == nil { - s.notify = ¬ifyMaps{ - insert: make(map[string][]chan<- bool), - remove: make(map[string][]chan<- bool), - } - } - // Check which list to add us to. - if hasInterest { - err = s.addRemoveNotify(key, notify) - } else { - err = s.addInsertNotify(key, notify) - } - s.Unlock() - - if err == nil { - sendNotification(notify, hasInterest) - } - return err -} - -// Lock should be held. -func chkAndRemove(key string, notify chan<- bool, ms map[string][]chan<- bool) bool { - chs := ms[key] - for i, ch := range chs { - if ch == notify { - chs[i] = chs[len(chs)-1] - chs = chs[:len(chs)-1] - if len(chs) == 0 { - delete(ms, key) - } - return true - } - } - return false -} - -func (s *Sublist) ClearNotification(subject string, notify chan<- bool) bool { - return s.clearNotification(subject, _EMPTY_, notify) -} - -func (s *Sublist) ClearQueueNotification(subject, queue string, notify chan<- bool) bool { - return s.clearNotification(subject, queue, notify) -} - -func (s *Sublist) clearNotification(subject, queue string, notify chan<- bool) bool { - s.Lock() - if s.notify == nil { - s.Unlock() - return false - } - key := keyFromSubjectAndQueue(subject, queue) - // Check both, start with remove. - didRemove := chkAndRemove(key, notify, s.notify.remove) - didRemove = didRemove || chkAndRemove(key, notify, s.notify.insert) - // Check if everything is gone - if len(s.notify.remove)+len(s.notify.insert) == 0 { - s.notify = nil - } - s.Unlock() - return didRemove -} - -func sendNotification(ch chan<- bool, hasInterest bool) { - select { - case ch <- hasInterest: - default: - } -} - -// Add a new channel for notification in insert map. -// Write lock should be held. -func (s *Sublist) addInsertNotify(subject string, notify chan<- bool) error { - return s.addNotify(s.notify.insert, subject, notify) -} - -// Add a new channel for notification in removal map. -// Write lock should be held. -func (s *Sublist) addRemoveNotify(subject string, notify chan<- bool) error { - return s.addNotify(s.notify.remove, subject, notify) -} - -// Add a new channel for notification. -// Write lock should be held. -func (s *Sublist) addNotify(m map[string][]chan<- bool, subject string, notify chan<- bool) error { - chs := m[subject] - if len(chs) > 0 { - // Check to see if this chan is already registered. - for _, ch := range chs { - if ch == notify { - return ErrAlreadyRegistered - } - } - } - - m[subject] = append(chs, notify) - return nil -} - -// To generate a key from subject and queue. We just add spc. -func keyFromSubjectAndQueue(subject, queue string) string { - if len(queue) == 0 { - return subject - } - var sb strings.Builder - sb.WriteString(subject) - sb.WriteString(" ") - sb.WriteString(queue) - return sb.String() -} - -// chkForInsertNotification will check to see if we need to notify on this subject. -// Write lock should be held. -func (s *Sublist) chkForInsertNotification(subject, queue string) { - key := keyFromSubjectAndQueue(subject, queue) - - // All notify subjects are also literal so just do a hash lookup here. - if chs := s.notify.insert[key]; len(chs) > 0 { - for _, ch := range chs { - sendNotification(ch, true) - } - // Move from the insert map to the remove map. - s.notify.remove[key] = append(s.notify.remove[key], chs...) - delete(s.notify.insert, key) - } -} - -// chkForRemoveNotification will check to see if we need to notify on this subject. -// Write lock should be held. -func (s *Sublist) chkForRemoveNotification(subject, queue string) { - key := keyFromSubjectAndQueue(subject, queue) - if chs := s.notify.remove[key]; len(chs) > 0 { - // We need to always check that we have no interest anymore. - var hasInterest bool - r := s.matchNoLock(subject) - - if len(r.psubs)+len(r.qsubs) > 0 { - if queue == _EMPTY_ { - for _, sub := range r.psubs { - if string(sub.subject) == subject { - hasInterest = true - break - } - } - } else { - for _, qsub := range r.qsubs { - qs := qsub[0] - if string(qs.subject) == subject && string(qs.queue) == queue { - hasInterest = true - break - } - } - } - } - if !hasInterest { - for _, ch := range chs { - sendNotification(ch, false) - } - // Move from the remove map to the insert map. - s.notify.insert[key] = append(s.notify.insert[key], chs...) - delete(s.notify.remove, key) - } - } -} - -// Insert adds a subscription into the sublist -func (s *Sublist) Insert(sub *subscription) error { - // copy the subject since we hold this and this might be part of a large byte slice. - subject := string(sub.subject) - tsa := [32]string{} - tokens := tsa[:0] - start := 0 - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - tokens = append(tokens, subject[start:i]) - start = i + 1 - } - } - tokens = append(tokens, subject[start:]) - - s.Lock() - - var sfwc, haswc, isnew bool - var n *node - l := s.root - - for _, t := range tokens { - lt := len(t) - if lt == 0 || sfwc { - s.Unlock() - return ErrInvalidSubject - } - - if lt > 1 { - n = l.nodes[t] - } else { - switch t[0] { - case pwc: - n = l.pwc - haswc = true - case fwc: - n = l.fwc - haswc, sfwc = true, true - default: - n = l.nodes[t] - } - } - if n == nil { - n = newNode() - if lt > 1 { - l.nodes[t] = n - } else { - switch t[0] { - case pwc: - l.pwc = n - case fwc: - l.fwc = n - default: - l.nodes[t] = n - } - } - } - if n.next == nil { - n.next = newLevel() - } - l = n.next - } - if sub.queue == nil { - n.psubs[sub] = struct{}{} - isnew = len(n.psubs) == 1 - if n.plist != nil { - n.plist = append(n.plist, sub) - } else if len(n.psubs) > plistMin { - n.plist = make([]*subscription, 0, len(n.psubs)) - // Populate - for psub := range n.psubs { - n.plist = append(n.plist, psub) - } - } - } else { - if n.qsubs == nil { - n.qsubs = make(map[string]map[*subscription]struct{}) - } - qname := string(sub.queue) - // This is a queue subscription - subs, ok := n.qsubs[qname] - if !ok { - subs = make(map[*subscription]struct{}) - n.qsubs[qname] = subs - isnew = true - } - subs[sub] = struct{}{} - } - - s.count++ - s.inserts++ - - s.addToCache(subject, sub) - atomic.AddUint64(&s.genid, 1) - - if s.notify != nil && isnew && !haswc && len(s.notify.insert) > 0 { - s.chkForInsertNotification(subject, string(sub.queue)) - } - s.Unlock() - - return nil -} - -// Deep copy -func copyResult(r *SublistResult) *SublistResult { - nr := &SublistResult{} - nr.psubs = append([]*subscription(nil), r.psubs...) - for _, qr := range r.qsubs { - nqr := append([]*subscription(nil), qr...) - nr.qsubs = append(nr.qsubs, nqr) - } - return nr -} - -// Adds a new sub to an existing result. -func (r *SublistResult) addSubToResult(sub *subscription) *SublistResult { - // Copy since others may have a reference. - nr := copyResult(r) - if sub.queue == nil { - nr.psubs = append(nr.psubs, sub) - } else { - if i := findQSlot(sub.queue, nr.qsubs); i >= 0 { - nr.qsubs[i] = append(nr.qsubs[i], sub) - } else { - nr.qsubs = append(nr.qsubs, []*subscription{sub}) - } - } - return nr -} - -// addToCache will add the new entry to the existing cache -// entries if needed. Assumes write lock is held. -// Assumes write lock is held. -func (s *Sublist) addToCache(subject string, sub *subscription) { - if s.cache == nil { - return - } - // If literal we can direct match. - if subjectIsLiteral(subject) { - if r := s.cache[subject]; r != nil { - s.cache[subject] = r.addSubToResult(sub) - } - return - } - for key, r := range s.cache { - if matchLiteral(key, subject) { - s.cache[key] = r.addSubToResult(sub) - } - } -} - -// removeFromCache will remove the sub from any active cache entries. -// Assumes write lock is held. -func (s *Sublist) removeFromCache(subject string) { - if s.cache == nil { - return - } - // If literal we can direct match. - if subjectIsLiteral(subject) { - delete(s.cache, subject) - return - } - // Wildcard here. - for key := range s.cache { - if matchLiteral(key, subject) { - delete(s.cache, key) - } - } -} - -// a place holder for an empty result. -var emptyResult = &SublistResult{} - -// Match will match all entries to the literal subject. -// It will return a set of results for both normal and queue subscribers. -func (s *Sublist) Match(subject string) *SublistResult { - return s.match(subject, true, false) -} - -// MatchBytes will match all entries to the literal subject. -// It will return a set of results for both normal and queue subscribers. -func (s *Sublist) MatchBytes(subject []byte) *SublistResult { - return s.match(bytesToString(subject), true, true) -} - -// HasInterest will return whether or not there is any interest in the subject. -// In cases where more detail is not required, this may be faster than Match. -func (s *Sublist) HasInterest(subject string) bool { - return s.hasInterest(subject, true, nil, nil) -} - -// NumInterest will return the number of subs/qsubs interested in the subject. -// In cases where more detail is not required, this may be faster than Match. -func (s *Sublist) NumInterest(subject string) (np, nq int) { - s.hasInterest(subject, true, &np, &nq) - return -} - -func (s *Sublist) matchNoLock(subject string) *SublistResult { - return s.match(subject, false, false) -} - -func (s *Sublist) match(subject string, doLock bool, doCopyOnCache bool) *SublistResult { - atomic.AddUint64(&s.matches, 1) - - // Check cache first. - if doLock { - s.RLock() - } - cacheEnabled := s.cache != nil - r, ok := s.cache[subject] - if doLock { - s.RUnlock() - } - if ok { - atomic.AddUint64(&s.cacheHits, 1) - return r - } - - tsa := [32]string{} - tokens := tsa[:0] - start := 0 - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - if i-start == 0 { - return emptyResult - } - tokens = append(tokens, subject[start:i]) - start = i + 1 - } - } - if start >= len(subject) { - return emptyResult - } - tokens = append(tokens, subject[start:]) - - // FIXME(dlc) - Make shared pool between sublist and client readLoop? - result := &SublistResult{} - - // Get result from the main structure and place into the shared cache. - // Hold the read lock to avoid race between match and store. - var n int - - if doLock { - if cacheEnabled { - s.Lock() - } else { - s.RLock() - } - } - - matchLevel(s.root, tokens, result) - // Check for empty result. - if len(result.psubs) == 0 && len(result.qsubs) == 0 { - result = emptyResult - } - if cacheEnabled { - if doCopyOnCache { - subject = copyString(subject) - } - s.cache[subject] = result - n = len(s.cache) - } - if doLock { - if cacheEnabled { - s.Unlock() - } else { - s.RUnlock() - } - } - - // Reduce the cache count if we have exceeded our set maximum. - if cacheEnabled && n > slCacheMax && atomic.CompareAndSwapInt32(&s.ccSweep, 0, 1) { - go s.reduceCacheCount() - } - - return result -} - -func (s *Sublist) hasInterest(subject string, doLock bool, np, nq *int) bool { - // Check cache first. - if doLock { - s.RLock() - } - var matched bool - if s.cache != nil { - if r, ok := s.cache[subject]; ok { - if np != nil && nq != nil { - *np += len(r.psubs) - for _, qsub := range r.qsubs { - *nq += len(qsub) - } - } - matched = len(r.psubs)+len(r.qsubs) > 0 - } - } - if doLock { - s.RUnlock() - } - if matched { - atomic.AddUint64(&s.cacheHits, 1) - return true - } - - tsa := [32]string{} - tokens := tsa[:0] - start := 0 - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - if i-start == 0 { - return false - } - tokens = append(tokens, subject[start:i]) - start = i + 1 - } - } - if start >= len(subject) { - return false - } - tokens = append(tokens, subject[start:]) - - if doLock { - s.RLock() - defer s.RUnlock() - } - return matchLevelForAny(s.root, tokens, np, nq) -} - -// Remove entries in the cache until we are under the maximum. -// TODO(dlc) this could be smarter now that its not inline. -func (s *Sublist) reduceCacheCount() { - defer atomic.StoreInt32(&s.ccSweep, 0) - // If we are over the cache limit randomly drop until under the limit. - s.Lock() - for key := range s.cache { - delete(s.cache, key) - if len(s.cache) <= slCacheSweep { - break - } - } - s.Unlock() -} - -// Helper function for auto-expanding remote qsubs. -func isRemoteQSub(sub *subscription) bool { - return sub != nil && sub.queue != nil && sub.client != nil && (sub.client.kind == ROUTER || sub.client.kind == LEAF) -} - -// UpdateRemoteQSub should be called when we update the weight of an existing -// remote queue sub. -func (s *Sublist) UpdateRemoteQSub(sub *subscription) { - // We could search to make sure we find it, but probably not worth - // it unless we are thrashing the cache. Just remove from our L2 and update - // the genid so L1 will be flushed. - s.Lock() - s.removeFromCache(string(sub.subject)) - atomic.AddUint64(&s.genid, 1) - s.Unlock() -} - -// This will add in a node's results to the total results. -func addNodeToResults(n *node, results *SublistResult) { - // Normal subscriptions - if n.plist != nil { - results.psubs = append(results.psubs, n.plist...) - } else { - for psub := range n.psubs { - results.psubs = append(results.psubs, psub) - } - } - // Queue subscriptions - for qname, qr := range n.qsubs { - if len(qr) == 0 { - continue - } - // Need to find matching list in results - var i int - if i = findQSlot([]byte(qname), results.qsubs); i < 0 { - i = len(results.qsubs) - nqsub := make([]*subscription, 0, len(qr)) - results.qsubs = append(results.qsubs, nqsub) - } - for sub := range qr { - if isRemoteQSub(sub) { - ns := atomic.LoadInt32(&sub.qw) - // Shadow these subscriptions - for n := 0; n < int(ns); n++ { - results.qsubs[i] = append(results.qsubs[i], sub) - } - } else { - results.qsubs[i] = append(results.qsubs[i], sub) - } - } - } -} - -// We do not use a map here since we want iteration to be past when -// processing publishes in L1 on client. So we need to walk sequentially -// for now. Keep an eye on this in case we start getting large number of -// different queue subscribers for the same subject. -func findQSlot(queue []byte, qsl [][]*subscription) int { - if queue == nil { - return -1 - } - for i, qr := range qsl { - if len(qr) > 0 && bytes.Equal(queue, qr[0].queue) { - return i - } - } - return -1 -} - -// matchLevel is used to recursively descend into the trie. -func matchLevel(l *level, toks []string, results *SublistResult) { - var pwc, n *node - for i, t := range toks { - if l == nil { - return - } - if l.fwc != nil { - addNodeToResults(l.fwc, results) - } - if pwc = l.pwc; pwc != nil { - matchLevel(pwc.next, toks[i+1:], results) - } - n = l.nodes[t] - if n != nil { - l = n.next - } else { - l = nil - } - } - if n != nil { - addNodeToResults(n, results) - } - if pwc != nil { - addNodeToResults(pwc, results) - } -} - -func matchLevelForAny(l *level, toks []string, np, nq *int) bool { - var pwc, n *node - for i, t := range toks { - if l == nil { - return false - } - if l.fwc != nil { - if np != nil && nq != nil { - *np += len(l.fwc.psubs) - for _, qsub := range l.fwc.qsubs { - *nq += len(qsub) - } - } - return true - } - if pwc = l.pwc; pwc != nil { - if match := matchLevelForAny(pwc.next, toks[i+1:], np, nq); match { - return true - } - } - n = l.nodes[t] - if n != nil { - l = n.next - } else { - l = nil - } - } - if n != nil { - if np != nil && nq != nil { - *np += len(n.psubs) - for _, qsub := range n.qsubs { - *nq += len(qsub) - } - } - return len(n.plist) > 0 || len(n.psubs) > 0 || len(n.qsubs) > 0 - } - if pwc != nil { - if np != nil && nq != nil { - *np += len(pwc.psubs) - for _, qsub := range pwc.qsubs { - *nq += len(qsub) - } - } - return len(pwc.plist) > 0 || len(pwc.psubs) > 0 || len(pwc.qsubs) > 0 - } - return false -} - -// lnt is used to track descent into levels for a removal for pruning. -type lnt struct { - l *level - n *node - t string -} - -// Raw low level remove, can do batches with lock held outside. -func (s *Sublist) remove(sub *subscription, shouldLock bool, doCacheUpdates bool) error { - subject := string(sub.subject) - tsa := [32]string{} - tokens := tsa[:0] - start := 0 - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - tokens = append(tokens, subject[start:i]) - start = i + 1 - } - } - tokens = append(tokens, subject[start:]) - - if shouldLock { - s.Lock() - defer s.Unlock() - } - - var sfwc, haswc bool - var n *node - l := s.root - - // Track levels for pruning - var lnts [32]lnt - levels := lnts[:0] - - for _, t := range tokens { - lt := len(t) - if lt == 0 || sfwc { - return ErrInvalidSubject - } - if l == nil { - return ErrNotFound - } - if lt > 1 { - n = l.nodes[t] - } else { - switch t[0] { - case pwc: - n = l.pwc - haswc = true - case fwc: - n = l.fwc - haswc, sfwc = true, true - default: - n = l.nodes[t] - } - } - if n != nil { - levels = append(levels, lnt{l, n, t}) - l = n.next - } else { - l = nil - } - } - removed, last := s.removeFromNode(n, sub) - if !removed { - return ErrNotFound - } - - s.count-- - s.removes++ - - for i := len(levels) - 1; i >= 0; i-- { - l, n, t := levels[i].l, levels[i].n, levels[i].t - if n.isEmpty() { - l.pruneNode(n, t) - } - } - if doCacheUpdates { - s.removeFromCache(subject) - atomic.AddUint64(&s.genid, 1) - } - - if s.notify != nil && last && !haswc && len(s.notify.remove) > 0 { - s.chkForRemoveNotification(subject, string(sub.queue)) - } - - return nil -} - -// Remove will remove a subscription. -func (s *Sublist) Remove(sub *subscription) error { - return s.remove(sub, true, true) -} - -// RemoveBatch will remove a list of subscriptions. -func (s *Sublist) RemoveBatch(subs []*subscription) error { - if len(subs) == 0 { - return nil - } - - s.Lock() - defer s.Unlock() - - // TODO(dlc) - We could try to be smarter here for a client going away but the account - // has a large number of subscriptions compared to this client. Quick and dirty testing - // though said just disabling all the time best for now. - - // Turn off our cache if enabled. - wasEnabled := s.cache != nil - s.cache = nil - // We will try to remove all subscriptions but will report the first that caused - // an error. In other words, we don't bail out at the first error which would - // possibly leave a bunch of subscriptions that could have been removed. - var err error - for _, sub := range subs { - if lerr := s.remove(sub, false, false); lerr != nil && err == nil { - err = lerr - } - } - // Turn caching back on here. - atomic.AddUint64(&s.genid, 1) - if wasEnabled { - s.cache = make(map[string]*SublistResult) - } - return err -} - -// pruneNode is used to prune an empty node from the tree. -func (l *level) pruneNode(n *node, t string) { - if n == nil { - return - } - if n == l.fwc { - l.fwc = nil - } else if n == l.pwc { - l.pwc = nil - } else { - delete(l.nodes, t) - } -} - -// isEmpty will test if the node has any entries. Used -// in pruning. -func (n *node) isEmpty() bool { - if len(n.psubs) == 0 && len(n.qsubs) == 0 { - if n.next == nil || n.next.numNodes() == 0 { - return true - } - } - return false -} - -// Return the number of nodes for the given level. -func (l *level) numNodes() int { - num := len(l.nodes) - if l.pwc != nil { - num++ - } - if l.fwc != nil { - num++ - } - return num -} - -// Remove the sub for the given node. -func (s *Sublist) removeFromNode(n *node, sub *subscription) (found, last bool) { - if n == nil { - return false, true - } - if sub.queue == nil { - _, found = n.psubs[sub] - delete(n.psubs, sub) - if found && n.plist != nil { - // This will brute force remove the plist to perform - // correct behavior. Will get re-populated on a call - // to Match as needed. - n.plist = nil - } - return found, len(n.psubs) == 0 - } - - // We have a queue group subscription here - qsub := n.qsubs[string(sub.queue)] - _, found = qsub[sub] - delete(qsub, sub) - if len(qsub) == 0 { - // This is the last queue subscription interest when len(qsub) == 0, not - // when n.qsubs is empty. - last = true - delete(n.qsubs, string(sub.queue)) - } - return found, last -} - -// Count returns the number of subscriptions. -func (s *Sublist) Count() uint32 { - s.RLock() - defer s.RUnlock() - return s.count -} - -// CacheCount returns the number of result sets in the cache. -func (s *Sublist) CacheCount() int { - s.RLock() - cc := len(s.cache) - s.RUnlock() - return cc -} - -// SublistStats are public stats for the sublist -type SublistStats struct { - NumSubs uint32 `json:"num_subscriptions"` - NumCache uint32 `json:"num_cache"` - NumInserts uint64 `json:"num_inserts"` - NumRemoves uint64 `json:"num_removes"` - NumMatches uint64 `json:"num_matches"` - CacheHitRate float64 `json:"cache_hit_rate"` - MaxFanout uint32 `json:"max_fanout"` - AvgFanout float64 `json:"avg_fanout"` - totFanout int - cacheCnt int - cacheHits uint64 -} - -func (s *SublistStats) add(stat *SublistStats) { - s.NumSubs += stat.NumSubs - s.NumCache += stat.NumCache - s.NumInserts += stat.NumInserts - s.NumRemoves += stat.NumRemoves - s.NumMatches += stat.NumMatches - s.cacheHits += stat.cacheHits - if s.MaxFanout < stat.MaxFanout { - s.MaxFanout = stat.MaxFanout - } - - // ignore slStats.AvgFanout, collect the values - // it's based on instead - s.totFanout += stat.totFanout - s.cacheCnt += stat.cacheCnt - if s.totFanout > 0 { - s.AvgFanout = float64(s.totFanout) / float64(s.cacheCnt) - } - if s.NumMatches > 0 { - s.CacheHitRate = float64(s.cacheHits) / float64(s.NumMatches) - } -} - -// Stats will return a stats structure for the current state. -func (s *Sublist) Stats() *SublistStats { - st := &SublistStats{} - - s.RLock() - cache := s.cache - cc := len(s.cache) - st.NumSubs = s.count - st.NumInserts = s.inserts - st.NumRemoves = s.removes - s.RUnlock() - - st.NumCache = uint32(cc) - st.NumMatches = atomic.LoadUint64(&s.matches) - st.cacheHits = atomic.LoadUint64(&s.cacheHits) - if st.NumMatches > 0 { - st.CacheHitRate = float64(st.cacheHits) / float64(st.NumMatches) - } - - // whip through cache for fanout stats, this can be off if cache is full and doing evictions. - // If this is called frequently, which it should not be, this could hurt performance. - if cache != nil { - tot, max, clen := 0, 0, 0 - s.RLock() - for _, r := range s.cache { - clen++ - l := len(r.psubs) + len(r.qsubs) - tot += l - if l > max { - max = l - } - } - s.RUnlock() - st.totFanout = tot - st.cacheCnt = clen - st.MaxFanout = uint32(max) - if tot > 0 { - st.AvgFanout = float64(tot) / float64(clen) - } - } - return st -} - -// numLevels will return the maximum number of levels -// contained in the Sublist tree. -func (s *Sublist) numLevels() int { - return visitLevel(s.root, 0) -} - -// visitLevel is used to descend the Sublist tree structure -// recursively. -func visitLevel(l *level, depth int) int { - if l == nil || l.numNodes() == 0 { - return depth - } - - depth++ - maxDepth := depth - - for _, n := range l.nodes { - if n == nil { - continue - } - newDepth := visitLevel(n.next, depth) - if newDepth > maxDepth { - maxDepth = newDepth - } - } - if l.pwc != nil { - pwcDepth := visitLevel(l.pwc.next, depth) - if pwcDepth > maxDepth { - maxDepth = pwcDepth - } - } - if l.fwc != nil { - fwcDepth := visitLevel(l.fwc.next, depth) - if fwcDepth > maxDepth { - maxDepth = fwcDepth - } - } - return maxDepth -} - -// Determine if a subject has any wildcard tokens. -func subjectHasWildcard(subject string) bool { - // This one exits earlier then !subjectIsLiteral(subject) - for i, c := range subject { - if c == pwc || c == fwc { - if (i == 0 || subject[i-1] == btsep) && - (i+1 == len(subject) || subject[i+1] == btsep) { - return true - } - } - } - return false -} - -// Determine if the subject has any wildcards. Fast version, does not check for -// valid subject. Used in caching layer. -func subjectIsLiteral(subject string) bool { - for i, c := range subject { - if c == pwc || c == fwc { - if (i == 0 || subject[i-1] == btsep) && - (i+1 == len(subject) || subject[i+1] == btsep) { - return false - } - } - } - return true -} - -// IsValidPublishSubject returns true if a subject is valid and a literal, false otherwise -func IsValidPublishSubject(subject string) bool { - return IsValidSubject(subject) && subjectIsLiteral(subject) -} - -// IsValidSubject returns true if a subject is valid, false otherwise -func IsValidSubject(subject string) bool { - return isValidSubject(subject, false) -} - -func isValidSubject(subject string, checkRunes bool) bool { - if subject == _EMPTY_ { - return false - } - if checkRunes { - // Check if we have embedded nulls. - if bytes.IndexByte(stringToBytes(subject), 0) >= 0 { - return false - } - // Since casting to a string will always produce valid UTF-8, we need to look for replacement runes. - // This signals something is off or corrupt. - for _, r := range subject { - if r == utf8.RuneError { - return false - } - } - } - sfwc := false - tokens := strings.Split(subject, tsep) - for _, t := range tokens { - length := len(t) - if length == 0 || sfwc { - return false - } - if length > 1 { - if strings.ContainsAny(t, "\t\n\f\r ") { - return false - } - continue - } - switch t[0] { - case fwc: - sfwc = true - case ' ', '\t', '\n', '\r', '\f': - return false - } - } - return true -} - -// IsValidLiteralSubject returns true if a subject is valid and literal (no wildcards), false otherwise -func IsValidLiteralSubject(subject string) bool { - return isValidLiteralSubject(strings.Split(subject, tsep)) -} - -// isValidLiteralSubject returns true if the tokens are valid and literal (no wildcards), false otherwise -func isValidLiteralSubject(tokens []string) bool { - for _, t := range tokens { - if len(t) == 0 { - return false - } - if len(t) > 1 { - continue - } - switch t[0] { - case pwc, fwc: - return false - } - } - return true -} - -// ValidateMapping returns nil error if the subject is a valid subject mapping destination subject -func ValidateMapping(src string, dest string) error { - if dest == _EMPTY_ { - return nil - } - subjectTokens := strings.Split(dest, tsep) - sfwc := false - for _, t := range subjectTokens { - length := len(t) - if length == 0 || sfwc { - return &mappingDestinationErr{t, ErrInvalidMappingDestinationSubject} - } - - // if it looks like it contains a mapping function, it should be a valid mapping function - if length > 4 && t[0] == '{' && t[1] == '{' && t[length-2] == '}' && t[length-1] == '}' { - if !partitionMappingFunctionRegEx.MatchString(t) && - !wildcardMappingFunctionRegEx.MatchString(t) && - !splitFromLeftMappingFunctionRegEx.MatchString(t) && - !splitFromRightMappingFunctionRegEx.MatchString(t) && - !sliceFromLeftMappingFunctionRegEx.MatchString(t) && - !sliceFromRightMappingFunctionRegEx.MatchString(t) && - !splitMappingFunctionRegEx.MatchString(t) { - return &mappingDestinationErr{t, ErrUnknownMappingDestinationFunction} - } else { - continue - } - } - - if length == 1 && t[0] == fwc { - sfwc = true - } else if strings.ContainsAny(t, "\t\n\f\r ") { - return ErrInvalidMappingDestinationSubject - } - } - - // Finally, verify that the transform can actually be created from the source and destination - _, err := NewSubjectTransform(src, dest) - return err -} - -// Will check tokens and report back if the have any partial or full wildcards. -func analyzeTokens(tokens []string) (hasPWC, hasFWC bool) { - for _, t := range tokens { - if lt := len(t); lt == 0 || lt > 1 { - continue - } - switch t[0] { - case pwc: - hasPWC = true - case fwc: - hasFWC = true - } - } - return -} - -// Check on a token basis if they could match. -func tokensCanMatch(t1, t2 string) bool { - if len(t1) == 0 || len(t2) == 0 { - return false - } - t1c, t2c := t1[0], t2[0] - if t1c == pwc || t2c == pwc || t1c == fwc || t2c == fwc { - return true - } - return t1 == t2 -} - -// SubjectsCollide will determine if two subjects could both match a single literal subject. -func SubjectsCollide(subj1, subj2 string) bool { - if subj1 == subj2 { - return true - } - toks1 := strings.Split(subj1, tsep) - toks2 := strings.Split(subj2, tsep) - pwc1, fwc1 := analyzeTokens(toks1) - pwc2, fwc2 := analyzeTokens(toks2) - // if both literal just string compare. - l1, l2 := !(pwc1 || fwc1), !(pwc2 || fwc2) - if l1 && l2 { - return subj1 == subj2 - } - // So one or both have wildcards. If one is literal than we can do subset matching. - if l1 && !l2 { - return isSubsetMatch(toks1, subj2) - } else if l2 && !l1 { - return isSubsetMatch(toks2, subj1) - } - // Both have wildcards. - // If they only have partials then the lengths must match. - if !fwc1 && !fwc2 && len(toks1) != len(toks2) { - return false - } - if lt1, lt2 := len(toks1), len(toks2); lt1 != lt2 { - // If the shorter one only has partials then these will not collide. - if lt1 < lt2 && !fwc1 || lt2 < lt1 && !fwc2 { - return false - } - } - - stop := len(toks1) - if len(toks2) < stop { - stop = len(toks2) - } - - // We look for reasons to say no. - for i := 0; i < stop; i++ { - t1, t2 := toks1[i], toks2[i] - if !tokensCanMatch(t1, t2) { - return false - } - } - - return true -} - -// Returns number of tokens in the subject. -func numTokens(subject string) int { - var numTokens int - if len(subject) == 0 { - return 0 - } - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - numTokens++ - } - } - return numTokens + 1 -} - -// Fast way to return an indexed token. -// This is one based, so first token is TokenAt(subject, 1) -func tokenAt(subject string, index uint8) string { - ti, start := uint8(1), 0 - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - if ti == index { - return subject[start:i] - } - start = i + 1 - ti++ - } - } - if ti == index { - return subject[start:] - } - return _EMPTY_ -} - -// use similar to append. meaning, the updated slice will be returned -func tokenizeSubjectIntoSlice(tts []string, subject string) []string { - start := 0 - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - tts = append(tts, subject[start:i]) - start = i + 1 - } - } - tts = append(tts, subject[start:]) - return tts -} - -// Calls into the function isSubsetMatch() -func subjectIsSubsetMatch(subject, test string) bool { - tsa := [32]string{} - tts := tokenizeSubjectIntoSlice(tsa[:0], subject) - return isSubsetMatch(tts, test) -} - -// This will test a subject as an array of tokens against a test subject -// Calls into the function isSubsetMatchTokenized -func isSubsetMatch(tokens []string, test string) bool { - tsa := [32]string{} - tts := tokenizeSubjectIntoSlice(tsa[:0], test) - return isSubsetMatchTokenized(tokens, tts) -} - -// This will test a subject as an array of tokens against a test subject (also encoded as array of tokens) -// and determine if the tokens are matched. Both test subject and tokens -// may contain wildcards. So foo.* is a subset match of [">", "*.*", "foo.*"], -// but not of foo.bar, etc. -func isSubsetMatchTokenized(tokens, test []string) bool { - // Walk the target tokens - for i, t2 := range test { - if i >= len(tokens) { - return false - } - l := len(t2) - if l == 0 { - return false - } - if t2[0] == fwc && l == 1 { - return true - } - t1 := tokens[i] - - l = len(t1) - if l == 0 || t1[0] == fwc && l == 1 { - return false - } - - if t1[0] == pwc && len(t1) == 1 { - m := t2[0] == pwc && len(t2) == 1 - if !m { - return false - } - if i >= len(test) { - return true - } - continue - } - if t2[0] != pwc && strings.Compare(t1, t2) != 0 { - return false - } - } - return len(tokens) == len(test) -} - -// matchLiteral is used to test literal subjects, those that do not have any -// wildcards, with a target subject. This is used in the cache layer. -func matchLiteral(literal, subject string) bool { - li := 0 - ll := len(literal) - ls := len(subject) - for i := 0; i < ls; i++ { - if li >= ll { - return false - } - // This function has been optimized for speed. - // For instance, do not set b:=subject[i] here since - // we may bump `i` in this loop to avoid `continue` or - // skipping common test in a particular test. - // Run Benchmark_SublistMatchLiteral before making any change. - switch subject[i] { - case pwc: - // NOTE: This is not testing validity of a subject, instead ensures - // that wildcards are treated as such if they follow some basic rules, - // namely that they are a token on their own. - if i == 0 || subject[i-1] == btsep { - if i == ls-1 { - // There is no more token in the subject after this wildcard. - // Skip token in literal and expect to not find a separator. - for { - // End of literal, this is a match. - if li >= ll { - return true - } - // Presence of separator, this can't be a match. - if literal[li] == btsep { - return false - } - li++ - } - } else if subject[i+1] == btsep { - // There is another token in the subject after this wildcard. - // Skip token in literal and expect to get a separator. - for { - // We found the end of the literal before finding a separator, - // this can't be a match. - if li >= ll { - return false - } - if literal[li] == btsep { - break - } - li++ - } - // Bump `i` since we know there is a `.` following, we are - // safe. The common test below is going to check `.` with `.` - // which is good. A `continue` here is too costly. - i++ - } - } - case fwc: - // For `>` to be a wildcard, it means being the only or last character - // in the string preceded by a `.` - if (i == 0 || subject[i-1] == btsep) && i == ls-1 { - return true - } - } - if subject[i] != literal[li] { - return false - } - li++ - } - // Make sure we have processed all of the literal's chars.. - return li >= ll -} - -func addLocalSub(sub *subscription, subs *[]*subscription, includeLeafHubs bool) { - if sub != nil && sub.client != nil { - kind := sub.client.kind - if kind == CLIENT || kind == SYSTEM || kind == JETSTREAM || kind == ACCOUNT || - (includeLeafHubs && sub.client.isHubLeafNode() /* implied kind==LEAF */) { - *subs = append(*subs, sub) - } - } -} - -func (s *Sublist) addNodeToSubs(n *node, subs *[]*subscription, includeLeafHubs bool) { - // Normal subscriptions - if n.plist != nil { - for _, sub := range n.plist { - addLocalSub(sub, subs, includeLeafHubs) - } - } else { - for sub := range n.psubs { - addLocalSub(sub, subs, includeLeafHubs) - } - } - // Queue subscriptions - for _, qr := range n.qsubs { - for sub := range qr { - addLocalSub(sub, subs, includeLeafHubs) - } - } -} - -func (s *Sublist) collectLocalSubs(l *level, subs *[]*subscription, includeLeafHubs bool) { - for _, n := range l.nodes { - s.addNodeToSubs(n, subs, includeLeafHubs) - s.collectLocalSubs(n.next, subs, includeLeafHubs) - } - if l.pwc != nil { - s.addNodeToSubs(l.pwc, subs, includeLeafHubs) - s.collectLocalSubs(l.pwc.next, subs, includeLeafHubs) - } - if l.fwc != nil { - s.addNodeToSubs(l.fwc, subs, includeLeafHubs) - s.collectLocalSubs(l.fwc.next, subs, includeLeafHubs) - } -} - -// Return all local client subscriptions. Use the supplied slice. -func (s *Sublist) localSubs(subs *[]*subscription, includeLeafHubs bool) { - s.RLock() - s.collectLocalSubs(s.root, subs, includeLeafHubs) - s.RUnlock() -} - -// All is used to collect all subscriptions. -func (s *Sublist) All(subs *[]*subscription) { - s.RLock() - s.collectAllSubs(s.root, subs) - s.RUnlock() -} - -func (s *Sublist) addAllNodeToSubs(n *node, subs *[]*subscription) { - // Normal subscriptions - if n.plist != nil { - *subs = append(*subs, n.plist...) - } else { - for sub := range n.psubs { - *subs = append(*subs, sub) - } - } - // Queue subscriptions - for _, qr := range n.qsubs { - for sub := range qr { - *subs = append(*subs, sub) - } - } -} - -func (s *Sublist) collectAllSubs(l *level, subs *[]*subscription) { - for _, n := range l.nodes { - s.addAllNodeToSubs(n, subs) - s.collectAllSubs(n.next, subs) - } - if l.pwc != nil { - s.addAllNodeToSubs(l.pwc, subs) - s.collectAllSubs(l.pwc.next, subs) - } - if l.fwc != nil { - s.addAllNodeToSubs(l.fwc, subs) - s.collectAllSubs(l.fwc.next, subs) - } -} - -// For a given subject (which may contain wildcards), this call returns all -// subscriptions that would match that subject. For instance, suppose that -// the sublist contains: foo.bar, foo.bar.baz and foo.baz, ReverseMatch("foo.*") -// would return foo.bar and foo.baz. -// This is used in situations where the sublist is likely to contain only -// literals and one wants to get all the subjects that would have been a match -// to a subscription on `subject`. -func (s *Sublist) ReverseMatch(subject string) *SublistResult { - tsa := [32]string{} - tokens := tsa[:0] - start := 0 - for i := 0; i < len(subject); i++ { - if subject[i] == btsep { - tokens = append(tokens, subject[start:i]) - start = i + 1 - } - } - tokens = append(tokens, subject[start:]) - - result := &SublistResult{} - - s.RLock() - reverseMatchLevel(s.root, tokens, nil, result) - // Check for empty result. - if len(result.psubs) == 0 && len(result.qsubs) == 0 { - result = emptyResult - } - s.RUnlock() - - return result -} - -func reverseMatchLevel(l *level, toks []string, n *node, results *SublistResult) { - if l == nil { - return - } - for i, t := range toks { - if len(t) == 1 { - if t[0] == fwc { - getAllNodes(l, results) - return - } else if t[0] == pwc { - for _, n := range l.nodes { - reverseMatchLevel(n.next, toks[i+1:], n, results) - } - if l.pwc != nil { - reverseMatchLevel(l.pwc.next, toks[i+1:], n, results) - } - if l.fwc != nil { - getAllNodes(l, results) - } - return - } - } - // If the sub tree has a fwc at this position, match as well. - if l.fwc != nil { - getAllNodes(l, results) - return - } else if l.pwc != nil { - reverseMatchLevel(l.pwc.next, toks[i+1:], n, results) - } - n = l.nodes[t] - if n == nil { - break - } - l = n.next - } - if n != nil { - addNodeToResults(n, results) - } -} - -func getAllNodes(l *level, results *SublistResult) { - if l == nil { - return - } - if l.pwc != nil { - addNodeToResults(l.pwc, results) - } - if l.fwc != nil { - addNodeToResults(l.fwc, results) - } - for _, n := range l.nodes { - addNodeToResults(n, results) - getAllNodes(n.next, results) - } -} - -// IntersectStree will match all items in the given subject tree that -// have interest expressed in the given sublist. The callback will only be called -// once for each subject, regardless of overlapping subscriptions in the sublist. -func IntersectStree[T any](st *stree.SubjectTree[T], sl *Sublist, cb func(subj []byte, entry *T)) { - var _subj [255]byte - intersectStree(st, sl.root, _subj[:0], cb) -} - -func intersectStree[T any](st *stree.SubjectTree[T], r *level, subj []byte, cb func(subj []byte, entry *T)) { - if r.numNodes() == 0 { - // For wildcards we can't avoid Match, but if it's a literal subject at - // this point, using Find is considerably cheaper. - if subjectHasWildcard(bytesToString(subj)) { - st.Match(subj, cb) - } else if e, ok := st.Find(subj); ok { - cb(subj, e) - } - return - } - nsubj := subj - if len(nsubj) > 0 { - nsubj = append(subj, '.') - } - switch { - case r.fwc != nil: - // We've reached a full wildcard, do a FWC match on the stree at this point - // and don't keep iterating downward. - nsubj := append(nsubj, '>') - st.Match(nsubj, cb) - case r.pwc != nil: - // We've found a partial wildcard. We'll keep iterating downwards, but first - // check whether there's interest at this level (without triggering dupes) and - // match if so. - nsubj := append(nsubj, '*') - if len(r.pwc.psubs)+len(r.pwc.qsubs) > 0 && r.pwc.next != nil && r.pwc.next.numNodes() > 0 { - st.Match(nsubj, cb) - } - intersectStree(st, r.pwc.next, nsubj, cb) - case r.numNodes() > 0: - // Normal node with subject literals, keep iterating. - for t, n := range r.nodes { - nsubj := append(nsubj, t...) - intersectStree(st, n.next, nsubj, cb) - } - } -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_bsd.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_bsd.go deleted file mode 100644 index 6cc63b1d..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_bsd.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2019-2021 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build freebsd || openbsd || dragonfly || netbsd - -package sysmem - -func Memory() int64 { - return sysctlInt64("hw.physmem") -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_darwin.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_darwin.go deleted file mode 100644 index f8e049b9..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_darwin.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2019-2021 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build darwin - -package sysmem - -func Memory() int64 { - return sysctlInt64("hw.memsize") -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_linux.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_linux.go deleted file mode 100644 index 26e0bd15..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_linux.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2019-2021 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build linux - -package sysmem - -import "syscall" - -func Memory() int64 { - var info syscall.Sysinfo_t - err := syscall.Sysinfo(&info) - if err != nil { - return 0 - } - return int64(info.Totalram) * int64(info.Unit) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_wasm.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_wasm.go deleted file mode 100644 index bbc43af7..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_wasm.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2022 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build wasm - -package sysmem - -func Memory() int64 { - // TODO: We don't know the system memory - return 0 -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_windows.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_windows.go deleted file mode 100644 index 3f070887..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_windows.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2019-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build windows - -package sysmem - -import ( - "unsafe" - - "golang.org/x/sys/windows" -) - -var winKernel32 = windows.NewLazySystemDLL("kernel32.dll") -var winGlobalMemoryStatusEx = winKernel32.NewProc("GlobalMemoryStatusEx") - -func init() { - if err := winKernel32.Load(); err != nil { - panic(err) - } - if err := winGlobalMemoryStatusEx.Find(); err != nil { - panic(err) - } -} - -// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex -type _memoryStatusEx struct { - dwLength uint32 - dwMemoryLoad uint32 - ullTotalPhys uint64 - unused [6]uint64 // ignore rest of struct -} - -func Memory() int64 { - msx := &_memoryStatusEx{dwLength: 64} - res, _, _ := winGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msx))) - if res == 0 { - return 0 - } - return int64(msx.ullTotalPhys) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_zos.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_zos.go deleted file mode 100644 index cc57620e..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/mem_zos.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2022-2023 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build zos - -package sysmem - -func Memory() int64 { - // TODO: We don't know the system memory - return 0 -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/sysctl.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/sysctl.go deleted file mode 100644 index 550961ae..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/sysmem/sysctl.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2019-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build darwin || freebsd || openbsd || dragonfly || netbsd - -package sysmem - -import ( - "syscall" - "unsafe" -) - -func sysctlInt64(name string) int64 { - s, err := syscall.Sysctl(name) - if err != nil { - return 0 - } - // Make sure it's 8 bytes when we do the cast below. - // We were getting fatal error: checkptr: converted pointer straddles multiple allocations in go 1.22.1 on darwin. - var b [8]byte - copy(b[:], s) - return *(*int64)(unsafe.Pointer(&b[0])) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/thw/thw.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/thw/thw.go deleted file mode 100644 index 5a544701..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/thw/thw.go +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright 2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package thw - -import ( - "encoding/binary" - "errors" - "io" - "math" - "time" -) - -// Error for when we can not locate a task for removal or updates. -var ErrTaskNotFound = errors.New("thw: task not found") - -// Error for when we try to decode a binary-encoded THW with an unknown version number. -var ErrInvalidVersion = errors.New("thw: encoded version not known") - -const ( - tickDuration = int64(time.Second) // Tick duration in nanoseconds. - wheelBits = 12 // 2^12 = 4096 slots. - wheelSize = 1 << wheelBits // Number of slots in the wheel. - wheelMask = wheelSize - 1 // Mask for calculating position. - headerLen = 17 // 1 byte magic + 2x uint64s -) - -// slot represents a single slot in the wheel. -type slot struct { - entries map[uint64]int64 // Map of sequence to expires. - lowest int64 // Lowest expiration time in this slot. -} - -// HashWheel represents the timing wheel. -type HashWheel struct { - wheel []*slot // Array of slots. - lowest int64 // Track the lowest expiration time across all slots. - count uint64 // How many entries are present? -} - -// NewHashWheel initializes a new HashWheel. -func NewHashWheel() *HashWheel { - return &HashWheel{ - wheel: make([]*slot, wheelSize), - lowest: math.MaxInt64, - } -} - -// getPosition calculates the slot position for a given expiration time. -func (hw *HashWheel) getPosition(expires int64) int64 { - return (expires / tickDuration) & wheelMask -} - -// updateLowestExpires finds the new lowest expiration time across all slots. -func (hw *HashWheel) updateLowestExpires() { - lowest := int64(math.MaxInt64) - for _, s := range hw.wheel { - if s != nil && s.lowest < lowest { - lowest = s.lowest - } - } - hw.lowest = lowest -} - -// newSlot creates a new slot. -func newSlot() *slot { - return &slot{ - entries: make(map[uint64]int64), - lowest: math.MaxInt64, - } -} - -// Add schedules a new timer task. -func (hw *HashWheel) Add(seq uint64, expires int64) error { - pos := hw.getPosition(expires) - // Initialize the slot lazily. - if hw.wheel[pos] == nil { - hw.wheel[pos] = newSlot() - } - if _, ok := hw.wheel[pos].entries[seq]; !ok { - hw.count++ - } - hw.wheel[pos].entries[seq] = expires - - // Update slot's lowest expiration if this is earlier. - if expires < hw.wheel[pos].lowest { - hw.wheel[pos].lowest = expires - // Update global lowest if this is now the earliest. - if expires < hw.lowest { - hw.lowest = expires - } - } - - return nil -} - -// Remove removes a timer task. -func (hw *HashWheel) Remove(seq uint64, expires int64) error { - pos := hw.getPosition(expires) - s := hw.wheel[pos] - if s == nil { - return ErrTaskNotFound - } - if _, exists := s.entries[seq]; !exists { - return ErrTaskNotFound - } - delete(s.entries, seq) - hw.count-- - - // If the slot is empty, we can set it to nil to free memory. - if len(s.entries) == 0 { - hw.wheel[pos] = nil - } else if expires == s.lowest { - // Find new lowest in this slot. - lowest := int64(math.MaxInt64) - for _, exp := range s.entries { - if exp < lowest { - lowest = exp - } - } - s.lowest = lowest - } - - // If we removed the global lowest, find the new one. - if expires == hw.lowest { - hw.updateLowestExpires() - } - - return nil -} - -// Update updates the expiration time of an existing timer task. -func (hw *HashWheel) Update(seq uint64, oldExpires int64, newExpires int64) error { - // Remove from old position. - if err := hw.Remove(seq, oldExpires); err != nil { - return err - } - // Add to new position. - return hw.Add(seq, newExpires) -} - -// ExpireTasks processes all expired tasks using a callback. -func (hw *HashWheel) ExpireTasks(callback func(seq uint64, expires int64)) { - now := time.Now().UnixNano() - - // Quick return if nothing is expired. - if hw.lowest > now { - return - } - - // Start from the slot containing the lowest expiration. - startPos, exitPos := hw.getPosition(hw.lowest), hw.getPosition(now+tickDuration) - var updateLowest bool - - for offset := int64(0); ; offset++ { - pos := (startPos + offset) & wheelMask - if pos == exitPos { - if updateLowest { - hw.updateLowestExpires() - } - return - } - // Grab our slot. - slot := hw.wheel[pos] - if slot == nil || slot.lowest > now { - continue - } - - // Track new lowest while processing expirations - newLowest := int64(math.MaxInt64) - for seq, expires := range slot.entries { - if expires <= now { - callback(seq, expires) - delete(slot.entries, seq) - hw.count-- - updateLowest = true - } else if expires < newLowest { - newLowest = expires - } - } - - // Nil out if we are empty. - if len(slot.entries) == 0 { - hw.wheel[pos] = nil - } else { - slot.lowest = newLowest - } - } -} - -// GetNextExpiration returns the earliest expiration time before the given time. -// Returns math.MaxInt64 if no expirations exist before the specified time. -func (hw *HashWheel) GetNextExpiration(before int64) int64 { - if hw.lowest < before { - return hw.lowest - } - return math.MaxInt64 -} - -// AppendEncode writes out the contents of the THW into a binary snapshot -// and returns it. The high seq number is included in the snapshot and will -// be returned on decode. -func (hw *HashWheel) Encode(highSeq uint64) []byte { - b := make([]byte, 0, headerLen+(hw.count*(2*binary.MaxVarintLen64))) - b = append(b, 1) // Magic version - b = binary.LittleEndian.AppendUint64(b, hw.count) // Entry count - b = binary.LittleEndian.AppendUint64(b, highSeq) // Stamp - for _, slot := range hw.wheel { - if slot == nil || slot.entries == nil { - continue - } - for v, ts := range slot.entries { - b = binary.AppendVarint(b, ts) - b = binary.AppendUvarint(b, v) - } - } - return b -} - -// Decode snapshots a binary-encoded THW and replaces the contents of this -// THW with them. Returns the high seq number from the snapshot. -func (hw *HashWheel) Decode(b []byte) (uint64, error) { - if len(b) < headerLen { - return 0, io.ErrShortBuffer - } - if b[0] != 1 { - return 0, ErrInvalidVersion - } - hw.wheel = make([]*slot, wheelSize) - hw.lowest = math.MaxInt64 - count := binary.LittleEndian.Uint64(b[1:]) - stamp := binary.LittleEndian.Uint64(b[9:]) - b = b[headerLen:] - for i := uint64(0); i < count; i++ { - ts, tn := binary.Varint(b) - if tn < 0 { - return 0, io.ErrUnexpectedEOF - } - v, vn := binary.Uvarint(b[tn:]) - if vn < 0 { - return 0, io.ErrUnexpectedEOF - } - hw.Add(v, ts) - b = b[tn+vn:] - } - return stamp, nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/tpm/js_ek_tpm_other.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/tpm/js_ek_tpm_other.go deleted file mode 100644 index a1ed593a..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/tpm/js_ek_tpm_other.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build !windows - -package tpm - -import "fmt" - -// LoadJetStreamEncryptionKeyFromTPM here is a stub for unsupported platforms. -func LoadJetStreamEncryptionKeyFromTPM(srkPassword, jsKeyFile, jsKeyPassword string, pcr int) (string, error) { - return "", fmt.Errorf("TPM functionality is not supported on this platform") -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/tpm/js_ek_tpm_windows.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/tpm/js_ek_tpm_windows.go deleted file mode 100644 index 5e401680..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/tpm/js_ek_tpm_windows.go +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright 2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//go:build windows - -package tpm - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "io" - "os" - "path/filepath" - - "github.com/google/go-tpm/legacy/tpm2" - "github.com/google/go-tpm/tpmutil" - "github.com/nats-io/nkeys" -) - -var ( - // Version of the NATS TPM JS implmentation - JsKeyTPMVersion = 1 -) - -// How this works: -// Create a Storage Root Key (SRK) in the TPM. -// If existing JS Encryption keys do not exist on disk. -// - Create a JetStream encryption key (js key) and seal it to the SRK -// using a provided js encryption key password. -// - Save the public and private blobs to a file on disk. -// - Return the new js encryption key (the private portion of the nkey) -// Otherwise (keys exist on disk) -// - Read the public and private blobs from disk -// - Load them into the TPM -// - Unseal the js key using the TPM, and the provided js encryption keys password. -// -// Note: a SRK password for the SRK is supported but not tested here. - -// Gets/Regenerates the Storage Root Key (SRK) from the TPM. Caller MUST flush this handle when done. -func regenerateSRK(rwc io.ReadWriteCloser, srkPassword string) (tpmutil.Handle, error) { - // Default EK template defined in: - // https://trustedcomputinggroup.org/wp-content/uploads/Credential_Profile_EK_V2.0_R14_published.pdf - // Shared SRK template based off of EK template and specified in: - // https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf - srkTemplate := tpm2.Public{ - Type: tpm2.AlgRSA, - NameAlg: tpm2.AlgSHA256, - Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin | tpm2.FlagUserWithAuth | tpm2.FlagRestricted | tpm2.FlagDecrypt | tpm2.FlagNoDA, - AuthPolicy: nil, - // We must use RSA 2048 for the intel TSS2 stack - RSAParameters: &tpm2.RSAParams{ - Symmetric: &tpm2.SymScheme{ - Alg: tpm2.AlgAES, - KeyBits: 128, - Mode: tpm2.AlgCFB, - }, - KeyBits: 2048, - ModulusRaw: make([]byte, 256), - }, - } - // Create the parent key against which to seal the data - srkHandle, _, err := tpm2.CreatePrimary(rwc, tpm2.HandleOwner, tpm2.PCRSelection{}, "", srkPassword, srkTemplate) - return srkHandle, err -} - -type natsTPMPersistedKeys struct { - Version int `json:"version"` - PrivateKey []byte `json:"private_key"` - PublicKey []byte `json:"public_key"` -} - -// Writes the private and public blobs to disk in a single file. If the directory does -// not exist, it will be created. If the file already exists it will be overwritten. -func writeTPMKeysToFile(filename string, privateBlob []byte, publicBlob []byte) error { - keyDir := filepath.Dir(filename) - if err := os.MkdirAll(keyDir, 0750); err != nil { - return fmt.Errorf("unable to create/access directory %q: %v", keyDir, err) - } - - // Create a new set of persisted keys. Note that the private key doesn't necessarily - // need to be protected as the TPM password is required to use unseal, although it's - // a good idea to put this in a secure location accessible to the server. - tpmKeys := natsTPMPersistedKeys{ - Version: JsKeyTPMVersion, - PrivateKey: make([]byte, base64.StdEncoding.EncodedLen(len(privateBlob))), - PublicKey: make([]byte, base64.StdEncoding.EncodedLen(len(publicBlob))), - } - base64.StdEncoding.Encode(tpmKeys.PrivateKey, privateBlob) - base64.StdEncoding.Encode(tpmKeys.PublicKey, publicBlob) - // Convert to JSON - keysJSON, err := json.Marshal(tpmKeys) - if err != nil { - return fmt.Errorf("unable to marshal keys to JSON: %v", err) - } - // Write the JSON to a file - if err := os.WriteFile(filename, keysJSON, 0640); err != nil { - return fmt.Errorf("unable to write keys file to %q: %v", filename, err) - } - return nil -} - -// Reads the private and public blobs from a single file. If the file does not exist, -// or the file cannot be read and the keys decoded, an error is returned. -func readTPMKeysFromFile(filename string) ([]byte, []byte, error) { - keysJSON, err := os.ReadFile(filename) - if err != nil { - return nil, nil, err - } - - var tpmKeys natsTPMPersistedKeys - if err := json.Unmarshal(keysJSON, &tpmKeys); err != nil { - return nil, nil, fmt.Errorf("unable to unmarshal TPM file keys JSON from %s: %v", filename, err) - } - - // Placeholder for future-proofing. Here is where we would - // check the current version against tpmKeys.Version and - // handle any changes. - - // Base64 decode the private and public blobs. - privateBlob := make([]byte, base64.StdEncoding.DecodedLen(len(tpmKeys.PrivateKey))) - publicBlob := make([]byte, base64.StdEncoding.DecodedLen(len(tpmKeys.PublicKey))) - prn, err := base64.StdEncoding.Decode(privateBlob, tpmKeys.PrivateKey) - if err != nil { - return nil, nil, fmt.Errorf("unable to decode privateBlob from base64: %v", err) - } - pun, err := base64.StdEncoding.Decode(publicBlob, tpmKeys.PublicKey) - if err != nil { - return nil, nil, fmt.Errorf("unable to decode publicBlob from base64: %v", err) - } - return publicBlob[:pun], privateBlob[:prn], nil -} - -// Creates a new JetStream encryption key, seals it to the TPM, and saves the public and -// private blobs to disk in a JSON encoded file. The key is returned as a string. -func createAndSealJsEncryptionKey(rwc io.ReadWriteCloser, srkHandle tpmutil.Handle, srkPassword, jsKeyFile, jsKeyPassword string, pcr int) (string, error) { - // Get the authorization policy that will protect the data to be sealed - sessHandle, policy, err := policyPCRPasswordSession(rwc, pcr) - if err != nil { - return "", fmt.Errorf("unable to get policy: %v", err) - } - if err := tpm2.FlushContext(rwc, sessHandle); err != nil { - return "", fmt.Errorf("unable to flush session: %v", err) - } - // Seal the data to the parent key and the policy - user, err := nkeys.CreateUser() - if err != nil { - return "", fmt.Errorf("unable to create seed: %v", err) - } - // We'll use the seed to represent the encryption key. - jsStoreKey, err := user.Seed() - if err != nil { - return "", fmt.Errorf("unable to get seed: %v", err) - } - privateArea, publicArea, err := tpm2.Seal(rwc, srkHandle, srkPassword, jsKeyPassword, policy, jsStoreKey) - if err != nil { - return "", fmt.Errorf("unable to seal data: %v", err) - } - err = writeTPMKeysToFile(jsKeyFile, privateArea, publicArea) - if err != nil { - return "", fmt.Errorf("unable to write key file: %v", err) - } - return string(jsStoreKey), nil -} - -// Unseals the JetStream encryption key from the TPM with the provided keys. -// The key is returned as a string. -func unsealJsEncrpytionKey(rwc io.ReadWriteCloser, pcr int, srkHandle tpmutil.Handle, srkPassword, objectPassword string, publicBlob, privateBlob []byte) (string, error) { - // Load the public/private blobs into the TPM for decryption. - objectHandle, _, err := tpm2.Load(rwc, srkHandle, srkPassword, publicBlob, privateBlob) - if err != nil { - return "", fmt.Errorf("unable to load data: %v", err) - } - defer tpm2.FlushContext(rwc, objectHandle) - - // Create the authorization session with TPM. - sessHandle, _, err := policyPCRPasswordSession(rwc, pcr) - if err != nil { - return "", fmt.Errorf("unable to get auth session: %v", err) - } - defer func() { - tpm2.FlushContext(rwc, sessHandle) - }() - // Unseal the data we've loaded into the TPM with the object (js key) password. - unsealedData, err := tpm2.UnsealWithSession(rwc, sessHandle, objectHandle, objectPassword) - if err != nil { - return "", fmt.Errorf("unable to unseal data: %v", err) - } - return string(unsealedData), nil -} - -// Returns session handle and policy digest. -func policyPCRPasswordSession(rwc io.ReadWriteCloser, pcr int) (sessHandle tpmutil.Handle, policy []byte, retErr error) { - sessHandle, _, err := tpm2.StartAuthSession( - rwc, - tpm2.HandleNull, /*tpmKey*/ - tpm2.HandleNull, /*bindKey*/ - make([]byte, 16), /*nonceCaller*/ - nil, /*secret*/ - tpm2.SessionPolicy, - tpm2.AlgNull, - tpm2.AlgSHA256) - if err != nil { - return tpm2.HandleNull, nil, fmt.Errorf("unable to start session: %v", err) - } - defer func() { - if sessHandle != tpm2.HandleNull && err != nil { - if err := tpm2.FlushContext(rwc, sessHandle); err != nil { - retErr = fmt.Errorf("%v\nunable to flush session: %v", retErr, err) - } - } - }() - - pcrSelection := tpm2.PCRSelection{ - Hash: tpm2.AlgSHA256, - PCRs: []int{pcr}, - } - if err := tpm2.PolicyPCR(rwc, sessHandle, nil, pcrSelection); err != nil { - return sessHandle, nil, fmt.Errorf("unable to bind PCRs to auth policy: %v", err) - } - if err := tpm2.PolicyPassword(rwc, sessHandle); err != nil { - return sessHandle, nil, fmt.Errorf("unable to require password for auth policy: %v", err) - } - policy, err = tpm2.PolicyGetDigest(rwc, sessHandle) - if err != nil { - return sessHandle, nil, fmt.Errorf("unable to get policy digest: %v", err) - } - return sessHandle, policy, nil -} - -// LoadJetStreamEncryptionKeyFromTPM loads the JetStream encryption key from the TPM. -// If the keyfile does not exist, a key will be created and sealed. Public and private blobs -// used to decrypt the key in future sessions will be saved to disk in the file provided. -// The key will be unsealed and returned only with the correct password and PCR value. -func LoadJetStreamEncryptionKeyFromTPM(srkPassword, jsKeyFile, jsKeyPassword string, pcr int) (string, error) { - rwc, err := tpm2.OpenTPM() - if err != nil { - return "", fmt.Errorf("could not open the TPM: %v", err) - } - defer rwc.Close() - - // Load the key from the TPM - srkHandle, err := regenerateSRK(rwc, srkPassword) - defer func() { - tpm2.FlushContext(rwc, srkHandle) - }() - if err != nil { - return "", fmt.Errorf("unable to regenerate SRK from the TPM: %v", err) - } - // Read the keys from the key file. If the filed doesn't exist it means we need to create - // a new js encrytpion key. - publicBlob, privateBlob, err := readTPMKeysFromFile(jsKeyFile) - if err != nil { - if os.IsNotExist(err) { - jsek, err := createAndSealJsEncryptionKey(rwc, srkHandle, srkPassword, jsKeyFile, jsKeyPassword, pcr) - if err != nil { - return "", fmt.Errorf("unable to generate new key from the TPM: %v", err) - } - // we've created and sealed the JS Encryption key, now we just return it. - return jsek, nil - } - return "", fmt.Errorf("unable to load key from TPM: %v", err) - } - - // Unseal the JetStream encryption key using the TPM. - jsek, err := unsealJsEncrpytionKey(rwc, pcr, srkHandle, srkPassword, jsKeyPassword, publicBlob, privateBlob) - if err != nil { - return "", fmt.Errorf("unable to unseal key from the TPM: %v", err) - } - return jsek, nil -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/util.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/util.go deleted file mode 100644 index f9fd695c..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/util.go +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright 2012-2024 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "math" - "net" - "net/url" - "reflect" - "strconv" - "strings" - "time" -) - -// This map is used to store URLs string as the key with a reference count as -// the value. This is used to handle gossiped URLs such as connect_urls, etc.. -type refCountedUrlSet map[string]int - -// Ascii numbers 0-9 -const ( - asciiZero = 48 - asciiNine = 57 -) - -func versionComponents(version string) (major, minor, patch int, err error) { - m := semVerRe.FindStringSubmatch(version) - if len(m) == 0 { - return 0, 0, 0, errors.New("invalid semver") - } - major, err = strconv.Atoi(m[1]) - if err != nil { - return -1, -1, -1, err - } - minor, err = strconv.Atoi(m[2]) - if err != nil { - return -1, -1, -1, err - } - patch, err = strconv.Atoi(m[3]) - if err != nil { - return -1, -1, -1, err - } - return major, minor, patch, err -} - -func versionAtLeastCheckError(version string, emajor, eminor, epatch int) (bool, error) { - major, minor, patch, err := versionComponents(version) - if err != nil { - return false, err - } - if major > emajor || - (major == emajor && minor > eminor) || - (major == emajor && minor == eminor && patch >= epatch) { - return true, nil - } - return false, err -} - -func versionAtLeast(version string, emajor, eminor, epatch int) bool { - res, _ := versionAtLeastCheckError(version, emajor, eminor, epatch) - return res -} - -// parseSize expects decimal positive numbers. We -// return -1 to signal error. -func parseSize(d []byte) (n int) { - const maxParseSizeLen = 9 //999M - - l := len(d) - if l == 0 || l > maxParseSizeLen { - return -1 - } - var ( - i int - dec byte - ) - - // Note: Use `goto` here to avoid for loop in order - // to have the function be inlined. - // See: https://github.com/golang/go/issues/14768 -loop: - dec = d[i] - if dec < asciiZero || dec > asciiNine { - return -1 - } - n = n*10 + (int(dec) - asciiZero) - - i++ - if i < l { - goto loop - } - return n -} - -// parseInt64 expects decimal positive numbers. We -// return -1 to signal error -func parseInt64(d []byte) (n int64) { - if len(d) == 0 { - return -1 - } - for _, dec := range d { - if dec < asciiZero || dec > asciiNine { - return -1 - } - n = n*10 + (int64(dec) - asciiZero) - } - return n -} - -// Helper to move from float seconds to time.Duration -func secondsToDuration(seconds float64) time.Duration { - ttl := seconds * float64(time.Second) - return time.Duration(ttl) -} - -// Parse a host/port string with a default port to use -// if none (or 0 or -1) is specified in `hostPort` string. -func parseHostPort(hostPort string, defaultPort int) (host string, port int, err error) { - if hostPort != "" { - host, sPort, err := net.SplitHostPort(hostPort) - if ae, ok := err.(*net.AddrError); ok && strings.Contains(ae.Err, "missing port") { - // try appending the current port - host, sPort, err = net.SplitHostPort(fmt.Sprintf("%s:%d", hostPort, defaultPort)) - } - if err != nil { - return "", -1, err - } - port, err = strconv.Atoi(strings.TrimSpace(sPort)) - if err != nil { - return "", -1, err - } - if port == 0 || port == -1 { - port = defaultPort - } - return strings.TrimSpace(host), port, nil - } - return "", -1, errors.New("no hostport specified") -} - -// Returns true if URL u1 represents the same URL than u2, -// false otherwise. -func urlsAreEqual(u1, u2 *url.URL) bool { - return reflect.DeepEqual(u1, u2) -} - -// comma produces a string form of the given number in base 10 with -// commas after every three orders of magnitude. -// -// e.g. comma(834142) -> 834,142 -// -// This function was copied from the github.com/dustin/go-humanize -// package and is Copyright Dustin Sallings -func comma(v int64) string { - sign := "" - - // Min int64 can't be negated to a usable value, so it has to be special cased. - if v == math.MinInt64 { - return "-9,223,372,036,854,775,808" - } - - if v < 0 { - sign = "-" - v = 0 - v - } - - parts := []string{"", "", "", "", "", "", ""} - j := len(parts) - 1 - - for v > 999 { - parts[j] = strconv.FormatInt(v%1000, 10) - switch len(parts[j]) { - case 2: - parts[j] = "0" + parts[j] - case 1: - parts[j] = "00" + parts[j] - } - v = v / 1000 - j-- - } - parts[j] = strconv.Itoa(int(v)) - return sign + strings.Join(parts[j:], ",") -} - -// Adds urlStr to the given map. If the string was already present, simply -// bumps the reference count. -// Returns true only if it was added for the first time. -func (m refCountedUrlSet) addUrl(urlStr string) bool { - m[urlStr]++ - return m[urlStr] == 1 -} - -// Removes urlStr from the given map. If the string is not present, nothing -// is done and false is returned. -// If the string was present, its reference count is decreased. Returns true -// if this was the last reference, false otherwise. -func (m refCountedUrlSet) removeUrl(urlStr string) bool { - removed := false - if ref, ok := m[urlStr]; ok { - if ref == 1 { - removed = true - delete(m, urlStr) - } else { - m[urlStr]-- - } - } - return removed -} - -// Returns the unique URLs in this map as a slice -func (m refCountedUrlSet) getAsStringSlice() []string { - a := make([]string, 0, len(m)) - for u := range m { - a = append(a, u) - } - return a -} - -// natsListenConfig provides a common configuration to match the one used by -// net.Listen() but with our own defaults. -// Go 1.13 introduced default-on TCP keepalives with aggressive timings and -// there's no sane portable way in Go with stdlib to split the initial timer -// from the retry timer. Linux/BSD defaults are 2hrs/75s and Go sets both -// to 15s; the issue re making them indepedently tunable has been open since -// 2014 and this code here is being written in 2020. -// The NATS protocol has its own L7 PING/PONG keepalive system and the Go -// defaults are inappropriate for IoT deployment scenarios. -// Replace any NATS-protocol calls to net.Listen(...) with -// natsListenConfig.Listen(ctx,...) or use natsListen(); leave calls for HTTP -// monitoring, etc, on the default. -var natsListenConfig = &net.ListenConfig{ - KeepAlive: -1, -} - -// natsListen() is the same as net.Listen() except that TCP keepalives are -// disabled (to match Go's behavior before Go 1.13). -func natsListen(network, address string) (net.Listener, error) { - return natsListenConfig.Listen(context.Background(), network, address) -} - -// natsDialTimeout is the same as net.DialTimeout() except the TCP keepalives -// are disabled (to match Go's behavior before Go 1.13). -func natsDialTimeout(network, address string, timeout time.Duration) (net.Conn, error) { - d := net.Dialer{ - Timeout: timeout, - KeepAlive: -1, - } - return d.Dial(network, address) -} - -// redactURLList() returns a copy of a list of URL pointers where each item -// in the list will either be the same pointer if the URL does not contain a -// password, or to a new object if there is a password. -// The intended use-case is for logging lists of URLs safely. -func redactURLList(unredacted []*url.URL) []*url.URL { - r := make([]*url.URL, len(unredacted)) - // In the common case of no passwords, if we don't let the new object leave - // this function then GC should be easier. - needCopy := false - for i := range unredacted { - if unredacted[i] == nil { - r[i] = nil - continue - } - if _, has := unredacted[i].User.Password(); !has { - r[i] = unredacted[i] - continue - } - needCopy = true - ru := *unredacted[i] - ru.User = url.UserPassword(ru.User.Username(), "xxxxx") - r[i] = &ru - } - if needCopy { - return r - } - return unredacted -} - -// redactURLString() attempts to redact a URL string. -func redactURLString(raw string) string { - if !strings.ContainsRune(raw, '@') { - return raw - } - u, err := url.Parse(raw) - if err != nil { - return raw - } - return u.Redacted() -} - -// getURLsAsString returns a slice of u.Host from the given slice of url.URL's -func getURLsAsString(urls []*url.URL) []string { - a := make([]string, 0, len(urls)) - for _, u := range urls { - a = append(a, u.Host) - } - return a -} - -// copyBytes make a new slice of the same size than `src` and copy its content. -// If `src` is nil or its length is 0, then this returns `nil` -func copyBytes(src []byte) []byte { - if len(src) == 0 { - return nil - } - dst := make([]byte, len(src)) - copy(dst, src) - return dst -} - -// copyStrings make a new slice of the same size than `src` and copy its content. -// If `src` is nil, then this returns `nil` -func copyStrings(src []string) []string { - if src == nil { - return nil - } - dst := make([]string, len(src)) - copy(dst, src) - return dst -} - -// Returns a byte slice for the INFO protocol. -func generateInfoJSON(info *Info) []byte { - b, _ := json.Marshal(info) - pcs := [][]byte{[]byte("INFO"), b, []byte(CR_LF)} - return bytes.Join(pcs, []byte(" ")) -} diff --git a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/websocket.go b/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/websocket.go deleted file mode 100644 index 67239c37..00000000 --- a/src/code.cloudfoundry.org/vendor/github.com/nats-io/nats-server/v2/server/websocket.go +++ /dev/null @@ -1,1529 +0,0 @@ -// Copyright 2020-2025 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package server - -import ( - "bytes" - crand "crypto/rand" - "crypto/sha1" - "crypto/tls" - "encoding/base64" - "encoding/binary" - "errors" - "fmt" - "io" - "log" - mrand "math/rand" - "net" - "net/http" - "net/url" - "strconv" - "strings" - "sync" - "time" - "unicode/utf8" - - "github.com/klauspost/compress/flate" -) - -type wsOpCode int - -const ( - // From https://tools.ietf.org/html/rfc6455#section-5.2 - wsTextMessage = wsOpCode(1) - wsBinaryMessage = wsOpCode(2) - wsCloseMessage = wsOpCode(8) - wsPingMessage = wsOpCode(9) - wsPongMessage = wsOpCode(10) - - wsFinalBit = 1 << 7 - wsRsv1Bit = 1 << 6 // Used for compression, from https://tools.ietf.org/html/rfc7692#section-6 - wsRsv2Bit = 1 << 5 - wsRsv3Bit = 1 << 4 - - wsMaskBit = 1 << 7 - - wsContinuationFrame = 0 - wsMaxFrameHeaderSize = 14 // Since LeafNode may need to behave as a client - wsMaxControlPayloadSize = 125 - wsFrameSizeForBrowsers = 4096 // From experiment, webrowsers behave better with limited frame size - wsCompressThreshold = 64 // Don't compress for small buffer(s) - wsCloseSatusSize = 2 - - // From https://tools.ietf.org/html/rfc6455#section-11.7 - wsCloseStatusNormalClosure = 1000 - wsCloseStatusGoingAway = 1001 - wsCloseStatusProtocolError = 1002 - wsCloseStatusUnsupportedData = 1003 - wsCloseStatusNoStatusReceived = 1005 - wsCloseStatusInvalidPayloadData = 1007 - wsCloseStatusPolicyViolation = 1008 - wsCloseStatusMessageTooBig = 1009 - wsCloseStatusInternalSrvError = 1011 - wsCloseStatusTLSHandshake = 1015 - - wsFirstFrame = true - wsContFrame = false - wsFinalFrame = true - wsUncompressedFrame = false - - wsSchemePrefix = "ws" - wsSchemePrefixTLS = "wss" - - wsNoMaskingHeader = "Nats-No-Masking" - wsNoMaskingValue = "true" - wsXForwardedForHeader = "X-Forwarded-For" - wsNoMaskingFullResponse = wsNoMaskingHeader + ": " + wsNoMaskingValue + CR_LF - wsPMCExtension = "permessage-deflate" // per-message compression - wsPMCSrvNoCtx = "server_no_context_takeover" - wsPMCCliNoCtx = "client_no_context_takeover" - wsPMCReqHeaderValue = wsPMCExtension + "; " + wsPMCSrvNoCtx + "; " + wsPMCCliNoCtx - wsPMCFullResponse = "Sec-WebSocket-Extensions: " + wsPMCExtension + "; " + wsPMCSrvNoCtx + "; " + wsPMCCliNoCtx + _CRLF_ - wsSecProto = "Sec-Websocket-Protocol" - wsMQTTSecProtoVal = "mqtt" - wsMQTTSecProto = wsSecProto + ": " + wsMQTTSecProtoVal + CR_LF -) - -var decompressorPool sync.Pool -var compressLastBlock = []byte{0x00, 0x00, 0xff, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff} - -// From https://tools.ietf.org/html/rfc6455#section-1.3 -var wsGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") - -// Test can enable this so that server does not support "no-masking" requests. -var wsTestRejectNoMasking = false - -type websocket struct { - frames net.Buffers - fs int64 - closeMsg []byte - compress bool - closeSent bool - browser bool - nocompfrag bool // No fragment for compressed frames - maskread bool - maskwrite bool - compressor *flate.Writer - cookieJwt string - cookieUsername string - cookiePassword string - cookieToken string - clientIP string -} - -type srvWebsocket struct { - mu sync.RWMutex - server *http.Server - listener net.Listener - listenerErr error - allowedOrigins map[string]*allowedOrigin // host will be the key - sameOrigin bool - connectURLs []string - connectURLsMap refCountedUrlSet - authOverride bool // indicate if there is auth override in websocket config - rawHeaders string // raw headers to be used in the upgrade response. - - // These are immutable and can be accessed without lock. - // This is the case when generating the client INFO. - tls bool // True if TLS is required (TLSConfig is specified). - host string // Host/IP the webserver is listening on (shortcut to opts.Websocket.Host). - port int // Port the webserver is listening on. This is after an ephemeral port may have been selected (shortcut to opts.Websocket.Port). -} - -type allowedOrigin struct { - scheme string - port string -} - -type wsUpgradeResult struct { - conn net.Conn - ws *websocket - kind int -} - -type wsReadInfo struct { - rem int - fs bool - ff bool - fc bool - mask bool // Incoming leafnode connections may not have masking. - mkpos byte - mkey [4]byte - cbufs [][]byte - coff int -} - -func (r *wsReadInfo) init() { - r.fs, r.ff = true, true -} - -// Returns a slice containing `needed` bytes from the given buffer `buf` -// starting at position `pos`, and possibly read from the given reader `r`. -// When bytes are present in `buf`, the `pos` is incremented by the number -// of bytes found up to `needed` and the new position is returned. If not -// enough bytes are found, the bytes found in `buf` are copied to the returned -// slice and the remaning bytes are read from `r`. -func wsGet(r io.Reader, buf []byte, pos, needed int) ([]byte, int, error) { - avail := len(buf) - pos - if avail >= needed { - return buf[pos : pos+needed], pos + needed, nil - } - b := make([]byte, needed) - start := copy(b, buf[pos:]) - for start != needed { - n, err := r.Read(b[start:cap(b)]) - if err != nil { - return nil, 0, err - } - start += n - } - return b, pos + avail, nil -} - -// Returns true if this connection is from a Websocket client. -// Lock held on entry. -func (c *client) isWebsocket() bool { - return c.ws != nil -} - -// Returns a slice of byte slices corresponding to payload of websocket frames. -// The byte slice `buf` is filled with bytes from the connection's read loop. -// This function will decode the frame headers and unmask the payload(s). -// It is possible that the returned slices point to the given `buf` slice, so -// `buf` should not be overwritten until the returned slices have been parsed. -// -// Client lock MUST NOT be held on entry. -func (c *client) wsRead(r *wsReadInfo, ior io.Reader, buf []byte) ([][]byte, error) { - var ( - bufs [][]byte - tmpBuf []byte - err error - pos int - max = len(buf) - ) - for pos != max { - if r.fs { - b0 := buf[pos] - frameType := wsOpCode(b0 & 0xF) - final := b0&wsFinalBit != 0 - compressed := b0&wsRsv1Bit != 0 - pos++ - - tmpBuf, pos, err = wsGet(ior, buf, pos, 1) - if err != nil { - return bufs, err - } - b1 := tmpBuf[0] - - // Clients MUST set the mask bit. If not set, reject. - // However, LEAF by default will not have masking, unless they are forced to, by configuration. - if r.mask && b1&wsMaskBit == 0 { - return bufs, c.wsHandleProtocolError("mask bit missing") - } - - // Store size in case it is < 125 - r.rem = int(b1 & 0x7F) - - switch frameType { - case wsPingMessage, wsPongMessage, wsCloseMessage: - if r.rem > wsMaxControlPayloadSize { - return bufs, c.wsHandleProtocolError( - fmt.Sprintf("control frame length bigger than maximum allowed of %v bytes", - wsMaxControlPayloadSize)) - } - if !final { - return bufs, c.wsHandleProtocolError("control frame does not have final bit set") - } - case wsTextMessage, wsBinaryMessage: - if !r.ff { - return bufs, c.wsHandleProtocolError("new message started before final frame for previous message was received") - } - r.ff = final - r.fc = compressed - case wsContinuationFrame: - // Compressed bit must be only set in the first frame - if r.ff || compressed { - return bufs, c.wsHandleProtocolError("invalid continuation frame") - } - r.ff = final - default: - return bufs, c.wsHandleProtocolError(fmt.Sprintf("unknown opcode %v", frameType)) - } - - switch r.rem { - case 126: - tmpBuf, pos, err = wsGet(ior, buf, pos, 2) - if err != nil { - return bufs, err - } - r.rem = int(binary.BigEndian.Uint16(tmpBuf)) - case 127: - tmpBuf, pos, err = wsGet(ior, buf, pos, 8) - if err != nil { - return bufs, err - } - r.rem = int(binary.BigEndian.Uint64(tmpBuf)) - } - - if r.mask { - // Read masking key - tmpBuf, pos, err = wsGet(ior, buf, pos, 4) - if err != nil { - return bufs, err - } - copy(r.mkey[:], tmpBuf) - r.mkpos = 0 - } - - // Handle control messages in place... - if wsIsControlFrame(frameType) { - pos, err = c.wsHandleControlFrame(r, frameType, ior, buf, pos) - if err != nil { - return bufs, err - } - continue - } - - // Done with the frame header - r.fs = false - } - if pos < max { - var b []byte - var n int - - n = r.rem - if pos+n > max { - n = max - pos - } - b = buf[pos : pos+n] - pos += n - r.rem -= n - // If needed, unmask the buffer - if r.mask { - r.unmask(b) - } - addToBufs := true - // Handle compressed message - if r.fc { - // Assume that we may have continuation frames or not the full payload. - addToBufs = false - // Make a copy of the buffer before adding it to the list - // of compressed fragments. - r.cbufs = append(r.cbufs, append([]byte(nil), b...)) - // When we have the final frame and we have read the full payload, - // we can decompress it. - if r.ff && r.rem == 0 { - b, err = r.decompress() - if err != nil { - return bufs, err - } - r.fc = false - // Now we can add to `bufs` - addToBufs = true - } - } - // For non compressed frames, or when we have decompressed the - // whole message. - if addToBufs { - bufs = append(bufs, b) - } - // If payload has been fully read, then indicate that next - // is the start of a frame. - if r.rem == 0 { - r.fs = true - } - } - } - return bufs, nil -} - -func (r *wsReadInfo) Read(dst []byte) (int, error) { - if len(dst) == 0 { - return 0, nil - } - if len(r.cbufs) == 0 { - return 0, io.EOF - } - copied := 0 - rem := len(dst) - for buf := r.cbufs[0]; buf != nil && rem > 0; { - n := len(buf[r.coff:]) - if n > rem { - n = rem - } - copy(dst[copied:], buf[r.coff:r.coff+n]) - copied += n - rem -= n - r.coff += n - buf = r.nextCBuf() - } - return copied, nil -} - -func (r *wsReadInfo) nextCBuf() []byte { - // We still have remaining data in the first buffer - if r.coff != len(r.cbufs[0]) { - return r.cbufs[0] - } - // We read the full first buffer. Reset offset. - r.coff = 0 - // We were at the last buffer, so we are done. - if len(r.cbufs) == 1 { - r.cbufs = nil - return nil - } - // Here we move to the next buffer. - r.cbufs = r.cbufs[1:] - return r.cbufs[0] -} - -func (r *wsReadInfo) ReadByte() (byte, error) { - if len(r.cbufs) == 0 { - return 0, io.EOF - } - b := r.cbufs[0][r.coff] - r.coff++ - r.nextCBuf() - return b, nil -} - -func (r *wsReadInfo) decompress() ([]byte, error) { - r.coff = 0 - // As per https://tools.ietf.org/html/rfc7692#section-7.2.2 - // add 0x00, 0x00, 0xff, 0xff and then a final block so that flate reader - // does not report unexpected EOF. - r.cbufs = append(r.cbufs, compressLastBlock) - // Get a decompressor from the pool and bind it to this object (wsReadInfo) - // that provides Read() and ReadByte() APIs that will consume the compressed - // buffers (r.cbufs). - d, _ := decompressorPool.Get().(io.ReadCloser) - if d == nil { - d = flate.NewReader(r) - } else { - d.(flate.Resetter).Reset(r, nil) - } - // This will do the decompression. - b, err := io.ReadAll(d) - decompressorPool.Put(d) - // Now reset the compressed buffers list. - r.cbufs = nil - return b, err -} - -// Handles the PING, PONG and CLOSE websocket control frames. -// -// Client lock MUST NOT be held on entry. -func (c *client) wsHandleControlFrame(r *wsReadInfo, frameType wsOpCode, nc io.Reader, buf []byte, pos int) (int, error) { - var payload []byte - var err error - - if r.rem > 0 { - payload, pos, err = wsGet(nc, buf, pos, r.rem) - if err != nil { - return pos, err - } - if r.mask { - r.unmask(payload) - } - r.rem = 0 - } - switch frameType { - case wsCloseMessage: - status := wsCloseStatusNoStatusReceived - var body string - lp := len(payload) - // If there is a payload, the status is represented as a 2-byte - // unsigned integer (in network byte order). Then, there may be an - // optional body. - hasStatus, hasBody := lp >= wsCloseSatusSize, lp > wsCloseSatusSize - if hasStatus { - // Decode the status - status = int(binary.BigEndian.Uint16(payload[:wsCloseSatusSize])) - // Now if there is a body, capture it and make sure this is a valid UTF-8. - if hasBody { - body = string(payload[wsCloseSatusSize:]) - if !utf8.ValidString(body) { - // https://tools.ietf.org/html/rfc6455#section-5.5.1 - // If body is present, it must be a valid utf8 - status = wsCloseStatusInvalidPayloadData - body = "invalid utf8 body in close frame" - } - } - } - // If the status indicates that nothing was received, then we don't - // send anything back. - // From https://datatracker.ietf.org/doc/html/rfc6455#section-7.4 - // it says that code 1005 is a reserved value and MUST NOT be set as a - // status code in a Close control frame by an endpoint. It is - // designated for use in applications expecting a status code to indicate - // that no status code was actually present. - var clm []byte - if status != wsCloseStatusNoStatusReceived { - clm = wsCreateCloseMessage(status, body) - } - c.wsEnqueueControlMessage(wsCloseMessage, clm) - if len(clm) > 0 { - nbPoolPut(clm) // wsEnqueueControlMessage has taken a copy. - } - // Return io.EOF so that readLoop will close the connection as ClientClosed - // after processing pending buffers. - return pos, io.EOF - case wsPingMessage: - c.wsEnqueueControlMessage(wsPongMessage, payload) - case wsPongMessage: - // Nothing to do.. - } - return pos, nil -} - -// Unmask the given slice. -func (r *wsReadInfo) unmask(buf []byte) { - p := int(r.mkpos) - if len(buf) < 16 { - for i := 0; i < len(buf); i++ { - buf[i] ^= r.mkey[p&3] - p++ - } - r.mkpos = byte(p & 3) - return - } - var k [8]byte - for i := 0; i < 8; i++ { - k[i] = r.mkey[(p+i)&3] - } - km := binary.BigEndian.Uint64(k[:]) - n := (len(buf) / 8) * 8 - for i := 0; i < n; i += 8 { - tmp := binary.BigEndian.Uint64(buf[i : i+8]) - tmp ^= km - binary.BigEndian.PutUint64(buf[i:], tmp) - } - buf = buf[n:] - for i := 0; i < len(buf); i++ { - buf[i] ^= r.mkey[p&3] - p++ - } - r.mkpos = byte(p & 3) -} - -// Returns true if the op code corresponds to a control frame. -func wsIsControlFrame(frameType wsOpCode) bool { - return frameType >= wsCloseMessage -} - -// Create the frame header. -// Encodes the frame type and optional compression flag, and the size of the payload. -func wsCreateFrameHeader(useMasking, compressed bool, frameType wsOpCode, l int) ([]byte, []byte) { - fh := nbPoolGet(wsMaxFrameHeaderSize)[:wsMaxFrameHeaderSize] - n, key := wsFillFrameHeader(fh, useMasking, wsFirstFrame, wsFinalFrame, compressed, frameType, l) - return fh[:n], key -} - -func wsFillFrameHeader(fh []byte, useMasking, first, final, compressed bool, frameType wsOpCode, l int) (int, []byte) { - var n int - var b byte - if first { - b = byte(frameType) - } - if final { - b |= wsFinalBit - } - if compressed { - b |= wsRsv1Bit - } - b1 := byte(0) - if useMasking { - b1 |= wsMaskBit - } - switch { - case l <= 125: - n = 2 - fh[0] = b - fh[1] = b1 | byte(l) - case l < 65536: - n = 4 - fh[0] = b - fh[1] = b1 | 126 - binary.BigEndian.PutUint16(fh[2:], uint16(l)) - default: - n = 10 - fh[0] = b - fh[1] = b1 | 127 - binary.BigEndian.PutUint64(fh[2:], uint64(l)) - } - var key []byte - if useMasking { - var keyBuf [4]byte - if _, err := io.ReadFull(crand.Reader, keyBuf[:4]); err != nil { - kv := mrand.Int31() - binary.LittleEndian.PutUint32(keyBuf[:4], uint32(kv)) - } - copy(fh[n:], keyBuf[:4]) - key = fh[n : n+4] - n += 4 - } - return n, key -} - -// Invokes wsEnqueueControlMessageLocked under client lock. -// -// Client lock MUST NOT be held on entry -func (c *client) wsEnqueueControlMessage(controlMsg wsOpCode, payload []byte) { - c.mu.Lock() - c.wsEnqueueControlMessageLocked(controlMsg, payload) - c.mu.Unlock() -} - -// Mask the buffer with the given key -func wsMaskBuf(key, buf []byte) { - for i := 0; i < len(buf); i++ { - buf[i] ^= key[i&3] - } -} - -// Mask the buffers, as if they were contiguous, with the given key -func wsMaskBufs(key []byte, bufs [][]byte) { - pos := 0 - for i := 0; i < len(bufs); i++ { - buf := bufs[i] - for j := 0; j < len(buf); j++ { - buf[j] ^= key[pos&3] - pos++ - } - } -} - -// Enqueues a websocket control message. -// If the control message is a wsCloseMessage, then marks this client -// has having sent the close message (since only one should be sent). -// This will prevent the generic closeConnection() to enqueue one. -// -// Client lock held on entry. -func (c *client) wsEnqueueControlMessageLocked(controlMsg wsOpCode, payload []byte) { - // Control messages are never compressed and their size will be - // less than wsMaxControlPayloadSize, which means the frame header - // will be only 2 or 6 bytes. - useMasking := c.ws.maskwrite - sz := 2 - if useMasking { - sz += 4 - } - cm := nbPoolGet(sz + len(payload)) - cm = cm[:cap(cm)] - n, key := wsFillFrameHeader(cm, useMasking, wsFirstFrame, wsFinalFrame, wsUncompressedFrame, controlMsg, len(payload)) - cm = cm[:n] - // Note that payload is optional. - if len(payload) > 0 { - cm = append(cm, payload...) - if useMasking { - wsMaskBuf(key, cm[n:]) - } - } - c.out.pb += int64(len(cm)) - if controlMsg == wsCloseMessage { - // We can't add the close message to the frames buffers - // now. It will be done on a flushOutbound() when there - // are no more pending buffers to send. - c.ws.closeSent = true - c.ws.closeMsg = cm - } else { - c.ws.frames = append(c.ws.frames, cm) - c.ws.fs += int64(len(cm)) - } - c.flushSignal() -} - -// Enqueues a websocket close message with a status mapped from the given `reason`. -// -// Client lock held on entry -func (c *client) wsEnqueueCloseMessage(reason ClosedState) { - var status int - switch reason { - case ClientClosed: - status = wsCloseStatusNormalClosure - case AuthenticationTimeout, AuthenticationViolation, SlowConsumerPendingBytes, SlowConsumerWriteDeadline, - MaxAccountConnectionsExceeded, MaxConnectionsExceeded, MaxControlLineExceeded, MaxSubscriptionsExceeded, - MissingAccount, AuthenticationExpired, Revocation: - status = wsCloseStatusPolicyViolation - case TLSHandshakeError: - status = wsCloseStatusTLSHandshake - case ParseError, ProtocolViolation, BadClientProtocolVersion: - status = wsCloseStatusProtocolError - case MaxPayloadExceeded: - status = wsCloseStatusMessageTooBig - case WriteError, ReadError, StaleConnection, ServerShutdown: - // We used to have WriteError, ReadError and StaleConnection result in - // code 1006, which the spec says that it must not be used to set the - // status in the close message. So using this one instead. - status = wsCloseStatusGoingAway - default: - status = wsCloseStatusInternalSrvError - } - body := wsCreateCloseMessage(status, reason.String()) - c.wsEnqueueControlMessageLocked(wsCloseMessage, body) - nbPoolPut(body) // wsEnqueueControlMessageLocked has taken a copy. -} - -// Create and then enqueue a close message with a protocol error and the -// given message. This is invoked when parsing websocket frames. -// -// Lock MUST NOT be held on entry. -func (c *client) wsHandleProtocolError(message string) error { - buf := wsCreateCloseMessage(wsCloseStatusProtocolError, message) - c.wsEnqueueControlMessage(wsCloseMessage, buf) - nbPoolPut(buf) // wsEnqueueControlMessage has taken a copy. - return errors.New(message) -} - -// Create a close message with the given `status` and `body`. -// If the `body` is more than the maximum allows control frame payload size, -// it is truncated and "..." is added at the end (as a hint that message -// is not complete). -func wsCreateCloseMessage(status int, body string) []byte { - // Since a control message payload is limited in size, we - // will limit the text and add trailing "..." if truncated. - // The body of a Close Message must be preceded with 2 bytes, - // so take that into account for limiting the body length. - if len(body) > wsMaxControlPayloadSize-2 { - body = body[:wsMaxControlPayloadSize-5] - body += "..." - } - buf := nbPoolGet(2 + len(body))[:2+len(body)] - // We need to have a 2 byte unsigned int that represents the error status code - // https://tools.ietf.org/html/rfc6455#section-5.5.1 - binary.BigEndian.PutUint16(buf[:2], uint16(status)) - copy(buf[2:], []byte(body)) - return buf -} - -// Process websocket client handshake. On success, returns the raw net.Conn that -// will be used to create a *client object. -// Invoked from the HTTP server listening on websocket port. -func (s *Server) wsUpgrade(w http.ResponseWriter, r *http.Request) (*wsUpgradeResult, error) { - kind := CLIENT - if r.URL != nil { - ep := r.URL.EscapedPath() - if strings.HasSuffix(ep, leafNodeWSPath) { - kind = LEAF - } else if strings.HasSuffix(ep, mqttWSPath) { - kind = MQTT - } - } - - opts := s.getOpts() - - // From https://tools.ietf.org/html/rfc6455#section-4.2.1 - // Point 1. - if r.Method != "GET" { - return nil, wsReturnHTTPError(w, r, http.StatusMethodNotAllowed, "request method must be GET") - } - // Point 2. - if r.Host == _EMPTY_ { - return nil, wsReturnHTTPError(w, r, http.StatusBadRequest, "'Host' missing in request") - } - // Point 3. - if !wsHeaderContains(r.Header, "Upgrade", "websocket") { - return nil, wsReturnHTTPError(w, r, http.StatusBadRequest, "invalid value for header 'Upgrade'") - } - // Point 4. - if !wsHeaderContains(r.Header, "Connection", "Upgrade") { - return nil, wsReturnHTTPError(w, r, http.StatusBadRequest, "invalid value for header 'Connection'") - } - // Point 5. - key := r.Header.Get("Sec-Websocket-Key") - if key == _EMPTY_ { - return nil, wsReturnHTTPError(w, r, http.StatusBadRequest, "key missing") - } - // Point 6. - if !wsHeaderContains(r.Header, "Sec-Websocket-Version", "13") { - return nil, wsReturnHTTPError(w, r, http.StatusBadRequest, "invalid version") - } - // Others are optional - // Point 7. - if err := s.websocket.checkOrigin(r); err != nil { - return nil, wsReturnHTTPError(w, r, http.StatusForbidden, fmt.Sprintf("origin not allowed: %v", err)) - } - // Point 8. - // We don't have protocols, so ignore. - // Point 9. - // Extensions, only support for compression at the moment - compress := opts.Websocket.Compression - if compress { - // Simply check if permessage-deflate extension is present. - compress, _ = wsPMCExtensionSupport(r.Header, true) - } - // We will do masking if asked (unless we reject for tests) - noMasking := r.Header.Get(wsNoMaskingHeader) == wsNoMaskingValue && !wsTestRejectNoMasking - - h := w.(http.Hijacker) - conn, brw, err := h.Hijack() - if err != nil { - if conn != nil { - conn.Close() - } - return nil, wsReturnHTTPError(w, r, http.StatusInternalServerError, err.Error()) - } - if brw.Reader.Buffered() > 0 { - conn.Close() - return nil, wsReturnHTTPError(w, r, http.StatusBadRequest, "client sent data before handshake is complete") - } - - var buf [1024]byte - p := buf[:0] - - // From https://tools.ietf.org/html/rfc6455#section-4.2.2 - p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...) - p = append(p, wsAcceptKey(key)...) - p = append(p, _CRLF_...) - if compress { - p = append(p, wsPMCFullResponse...) - } - if noMasking { - p = append(p, wsNoMaskingFullResponse...) - } - if kind == MQTT { - p = append(p, wsMQTTSecProto...) - } - if s.websocket.rawHeaders != _EMPTY_ { - p = append(p, s.websocket.rawHeaders...) - } - p = append(p, _CRLF_...) - - if _, err = conn.Write(p); err != nil { - conn.Close() - return nil, err - } - // If there was a deadline set for the handshake, clear it now. - if opts.Websocket.HandshakeTimeout > 0 { - conn.SetDeadline(time.Time{}) - } - // Server always expect "clients" to send masked payload, unless the option - // "no-masking" has been enabled. - ws := &websocket{compress: compress, maskread: !noMasking} - - // Check for X-Forwarded-For header - if cips, ok := r.Header[wsXForwardedForHeader]; ok { - cip := cips[0] - if net.ParseIP(cip) != nil { - ws.clientIP = cip - } - } - - if kind == CLIENT || kind == MQTT { - // Indicate if this is likely coming from a browser. - if ua := r.Header.Get("User-Agent"); ua != _EMPTY_ && strings.HasPrefix(ua, "Mozilla/") { - ws.browser = true - // Disable fragmentation of compressed frames for Safari browsers. - // Unfortunately, you could be running Chrome on macOS and this - // string will contain "Safari/" (along "Chrome/"). However, what - // I have found is that actual Safari browser also have "Version/". - // So make the combination of the two. - ws.nocompfrag = ws.compress && strings.Contains(ua, "Version/") && strings.Contains(ua, "Safari/") - } - - if cookies := r.Cookies(); len(cookies) > 0 { - ows := &opts.Websocket - for _, c := range cookies { - if ows.JWTCookie == c.Name { - ws.cookieJwt = c.Value - } else if ows.UsernameCookie == c.Name { - ws.cookieUsername = c.Value - } else if ows.PasswordCookie == c.Name { - ws.cookiePassword = c.Value - } else if ows.TokenCookie == c.Name { - ws.cookieToken = c.Value - } - } - } - } - return &wsUpgradeResult{conn: conn, ws: ws, kind: kind}, nil -} - -// Returns true if the header named `name` contains a token with value `value`. -func wsHeaderContains(header http.Header, name string, value string) bool { - for _, s := range header[name] { - tokens := strings.Split(s, ",") - for _, t := range tokens { - t = strings.Trim(t, " \t") - if strings.EqualFold(t, value) { - return true - } - } - } - return false -} - -func wsPMCExtensionSupport(header http.Header, checkPMCOnly bool) (bool, bool) { - for _, extensionList := range header["Sec-Websocket-Extensions"] { - extensions := strings.Split(extensionList, ",") - for _, extension := range extensions { - extension = strings.Trim(extension, " \t") - params := strings.Split(extension, ";") - for i, p := range params { - p = strings.Trim(p, " \t") - if strings.EqualFold(p, wsPMCExtension) { - if checkPMCOnly { - return true, false - } - var snc bool - var cnc bool - for j := i + 1; j < len(params); j++ { - p = params[j] - p = strings.Trim(p, " \t") - if strings.EqualFold(p, wsPMCSrvNoCtx) { - snc = true - } else if strings.EqualFold(p, wsPMCCliNoCtx) { - cnc = true - } - if snc && cnc { - return true, true - } - } - return true, false - } - } - } - } - return false, false -} - -// Send an HTTP error with the given `status` to the given http response writer `w`. -// Return an error created based on the `reason` string. -func wsReturnHTTPError(w http.ResponseWriter, r *http.Request, status int, reason string) error { - err := fmt.Errorf("%s - websocket handshake error: %s", r.RemoteAddr, reason) - w.Header().Set("Sec-Websocket-Version", "13") - http.Error(w, http.StatusText(status), status) - return err -} - -// If the server is configured to accept any origin, then this function returns -// `nil` without checking if the Origin is present and valid. This is also -// the case if the request does not have the Origin header. -// Otherwise, this will check that the Origin matches the same origin or -// any origin in the allowed list. -func (w *srvWebsocket) checkOrigin(r *http.Request) error { - w.mu.RLock() - checkSame := w.sameOrigin - listEmpty := len(w.allowedOrigins) == 0 - w.mu.RUnlock() - if !checkSame && listEmpty { - return nil - } - origin := r.Header.Get("Origin") - if origin == _EMPTY_ { - origin = r.Header.Get("Sec-Websocket-Origin") - } - // If the header is not present, we will accept. - // From https://datatracker.ietf.org/doc/html/rfc6455#section-1.6 - // "Naturally, when the WebSocket Protocol is used by a dedicated client - // directly (i.e., not from a web page through a web browser), the origin - // model is not useful, as the client can provide any arbitrary origin string." - if origin == _EMPTY_ { - return nil - } - u, err := url.ParseRequestURI(origin) - if err != nil { - return err - } - oh, op, err := wsGetHostAndPort(u.Scheme == "https", u.Host) - if err != nil { - return err - } - // If checking same origin, compare with the http's request's Host. - if checkSame { - rh, rp, err := wsGetHostAndPort(r.TLS != nil, r.Host) - if err != nil { - return err - } - if oh != rh || op != rp { - return errors.New("not same origin") - } - // I guess it is possible to have cases where one wants to check - // same origin, but also that the origin is in the allowed list. - // So continue with the next check. - } - if !listEmpty { - w.mu.RLock() - ao := w.allowedOrigins[oh] - w.mu.RUnlock() - if ao == nil || u.Scheme != ao.scheme || op != ao.port { - return errors.New("not in the allowed list") - } - } - return nil -} - -func wsGetHostAndPort(tls bool, hostport string) (string, string, error) { - host, port, err := net.SplitHostPort(hostport) - if err != nil { - // If error is missing port, then use defaults based on the scheme - if ae, ok := err.(*net.AddrError); ok && strings.Contains(ae.Err, "missing port") { - err = nil - host = hostport - if tls { - port = "443" - } else { - port = "80" - } - } - } - return strings.ToLower(host), port, err -} - -// Concatenate the key sent by the client with the GUID, then computes the SHA1 hash -// and returns it as a based64 encoded string. -func wsAcceptKey(key string) string { - h := sha1.New() - h.Write([]byte(key)) - h.Write(wsGUID) - return base64.StdEncoding.EncodeToString(h.Sum(nil)) -} - -func wsMakeChallengeKey() (string, error) { - p := make([]byte, 16) - if _, err := io.ReadFull(crand.Reader, p); err != nil { - return _EMPTY_, err - } - return base64.StdEncoding.EncodeToString(p), nil -} - -// Validate the websocket related options. -func validateWebsocketOptions(o *Options) error { - wo := &o.Websocket - // If no port is defined, we don't care about other options - if wo.Port == 0 { - return nil - } - // Enforce TLS... unless NoTLS is set to true. - if wo.TLSConfig == nil && !wo.NoTLS { - return errors.New("websocket requires TLS configuration") - } - // Make sure that allowed origins, if specified, can be parsed. - for _, ao := range wo.AllowedOrigins { - if _, err := url.Parse(ao); err != nil { - return fmt.Errorf("unable to parse allowed origin: %v", err) - } - } - // If there is a NoAuthUser, we need to have Users defined and - // the user to be present. - if wo.NoAuthUser != _EMPTY_ { - if err := validateNoAuthUser(o, wo.NoAuthUser); err != nil { - return err - } - } - // Token/Username not possible if there are users/nkeys - if len(o.Users) > 0 || len(o.Nkeys) > 0 { - if wo.Username != _EMPTY_ { - return fmt.Errorf("websocket authentication username not compatible with presence of users/nkeys") - } - if wo.Token != _EMPTY_ { - return fmt.Errorf("websocket authentication token not compatible with presence of users/nkeys") - } - } - // Using JWT requires Trusted Keys - if wo.JWTCookie != _EMPTY_ { - if len(o.TrustedOperators) == 0 && len(o.TrustedKeys) == 0 { - return fmt.Errorf("trusted operators or trusted keys configuration is required for JWT authentication via cookie %q", wo.JWTCookie) - } - } - if err := validatePinnedCerts(wo.TLSPinnedCerts); err != nil { - return fmt.Errorf("websocket: %v", err) - } - - // Check for invalid headers here. - for key := range wo.Headers { - k := strings.ToLower(key) - switch k { - case "host", - "content-length", - "connection", - "upgrade", - "nats-no-masking": - return fmt.Errorf("websocket: invalid header %q not allowed", key) - } - - if strings.HasPrefix(k, "sec-websocket-") { - return fmt.Errorf("websocket: invalid header %q, \"Sec-WebSocket-\" prefix not allowed", key) - } - } - - return nil -} - -// Creates or updates the existing map -func (s *Server) wsSetOriginOptions(o *WebsocketOpts) { - ws := &s.websocket - ws.mu.Lock() - defer ws.mu.Unlock() - // Copy over the option's same origin boolean - ws.sameOrigin = o.SameOrigin - // Reset the map. Will help for config reload if/when we support it. - ws.allowedOrigins = nil - if o.AllowedOrigins == nil { - return - } - for _, ao := range o.AllowedOrigins { - // We have previously checked (during options validation) that the urls - // are parseable, but if we get an error, report and skip. - u, err := url.ParseRequestURI(ao) - if err != nil { - s.Errorf("error parsing allowed origin: %v", err) - continue - } - h, p, _ := wsGetHostAndPort(u.Scheme == "https", u.Host) - if ws.allowedOrigins == nil { - ws.allowedOrigins = make(map[string]*allowedOrigin, len(o.AllowedOrigins)) - } - ws.allowedOrigins[h] = &allowedOrigin{scheme: u.Scheme, port: p} - } -} - -// Calculate the raw headers for websocket upgrade response. -func (s *Server) wsSetHeadersOptions(o *WebsocketOpts) { - var sb strings.Builder - for k, v := range o.Headers { - sb.WriteString(k) - sb.WriteString(": ") - sb.WriteString(v) - sb.WriteString(_CRLF_) - } - ws := &s.websocket - ws.mu.Lock() - defer ws.mu.Unlock() - ws.rawHeaders = sb.String() -} - -// Given the websocket options, we check if any auth configuration -// has been provided. If so, possibly create users/nkey users and -// store them in s.websocket.users/nkeys. -// Also update a boolean that indicates if auth is required for -// websocket clients. -// Server lock is held on entry. -func (s *Server) wsConfigAuth(opts *WebsocketOpts) { - ws := &s.websocket - // If any of those is specified, we consider that there is an override. - ws.authOverride = opts.Username != _EMPTY_ || opts.Token != _EMPTY_ || opts.NoAuthUser != _EMPTY_ -} - -func (s *Server) startWebsocketServer() { - if s.isShuttingDown() { - return - } - - sopts := s.getOpts() - o := &sopts.Websocket - - s.wsSetOriginOptions(o) - s.wsSetHeadersOptions(o) - - var hl net.Listener - var proto string - var err error - - port := o.Port - if port == -1 { - port = 0 - } - hp := net.JoinHostPort(o.Host, strconv.Itoa(port)) - - // We are enforcing (when validating the options) the use of TLS, but the - // code was originally supporting both modes. The reason for TLS only is - // that we expect users to send JWTs with bearer tokens and we want to - // avoid the possibility of it being "intercepted". - - s.mu.Lock() - // Do not check o.NoTLS here. If a TLS configuration is available, use it, - // regardless of NoTLS. If we don't have a TLS config, it means that the - // user has configured NoTLS because otherwise the server would have failed - // to start due to options validation. - if o.TLSConfig != nil { - proto = wsSchemePrefixTLS - config := o.TLSConfig.Clone() - config.GetConfigForClient = s.wsGetTLSConfig - hl, err = tls.Listen("tcp", hp, config) - } else { - proto = wsSchemePrefix - hl, err = net.Listen("tcp", hp) - } - s.websocket.listenerErr = err - if err != nil { - s.mu.Unlock() - s.Fatalf("Unable to listen for websocket connections: %v", err) - return - } - if port == 0 { - o.Port = hl.Addr().(*net.TCPAddr).Port - } - s.Noticef("Listening for websocket clients on %s://%s:%d", proto, o.Host, o.Port) - if proto == wsSchemePrefix { - s.Warnf("Websocket not configured with TLS. DO NOT USE IN PRODUCTION!") - } - - // These 3 are immutable and will be accessed without lock by the client - // when generating/sending the INFO protocols. - s.websocket.tls = proto == wsSchemePrefixTLS - s.websocket.host, s.websocket.port = o.Host, o.Port - - // This will be updated when/if the cluster changes. - s.websocket.connectURLs, err = s.getConnectURLs(o.Advertise, o.Host, o.Port) - if err != nil { - s.Fatalf("Unable to get websocket connect URLs: %v", err) - hl.Close() - s.mu.Unlock() - return - } - hasLeaf := sopts.LeafNode.Port != 0 - mux := http.NewServeMux() - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - res, err := s.wsUpgrade(w, r) - if err != nil { - s.Errorf(err.Error()) - return - } - switch res.kind { - case CLIENT: - s.createWSClient(res.conn, res.ws) - case MQTT: - s.createMQTTClient(res.conn, res.ws) - case LEAF: - if !hasLeaf { - s.Errorf("Not configured to accept leaf node connections") - // Silently close for now. If we want to send an error back, we would - // need to create the leafnode client anyway, so that is handling websocket - // frames, then send the error to the remote. - res.conn.Close() - return - } - s.createLeafNode(res.conn, nil, nil, res.ws) - } - }) - hs := &http.Server{ - Addr: hp, - Handler: mux, - ReadTimeout: o.HandshakeTimeout, - ErrorLog: log.New(&captureHTTPServerLog{s, "websocket: "}, _EMPTY_, 0), - } - s.websocket.mu.Lock() - s.websocket.server = hs - s.websocket.listener = hl - s.websocket.mu.Unlock() - go func() { - if err := hs.Serve(hl); err != http.ErrServerClosed { - s.Fatalf("websocket listener error: %v", err) - } - if s.isLameDuckMode() { - // Signal that we are not accepting new clients - s.ldmCh <- true - // Now wait for the Shutdown... - <-s.quitCh - return - } - s.done <- true - }() - s.mu.Unlock() -} - -// The TLS configuration is passed to the listener when the websocket -// "server" is setup. That prevents TLS configuration updates on reload -// from being used. By setting this function in tls.Config.GetConfigForClient -// we instruct the TLS handshake to ask for the tls configuration to be -// used for a specific client. We don't care which client, we always use -// the same TLS configuration. -func (s *Server) wsGetTLSConfig(_ *tls.ClientHelloInfo) (*tls.Config, error) { - opts := s.getOpts() - return opts.Websocket.TLSConfig, nil -} - -// This is similar to createClient() but has some modifications -// specific to handle websocket clients. -// The comments have been kept to minimum to reduce code size. -// Check createClient() for more details. -func (s *Server) createWSClient(conn net.Conn, ws *websocket) *client { - opts := s.getOpts() - - maxPay := int32(opts.MaxPayload) - maxSubs := int32(opts.MaxSubs) - if maxSubs == 0 { - maxSubs = -1 - } - now := time.Now().UTC() - - c := &client{srv: s, nc: conn, opts: defaultOpts, mpay: maxPay, msubs: maxSubs, start: now, last: now, ws: ws} - - c.registerWithAccount(s.globalAccount()) - - var info Info - var authRequired bool - - s.mu.Lock() - info = s.copyInfo() - // Check auth, override if applicable. - if !info.AuthRequired { - // Set info.AuthRequired since this is what is sent to the client. - info.AuthRequired = s.websocket.authOverride - } - if s.nonceRequired() { - var raw [nonceLen]byte - nonce := raw[:] - s.generateNonce(nonce) - info.Nonce = string(nonce) - } - c.nonce = []byte(info.Nonce) - authRequired = info.AuthRequired - - s.totalClients++ - s.mu.Unlock() - - c.mu.Lock() - if authRequired { - c.flags.set(expectConnect) - } - c.initClient() - c.Debugf("Client connection created") - c.sendProtoNow(c.generateClientInfoJSON(info)) - c.mu.Unlock() - - s.mu.Lock() - if !s.isRunning() || s.ldm { - if s.isShuttingDown() { - conn.Close() - } - s.mu.Unlock() - return c - } - - if opts.MaxConn > 0 && len(s.clients) >= opts.MaxConn { - s.mu.Unlock() - c.maxConnExceeded() - return nil - } - s.clients[c.cid] = c - s.mu.Unlock() - - c.mu.Lock() - // Websocket clients do TLS in the websocket http server. - // So no TLS initiation here... - if _, ok := conn.(*tls.Conn); ok { - c.flags.set(handshakeComplete) - } - - if c.isClosed() { - c.mu.Unlock() - c.closeConnection(WriteError) - return nil - } - - if authRequired { - timeout := opts.AuthTimeout - // Possibly override with Websocket specific value. - if opts.Websocket.AuthTimeout != 0 { - timeout = opts.Websocket.AuthTimeout - } - c.setAuthTimer(secondsToDuration(timeout)) - } - - c.setPingTimer() - - s.startGoRoutine(func() { c.readLoop(nil) }) - s.startGoRoutine(func() { c.writeLoop() }) - - c.mu.Unlock() - - return c -} - -func (c *client) wsCollapsePtoNB() (net.Buffers, int64) { - nb := c.out.nb - var mfs int - var usz int - if c.ws.browser { - mfs = wsFrameSizeForBrowsers - } - mask := c.ws.maskwrite - // Start with possible already framed buffers (that we could have - // got from partials or control messages such as ws pings or pongs). - bufs := c.ws.frames - compress := c.ws.compress - if compress && len(nb) > 0 { - // First, make sure we don't compress for very small cumulative buffers. - for _, b := range nb { - usz += len(b) - } - if usz <= wsCompressThreshold { - compress = false - if cp := c.ws.compressor; cp != nil { - cp.Reset(nil) - } - } - } - if compress && len(nb) > 0 { - // Overwrite mfs if this connection does not support fragmented compressed frames. - if mfs > 0 && c.ws.nocompfrag { - mfs = 0 - } - buf := bytes.NewBuffer(nbPoolGet(usz)) - cp := c.ws.compressor - if cp == nil { - c.ws.compressor, _ = flate.NewWriter(buf, flate.BestSpeed) - cp = c.ws.compressor - } else { - cp.Reset(buf) - } - var csz int - for _, b := range nb { - for len(b) > 0 { - n, err := cp.Write(b) - if err != nil { - // Whatever this error is, it'll be handled by the cp.Flush() - // call below, as the same error will be returned there. - // Let the outer loop return all the buffers back to the pool - // and fall through naturally. - break - } - b = b[n:] - } - nbPoolPut(b) // No longer needed as contents written to compressor. - } - if err := cp.Flush(); err != nil { - c.Errorf("Error during compression: %v", err) - c.markConnAsClosed(WriteError) - cp.Reset(nil) - return nil, 0 - } - b := buf.Bytes() - p := b[:len(b)-4] - if mfs > 0 && len(p) > mfs { - for first, final := true, false; len(p) > 0; first = false { - lp := len(p) - if lp > mfs { - lp = mfs - } else { - final = true - } - // Only the first frame should be marked as compressed, so pass - // `first` for the compressed boolean. - fh := nbPoolGet(wsMaxFrameHeaderSize)[:wsMaxFrameHeaderSize] - n, key := wsFillFrameHeader(fh, mask, first, final, first, wsBinaryMessage, lp) - if mask { - wsMaskBuf(key, p[:lp]) - } - bufs = append(bufs, fh[:n], p[:lp]) - csz += n + lp - p = p[lp:] - } - } else { - ol := len(p) - h, key := wsCreateFrameHeader(mask, true, wsBinaryMessage, ol) - if mask { - wsMaskBuf(key, p) - } - if ol > 0 { - bufs = append(bufs, h, p) - } - csz = len(h) + ol - } - // Make sure that the compressor no longer holds a reference to - // the bytes.Buffer, so that the underlying memory gets cleaned - // up after flushOutbound/flushAndClose. For this to be safe, we - // always cp.Reset(...) before reusing the compressor again. - cp.Reset(nil) - // Add to pb the compressed data size (including headers), but - // remove the original uncompressed data size that was added - // during the queueing. - c.out.pb += int64(csz) - int64(usz) - c.ws.fs += int64(csz) - } else if len(nb) > 0 { - var total int - if mfs > 0 { - // We are limiting the frame size. - startFrame := func() int { - bufs = append(bufs, nbPoolGet(wsMaxFrameHeaderSize)) - return len(bufs) - 1 - } - endFrame := func(idx, size int) { - bufs[idx] = bufs[idx][:wsMaxFrameHeaderSize] - n, key := wsFillFrameHeader(bufs[idx], mask, wsFirstFrame, wsFinalFrame, wsUncompressedFrame, wsBinaryMessage, size) - bufs[idx] = bufs[idx][:n] - c.out.pb += int64(n) - c.ws.fs += int64(n + size) - if mask { - wsMaskBufs(key, bufs[idx+1:]) - } - } - - fhIdx := startFrame() - for i := 0; i < len(nb); i++ { - b := nb[i] - if total+len(b) <= mfs { - buf := nbPoolGet(len(b)) - bufs = append(bufs, append(buf, b...)) - total += len(b) - nbPoolPut(nb[i]) - continue - } - for len(b) > 0 { - endStart := total != 0 - if endStart { - endFrame(fhIdx, total) - } - total = len(b) - if total >= mfs { - total = mfs - } - if endStart { - fhIdx = startFrame() - } - buf := nbPoolGet(total) - bufs = append(bufs, append(buf, b[:total]...)) - b = b[total:] - } - nbPoolPut(nb[i]) // No longer needed as copied into smaller frames. - } - if total > 0 { - endFrame(fhIdx, total) - } - } else { - // If there is no limit on the frame size, create a single frame for - // all pending buffers. - for _, b := range nb { - total += len(b) - } - wsfh, key := wsCreateFrameHeader(mask, false, wsBinaryMessage, total) - c.out.pb += int64(len(wsfh)) - bufs = append(bufs, wsfh) - idx := len(bufs) - bufs = append(bufs, nb...) - if mask { - wsMaskBufs(key, bufs[idx:]) - } - c.ws.fs += int64(len(wsfh) + total) - } - } - if len(c.ws.closeMsg) > 0 { - bufs = append(bufs, c.ws.closeMsg) - c.ws.fs += int64(len(c.ws.closeMsg)) - c.ws.closeMsg = nil - c.ws.compressor = nil - } - c.ws.frames = nil - return bufs, c.ws.fs -} - -func isWSURL(u *url.URL) bool { - return strings.HasPrefix(strings.ToLower(u.Scheme), wsSchemePrefix) -} - -func isWSSURL(u *url.URL) bool { - return strings.HasPrefix(strings.ToLower(u.Scheme), wsSchemePrefixTLS) -} diff --git a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/LICENSE b/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/LICENSE deleted file mode 100644 index 20dcf51d..00000000 --- a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2017 Uber Technologies, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroup.go b/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroup.go deleted file mode 100644 index fe4ecf56..00000000 --- a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroup.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -//go:build linux -// +build linux - -package cgroups - -import ( - "bufio" - "io" - "os" - "path/filepath" - "strconv" -) - -// CGroup represents the data structure for a Linux control group. -type CGroup struct { - path string -} - -// NewCGroup returns a new *CGroup from a given path. -func NewCGroup(path string) *CGroup { - return &CGroup{path: path} -} - -// Path returns the path of the CGroup*. -func (cg *CGroup) Path() string { - return cg.path -} - -// ParamPath returns the path of the given cgroup param under itself. -func (cg *CGroup) ParamPath(param string) string { - return filepath.Join(cg.path, param) -} - -// readFirstLine reads the first line from a cgroup param file. -func (cg *CGroup) readFirstLine(param string) (string, error) { - paramFile, err := os.Open(cg.ParamPath(param)) - if err != nil { - return "", err - } - defer paramFile.Close() - - scanner := bufio.NewScanner(paramFile) - if scanner.Scan() { - return scanner.Text(), nil - } - if err := scanner.Err(); err != nil { - return "", err - } - return "", io.ErrUnexpectedEOF -} - -// readInt parses the first line from a cgroup param file as int. -func (cg *CGroup) readInt(param string) (int, error) { - text, err := cg.readFirstLine(param) - if err != nil { - return 0, err - } - return strconv.Atoi(text) -} diff --git a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups.go b/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups.go deleted file mode 100644 index e89f5436..00000000 --- a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -//go:build linux -// +build linux - -package cgroups - -const ( - // _cgroupFSType is the Linux CGroup file system type used in - // `/proc/$PID/mountinfo`. - _cgroupFSType = "cgroup" - // _cgroupSubsysCPU is the CPU CGroup subsystem. - _cgroupSubsysCPU = "cpu" - // _cgroupSubsysCPUAcct is the CPU accounting CGroup subsystem. - _cgroupSubsysCPUAcct = "cpuacct" - // _cgroupSubsysCPUSet is the CPUSet CGroup subsystem. - _cgroupSubsysCPUSet = "cpuset" - // _cgroupSubsysMemory is the Memory CGroup subsystem. - _cgroupSubsysMemory = "memory" - - // _cgroupCPUCFSQuotaUsParam is the file name for the CGroup CFS quota - // parameter. - _cgroupCPUCFSQuotaUsParam = "cpu.cfs_quota_us" - // _cgroupCPUCFSPeriodUsParam is the file name for the CGroup CFS period - // parameter. - _cgroupCPUCFSPeriodUsParam = "cpu.cfs_period_us" -) - -const ( - _procPathCGroup = "/proc/self/cgroup" - _procPathMountInfo = "/proc/self/mountinfo" -) - -// CGroups is a map that associates each CGroup with its subsystem name. -type CGroups map[string]*CGroup - -// NewCGroups returns a new *CGroups from given `mountinfo` and `cgroup` files -// under for some process under `/proc` file system (see also proc(5) for more -// information). -func NewCGroups(procPathMountInfo, procPathCGroup string) (CGroups, error) { - cgroupSubsystems, err := parseCGroupSubsystems(procPathCGroup) - if err != nil { - return nil, err - } - - cgroups := make(CGroups) - newMountPoint := func(mp *MountPoint) error { - if mp.FSType != _cgroupFSType { - return nil - } - - for _, opt := range mp.SuperOptions { - subsys, exists := cgroupSubsystems[opt] - if !exists { - continue - } - - cgroupPath, err := mp.Translate(subsys.Name) - if err != nil { - return err - } - cgroups[opt] = NewCGroup(cgroupPath) - } - - return nil - } - - if err := parseMountInfo(procPathMountInfo, newMountPoint); err != nil { - return nil, err - } - return cgroups, nil -} - -// NewCGroupsForCurrentProcess returns a new *CGroups instance for the current -// process. -func NewCGroupsForCurrentProcess() (CGroups, error) { - return NewCGroups(_procPathMountInfo, _procPathCGroup) -} - -// CPUQuota returns the CPU quota applied with the CPU cgroup controller. -// It is a result of `cpu.cfs_quota_us / cpu.cfs_period_us`. If the value of -// `cpu.cfs_quota_us` was not set (-1), the method returns `(-1, nil)`. -func (cg CGroups) CPUQuota() (float64, bool, error) { - cpuCGroup, exists := cg[_cgroupSubsysCPU] - if !exists { - return -1, false, nil - } - - cfsQuotaUs, err := cpuCGroup.readInt(_cgroupCPUCFSQuotaUsParam) - if defined := cfsQuotaUs > 0; err != nil || !defined { - return -1, defined, err - } - - cfsPeriodUs, err := cpuCGroup.readInt(_cgroupCPUCFSPeriodUsParam) - if defined := cfsPeriodUs > 0; err != nil || !defined { - return -1, defined, err - } - - return float64(cfsQuotaUs) / float64(cfsPeriodUs), true, nil -} diff --git a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups2.go b/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups2.go deleted file mode 100644 index 78556062..00000000 --- a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/cgroups2.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) 2022 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -//go:build linux -// +build linux - -package cgroups - -import ( - "bufio" - "errors" - "fmt" - "io" - "os" - "path" - "strconv" - "strings" -) - -const ( - // _cgroupv2CPUMax is the file name for the CGroup-V2 CPU max and period - // parameter. - _cgroupv2CPUMax = "cpu.max" - // _cgroupFSType is the Linux CGroup-V2 file system type used in - // `/proc/$PID/mountinfo`. - _cgroupv2FSType = "cgroup2" - - _cgroupv2MountPoint = "/sys/fs/cgroup" - - _cgroupV2CPUMaxDefaultPeriod = 100000 - _cgroupV2CPUMaxQuotaMax = "max" -) - -const ( - _cgroupv2CPUMaxQuotaIndex = iota - _cgroupv2CPUMaxPeriodIndex -) - -// ErrNotV2 indicates that the system is not using cgroups2. -var ErrNotV2 = errors.New("not using cgroups2") - -// CGroups2 provides access to cgroups data for systems using cgroups2. -type CGroups2 struct { - mountPoint string - groupPath string - cpuMaxFile string -} - -// NewCGroups2ForCurrentProcess builds a CGroups2 for the current process. -// -// This returns ErrNotV2 if the system is not using cgroups2. -func NewCGroups2ForCurrentProcess() (*CGroups2, error) { - return newCGroups2From(_procPathMountInfo, _procPathCGroup) -} - -func newCGroups2From(mountInfoPath, procPathCGroup string) (*CGroups2, error) { - isV2, err := isCGroupV2(mountInfoPath) - if err != nil { - return nil, err - } - - if !isV2 { - return nil, ErrNotV2 - } - - subsystems, err := parseCGroupSubsystems(procPathCGroup) - if err != nil { - return nil, err - } - - // Find v2 subsystem by looking for the `0` id - var v2subsys *CGroupSubsys - for _, subsys := range subsystems { - if subsys.ID == 0 { - v2subsys = subsys - break - } - } - - if v2subsys == nil { - return nil, ErrNotV2 - } - - return &CGroups2{ - mountPoint: _cgroupv2MountPoint, - groupPath: v2subsys.Name, - cpuMaxFile: _cgroupv2CPUMax, - }, nil -} - -func isCGroupV2(procPathMountInfo string) (bool, error) { - var ( - isV2 bool - newMountPoint = func(mp *MountPoint) error { - isV2 = isV2 || (mp.FSType == _cgroupv2FSType && mp.MountPoint == _cgroupv2MountPoint) - return nil - } - ) - - if err := parseMountInfo(procPathMountInfo, newMountPoint); err != nil { - return false, err - } - - return isV2, nil -} - -// CPUQuota returns the CPU quota applied with the CPU cgroup2 controller. -// It is a result of reading cpu quota and period from cpu.max file. -// It will return `cpu.max / cpu.period`. If cpu.max is set to max, it returns -// (-1, false, nil) -func (cg *CGroups2) CPUQuota() (float64, bool, error) { - cpuMaxParams, err := os.Open(path.Join(cg.mountPoint, cg.groupPath, cg.cpuMaxFile)) - if err != nil { - if os.IsNotExist(err) { - return -1, false, nil - } - return -1, false, err - } - defer cpuMaxParams.Close() - - scanner := bufio.NewScanner(cpuMaxParams) - if scanner.Scan() { - fields := strings.Fields(scanner.Text()) - if len(fields) == 0 || len(fields) > 2 { - return -1, false, fmt.Errorf("invalid format") - } - - if fields[_cgroupv2CPUMaxQuotaIndex] == _cgroupV2CPUMaxQuotaMax { - return -1, false, nil - } - - max, err := strconv.Atoi(fields[_cgroupv2CPUMaxQuotaIndex]) - if err != nil { - return -1, false, err - } - - var period int - if len(fields) == 1 { - period = _cgroupV2CPUMaxDefaultPeriod - } else { - period, err = strconv.Atoi(fields[_cgroupv2CPUMaxPeriodIndex]) - if err != nil { - return -1, false, err - } - - if period == 0 { - return -1, false, errors.New("zero value for period is not allowed") - } - } - - return float64(max) / float64(period), true, nil - } - - if err := scanner.Err(); err != nil { - return -1, false, err - } - - return 0, false, io.ErrUnexpectedEOF -} diff --git a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/doc.go b/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/doc.go deleted file mode 100644 index 113555f6..00000000 --- a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -// Package cgroups provides utilities to access Linux control group (CGroups) -// parameters (CPU quota, for example) for a given process. -package cgroups diff --git a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/errors.go b/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/errors.go deleted file mode 100644 index 94ac75a4..00000000 --- a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/errors.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -//go:build linux -// +build linux - -package cgroups - -import "fmt" - -type cgroupSubsysFormatInvalidError struct { - line string -} - -type mountPointFormatInvalidError struct { - line string -} - -type pathNotExposedFromMountPointError struct { - mountPoint string - root string - path string -} - -func (err cgroupSubsysFormatInvalidError) Error() string { - return fmt.Sprintf("invalid format for CGroupSubsys: %q", err.line) -} - -func (err mountPointFormatInvalidError) Error() string { - return fmt.Sprintf("invalid format for MountPoint: %q", err.line) -} - -func (err pathNotExposedFromMountPointError) Error() string { - return fmt.Sprintf("path %q is not a descendant of mount point root %q and cannot be exposed from %q", err.path, err.root, err.mountPoint) -} diff --git a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/mountpoint.go b/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/mountpoint.go deleted file mode 100644 index f3877f78..00000000 --- a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/mountpoint.go +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -//go:build linux -// +build linux - -package cgroups - -import ( - "bufio" - "os" - "path/filepath" - "strconv" - "strings" -) - -const ( - _mountInfoSep = " " - _mountInfoOptsSep = "," - _mountInfoOptionalFieldsSep = "-" -) - -const ( - _miFieldIDMountID = iota - _miFieldIDParentID - _miFieldIDDeviceID - _miFieldIDRoot - _miFieldIDMountPoint - _miFieldIDOptions - _miFieldIDOptionalFields - - _miFieldCountFirstHalf -) - -const ( - _miFieldOffsetFSType = iota - _miFieldOffsetMountSource - _miFieldOffsetSuperOptions - - _miFieldCountSecondHalf -) - -const _miFieldCountMin = _miFieldCountFirstHalf + _miFieldCountSecondHalf - -// MountPoint is the data structure for the mount points in -// `/proc/$PID/mountinfo`. See also proc(5) for more information. -type MountPoint struct { - MountID int - ParentID int - DeviceID string - Root string - MountPoint string - Options []string - OptionalFields []string - FSType string - MountSource string - SuperOptions []string -} - -// NewMountPointFromLine parses a line read from `/proc/$PID/mountinfo` and -// returns a new *MountPoint. -func NewMountPointFromLine(line string) (*MountPoint, error) { - fields := strings.Split(line, _mountInfoSep) - - if len(fields) < _miFieldCountMin { - return nil, mountPointFormatInvalidError{line} - } - - mountID, err := strconv.Atoi(fields[_miFieldIDMountID]) - if err != nil { - return nil, err - } - - parentID, err := strconv.Atoi(fields[_miFieldIDParentID]) - if err != nil { - return nil, err - } - - for i, field := range fields[_miFieldIDOptionalFields:] { - if field == _mountInfoOptionalFieldsSep { - // End of optional fields. - fsTypeStart := _miFieldIDOptionalFields + i + 1 - - // Now we know where the optional fields end, split the line again with a - // limit to avoid issues with spaces in super options as present on WSL. - fields = strings.SplitN(line, _mountInfoSep, fsTypeStart+_miFieldCountSecondHalf) - if len(fields) != fsTypeStart+_miFieldCountSecondHalf { - return nil, mountPointFormatInvalidError{line} - } - - miFieldIDFSType := _miFieldOffsetFSType + fsTypeStart - miFieldIDMountSource := _miFieldOffsetMountSource + fsTypeStart - miFieldIDSuperOptions := _miFieldOffsetSuperOptions + fsTypeStart - - return &MountPoint{ - MountID: mountID, - ParentID: parentID, - DeviceID: fields[_miFieldIDDeviceID], - Root: fields[_miFieldIDRoot], - MountPoint: fields[_miFieldIDMountPoint], - Options: strings.Split(fields[_miFieldIDOptions], _mountInfoOptsSep), - OptionalFields: fields[_miFieldIDOptionalFields:(fsTypeStart - 1)], - FSType: fields[miFieldIDFSType], - MountSource: fields[miFieldIDMountSource], - SuperOptions: strings.Split(fields[miFieldIDSuperOptions], _mountInfoOptsSep), - }, nil - } - } - - return nil, mountPointFormatInvalidError{line} -} - -// Translate converts an absolute path inside the *MountPoint's file system to -// the host file system path in the mount namespace the *MountPoint belongs to. -func (mp *MountPoint) Translate(absPath string) (string, error) { - relPath, err := filepath.Rel(mp.Root, absPath) - - if err != nil { - return "", err - } - if relPath == ".." || strings.HasPrefix(relPath, "../") { - return "", pathNotExposedFromMountPointError{ - mountPoint: mp.MountPoint, - root: mp.Root, - path: absPath, - } - } - - return filepath.Join(mp.MountPoint, relPath), nil -} - -// parseMountInfo parses procPathMountInfo (usually at `/proc/$PID/mountinfo`) -// and yields parsed *MountPoint into newMountPoint. -func parseMountInfo(procPathMountInfo string, newMountPoint func(*MountPoint) error) error { - mountInfoFile, err := os.Open(procPathMountInfo) - if err != nil { - return err - } - defer mountInfoFile.Close() - - scanner := bufio.NewScanner(mountInfoFile) - - for scanner.Scan() { - mountPoint, err := NewMountPointFromLine(scanner.Text()) - if err != nil { - return err - } - if err := newMountPoint(mountPoint); err != nil { - return err - } - } - - return scanner.Err() -} diff --git a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/subsys.go b/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/subsys.go deleted file mode 100644 index cddc3eae..00000000 --- a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/cgroups/subsys.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -//go:build linux -// +build linux - -package cgroups - -import ( - "bufio" - "os" - "strconv" - "strings" -) - -const ( - _cgroupSep = ":" - _cgroupSubsysSep = "," -) - -const ( - _csFieldIDID = iota - _csFieldIDSubsystems - _csFieldIDName - _csFieldCount -) - -// CGroupSubsys represents the data structure for entities in -// `/proc/$PID/cgroup`. See also proc(5) for more information. -type CGroupSubsys struct { - ID int - Subsystems []string - Name string -} - -// NewCGroupSubsysFromLine returns a new *CGroupSubsys by parsing a string in -// the format of `/proc/$PID/cgroup` -func NewCGroupSubsysFromLine(line string) (*CGroupSubsys, error) { - fields := strings.SplitN(line, _cgroupSep, _csFieldCount) - - if len(fields) != _csFieldCount { - return nil, cgroupSubsysFormatInvalidError{line} - } - - id, err := strconv.Atoi(fields[_csFieldIDID]) - if err != nil { - return nil, err - } - - cgroup := &CGroupSubsys{ - ID: id, - Subsystems: strings.Split(fields[_csFieldIDSubsystems], _cgroupSubsysSep), - Name: fields[_csFieldIDName], - } - - return cgroup, nil -} - -// parseCGroupSubsystems parses procPathCGroup (usually at `/proc/$PID/cgroup`) -// and returns a new map[string]*CGroupSubsys. -func parseCGroupSubsystems(procPathCGroup string) (map[string]*CGroupSubsys, error) { - cgroupFile, err := os.Open(procPathCGroup) - if err != nil { - return nil, err - } - defer cgroupFile.Close() - - scanner := bufio.NewScanner(cgroupFile) - subsystems := make(map[string]*CGroupSubsys) - - for scanner.Scan() { - cgroup, err := NewCGroupSubsysFromLine(scanner.Text()) - if err != nil { - return nil, err - } - for _, subsys := range cgroup.Subsystems { - subsystems[subsys] = cgroup - } - } - - if err := scanner.Err(); err != nil { - return nil, err - } - - return subsystems, nil -} diff --git a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go b/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go deleted file mode 100644 index f9057fd2..00000000 --- a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_linux.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -//go:build linux -// +build linux - -package runtime - -import ( - "errors" - - cg "go.uber.org/automaxprocs/internal/cgroups" -) - -// CPUQuotaToGOMAXPROCS converts the CPU quota applied to the calling process -// to a valid GOMAXPROCS value. The quota is converted from float to int using round. -// If round == nil, DefaultRoundFunc is used. -func CPUQuotaToGOMAXPROCS(minValue int, round func(v float64) int) (int, CPUQuotaStatus, error) { - if round == nil { - round = DefaultRoundFunc - } - cgroups, err := _newQueryer() - if err != nil { - return -1, CPUQuotaUndefined, err - } - - quota, defined, err := cgroups.CPUQuota() - if !defined || err != nil { - return -1, CPUQuotaUndefined, err - } - - maxProcs := round(quota) - if minValue > 0 && maxProcs < minValue { - return minValue, CPUQuotaMinUsed, nil - } - return maxProcs, CPUQuotaUsed, nil -} - -type queryer interface { - CPUQuota() (float64, bool, error) -} - -var ( - _newCgroups2 = cg.NewCGroups2ForCurrentProcess - _newCgroups = cg.NewCGroupsForCurrentProcess - _newQueryer = newQueryer -) - -func newQueryer() (queryer, error) { - cgroups, err := _newCgroups2() - if err == nil { - return cgroups, nil - } - if errors.Is(err, cg.ErrNotV2) { - return _newCgroups() - } - return nil, err -} diff --git a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go b/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go deleted file mode 100644 index e7470150..00000000 --- a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/runtime/cpu_quota_unsupported.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -//go:build !linux -// +build !linux - -package runtime - -// CPUQuotaToGOMAXPROCS converts the CPU quota applied to the calling process -// to a valid GOMAXPROCS value. This is Linux-specific and not supported in the -// current OS. -func CPUQuotaToGOMAXPROCS(_ int, _ func(v float64) int) (int, CPUQuotaStatus, error) { - return -1, CPUQuotaUndefined, nil -} diff --git a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/runtime/runtime.go b/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/runtime/runtime.go deleted file mode 100644 index f8a2834a..00000000 --- a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/internal/runtime/runtime.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package runtime - -import "math" - -// CPUQuotaStatus presents the status of how CPU quota is used -type CPUQuotaStatus int - -const ( - // CPUQuotaUndefined is returned when CPU quota is undefined - CPUQuotaUndefined CPUQuotaStatus = iota - // CPUQuotaUsed is returned when a valid CPU quota can be used - CPUQuotaUsed - // CPUQuotaMinUsed is returned when CPU quota is smaller than the min value - CPUQuotaMinUsed -) - -// DefaultRoundFunc is the default function to convert CPU quota from float to int. It rounds the value down (floor). -func DefaultRoundFunc(v float64) int { - return int(math.Floor(v)) -} diff --git a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/maxprocs/maxprocs.go b/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/maxprocs/maxprocs.go deleted file mode 100644 index e561fe60..00000000 --- a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/maxprocs/maxprocs.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -// Package maxprocs lets Go programs easily configure runtime.GOMAXPROCS to -// match the configured Linux CPU quota. Unlike the top-level automaxprocs -// package, it lets the caller configure logging and handle errors. -package maxprocs // import "go.uber.org/automaxprocs/maxprocs" - -import ( - "os" - "runtime" - - iruntime "go.uber.org/automaxprocs/internal/runtime" -) - -const _maxProcsKey = "GOMAXPROCS" - -func currentMaxProcs() int { - return runtime.GOMAXPROCS(0) -} - -type config struct { - printf func(string, ...interface{}) - procs func(int, func(v float64) int) (int, iruntime.CPUQuotaStatus, error) - minGOMAXPROCS int - roundQuotaFunc func(v float64) int -} - -func (c *config) log(fmt string, args ...interface{}) { - if c.printf != nil { - c.printf(fmt, args...) - } -} - -// An Option alters the behavior of Set. -type Option interface { - apply(*config) -} - -// Logger uses the supplied printf implementation for log output. By default, -// Set doesn't log anything. -func Logger(printf func(string, ...interface{})) Option { - return optionFunc(func(cfg *config) { - cfg.printf = printf - }) -} - -// Min sets the minimum GOMAXPROCS value that will be used. -// Any value below 1 is ignored. -func Min(n int) Option { - return optionFunc(func(cfg *config) { - if n >= 1 { - cfg.minGOMAXPROCS = n - } - }) -} - -// RoundQuotaFunc sets the function that will be used to covert the CPU quota from float to int. -func RoundQuotaFunc(rf func(v float64) int) Option { - return optionFunc(func(cfg *config) { - cfg.roundQuotaFunc = rf - }) -} - -type optionFunc func(*config) - -func (of optionFunc) apply(cfg *config) { of(cfg) } - -// Set GOMAXPROCS to match the Linux container CPU quota (if any), returning -// any error encountered and an undo function. -// -// Set is a no-op on non-Linux systems and in Linux environments without a -// configured CPU quota. -func Set(opts ...Option) (func(), error) { - cfg := &config{ - procs: iruntime.CPUQuotaToGOMAXPROCS, - roundQuotaFunc: iruntime.DefaultRoundFunc, - minGOMAXPROCS: 1, - } - for _, o := range opts { - o.apply(cfg) - } - - undoNoop := func() { - cfg.log("maxprocs: No GOMAXPROCS change to reset") - } - - // Honor the GOMAXPROCS environment variable if present. Otherwise, amend - // `runtime.GOMAXPROCS()` with the current process' CPU quota if the OS is - // Linux, and guarantee a minimum value of 1. The minimum guaranteed value - // can be overridden using `maxprocs.Min()`. - if max, exists := os.LookupEnv(_maxProcsKey); exists { - cfg.log("maxprocs: Honoring GOMAXPROCS=%q as set in environment", max) - return undoNoop, nil - } - - maxProcs, status, err := cfg.procs(cfg.minGOMAXPROCS, cfg.roundQuotaFunc) - if err != nil { - return undoNoop, err - } - - if status == iruntime.CPUQuotaUndefined { - cfg.log("maxprocs: Leaving GOMAXPROCS=%v: CPU quota undefined", currentMaxProcs()) - return undoNoop, nil - } - - prev := currentMaxProcs() - undo := func() { - cfg.log("maxprocs: Resetting GOMAXPROCS to %v", prev) - runtime.GOMAXPROCS(prev) - } - - switch status { - case iruntime.CPUQuotaMinUsed: - cfg.log("maxprocs: Updating GOMAXPROCS=%v: using minimum allowed GOMAXPROCS", maxProcs) - case iruntime.CPUQuotaUsed: - cfg.log("maxprocs: Updating GOMAXPROCS=%v: determined from CPU quota", maxProcs) - } - - runtime.GOMAXPROCS(maxProcs) - return undo, nil -} diff --git a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/maxprocs/version.go b/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/maxprocs/version.go deleted file mode 100644 index cc7fc5ae..00000000 --- a/src/code.cloudfoundry.org/vendor/go.uber.org/automaxprocs/maxprocs/version.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2017 Uber Technologies, Inc. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -package maxprocs - -// Version is the current package version. -const Version = "1.6.0" diff --git a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305.go b/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305.go deleted file mode 100644 index 8cf5d811..00000000 --- a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package chacha20poly1305 implements the ChaCha20-Poly1305 AEAD and its -// extended nonce variant XChaCha20-Poly1305, as specified in RFC 8439 and -// draft-irtf-cfrg-xchacha-01. -package chacha20poly1305 - -import ( - "crypto/cipher" - "errors" -) - -const ( - // KeySize is the size of the key used by this AEAD, in bytes. - KeySize = 32 - - // NonceSize is the size of the nonce used with the standard variant of this - // AEAD, in bytes. - // - // Note that this is too short to be safely generated at random if the same - // key is reused more than 2³² times. - NonceSize = 12 - - // NonceSizeX is the size of the nonce used with the XChaCha20-Poly1305 - // variant of this AEAD, in bytes. - NonceSizeX = 24 - - // Overhead is the size of the Poly1305 authentication tag, and the - // difference between a ciphertext length and its plaintext. - Overhead = 16 -) - -type chacha20poly1305 struct { - key [KeySize]byte -} - -// New returns a ChaCha20-Poly1305 AEAD that uses the given 256-bit key. -func New(key []byte) (cipher.AEAD, error) { - if len(key) != KeySize { - return nil, errors.New("chacha20poly1305: bad key length") - } - ret := new(chacha20poly1305) - copy(ret.key[:], key) - return ret, nil -} - -func (c *chacha20poly1305) NonceSize() int { - return NonceSize -} - -func (c *chacha20poly1305) Overhead() int { - return Overhead -} - -func (c *chacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte { - if len(nonce) != NonceSize { - panic("chacha20poly1305: bad nonce length passed to Seal") - } - - if uint64(len(plaintext)) > (1<<38)-64 { - panic("chacha20poly1305: plaintext too large") - } - - return c.seal(dst, nonce, plaintext, additionalData) -} - -var errOpen = errors.New("chacha20poly1305: message authentication failed") - -func (c *chacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { - if len(nonce) != NonceSize { - panic("chacha20poly1305: bad nonce length passed to Open") - } - if len(ciphertext) < 16 { - return nil, errOpen - } - if uint64(len(ciphertext)) > (1<<38)-48 { - panic("chacha20poly1305: ciphertext too large") - } - - return c.open(dst, nonce, ciphertext, additionalData) -} - -// sliceForAppend takes a slice and a requested number of bytes. It returns a -// slice with the contents of the given slice followed by that many bytes and a -// second slice that aliases into it and contains only the extra bytes. If the -// original slice has sufficient capacity then no allocation is performed. -func sliceForAppend(in []byte, n int) (head, tail []byte) { - if total := len(in) + n; cap(in) >= total { - head = in[:total] - } else { - head = make([]byte, total) - copy(head, in) - } - tail = head[len(in):] - return -} diff --git a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.go b/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.go deleted file mode 100644 index 50695a14..00000000 --- a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build gc && !purego - -package chacha20poly1305 - -import ( - "encoding/binary" - - "golang.org/x/crypto/internal/alias" - "golang.org/x/sys/cpu" -) - -//go:noescape -func chacha20Poly1305Open(dst []byte, key []uint32, src, ad []byte) bool - -//go:noescape -func chacha20Poly1305Seal(dst []byte, key []uint32, src, ad []byte) - -var ( - useAVX2 = cpu.X86.HasAVX2 && cpu.X86.HasBMI2 -) - -// setupState writes a ChaCha20 input matrix to state. See -// https://tools.ietf.org/html/rfc7539#section-2.3. -func setupState(state *[16]uint32, key *[32]byte, nonce []byte) { - state[0] = 0x61707865 - state[1] = 0x3320646e - state[2] = 0x79622d32 - state[3] = 0x6b206574 - - state[4] = binary.LittleEndian.Uint32(key[0:4]) - state[5] = binary.LittleEndian.Uint32(key[4:8]) - state[6] = binary.LittleEndian.Uint32(key[8:12]) - state[7] = binary.LittleEndian.Uint32(key[12:16]) - state[8] = binary.LittleEndian.Uint32(key[16:20]) - state[9] = binary.LittleEndian.Uint32(key[20:24]) - state[10] = binary.LittleEndian.Uint32(key[24:28]) - state[11] = binary.LittleEndian.Uint32(key[28:32]) - - state[12] = 0 - state[13] = binary.LittleEndian.Uint32(nonce[0:4]) - state[14] = binary.LittleEndian.Uint32(nonce[4:8]) - state[15] = binary.LittleEndian.Uint32(nonce[8:12]) -} - -func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []byte { - if !cpu.X86.HasSSSE3 { - return c.sealGeneric(dst, nonce, plaintext, additionalData) - } - - var state [16]uint32 - setupState(&state, &c.key, nonce) - - ret, out := sliceForAppend(dst, len(plaintext)+16) - if alias.InexactOverlap(out, plaintext) { - panic("chacha20poly1305: invalid buffer overlap") - } - chacha20Poly1305Seal(out[:], state[:], plaintext, additionalData) - return ret -} - -func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { - if !cpu.X86.HasSSSE3 { - return c.openGeneric(dst, nonce, ciphertext, additionalData) - } - - var state [16]uint32 - setupState(&state, &c.key, nonce) - - ciphertext = ciphertext[:len(ciphertext)-16] - ret, out := sliceForAppend(dst, len(ciphertext)) - if alias.InexactOverlap(out, ciphertext) { - panic("chacha20poly1305: invalid buffer overlap") - } - if !chacha20Poly1305Open(out, state[:], ciphertext, additionalData) { - for i := range out { - out[i] = 0 - } - return nil, errOpen - } - - return ret, nil -} diff --git a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.s b/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.s deleted file mode 100644 index fd5ee845..00000000 --- a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_amd64.s +++ /dev/null @@ -1,9762 +0,0 @@ -// Code generated by command: go run chacha20poly1305_amd64_asm.go -out ../chacha20poly1305_amd64.s -pkg chacha20poly1305. DO NOT EDIT. - -//go:build gc && !purego - -#include "textflag.h" - -// func polyHashADInternal<>() -TEXT polyHashADInternal<>(SB), NOSPLIT, $0 - // Hack: Must declare #define macros inside of a function due to Avo constraints - // ROL rotates the uint32s in register R left by N bits, using temporary T. - #define ROL(N, R, T) \ - MOVO R, T; \ - PSLLL $(N), T; \ - PSRLL $(32-(N)), R; \ - PXOR T, R - - // ROL8 rotates the uint32s in register R left by 8, using temporary T if needed. - #ifdef GOAMD64_v2 - #define ROL8(R, T) PSHUFB ·rol8<>(SB), R - #else - #define ROL8(R, T) ROL(8, R, T) - #endif - - // ROL16 rotates the uint32s in register R left by 16, using temporary T if needed. - #ifdef GOAMD64_v2 - #define ROL16(R, T) PSHUFB ·rol16<>(SB), R - #else - #define ROL16(R, T) ROL(16, R, T) - #endif - XORQ R10, R10 - XORQ R11, R11 - XORQ R12, R12 - CMPQ R9, $0x0d - JNE hashADLoop - MOVQ (CX), R10 - MOVQ 5(CX), R11 - SHRQ $0x18, R11 - MOVQ $0x00000001, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - RET - -hashADLoop: - // Hash in 16 byte chunks - CMPQ R9, $0x10 - JB hashADTail - ADDQ (CX), R10 - ADCQ 8(CX), R11 - ADCQ $0x01, R12 - LEAQ 16(CX), CX - SUBQ $0x10, R9 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - JMP hashADLoop - -hashADTail: - CMPQ R9, $0x00 - JE hashADDone - - // Hash last < 16 byte tail - XORQ R13, R13 - XORQ R14, R14 - XORQ R15, R15 - ADDQ R9, CX - -hashADTailLoop: - SHLQ $0x08, R13, R14 - SHLQ $0x08, R13 - MOVB -1(CX), R15 - XORQ R15, R13 - DECQ CX - DECQ R9 - JNE hashADTailLoop - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - -hashADDone: - RET - -// func chacha20Poly1305Open(dst []byte, key []uint32, src []byte, ad []byte) bool -// Requires: AVX, AVX2, BMI2, CMOV, SSE2 -TEXT ·chacha20Poly1305Open(SB), $288-97 - // For aligned stack access - MOVQ SP, BP - ADDQ $0x20, BP - ANDQ $-32, BP - MOVQ dst_base+0(FP), DI - MOVQ key_base+24(FP), R8 - MOVQ src_base+48(FP), SI - MOVQ src_len+56(FP), BX - MOVQ ad_base+72(FP), CX - - // Check for AVX2 support - CMPB ·useAVX2+0(SB), $0x01 - JE chacha20Poly1305Open_AVX2 - - // Special optimization, for very short buffers - CMPQ BX, $0x80 - JBE openSSE128 - - // For long buffers, prepare the poly key first - MOVOU ·chacha20Constants<>+0(SB), X0 - MOVOU 16(R8), X3 - MOVOU 32(R8), X6 - MOVOU 48(R8), X9 - MOVO X9, X13 - - // Store state on stack for future use - MOVO X3, 32(BP) - MOVO X6, 48(BP) - MOVO X9, 128(BP) - MOVQ $0x0000000a, R9 - -openSSEPreparePolyKey: - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x0c - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x04 - DECQ R9 - JNE openSSEPreparePolyKey - - // A0|B0 hold the Poly1305 32-byte key, C0,D0 can be discarded - PADDL ·chacha20Constants<>+0(SB), X0 - PADDL 32(BP), X3 - - // Clamp and store the key - PAND ·polyClampMask<>+0(SB), X0 - MOVO X0, (BP) - MOVO X3, 16(BP) - - // Hash AAD - MOVQ ad_len+80(FP), R9 - CALL polyHashADInternal<>(SB) - -openSSEMainLoop: - CMPQ BX, $0x00000100 - JB openSSEMainLoopDone - - // Load state, increment counter blocks - MOVO ·chacha20Constants<>+0(SB), X0 - MOVO 32(BP), X3 - MOVO 48(BP), X6 - MOVO 128(BP), X9 - PADDL ·sseIncMask<>+0(SB), X9 - MOVO X0, X1 - MOVO X3, X4 - MOVO X6, X7 - MOVO X9, X10 - PADDL ·sseIncMask<>+0(SB), X10 - MOVO X1, X2 - MOVO X4, X5 - MOVO X7, X8 - MOVO X10, X11 - PADDL ·sseIncMask<>+0(SB), X11 - MOVO X2, X12 - MOVO X5, X13 - MOVO X8, X14 - MOVO X11, X15 - PADDL ·sseIncMask<>+0(SB), X15 - - // Store counters - MOVO X9, 80(BP) - MOVO X10, 96(BP) - MOVO X11, 112(BP) - MOVO X15, 128(BP) - - // There are 10 ChaCha20 iterations of 2QR each, so for 6 iterations we hash - // 2 blocks, and for the remaining 4 only 1 block - for a total of 16 - MOVQ $0x00000004, CX - MOVQ SI, R9 - -openSSEInternalLoop: - MOVO X14, 64(BP) - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X3 - PXOR X14, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X3 - PXOR X14, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X4 - PXOR X14, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X4 - PXOR X14, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X5 - PXOR X14, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X5 - PXOR X14, X5 - MOVO 64(BP), X14 - MOVO X7, 64(BP) - PADDD X13, X12 - PXOR X12, X15 - ROL16(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x0c, X7 - PSRLL $0x14, X13 - PXOR X7, X13 - PADDD X13, X12 - PXOR X12, X15 - ROL8(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x07, X7 - PSRLL $0x19, X13 - PXOR X7, X13 - MOVO 64(BP), X7 - ADDQ (R9), R10 - ADCQ 8(R9), R11 - ADCQ $0x01, R12 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x0c - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - LEAQ 16(R9), R9 - MOVO X14, 64(BP) - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X3 - PXOR X14, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X3 - PXOR X14, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X4 - PXOR X14, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X4 - PXOR X14, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X5 - PXOR X14, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X5 - PXOR X14, X5 - MOVO 64(BP), X14 - MOVO X7, 64(BP) - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - PADDD X13, X12 - PXOR X12, X15 - ROL16(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x0c, X7 - PSRLL $0x14, X13 - PXOR X7, X13 - PADDD X13, X12 - PXOR X12, X15 - ROL8(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x07, X7 - PSRLL $0x19, X13 - PXOR X7, X13 - MOVO 64(BP), X7 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x04 - DECQ CX - JGE openSSEInternalLoop - ADDQ (R9), R10 - ADCQ 8(R9), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(R9), R9 - CMPQ CX, $-6 - JG openSSEInternalLoop - - // Add in the state - PADDD ·chacha20Constants<>+0(SB), X0 - PADDD ·chacha20Constants<>+0(SB), X1 - PADDD ·chacha20Constants<>+0(SB), X2 - PADDD ·chacha20Constants<>+0(SB), X12 - PADDD 32(BP), X3 - PADDD 32(BP), X4 - PADDD 32(BP), X5 - PADDD 32(BP), X13 - PADDD 48(BP), X6 - PADDD 48(BP), X7 - PADDD 48(BP), X8 - PADDD 48(BP), X14 - PADDD 80(BP), X9 - PADDD 96(BP), X10 - PADDD 112(BP), X11 - PADDD 128(BP), X15 - - // Load - xor - store - MOVO X15, 64(BP) - MOVOU (SI), X15 - PXOR X15, X0 - MOVOU X0, (DI) - MOVOU 16(SI), X15 - PXOR X15, X3 - MOVOU X3, 16(DI) - MOVOU 32(SI), X15 - PXOR X15, X6 - MOVOU X6, 32(DI) - MOVOU 48(SI), X15 - PXOR X15, X9 - MOVOU X9, 48(DI) - MOVOU 64(SI), X9 - PXOR X9, X1 - MOVOU X1, 64(DI) - MOVOU 80(SI), X9 - PXOR X9, X4 - MOVOU X4, 80(DI) - MOVOU 96(SI), X9 - PXOR X9, X7 - MOVOU X7, 96(DI) - MOVOU 112(SI), X9 - PXOR X9, X10 - MOVOU X10, 112(DI) - MOVOU 128(SI), X9 - PXOR X9, X2 - MOVOU X2, 128(DI) - MOVOU 144(SI), X9 - PXOR X9, X5 - MOVOU X5, 144(DI) - MOVOU 160(SI), X9 - PXOR X9, X8 - MOVOU X8, 160(DI) - MOVOU 176(SI), X9 - PXOR X9, X11 - MOVOU X11, 176(DI) - MOVOU 192(SI), X9 - PXOR X9, X12 - MOVOU X12, 192(DI) - MOVOU 208(SI), X9 - PXOR X9, X13 - MOVOU X13, 208(DI) - MOVOU 224(SI), X9 - PXOR X9, X14 - MOVOU X14, 224(DI) - MOVOU 240(SI), X9 - PXOR 64(BP), X9 - MOVOU X9, 240(DI) - LEAQ 256(SI), SI - LEAQ 256(DI), DI - SUBQ $0x00000100, BX - JMP openSSEMainLoop - -openSSEMainLoopDone: - // Handle the various tail sizes efficiently - TESTQ BX, BX - JE openSSEFinalize - CMPQ BX, $0x40 - JBE openSSETail64 - CMPQ BX, $0x80 - JBE openSSETail128 - CMPQ BX, $0xc0 - JBE openSSETail192 - JMP openSSETail256 - -openSSEFinalize: - // Hash in the PT, AAD lengths - ADDQ ad_len+80(FP), R10 - ADCQ src_len+56(FP), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - - // Final reduce - MOVQ R10, R13 - MOVQ R11, R14 - MOVQ R12, R15 - SUBQ $-5, R10 - SBBQ $-1, R11 - SBBQ $0x03, R12 - CMOVQCS R13, R10 - CMOVQCS R14, R11 - CMOVQCS R15, R12 - - // Add in the "s" part of the key - ADDQ 16(BP), R10 - ADCQ 24(BP), R11 - - // Finally, constant time compare to the tag at the end of the message - XORQ AX, AX - MOVQ $0x00000001, DX - XORQ (SI), R10 - XORQ 8(SI), R11 - ORQ R11, R10 - CMOVQEQ DX, AX - - // Return true iff tags are equal - MOVB AX, ret+96(FP) - RET - -openSSE128: - MOVOU ·chacha20Constants<>+0(SB), X0 - MOVOU 16(R8), X3 - MOVOU 32(R8), X6 - MOVOU 48(R8), X9 - MOVO X0, X1 - MOVO X3, X4 - MOVO X6, X7 - MOVO X9, X10 - PADDL ·sseIncMask<>+0(SB), X10 - MOVO X1, X2 - MOVO X4, X5 - MOVO X7, X8 - MOVO X10, X11 - PADDL ·sseIncMask<>+0(SB), X11 - MOVO X3, X13 - MOVO X6, X14 - MOVO X10, X15 - MOVQ $0x0000000a, R9 - -openSSE128InnerCipherLoop: - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X4 - PXOR X12, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X4 - PXOR X12, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X5 - PXOR X12, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X5 - PXOR X12, X5 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X4 - PXOR X12, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X4 - PXOR X12, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X5 - PXOR X12, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X5 - PXOR X12, X5 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - DECQ R9 - JNE openSSE128InnerCipherLoop - - // A0|B0 hold the Poly1305 32-byte key, C0,D0 can be discarded - PADDL ·chacha20Constants<>+0(SB), X0 - PADDL ·chacha20Constants<>+0(SB), X1 - PADDL ·chacha20Constants<>+0(SB), X2 - PADDL X13, X3 - PADDL X13, X4 - PADDL X13, X5 - PADDL X14, X7 - PADDL X14, X8 - PADDL X15, X10 - PADDL ·sseIncMask<>+0(SB), X15 - PADDL X15, X11 - - // Clamp and store the key - PAND ·polyClampMask<>+0(SB), X0 - MOVOU X0, (BP) - MOVOU X3, 16(BP) - - // Hash - MOVQ ad_len+80(FP), R9 - CALL polyHashADInternal<>(SB) - -openSSE128Open: - CMPQ BX, $0x10 - JB openSSETail16 - SUBQ $0x10, BX - - // Load for hashing - ADDQ (SI), R10 - ADCQ 8(SI), R11 - ADCQ $0x01, R12 - - // Load for decryption - MOVOU (SI), X12 - PXOR X12, X1 - MOVOU X1, (DI) - LEAQ 16(SI), SI - LEAQ 16(DI), DI - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - - // Shift the stream "left" - MOVO X4, X1 - MOVO X7, X4 - MOVO X10, X7 - MOVO X2, X10 - MOVO X5, X2 - MOVO X8, X5 - MOVO X11, X8 - JMP openSSE128Open - -openSSETail16: - TESTQ BX, BX - JE openSSEFinalize - - // We can safely load the CT from the end, because it is padded with the MAC - MOVQ BX, R9 - SHLQ $0x04, R9 - LEAQ ·andMask<>+0(SB), R13 - MOVOU (SI), X12 - ADDQ BX, SI - PAND -16(R13)(R9*1), X12 - MOVO X12, 64(BP) - MOVQ X12, R13 - MOVQ 72(BP), R14 - PXOR X1, X12 - - // We can only store one byte at a time, since plaintext can be shorter than 16 bytes -openSSETail16Store: - MOVQ X12, R8 - MOVB R8, (DI) - PSRLDQ $0x01, X12 - INCQ DI - DECQ BX - JNE openSSETail16Store - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - JMP openSSEFinalize - -openSSETail64: - MOVO ·chacha20Constants<>+0(SB), X0 - MOVO 32(BP), X3 - MOVO 48(BP), X6 - MOVO 128(BP), X9 - PADDL ·sseIncMask<>+0(SB), X9 - MOVO X9, 80(BP) - XORQ R9, R9 - MOVQ BX, CX - CMPQ CX, $0x10 - JB openSSETail64LoopB - -openSSETail64LoopA: - ADDQ (SI)(R9*1), R10 - ADCQ 8(SI)(R9*1), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - SUBQ $0x10, CX - -openSSETail64LoopB: - ADDQ $0x10, R9 - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x0c - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x04 - CMPQ CX, $0x10 - JAE openSSETail64LoopA - CMPQ R9, $0xa0 - JNE openSSETail64LoopB - PADDL ·chacha20Constants<>+0(SB), X0 - PADDL 32(BP), X3 - PADDL 48(BP), X6 - PADDL 80(BP), X9 - -openSSETail64DecLoop: - CMPQ BX, $0x10 - JB openSSETail64DecLoopDone - SUBQ $0x10, BX - MOVOU (SI), X12 - PXOR X12, X0 - MOVOU X0, (DI) - LEAQ 16(SI), SI - LEAQ 16(DI), DI - MOVO X3, X0 - MOVO X6, X3 - MOVO X9, X6 - JMP openSSETail64DecLoop - -openSSETail64DecLoopDone: - MOVO X0, X1 - JMP openSSETail16 - -openSSETail128: - MOVO ·chacha20Constants<>+0(SB), X1 - MOVO 32(BP), X4 - MOVO 48(BP), X7 - MOVO 128(BP), X10 - PADDL ·sseIncMask<>+0(SB), X10 - MOVO X10, 80(BP) - MOVO X1, X0 - MOVO X4, X3 - MOVO X7, X6 - MOVO X10, X9 - PADDL ·sseIncMask<>+0(SB), X9 - MOVO X9, 96(BP) - XORQ R9, R9 - MOVQ BX, CX - ANDQ $-16, CX - -openSSETail128LoopA: - ADDQ (SI)(R9*1), R10 - ADCQ 8(SI)(R9*1), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - -openSSETail128LoopB: - ADDQ $0x10, R9 - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X4 - PXOR X12, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X4 - PXOR X12, X4 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x0c - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X4 - PXOR X12, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X4 - PXOR X12, X4 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x04 - CMPQ R9, CX - JB openSSETail128LoopA - CMPQ R9, $0xa0 - JNE openSSETail128LoopB - PADDL ·chacha20Constants<>+0(SB), X0 - PADDL ·chacha20Constants<>+0(SB), X1 - PADDL 32(BP), X3 - PADDL 32(BP), X4 - PADDL 48(BP), X6 - PADDL 48(BP), X7 - PADDL 96(BP), X9 - PADDL 80(BP), X10 - MOVOU (SI), X12 - MOVOU 16(SI), X13 - MOVOU 32(SI), X14 - MOVOU 48(SI), X15 - PXOR X12, X1 - PXOR X13, X4 - PXOR X14, X7 - PXOR X15, X10 - MOVOU X1, (DI) - MOVOU X4, 16(DI) - MOVOU X7, 32(DI) - MOVOU X10, 48(DI) - SUBQ $0x40, BX - LEAQ 64(SI), SI - LEAQ 64(DI), DI - JMP openSSETail64DecLoop - -openSSETail192: - MOVO ·chacha20Constants<>+0(SB), X2 - MOVO 32(BP), X5 - MOVO 48(BP), X8 - MOVO 128(BP), X11 - PADDL ·sseIncMask<>+0(SB), X11 - MOVO X11, 80(BP) - MOVO X2, X1 - MOVO X5, X4 - MOVO X8, X7 - MOVO X11, X10 - PADDL ·sseIncMask<>+0(SB), X10 - MOVO X10, 96(BP) - MOVO X1, X0 - MOVO X4, X3 - MOVO X7, X6 - MOVO X10, X9 - PADDL ·sseIncMask<>+0(SB), X9 - MOVO X9, 112(BP) - MOVQ BX, CX - MOVQ $0x000000a0, R9 - CMPQ CX, $0xa0 - CMOVQGT R9, CX - ANDQ $-16, CX - XORQ R9, R9 - -openSSLTail192LoopA: - ADDQ (SI)(R9*1), R10 - ADCQ 8(SI)(R9*1), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - -openSSLTail192LoopB: - ADDQ $0x10, R9 - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X4 - PXOR X12, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X4 - PXOR X12, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X5 - PXOR X12, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X5 - PXOR X12, X5 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X4 - PXOR X12, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X4 - PXOR X12, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X5 - PXOR X12, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X5 - PXOR X12, X5 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - CMPQ R9, CX - JB openSSLTail192LoopA - CMPQ R9, $0xa0 - JNE openSSLTail192LoopB - CMPQ BX, $0xb0 - JB openSSLTail192Store - ADDQ 160(SI), R10 - ADCQ 168(SI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - CMPQ BX, $0xc0 - JB openSSLTail192Store - ADDQ 176(SI), R10 - ADCQ 184(SI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - -openSSLTail192Store: - PADDL ·chacha20Constants<>+0(SB), X0 - PADDL ·chacha20Constants<>+0(SB), X1 - PADDL ·chacha20Constants<>+0(SB), X2 - PADDL 32(BP), X3 - PADDL 32(BP), X4 - PADDL 32(BP), X5 - PADDL 48(BP), X6 - PADDL 48(BP), X7 - PADDL 48(BP), X8 - PADDL 112(BP), X9 - PADDL 96(BP), X10 - PADDL 80(BP), X11 - MOVOU (SI), X12 - MOVOU 16(SI), X13 - MOVOU 32(SI), X14 - MOVOU 48(SI), X15 - PXOR X12, X2 - PXOR X13, X5 - PXOR X14, X8 - PXOR X15, X11 - MOVOU X2, (DI) - MOVOU X5, 16(DI) - MOVOU X8, 32(DI) - MOVOU X11, 48(DI) - MOVOU 64(SI), X12 - MOVOU 80(SI), X13 - MOVOU 96(SI), X14 - MOVOU 112(SI), X15 - PXOR X12, X1 - PXOR X13, X4 - PXOR X14, X7 - PXOR X15, X10 - MOVOU X1, 64(DI) - MOVOU X4, 80(DI) - MOVOU X7, 96(DI) - MOVOU X10, 112(DI) - SUBQ $0x80, BX - LEAQ 128(SI), SI - LEAQ 128(DI), DI - JMP openSSETail64DecLoop - -openSSETail256: - MOVO ·chacha20Constants<>+0(SB), X0 - MOVO 32(BP), X3 - MOVO 48(BP), X6 - MOVO 128(BP), X9 - PADDL ·sseIncMask<>+0(SB), X9 - MOVO X0, X1 - MOVO X3, X4 - MOVO X6, X7 - MOVO X9, X10 - PADDL ·sseIncMask<>+0(SB), X10 - MOVO X1, X2 - MOVO X4, X5 - MOVO X7, X8 - MOVO X10, X11 - PADDL ·sseIncMask<>+0(SB), X11 - MOVO X2, X12 - MOVO X5, X13 - MOVO X8, X14 - MOVO X11, X15 - PADDL ·sseIncMask<>+0(SB), X15 - - // Store counters - MOVO X9, 80(BP) - MOVO X10, 96(BP) - MOVO X11, 112(BP) - MOVO X15, 128(BP) - XORQ R9, R9 - -openSSETail256Loop: - ADDQ (SI)(R9*1), R10 - ADCQ 8(SI)(R9*1), R11 - ADCQ $0x01, R12 - MOVO X14, 64(BP) - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X3 - PXOR X14, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X3 - PXOR X14, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X4 - PXOR X14, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X4 - PXOR X14, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X5 - PXOR X14, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X5 - PXOR X14, X5 - MOVO 64(BP), X14 - MOVO X7, 64(BP) - PADDD X13, X12 - PXOR X12, X15 - ROL16(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x0c, X7 - PSRLL $0x14, X13 - PXOR X7, X13 - PADDD X13, X12 - PXOR X12, X15 - ROL8(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x07, X7 - PSRLL $0x19, X13 - PXOR X7, X13 - MOVO 64(BP), X7 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x0c - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - MOVO X14, 64(BP) - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X3 - PXOR X14, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X3 - PXOR X14, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X4 - PXOR X14, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X4 - PXOR X14, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X5 - PXOR X14, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X5 - PXOR X14, X5 - MOVO 64(BP), X14 - MOVO X7, 64(BP) - PADDD X13, X12 - PXOR X12, X15 - ROL16(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x0c, X7 - PSRLL $0x14, X13 - PXOR X7, X13 - PADDD X13, X12 - PXOR X12, X15 - ROL8(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x07, X7 - PSRLL $0x19, X13 - PXOR X7, X13 - MOVO 64(BP), X7 - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x04 - ADDQ $0x10, R9 - CMPQ R9, $0xa0 - JB openSSETail256Loop - MOVQ BX, CX - ANDQ $-16, CX - -openSSETail256HashLoop: - ADDQ (SI)(R9*1), R10 - ADCQ 8(SI)(R9*1), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - ADDQ $0x10, R9 - CMPQ R9, CX - JB openSSETail256HashLoop - - // Add in the state - PADDD ·chacha20Constants<>+0(SB), X0 - PADDD ·chacha20Constants<>+0(SB), X1 - PADDD ·chacha20Constants<>+0(SB), X2 - PADDD ·chacha20Constants<>+0(SB), X12 - PADDD 32(BP), X3 - PADDD 32(BP), X4 - PADDD 32(BP), X5 - PADDD 32(BP), X13 - PADDD 48(BP), X6 - PADDD 48(BP), X7 - PADDD 48(BP), X8 - PADDD 48(BP), X14 - PADDD 80(BP), X9 - PADDD 96(BP), X10 - PADDD 112(BP), X11 - PADDD 128(BP), X15 - MOVO X15, 64(BP) - - // Load - xor - store - MOVOU (SI), X15 - PXOR X15, X0 - MOVOU 16(SI), X15 - PXOR X15, X3 - MOVOU 32(SI), X15 - PXOR X15, X6 - MOVOU 48(SI), X15 - PXOR X15, X9 - MOVOU X0, (DI) - MOVOU X3, 16(DI) - MOVOU X6, 32(DI) - MOVOU X9, 48(DI) - MOVOU 64(SI), X0 - MOVOU 80(SI), X3 - MOVOU 96(SI), X6 - MOVOU 112(SI), X9 - PXOR X0, X1 - PXOR X3, X4 - PXOR X6, X7 - PXOR X9, X10 - MOVOU X1, 64(DI) - MOVOU X4, 80(DI) - MOVOU X7, 96(DI) - MOVOU X10, 112(DI) - MOVOU 128(SI), X0 - MOVOU 144(SI), X3 - MOVOU 160(SI), X6 - MOVOU 176(SI), X9 - PXOR X0, X2 - PXOR X3, X5 - PXOR X6, X8 - PXOR X9, X11 - MOVOU X2, 128(DI) - MOVOU X5, 144(DI) - MOVOU X8, 160(DI) - MOVOU X11, 176(DI) - LEAQ 192(SI), SI - LEAQ 192(DI), DI - SUBQ $0xc0, BX - MOVO X12, X0 - MOVO X13, X3 - MOVO X14, X6 - MOVO 64(BP), X9 - JMP openSSETail64DecLoop - -chacha20Poly1305Open_AVX2: - VZEROUPPER - VMOVDQU ·chacha20Constants<>+0(SB), Y0 - BYTE $0xc4 - BYTE $0x42 - BYTE $0x7d - BYTE $0x5a - BYTE $0x70 - BYTE $0x10 - BYTE $0xc4 - BYTE $0x42 - BYTE $0x7d - BYTE $0x5a - BYTE $0x60 - BYTE $0x20 - BYTE $0xc4 - BYTE $0xc2 - BYTE $0x7d - BYTE $0x5a - BYTE $0x60 - BYTE $0x30 - VPADDD ·avx2InitMask<>+0(SB), Y4, Y4 - - // Special optimization, for very short buffers - CMPQ BX, $0xc0 - JBE openAVX2192 - CMPQ BX, $0x00000140 - JBE openAVX2320 - - // For the general key prepare the key first - as a byproduct we have 64 bytes of cipher stream - VMOVDQA Y14, 32(BP) - VMOVDQA Y12, 64(BP) - VMOVDQA Y4, 192(BP) - MOVQ $0x0000000a, R9 - -openAVX2PreparePolyKey: - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x04, Y4, Y4, Y4 - DECQ R9 - JNE openAVX2PreparePolyKey - VPADDD ·chacha20Constants<>+0(SB), Y0, Y0 - VPADDD 32(BP), Y14, Y14 - VPADDD 64(BP), Y12, Y12 - VPADDD 192(BP), Y4, Y4 - VPERM2I128 $0x02, Y0, Y14, Y3 - - // Clamp and store poly key - VPAND ·polyClampMask<>+0(SB), Y3, Y3 - VMOVDQA Y3, (BP) - - // Stream for the first 64 bytes - VPERM2I128 $0x13, Y0, Y14, Y0 - VPERM2I128 $0x13, Y12, Y4, Y14 - - // Hash AD + first 64 bytes - MOVQ ad_len+80(FP), R9 - CALL polyHashADInternal<>(SB) - XORQ CX, CX - -openAVX2InitialHash64: - ADDQ (SI)(CX*1), R10 - ADCQ 8(SI)(CX*1), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - ADDQ $0x10, CX - CMPQ CX, $0x40 - JNE openAVX2InitialHash64 - - // Decrypt the first 64 bytes - VPXOR (SI), Y0, Y0 - VPXOR 32(SI), Y14, Y14 - VMOVDQU Y0, (DI) - VMOVDQU Y14, 32(DI) - LEAQ 64(SI), SI - LEAQ 64(DI), DI - SUBQ $0x40, BX - -openAVX2MainLoop: - CMPQ BX, $0x00000200 - JB openAVX2MainLoopDone - - // Load state, increment counter blocks, store the incremented counters - VMOVDQU ·chacha20Constants<>+0(SB), Y0 - VMOVDQA Y0, Y5 - VMOVDQA Y0, Y6 - VMOVDQA Y0, Y7 - VMOVDQA 32(BP), Y14 - VMOVDQA Y14, Y9 - VMOVDQA Y14, Y10 - VMOVDQA Y14, Y11 - VMOVDQA 64(BP), Y12 - VMOVDQA Y12, Y13 - VMOVDQA Y12, Y8 - VMOVDQA Y12, Y15 - VMOVDQA 192(BP), Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y1 - VPADDD ·avx2IncMask<>+0(SB), Y1, Y2 - VPADDD ·avx2IncMask<>+0(SB), Y2, Y3 - VMOVDQA Y4, 96(BP) - VMOVDQA Y1, 128(BP) - VMOVDQA Y2, 160(BP) - VMOVDQA Y3, 192(BP) - XORQ CX, CX - -openAVX2InternalLoop: - ADDQ (SI)(CX*1), R10 - ADCQ 8(SI)(CX*1), R11 - ADCQ $0x01, R12 - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y3, Y3 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - VMOVDQA Y15, 224(BP) - VPSLLD $0x0c, Y14, Y15 - VPSRLD $0x14, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x0c, Y9, Y15 - VPSRLD $0x14, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x0c, Y10, Y15 - VPSRLD $0x14, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x0c, Y11, Y15 - VPSRLD $0x14, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y3, Y3 - ADDQ 16(SI)(CX*1), R10 - ADCQ 24(SI)(CX*1), R11 - ADCQ $0x01, R12 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - VMOVDQA Y15, 224(BP) - VPSLLD $0x07, Y14, Y15 - VPSRLD $0x19, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x07, Y9, Y15 - VPSRLD $0x19, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x07, Y10, Y15 - VPSRLD $0x19, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x07, Y11, Y15 - VPSRLD $0x19, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x04, Y9, Y9, Y9 - VPALIGNR $0x04, Y10, Y10, Y10 - VPALIGNR $0x04, Y11, Y11, Y11 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x08, Y15, Y15, Y15 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPALIGNR $0x0c, Y1, Y1, Y1 - VPALIGNR $0x0c, Y2, Y2, Y2 - VPALIGNR $0x0c, Y3, Y3, Y3 - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y3, Y3 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - ADDQ 32(SI)(CX*1), R10 - ADCQ 40(SI)(CX*1), R11 - ADCQ $0x01, R12 - LEAQ 48(CX), CX - VMOVDQA Y15, 224(BP) - VPSLLD $0x0c, Y14, Y15 - VPSRLD $0x14, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x0c, Y9, Y15 - VPSRLD $0x14, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x0c, Y10, Y15 - VPSRLD $0x14, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x0c, Y11, Y15 - VPSRLD $0x14, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y3, Y3 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - VMOVDQA Y15, 224(BP) - VPSLLD $0x07, Y14, Y15 - VPSRLD $0x19, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x07, Y9, Y15 - VPSRLD $0x19, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x07, Y10, Y15 - VPSRLD $0x19, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x07, Y11, Y15 - VPSRLD $0x19, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x0c, Y9, Y9, Y9 - VPALIGNR $0x0c, Y10, Y10, Y10 - VPALIGNR $0x0c, Y11, Y11, Y11 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x08, Y15, Y15, Y15 - VPALIGNR $0x04, Y4, Y4, Y4 - VPALIGNR $0x04, Y1, Y1, Y1 - VPALIGNR $0x04, Y2, Y2, Y2 - VPALIGNR $0x04, Y3, Y3, Y3 - CMPQ CX, $0x000001e0 - JNE openAVX2InternalLoop - VPADDD ·chacha20Constants<>+0(SB), Y0, Y0 - VPADDD ·chacha20Constants<>+0(SB), Y5, Y5 - VPADDD ·chacha20Constants<>+0(SB), Y6, Y6 - VPADDD ·chacha20Constants<>+0(SB), Y7, Y7 - VPADDD 32(BP), Y14, Y14 - VPADDD 32(BP), Y9, Y9 - VPADDD 32(BP), Y10, Y10 - VPADDD 32(BP), Y11, Y11 - VPADDD 64(BP), Y12, Y12 - VPADDD 64(BP), Y13, Y13 - VPADDD 64(BP), Y8, Y8 - VPADDD 64(BP), Y15, Y15 - VPADDD 96(BP), Y4, Y4 - VPADDD 128(BP), Y1, Y1 - VPADDD 160(BP), Y2, Y2 - VPADDD 192(BP), Y3, Y3 - VMOVDQA Y15, 224(BP) - - // We only hashed 480 of the 512 bytes available - hash the remaining 32 here - ADDQ 480(SI), R10 - ADCQ 488(SI), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - VPERM2I128 $0x02, Y0, Y14, Y15 - VPERM2I128 $0x13, Y0, Y14, Y14 - VPERM2I128 $0x02, Y12, Y4, Y0 - VPERM2I128 $0x13, Y12, Y4, Y12 - VPXOR (SI), Y15, Y15 - VPXOR 32(SI), Y0, Y0 - VPXOR 64(SI), Y14, Y14 - VPXOR 96(SI), Y12, Y12 - VMOVDQU Y15, (DI) - VMOVDQU Y0, 32(DI) - VMOVDQU Y14, 64(DI) - VMOVDQU Y12, 96(DI) - VPERM2I128 $0x02, Y5, Y9, Y0 - VPERM2I128 $0x02, Y13, Y1, Y14 - VPERM2I128 $0x13, Y5, Y9, Y12 - VPERM2I128 $0x13, Y13, Y1, Y4 - VPXOR 128(SI), Y0, Y0 - VPXOR 160(SI), Y14, Y14 - VPXOR 192(SI), Y12, Y12 - VPXOR 224(SI), Y4, Y4 - VMOVDQU Y0, 128(DI) - VMOVDQU Y14, 160(DI) - VMOVDQU Y12, 192(DI) - VMOVDQU Y4, 224(DI) - - // and here - ADDQ 496(SI), R10 - ADCQ 504(SI), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - VPERM2I128 $0x02, Y6, Y10, Y0 - VPERM2I128 $0x02, Y8, Y2, Y14 - VPERM2I128 $0x13, Y6, Y10, Y12 - VPERM2I128 $0x13, Y8, Y2, Y4 - VPXOR 256(SI), Y0, Y0 - VPXOR 288(SI), Y14, Y14 - VPXOR 320(SI), Y12, Y12 - VPXOR 352(SI), Y4, Y4 - VMOVDQU Y0, 256(DI) - VMOVDQU Y14, 288(DI) - VMOVDQU Y12, 320(DI) - VMOVDQU Y4, 352(DI) - VPERM2I128 $0x02, Y7, Y11, Y0 - VPERM2I128 $0x02, 224(BP), Y3, Y14 - VPERM2I128 $0x13, Y7, Y11, Y12 - VPERM2I128 $0x13, 224(BP), Y3, Y4 - VPXOR 384(SI), Y0, Y0 - VPXOR 416(SI), Y14, Y14 - VPXOR 448(SI), Y12, Y12 - VPXOR 480(SI), Y4, Y4 - VMOVDQU Y0, 384(DI) - VMOVDQU Y14, 416(DI) - VMOVDQU Y12, 448(DI) - VMOVDQU Y4, 480(DI) - LEAQ 512(SI), SI - LEAQ 512(DI), DI - SUBQ $0x00000200, BX - JMP openAVX2MainLoop - -openAVX2MainLoopDone: - // Handle the various tail sizes efficiently - TESTQ BX, BX - JE openSSEFinalize - CMPQ BX, $0x80 - JBE openAVX2Tail128 - CMPQ BX, $0x00000100 - JBE openAVX2Tail256 - CMPQ BX, $0x00000180 - JBE openAVX2Tail384 - JMP openAVX2Tail512 - -openAVX2192: - VMOVDQA Y0, Y5 - VMOVDQA Y14, Y9 - VMOVDQA Y12, Y13 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y1 - VMOVDQA Y0, Y6 - VMOVDQA Y14, Y10 - VMOVDQA Y12, Y8 - VMOVDQA Y4, Y2 - VMOVDQA Y1, Y15 - MOVQ $0x0000000a, R9 - -openAVX2192InnerCipherLoop: - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x04, Y9, Y9, Y9 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPALIGNR $0x0c, Y1, Y1, Y1 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x0c, Y9, Y9, Y9 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x04, Y4, Y4, Y4 - VPALIGNR $0x04, Y1, Y1, Y1 - DECQ R9 - JNE openAVX2192InnerCipherLoop - VPADDD Y6, Y0, Y0 - VPADDD Y6, Y5, Y5 - VPADDD Y10, Y14, Y14 - VPADDD Y10, Y9, Y9 - VPADDD Y8, Y12, Y12 - VPADDD Y8, Y13, Y13 - VPADDD Y2, Y4, Y4 - VPADDD Y15, Y1, Y1 - VPERM2I128 $0x02, Y0, Y14, Y3 - - // Clamp and store poly key - VPAND ·polyClampMask<>+0(SB), Y3, Y3 - VMOVDQA Y3, (BP) - - // Stream for up to 192 bytes - VPERM2I128 $0x13, Y0, Y14, Y0 - VPERM2I128 $0x13, Y12, Y4, Y14 - VPERM2I128 $0x02, Y5, Y9, Y12 - VPERM2I128 $0x02, Y13, Y1, Y4 - VPERM2I128 $0x13, Y5, Y9, Y5 - VPERM2I128 $0x13, Y13, Y1, Y9 - -openAVX2ShortOpen: - // Hash - MOVQ ad_len+80(FP), R9 - CALL polyHashADInternal<>(SB) - -openAVX2ShortOpenLoop: - CMPQ BX, $0x20 - JB openAVX2ShortTail32 - SUBQ $0x20, BX - - // Load for hashing - ADDQ (SI), R10 - ADCQ 8(SI), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - ADDQ 16(SI), R10 - ADCQ 24(SI), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - - // Load for decryption - VPXOR (SI), Y0, Y0 - VMOVDQU Y0, (DI) - LEAQ 32(SI), SI - LEAQ 32(DI), DI - - // Shift stream left - VMOVDQA Y14, Y0 - VMOVDQA Y12, Y14 - VMOVDQA Y4, Y12 - VMOVDQA Y5, Y4 - VMOVDQA Y9, Y5 - VMOVDQA Y13, Y9 - VMOVDQA Y1, Y13 - VMOVDQA Y6, Y1 - VMOVDQA Y10, Y6 - JMP openAVX2ShortOpenLoop - -openAVX2ShortTail32: - CMPQ BX, $0x10 - VMOVDQA X0, X1 - JB openAVX2ShortDone - SUBQ $0x10, BX - - // Load for hashing - ADDQ (SI), R10 - ADCQ 8(SI), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - - // Load for decryption - VPXOR (SI), X0, X12 - VMOVDQU X12, (DI) - LEAQ 16(SI), SI - LEAQ 16(DI), DI - VPERM2I128 $0x11, Y0, Y0, Y0 - VMOVDQA X0, X1 - -openAVX2ShortDone: - VZEROUPPER - JMP openSSETail16 - -openAVX2320: - VMOVDQA Y0, Y5 - VMOVDQA Y14, Y9 - VMOVDQA Y12, Y13 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y1 - VMOVDQA Y0, Y6 - VMOVDQA Y14, Y10 - VMOVDQA Y12, Y8 - VPADDD ·avx2IncMask<>+0(SB), Y1, Y2 - VMOVDQA Y14, Y7 - VMOVDQA Y12, Y11 - VMOVDQA Y4, Y15 - MOVQ $0x0000000a, R9 - -openAVX2320InnerCipherLoop: - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x0c, Y10, Y3 - VPSRLD $0x14, Y10, Y10 - VPXOR Y3, Y10, Y10 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x07, Y10, Y3 - VPSRLD $0x19, Y10, Y10 - VPXOR Y3, Y10, Y10 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x04, Y9, Y9, Y9 - VPALIGNR $0x04, Y10, Y10, Y10 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPALIGNR $0x0c, Y1, Y1, Y1 - VPALIGNR $0x0c, Y2, Y2, Y2 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x0c, Y10, Y3 - VPSRLD $0x14, Y10, Y10 - VPXOR Y3, Y10, Y10 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x07, Y10, Y3 - VPSRLD $0x19, Y10, Y10 - VPXOR Y3, Y10, Y10 - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x0c, Y9, Y9, Y9 - VPALIGNR $0x0c, Y10, Y10, Y10 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x04, Y4, Y4, Y4 - VPALIGNR $0x04, Y1, Y1, Y1 - VPALIGNR $0x04, Y2, Y2, Y2 - DECQ R9 - JNE openAVX2320InnerCipherLoop - VMOVDQA ·chacha20Constants<>+0(SB), Y3 - VPADDD Y3, Y0, Y0 - VPADDD Y3, Y5, Y5 - VPADDD Y3, Y6, Y6 - VPADDD Y7, Y14, Y14 - VPADDD Y7, Y9, Y9 - VPADDD Y7, Y10, Y10 - VPADDD Y11, Y12, Y12 - VPADDD Y11, Y13, Y13 - VPADDD Y11, Y8, Y8 - VMOVDQA ·avx2IncMask<>+0(SB), Y3 - VPADDD Y15, Y4, Y4 - VPADDD Y3, Y15, Y15 - VPADDD Y15, Y1, Y1 - VPADDD Y3, Y15, Y15 - VPADDD Y15, Y2, Y2 - - // Clamp and store poly key - VPERM2I128 $0x02, Y0, Y14, Y3 - VPAND ·polyClampMask<>+0(SB), Y3, Y3 - VMOVDQA Y3, (BP) - - // Stream for up to 320 bytes - VPERM2I128 $0x13, Y0, Y14, Y0 - VPERM2I128 $0x13, Y12, Y4, Y14 - VPERM2I128 $0x02, Y5, Y9, Y12 - VPERM2I128 $0x02, Y13, Y1, Y4 - VPERM2I128 $0x13, Y5, Y9, Y5 - VPERM2I128 $0x13, Y13, Y1, Y9 - VPERM2I128 $0x02, Y6, Y10, Y13 - VPERM2I128 $0x02, Y8, Y2, Y1 - VPERM2I128 $0x13, Y6, Y10, Y6 - VPERM2I128 $0x13, Y8, Y2, Y10 - JMP openAVX2ShortOpen - -openAVX2Tail128: - // Need to decrypt up to 128 bytes - prepare two blocks - VMOVDQA ·chacha20Constants<>+0(SB), Y5 - VMOVDQA 32(BP), Y9 - VMOVDQA 64(BP), Y13 - VMOVDQA 192(BP), Y1 - VPADDD ·avx2IncMask<>+0(SB), Y1, Y1 - VMOVDQA Y1, Y4 - XORQ R9, R9 - MOVQ BX, CX - ANDQ $-16, CX - TESTQ CX, CX - JE openAVX2Tail128LoopB - -openAVX2Tail128LoopA: - ADDQ (SI)(R9*1), R10 - ADCQ 8(SI)(R9*1), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - -openAVX2Tail128LoopB: - ADDQ $0x10, R9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPALIGNR $0x04, Y9, Y9, Y9 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x0c, Y1, Y1, Y1 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPALIGNR $0x0c, Y9, Y9, Y9 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x04, Y1, Y1, Y1 - CMPQ R9, CX - JB openAVX2Tail128LoopA - CMPQ R9, $0xa0 - JNE openAVX2Tail128LoopB - VPADDD ·chacha20Constants<>+0(SB), Y5, Y5 - VPADDD 32(BP), Y9, Y9 - VPADDD 64(BP), Y13, Y13 - VPADDD Y4, Y1, Y1 - VPERM2I128 $0x02, Y5, Y9, Y0 - VPERM2I128 $0x02, Y13, Y1, Y14 - VPERM2I128 $0x13, Y5, Y9, Y12 - VPERM2I128 $0x13, Y13, Y1, Y4 - -openAVX2TailLoop: - CMPQ BX, $0x20 - JB openAVX2Tail - SUBQ $0x20, BX - - // Load for decryption - VPXOR (SI), Y0, Y0 - VMOVDQU Y0, (DI) - LEAQ 32(SI), SI - LEAQ 32(DI), DI - VMOVDQA Y14, Y0 - VMOVDQA Y12, Y14 - VMOVDQA Y4, Y12 - JMP openAVX2TailLoop - -openAVX2Tail: - CMPQ BX, $0x10 - VMOVDQA X0, X1 - JB openAVX2TailDone - SUBQ $0x10, BX - - // Load for decryption - VPXOR (SI), X0, X12 - VMOVDQU X12, (DI) - LEAQ 16(SI), SI - LEAQ 16(DI), DI - VPERM2I128 $0x11, Y0, Y0, Y0 - VMOVDQA X0, X1 - -openAVX2TailDone: - VZEROUPPER - JMP openSSETail16 - -openAVX2Tail256: - VMOVDQA ·chacha20Constants<>+0(SB), Y0 - VMOVDQA Y0, Y5 - VMOVDQA 32(BP), Y14 - VMOVDQA Y14, Y9 - VMOVDQA 64(BP), Y12 - VMOVDQA Y12, Y13 - VMOVDQA 192(BP), Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y1 - VMOVDQA Y4, Y7 - VMOVDQA Y1, Y11 - - // Compute the number of iterations that will hash data - MOVQ BX, 224(BP) - MOVQ BX, CX - SUBQ $0x80, CX - SHRQ $0x04, CX - MOVQ $0x0000000a, R9 - CMPQ CX, $0x0a - CMOVQGT R9, CX - MOVQ SI, BX - XORQ R9, R9 - -openAVX2Tail256LoopA: - ADDQ (BX), R10 - ADCQ 8(BX), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(BX), BX - -openAVX2Tail256LoopB: - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x04, Y9, Y9, Y9 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPALIGNR $0x0c, Y1, Y1, Y1 - INCQ R9 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x0c, Y9, Y9, Y9 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x04, Y4, Y4, Y4 - VPALIGNR $0x04, Y1, Y1, Y1 - CMPQ R9, CX - JB openAVX2Tail256LoopA - CMPQ R9, $0x0a - JNE openAVX2Tail256LoopB - MOVQ BX, R9 - SUBQ SI, BX - MOVQ BX, CX - MOVQ 224(BP), BX - -openAVX2Tail256Hash: - ADDQ $0x10, CX - CMPQ CX, BX - JGT openAVX2Tail256HashEnd - ADDQ (R9), R10 - ADCQ 8(R9), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(R9), R9 - JMP openAVX2Tail256Hash - -openAVX2Tail256HashEnd: - VPADDD ·chacha20Constants<>+0(SB), Y0, Y0 - VPADDD ·chacha20Constants<>+0(SB), Y5, Y5 - VPADDD 32(BP), Y14, Y14 - VPADDD 32(BP), Y9, Y9 - VPADDD 64(BP), Y12, Y12 - VPADDD 64(BP), Y13, Y13 - VPADDD Y7, Y4, Y4 - VPADDD Y11, Y1, Y1 - VPERM2I128 $0x02, Y0, Y14, Y6 - VPERM2I128 $0x02, Y12, Y4, Y10 - VPERM2I128 $0x13, Y0, Y14, Y8 - VPERM2I128 $0x13, Y12, Y4, Y2 - VPERM2I128 $0x02, Y5, Y9, Y0 - VPERM2I128 $0x02, Y13, Y1, Y14 - VPERM2I128 $0x13, Y5, Y9, Y12 - VPERM2I128 $0x13, Y13, Y1, Y4 - VPXOR (SI), Y6, Y6 - VPXOR 32(SI), Y10, Y10 - VPXOR 64(SI), Y8, Y8 - VPXOR 96(SI), Y2, Y2 - VMOVDQU Y6, (DI) - VMOVDQU Y10, 32(DI) - VMOVDQU Y8, 64(DI) - VMOVDQU Y2, 96(DI) - LEAQ 128(SI), SI - LEAQ 128(DI), DI - SUBQ $0x80, BX - JMP openAVX2TailLoop - -openAVX2Tail384: - // Need to decrypt up to 384 bytes - prepare six blocks - VMOVDQA ·chacha20Constants<>+0(SB), Y0 - VMOVDQA Y0, Y5 - VMOVDQA Y0, Y6 - VMOVDQA 32(BP), Y14 - VMOVDQA Y14, Y9 - VMOVDQA Y14, Y10 - VMOVDQA 64(BP), Y12 - VMOVDQA Y12, Y13 - VMOVDQA Y12, Y8 - VMOVDQA 192(BP), Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y1 - VPADDD ·avx2IncMask<>+0(SB), Y1, Y2 - VMOVDQA Y4, 96(BP) - VMOVDQA Y1, 128(BP) - VMOVDQA Y2, 160(BP) - - // Compute the number of iterations that will hash two blocks of data - MOVQ BX, 224(BP) - MOVQ BX, CX - SUBQ $0x00000100, CX - SHRQ $0x04, CX - ADDQ $0x06, CX - MOVQ $0x0000000a, R9 - CMPQ CX, $0x0a - CMOVQGT R9, CX - MOVQ SI, BX - XORQ R9, R9 - -openAVX2Tail384LoopB: - ADDQ (BX), R10 - ADCQ 8(BX), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(BX), BX - -openAVX2Tail384LoopA: - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x0c, Y10, Y3 - VPSRLD $0x14, Y10, Y10 - VPXOR Y3, Y10, Y10 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x07, Y10, Y3 - VPSRLD $0x19, Y10, Y10 - VPXOR Y3, Y10, Y10 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x04, Y9, Y9, Y9 - VPALIGNR $0x04, Y10, Y10, Y10 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPALIGNR $0x0c, Y1, Y1, Y1 - VPALIGNR $0x0c, Y2, Y2, Y2 - ADDQ (BX), R10 - ADCQ 8(BX), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(BX), BX - INCQ R9 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x0c, Y10, Y3 - VPSRLD $0x14, Y10, Y10 - VPXOR Y3, Y10, Y10 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x07, Y10, Y3 - VPSRLD $0x19, Y10, Y10 - VPXOR Y3, Y10, Y10 - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x0c, Y9, Y9, Y9 - VPALIGNR $0x0c, Y10, Y10, Y10 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x04, Y4, Y4, Y4 - VPALIGNR $0x04, Y1, Y1, Y1 - VPALIGNR $0x04, Y2, Y2, Y2 - CMPQ R9, CX - JB openAVX2Tail384LoopB - CMPQ R9, $0x0a - JNE openAVX2Tail384LoopA - MOVQ BX, R9 - SUBQ SI, BX - MOVQ BX, CX - MOVQ 224(BP), BX - -openAVX2Tail384Hash: - ADDQ $0x10, CX - CMPQ CX, BX - JGT openAVX2Tail384HashEnd - ADDQ (R9), R10 - ADCQ 8(R9), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(R9), R9 - JMP openAVX2Tail384Hash - -openAVX2Tail384HashEnd: - VPADDD ·chacha20Constants<>+0(SB), Y0, Y0 - VPADDD ·chacha20Constants<>+0(SB), Y5, Y5 - VPADDD ·chacha20Constants<>+0(SB), Y6, Y6 - VPADDD 32(BP), Y14, Y14 - VPADDD 32(BP), Y9, Y9 - VPADDD 32(BP), Y10, Y10 - VPADDD 64(BP), Y12, Y12 - VPADDD 64(BP), Y13, Y13 - VPADDD 64(BP), Y8, Y8 - VPADDD 96(BP), Y4, Y4 - VPADDD 128(BP), Y1, Y1 - VPADDD 160(BP), Y2, Y2 - VPERM2I128 $0x02, Y0, Y14, Y3 - VPERM2I128 $0x02, Y12, Y4, Y7 - VPERM2I128 $0x13, Y0, Y14, Y11 - VPERM2I128 $0x13, Y12, Y4, Y15 - VPXOR (SI), Y3, Y3 - VPXOR 32(SI), Y7, Y7 - VPXOR 64(SI), Y11, Y11 - VPXOR 96(SI), Y15, Y15 - VMOVDQU Y3, (DI) - VMOVDQU Y7, 32(DI) - VMOVDQU Y11, 64(DI) - VMOVDQU Y15, 96(DI) - VPERM2I128 $0x02, Y5, Y9, Y3 - VPERM2I128 $0x02, Y13, Y1, Y7 - VPERM2I128 $0x13, Y5, Y9, Y11 - VPERM2I128 $0x13, Y13, Y1, Y15 - VPXOR 128(SI), Y3, Y3 - VPXOR 160(SI), Y7, Y7 - VPXOR 192(SI), Y11, Y11 - VPXOR 224(SI), Y15, Y15 - VMOVDQU Y3, 128(DI) - VMOVDQU Y7, 160(DI) - VMOVDQU Y11, 192(DI) - VMOVDQU Y15, 224(DI) - VPERM2I128 $0x02, Y6, Y10, Y0 - VPERM2I128 $0x02, Y8, Y2, Y14 - VPERM2I128 $0x13, Y6, Y10, Y12 - VPERM2I128 $0x13, Y8, Y2, Y4 - LEAQ 256(SI), SI - LEAQ 256(DI), DI - SUBQ $0x00000100, BX - JMP openAVX2TailLoop - -openAVX2Tail512: - VMOVDQU ·chacha20Constants<>+0(SB), Y0 - VMOVDQA Y0, Y5 - VMOVDQA Y0, Y6 - VMOVDQA Y0, Y7 - VMOVDQA 32(BP), Y14 - VMOVDQA Y14, Y9 - VMOVDQA Y14, Y10 - VMOVDQA Y14, Y11 - VMOVDQA 64(BP), Y12 - VMOVDQA Y12, Y13 - VMOVDQA Y12, Y8 - VMOVDQA Y12, Y15 - VMOVDQA 192(BP), Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y1 - VPADDD ·avx2IncMask<>+0(SB), Y1, Y2 - VPADDD ·avx2IncMask<>+0(SB), Y2, Y3 - VMOVDQA Y4, 96(BP) - VMOVDQA Y1, 128(BP) - VMOVDQA Y2, 160(BP) - VMOVDQA Y3, 192(BP) - XORQ CX, CX - MOVQ SI, R9 - -openAVX2Tail512LoopB: - ADDQ (R9), R10 - ADCQ 8(R9), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(R9), R9 - -openAVX2Tail512LoopA: - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y3, Y3 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - VMOVDQA Y15, 224(BP) - VPSLLD $0x0c, Y14, Y15 - VPSRLD $0x14, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x0c, Y9, Y15 - VPSRLD $0x14, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x0c, Y10, Y15 - VPSRLD $0x14, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x0c, Y11, Y15 - VPSRLD $0x14, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - ADDQ (R9), R10 - ADCQ 8(R9), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y3, Y3 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - VMOVDQA Y15, 224(BP) - VPSLLD $0x07, Y14, Y15 - VPSRLD $0x19, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x07, Y9, Y15 - VPSRLD $0x19, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x07, Y10, Y15 - VPSRLD $0x19, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x07, Y11, Y15 - VPSRLD $0x19, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x04, Y9, Y9, Y9 - VPALIGNR $0x04, Y10, Y10, Y10 - VPALIGNR $0x04, Y11, Y11, Y11 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x08, Y15, Y15, Y15 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPALIGNR $0x0c, Y1, Y1, Y1 - VPALIGNR $0x0c, Y2, Y2, Y2 - VPALIGNR $0x0c, Y3, Y3, Y3 - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y3, Y3 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - ADDQ 16(R9), R10 - ADCQ 24(R9), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 32(R9), R9 - VMOVDQA Y15, 224(BP) - VPSLLD $0x0c, Y14, Y15 - VPSRLD $0x14, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x0c, Y9, Y15 - VPSRLD $0x14, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x0c, Y10, Y15 - VPSRLD $0x14, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x0c, Y11, Y15 - VPSRLD $0x14, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y3, Y3 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - VMOVDQA Y15, 224(BP) - VPSLLD $0x07, Y14, Y15 - VPSRLD $0x19, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x07, Y9, Y15 - VPSRLD $0x19, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x07, Y10, Y15 - VPSRLD $0x19, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x07, Y11, Y15 - VPSRLD $0x19, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x0c, Y9, Y9, Y9 - VPALIGNR $0x0c, Y10, Y10, Y10 - VPALIGNR $0x0c, Y11, Y11, Y11 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x08, Y15, Y15, Y15 - VPALIGNR $0x04, Y4, Y4, Y4 - VPALIGNR $0x04, Y1, Y1, Y1 - VPALIGNR $0x04, Y2, Y2, Y2 - VPALIGNR $0x04, Y3, Y3, Y3 - INCQ CX - CMPQ CX, $0x04 - JLT openAVX2Tail512LoopB - CMPQ CX, $0x0a - JNE openAVX2Tail512LoopA - MOVQ BX, CX - SUBQ $0x00000180, CX - ANDQ $-16, CX - -openAVX2Tail512HashLoop: - TESTQ CX, CX - JE openAVX2Tail512HashEnd - ADDQ (R9), R10 - ADCQ 8(R9), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(R9), R9 - SUBQ $0x10, CX - JMP openAVX2Tail512HashLoop - -openAVX2Tail512HashEnd: - VPADDD ·chacha20Constants<>+0(SB), Y0, Y0 - VPADDD ·chacha20Constants<>+0(SB), Y5, Y5 - VPADDD ·chacha20Constants<>+0(SB), Y6, Y6 - VPADDD ·chacha20Constants<>+0(SB), Y7, Y7 - VPADDD 32(BP), Y14, Y14 - VPADDD 32(BP), Y9, Y9 - VPADDD 32(BP), Y10, Y10 - VPADDD 32(BP), Y11, Y11 - VPADDD 64(BP), Y12, Y12 - VPADDD 64(BP), Y13, Y13 - VPADDD 64(BP), Y8, Y8 - VPADDD 64(BP), Y15, Y15 - VPADDD 96(BP), Y4, Y4 - VPADDD 128(BP), Y1, Y1 - VPADDD 160(BP), Y2, Y2 - VPADDD 192(BP), Y3, Y3 - VMOVDQA Y15, 224(BP) - VPERM2I128 $0x02, Y0, Y14, Y15 - VPERM2I128 $0x13, Y0, Y14, Y14 - VPERM2I128 $0x02, Y12, Y4, Y0 - VPERM2I128 $0x13, Y12, Y4, Y12 - VPXOR (SI), Y15, Y15 - VPXOR 32(SI), Y0, Y0 - VPXOR 64(SI), Y14, Y14 - VPXOR 96(SI), Y12, Y12 - VMOVDQU Y15, (DI) - VMOVDQU Y0, 32(DI) - VMOVDQU Y14, 64(DI) - VMOVDQU Y12, 96(DI) - VPERM2I128 $0x02, Y5, Y9, Y0 - VPERM2I128 $0x02, Y13, Y1, Y14 - VPERM2I128 $0x13, Y5, Y9, Y12 - VPERM2I128 $0x13, Y13, Y1, Y4 - VPXOR 128(SI), Y0, Y0 - VPXOR 160(SI), Y14, Y14 - VPXOR 192(SI), Y12, Y12 - VPXOR 224(SI), Y4, Y4 - VMOVDQU Y0, 128(DI) - VMOVDQU Y14, 160(DI) - VMOVDQU Y12, 192(DI) - VMOVDQU Y4, 224(DI) - VPERM2I128 $0x02, Y6, Y10, Y0 - VPERM2I128 $0x02, Y8, Y2, Y14 - VPERM2I128 $0x13, Y6, Y10, Y12 - VPERM2I128 $0x13, Y8, Y2, Y4 - VPXOR 256(SI), Y0, Y0 - VPXOR 288(SI), Y14, Y14 - VPXOR 320(SI), Y12, Y12 - VPXOR 352(SI), Y4, Y4 - VMOVDQU Y0, 256(DI) - VMOVDQU Y14, 288(DI) - VMOVDQU Y12, 320(DI) - VMOVDQU Y4, 352(DI) - VPERM2I128 $0x02, Y7, Y11, Y0 - VPERM2I128 $0x02, 224(BP), Y3, Y14 - VPERM2I128 $0x13, Y7, Y11, Y12 - VPERM2I128 $0x13, 224(BP), Y3, Y4 - LEAQ 384(SI), SI - LEAQ 384(DI), DI - SUBQ $0x00000180, BX - JMP openAVX2TailLoop - -DATA ·chacha20Constants<>+0(SB)/4, $0x61707865 -DATA ·chacha20Constants<>+4(SB)/4, $0x3320646e -DATA ·chacha20Constants<>+8(SB)/4, $0x79622d32 -DATA ·chacha20Constants<>+12(SB)/4, $0x6b206574 -DATA ·chacha20Constants<>+16(SB)/4, $0x61707865 -DATA ·chacha20Constants<>+20(SB)/4, $0x3320646e -DATA ·chacha20Constants<>+24(SB)/4, $0x79622d32 -DATA ·chacha20Constants<>+28(SB)/4, $0x6b206574 -GLOBL ·chacha20Constants<>(SB), RODATA|NOPTR, $32 - -DATA ·polyClampMask<>+0(SB)/8, $0x0ffffffc0fffffff -DATA ·polyClampMask<>+8(SB)/8, $0x0ffffffc0ffffffc -DATA ·polyClampMask<>+16(SB)/8, $0xffffffffffffffff -DATA ·polyClampMask<>+24(SB)/8, $0xffffffffffffffff -GLOBL ·polyClampMask<>(SB), RODATA|NOPTR, $32 - -DATA ·sseIncMask<>+0(SB)/8, $0x0000000000000001 -DATA ·sseIncMask<>+8(SB)/8, $0x0000000000000000 -GLOBL ·sseIncMask<>(SB), RODATA|NOPTR, $16 - -DATA ·andMask<>+0(SB)/8, $0x00000000000000ff -DATA ·andMask<>+8(SB)/8, $0x0000000000000000 -DATA ·andMask<>+16(SB)/8, $0x000000000000ffff -DATA ·andMask<>+24(SB)/8, $0x0000000000000000 -DATA ·andMask<>+32(SB)/8, $0x0000000000ffffff -DATA ·andMask<>+40(SB)/8, $0x0000000000000000 -DATA ·andMask<>+48(SB)/8, $0x00000000ffffffff -DATA ·andMask<>+56(SB)/8, $0x0000000000000000 -DATA ·andMask<>+64(SB)/8, $0x000000ffffffffff -DATA ·andMask<>+72(SB)/8, $0x0000000000000000 -DATA ·andMask<>+80(SB)/8, $0x0000ffffffffffff -DATA ·andMask<>+88(SB)/8, $0x0000000000000000 -DATA ·andMask<>+96(SB)/8, $0x00ffffffffffffff -DATA ·andMask<>+104(SB)/8, $0x0000000000000000 -DATA ·andMask<>+112(SB)/8, $0xffffffffffffffff -DATA ·andMask<>+120(SB)/8, $0x0000000000000000 -DATA ·andMask<>+128(SB)/8, $0xffffffffffffffff -DATA ·andMask<>+136(SB)/8, $0x00000000000000ff -DATA ·andMask<>+144(SB)/8, $0xffffffffffffffff -DATA ·andMask<>+152(SB)/8, $0x000000000000ffff -DATA ·andMask<>+160(SB)/8, $0xffffffffffffffff -DATA ·andMask<>+168(SB)/8, $0x0000000000ffffff -DATA ·andMask<>+176(SB)/8, $0xffffffffffffffff -DATA ·andMask<>+184(SB)/8, $0x00000000ffffffff -DATA ·andMask<>+192(SB)/8, $0xffffffffffffffff -DATA ·andMask<>+200(SB)/8, $0x000000ffffffffff -DATA ·andMask<>+208(SB)/8, $0xffffffffffffffff -DATA ·andMask<>+216(SB)/8, $0x0000ffffffffffff -DATA ·andMask<>+224(SB)/8, $0xffffffffffffffff -DATA ·andMask<>+232(SB)/8, $0x00ffffffffffffff -GLOBL ·andMask<>(SB), RODATA|NOPTR, $240 - -DATA ·avx2InitMask<>+0(SB)/8, $0x0000000000000000 -DATA ·avx2InitMask<>+8(SB)/8, $0x0000000000000000 -DATA ·avx2InitMask<>+16(SB)/8, $0x0000000000000001 -DATA ·avx2InitMask<>+24(SB)/8, $0x0000000000000000 -GLOBL ·avx2InitMask<>(SB), RODATA|NOPTR, $32 - -DATA ·rol16<>+0(SB)/8, $0x0504070601000302 -DATA ·rol16<>+8(SB)/8, $0x0d0c0f0e09080b0a -DATA ·rol16<>+16(SB)/8, $0x0504070601000302 -DATA ·rol16<>+24(SB)/8, $0x0d0c0f0e09080b0a -GLOBL ·rol16<>(SB), RODATA|NOPTR, $32 - -DATA ·rol8<>+0(SB)/8, $0x0605040702010003 -DATA ·rol8<>+8(SB)/8, $0x0e0d0c0f0a09080b -DATA ·rol8<>+16(SB)/8, $0x0605040702010003 -DATA ·rol8<>+24(SB)/8, $0x0e0d0c0f0a09080b -GLOBL ·rol8<>(SB), RODATA|NOPTR, $32 - -DATA ·avx2IncMask<>+0(SB)/8, $0x0000000000000002 -DATA ·avx2IncMask<>+8(SB)/8, $0x0000000000000000 -DATA ·avx2IncMask<>+16(SB)/8, $0x0000000000000002 -DATA ·avx2IncMask<>+24(SB)/8, $0x0000000000000000 -GLOBL ·avx2IncMask<>(SB), RODATA|NOPTR, $32 - -// func chacha20Poly1305Seal(dst []byte, key []uint32, src []byte, ad []byte) -// Requires: AVX, AVX2, BMI2, CMOV, SSE2 -TEXT ·chacha20Poly1305Seal(SB), $288-96 - MOVQ SP, BP - ADDQ $0x20, BP - ANDQ $-32, BP - MOVQ dst_base+0(FP), DI - MOVQ key_base+24(FP), R8 - MOVQ src_base+48(FP), SI - MOVQ src_len+56(FP), BX - MOVQ ad_base+72(FP), CX - CMPB ·useAVX2+0(SB), $0x01 - JE chacha20Poly1305Seal_AVX2 - - // Special optimization, for very short buffers - CMPQ BX, $0x80 - JBE sealSSE128 - - // In the seal case - prepare the poly key + 3 blocks of stream in the first iteration - MOVOU ·chacha20Constants<>+0(SB), X0 - MOVOU 16(R8), X3 - MOVOU 32(R8), X6 - MOVOU 48(R8), X9 - - // Store state on stack for future use - MOVO X3, 32(BP) - MOVO X6, 48(BP) - - // Load state, increment counter blocks - MOVO X0, X1 - MOVO X3, X4 - MOVO X6, X7 - MOVO X9, X10 - PADDL ·sseIncMask<>+0(SB), X10 - MOVO X1, X2 - MOVO X4, X5 - MOVO X7, X8 - MOVO X10, X11 - PADDL ·sseIncMask<>+0(SB), X11 - MOVO X2, X12 - MOVO X5, X13 - MOVO X8, X14 - MOVO X11, X15 - PADDL ·sseIncMask<>+0(SB), X15 - - // Store counters - MOVO X9, 80(BP) - MOVO X10, 96(BP) - MOVO X11, 112(BP) - MOVO X15, 128(BP) - MOVQ $0x0000000a, R9 - -sealSSEIntroLoop: - MOVO X14, 64(BP) - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X3 - PXOR X14, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X3 - PXOR X14, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X4 - PXOR X14, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X4 - PXOR X14, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X5 - PXOR X14, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X5 - PXOR X14, X5 - MOVO 64(BP), X14 - MOVO X7, 64(BP) - PADDD X13, X12 - PXOR X12, X15 - ROL16(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x0c, X7 - PSRLL $0x14, X13 - PXOR X7, X13 - PADDD X13, X12 - PXOR X12, X15 - ROL8(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x07, X7 - PSRLL $0x19, X13 - PXOR X7, X13 - MOVO 64(BP), X7 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x0c - MOVO X14, 64(BP) - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X3 - PXOR X14, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X3 - PXOR X14, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X4 - PXOR X14, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X4 - PXOR X14, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X5 - PXOR X14, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X5 - PXOR X14, X5 - MOVO 64(BP), X14 - MOVO X7, 64(BP) - PADDD X13, X12 - PXOR X12, X15 - ROL16(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x0c, X7 - PSRLL $0x14, X13 - PXOR X7, X13 - PADDD X13, X12 - PXOR X12, X15 - ROL8(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x07, X7 - PSRLL $0x19, X13 - PXOR X7, X13 - MOVO 64(BP), X7 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x04 - DECQ R9 - JNE sealSSEIntroLoop - - // Add in the state - PADDD ·chacha20Constants<>+0(SB), X0 - PADDD ·chacha20Constants<>+0(SB), X1 - PADDD ·chacha20Constants<>+0(SB), X2 - PADDD ·chacha20Constants<>+0(SB), X12 - PADDD 32(BP), X3 - PADDD 32(BP), X4 - PADDD 32(BP), X5 - PADDD 32(BP), X13 - PADDD 48(BP), X7 - PADDD 48(BP), X8 - PADDD 48(BP), X14 - PADDD 96(BP), X10 - PADDD 112(BP), X11 - PADDD 128(BP), X15 - - // Clamp and store the key - PAND ·polyClampMask<>+0(SB), X0 - MOVO X0, (BP) - MOVO X3, 16(BP) - - // Hash AAD - MOVQ ad_len+80(FP), R9 - CALL polyHashADInternal<>(SB) - MOVOU (SI), X0 - MOVOU 16(SI), X3 - MOVOU 32(SI), X6 - MOVOU 48(SI), X9 - PXOR X0, X1 - PXOR X3, X4 - PXOR X6, X7 - PXOR X9, X10 - MOVOU X1, (DI) - MOVOU X4, 16(DI) - MOVOU X7, 32(DI) - MOVOU X10, 48(DI) - MOVOU 64(SI), X0 - MOVOU 80(SI), X3 - MOVOU 96(SI), X6 - MOVOU 112(SI), X9 - PXOR X0, X2 - PXOR X3, X5 - PXOR X6, X8 - PXOR X9, X11 - MOVOU X2, 64(DI) - MOVOU X5, 80(DI) - MOVOU X8, 96(DI) - MOVOU X11, 112(DI) - MOVQ $0x00000080, CX - SUBQ $0x80, BX - LEAQ 128(SI), SI - MOVO X12, X1 - MOVO X13, X4 - MOVO X14, X7 - MOVO X15, X10 - CMPQ BX, $0x40 - JBE sealSSE128SealHash - MOVOU (SI), X0 - MOVOU 16(SI), X3 - MOVOU 32(SI), X6 - MOVOU 48(SI), X9 - PXOR X0, X12 - PXOR X3, X13 - PXOR X6, X14 - PXOR X9, X15 - MOVOU X12, 128(DI) - MOVOU X13, 144(DI) - MOVOU X14, 160(DI) - MOVOU X15, 176(DI) - ADDQ $0x40, CX - SUBQ $0x40, BX - LEAQ 64(SI), SI - MOVQ $0x00000002, CX - MOVQ $0x00000008, R9 - CMPQ BX, $0x40 - JBE sealSSETail64 - CMPQ BX, $0x80 - JBE sealSSETail128 - CMPQ BX, $0xc0 - JBE sealSSETail192 - -sealSSEMainLoop: - // Load state, increment counter blocks - MOVO ·chacha20Constants<>+0(SB), X0 - MOVO 32(BP), X3 - MOVO 48(BP), X6 - MOVO 128(BP), X9 - PADDL ·sseIncMask<>+0(SB), X9 - MOVO X0, X1 - MOVO X3, X4 - MOVO X6, X7 - MOVO X9, X10 - PADDL ·sseIncMask<>+0(SB), X10 - MOVO X1, X2 - MOVO X4, X5 - MOVO X7, X8 - MOVO X10, X11 - PADDL ·sseIncMask<>+0(SB), X11 - MOVO X2, X12 - MOVO X5, X13 - MOVO X8, X14 - MOVO X11, X15 - PADDL ·sseIncMask<>+0(SB), X15 - - // Store counters - MOVO X9, 80(BP) - MOVO X10, 96(BP) - MOVO X11, 112(BP) - MOVO X15, 128(BP) - -sealSSEInnerLoop: - MOVO X14, 64(BP) - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X3 - PXOR X14, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X3 - PXOR X14, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X4 - PXOR X14, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X4 - PXOR X14, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X5 - PXOR X14, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X5 - PXOR X14, X5 - MOVO 64(BP), X14 - MOVO X7, 64(BP) - PADDD X13, X12 - PXOR X12, X15 - ROL16(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x0c, X7 - PSRLL $0x14, X13 - PXOR X7, X13 - PADDD X13, X12 - PXOR X12, X15 - ROL8(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x07, X7 - PSRLL $0x19, X13 - PXOR X7, X13 - MOVO 64(BP), X7 - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x0c - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - LEAQ 16(DI), DI - MOVO X14, 64(BP) - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X3 - PXOR X14, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X14) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X3 - PXOR X14, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X4 - PXOR X14, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X14) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X4 - PXOR X14, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x0c, X14 - PSRLL $0x14, X5 - PXOR X14, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X14) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X14 - PSLLL $0x07, X14 - PSRLL $0x19, X5 - PXOR X14, X5 - MOVO 64(BP), X14 - MOVO X7, 64(BP) - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - PADDD X13, X12 - PXOR X12, X15 - ROL16(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x0c, X7 - PSRLL $0x14, X13 - PXOR X7, X13 - PADDD X13, X12 - PXOR X12, X15 - ROL8(X15, X7) - PADDD X15, X14 - PXOR X14, X13 - MOVO X13, X7 - PSLLL $0x07, X7 - PSRLL $0x19, X13 - PXOR X7, X13 - MOVO 64(BP), X7 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x04 - DECQ R9 - JGE sealSSEInnerLoop - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(DI), DI - DECQ CX - JG sealSSEInnerLoop - - // Add in the state - PADDD ·chacha20Constants<>+0(SB), X0 - PADDD ·chacha20Constants<>+0(SB), X1 - PADDD ·chacha20Constants<>+0(SB), X2 - PADDD ·chacha20Constants<>+0(SB), X12 - PADDD 32(BP), X3 - PADDD 32(BP), X4 - PADDD 32(BP), X5 - PADDD 32(BP), X13 - PADDD 48(BP), X6 - PADDD 48(BP), X7 - PADDD 48(BP), X8 - PADDD 48(BP), X14 - PADDD 80(BP), X9 - PADDD 96(BP), X10 - PADDD 112(BP), X11 - PADDD 128(BP), X15 - MOVO X15, 64(BP) - - // Load - xor - store - MOVOU (SI), X15 - PXOR X15, X0 - MOVOU 16(SI), X15 - PXOR X15, X3 - MOVOU 32(SI), X15 - PXOR X15, X6 - MOVOU 48(SI), X15 - PXOR X15, X9 - MOVOU X0, (DI) - MOVOU X3, 16(DI) - MOVOU X6, 32(DI) - MOVOU X9, 48(DI) - MOVO 64(BP), X15 - MOVOU 64(SI), X0 - MOVOU 80(SI), X3 - MOVOU 96(SI), X6 - MOVOU 112(SI), X9 - PXOR X0, X1 - PXOR X3, X4 - PXOR X6, X7 - PXOR X9, X10 - MOVOU X1, 64(DI) - MOVOU X4, 80(DI) - MOVOU X7, 96(DI) - MOVOU X10, 112(DI) - MOVOU 128(SI), X0 - MOVOU 144(SI), X3 - MOVOU 160(SI), X6 - MOVOU 176(SI), X9 - PXOR X0, X2 - PXOR X3, X5 - PXOR X6, X8 - PXOR X9, X11 - MOVOU X2, 128(DI) - MOVOU X5, 144(DI) - MOVOU X8, 160(DI) - MOVOU X11, 176(DI) - ADDQ $0xc0, SI - MOVQ $0x000000c0, CX - SUBQ $0xc0, BX - MOVO X12, X1 - MOVO X13, X4 - MOVO X14, X7 - MOVO X15, X10 - CMPQ BX, $0x40 - JBE sealSSE128SealHash - MOVOU (SI), X0 - MOVOU 16(SI), X3 - MOVOU 32(SI), X6 - MOVOU 48(SI), X9 - PXOR X0, X12 - PXOR X3, X13 - PXOR X6, X14 - PXOR X9, X15 - MOVOU X12, 192(DI) - MOVOU X13, 208(DI) - MOVOU X14, 224(DI) - MOVOU X15, 240(DI) - LEAQ 64(SI), SI - SUBQ $0x40, BX - MOVQ $0x00000006, CX - MOVQ $0x00000004, R9 - CMPQ BX, $0xc0 - JG sealSSEMainLoop - MOVQ BX, CX - TESTQ BX, BX - JE sealSSE128SealHash - MOVQ $0x00000006, CX - CMPQ BX, $0x40 - JBE sealSSETail64 - CMPQ BX, $0x80 - JBE sealSSETail128 - JMP sealSSETail192 - -sealSSETail64: - MOVO ·chacha20Constants<>+0(SB), X1 - MOVO 32(BP), X4 - MOVO 48(BP), X7 - MOVO 128(BP), X10 - PADDL ·sseIncMask<>+0(SB), X10 - MOVO X10, 80(BP) - -sealSSETail64LoopA: - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(DI), DI - -sealSSETail64LoopB: - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X13) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X13 - PSLLL $0x0c, X13 - PSRLL $0x14, X4 - PXOR X13, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X13) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X13 - PSLLL $0x07, X13 - PSRLL $0x19, X4 - PXOR X13, X4 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x0c - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X13) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X13 - PSLLL $0x0c, X13 - PSRLL $0x14, X4 - PXOR X13, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X13) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X13 - PSLLL $0x07, X13 - PSRLL $0x19, X4 - PXOR X13, X4 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x04 - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(DI), DI - DECQ CX - JG sealSSETail64LoopA - DECQ R9 - JGE sealSSETail64LoopB - PADDL ·chacha20Constants<>+0(SB), X1 - PADDL 32(BP), X4 - PADDL 48(BP), X7 - PADDL 80(BP), X10 - JMP sealSSE128Seal - -sealSSETail128: - MOVO ·chacha20Constants<>+0(SB), X0 - MOVO 32(BP), X3 - MOVO 48(BP), X6 - MOVO 128(BP), X9 - PADDL ·sseIncMask<>+0(SB), X9 - MOVO X9, 80(BP) - MOVO X0, X1 - MOVO X3, X4 - MOVO X6, X7 - MOVO X9, X10 - PADDL ·sseIncMask<>+0(SB), X10 - MOVO X10, 96(BP) - -sealSSETail128LoopA: - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(DI), DI - -sealSSETail128LoopB: - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X4 - PXOR X12, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X4 - PXOR X12, X4 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x0c - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(DI), DI - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X4 - PXOR X12, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X4 - PXOR X12, X4 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x04 - DECQ CX - JG sealSSETail128LoopA - DECQ R9 - JGE sealSSETail128LoopB - PADDL ·chacha20Constants<>+0(SB), X0 - PADDL ·chacha20Constants<>+0(SB), X1 - PADDL 32(BP), X3 - PADDL 32(BP), X4 - PADDL 48(BP), X6 - PADDL 48(BP), X7 - PADDL 80(BP), X9 - PADDL 96(BP), X10 - MOVOU (SI), X12 - MOVOU 16(SI), X13 - MOVOU 32(SI), X14 - MOVOU 48(SI), X15 - PXOR X12, X0 - PXOR X13, X3 - PXOR X14, X6 - PXOR X15, X9 - MOVOU X0, (DI) - MOVOU X3, 16(DI) - MOVOU X6, 32(DI) - MOVOU X9, 48(DI) - MOVQ $0x00000040, CX - LEAQ 64(SI), SI - SUBQ $0x40, BX - JMP sealSSE128SealHash - -sealSSETail192: - MOVO ·chacha20Constants<>+0(SB), X0 - MOVO 32(BP), X3 - MOVO 48(BP), X6 - MOVO 128(BP), X9 - PADDL ·sseIncMask<>+0(SB), X9 - MOVO X9, 80(BP) - MOVO X0, X1 - MOVO X3, X4 - MOVO X6, X7 - MOVO X9, X10 - PADDL ·sseIncMask<>+0(SB), X10 - MOVO X10, 96(BP) - MOVO X1, X2 - MOVO X4, X5 - MOVO X7, X8 - MOVO X10, X11 - PADDL ·sseIncMask<>+0(SB), X11 - MOVO X11, 112(BP) - -sealSSETail192LoopA: - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(DI), DI - -sealSSETail192LoopB: - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X4 - PXOR X12, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X4 - PXOR X12, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X5 - PXOR X12, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X5 - PXOR X12, X5 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(DI), DI - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X4 - PXOR X12, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X4 - PXOR X12, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X5 - PXOR X12, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X5 - PXOR X12, X5 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - DECQ CX - JG sealSSETail192LoopA - DECQ R9 - JGE sealSSETail192LoopB - PADDL ·chacha20Constants<>+0(SB), X0 - PADDL ·chacha20Constants<>+0(SB), X1 - PADDL ·chacha20Constants<>+0(SB), X2 - PADDL 32(BP), X3 - PADDL 32(BP), X4 - PADDL 32(BP), X5 - PADDL 48(BP), X6 - PADDL 48(BP), X7 - PADDL 48(BP), X8 - PADDL 80(BP), X9 - PADDL 96(BP), X10 - PADDL 112(BP), X11 - MOVOU (SI), X12 - MOVOU 16(SI), X13 - MOVOU 32(SI), X14 - MOVOU 48(SI), X15 - PXOR X12, X0 - PXOR X13, X3 - PXOR X14, X6 - PXOR X15, X9 - MOVOU X0, (DI) - MOVOU X3, 16(DI) - MOVOU X6, 32(DI) - MOVOU X9, 48(DI) - MOVOU 64(SI), X12 - MOVOU 80(SI), X13 - MOVOU 96(SI), X14 - MOVOU 112(SI), X15 - PXOR X12, X1 - PXOR X13, X4 - PXOR X14, X7 - PXOR X15, X10 - MOVOU X1, 64(DI) - MOVOU X4, 80(DI) - MOVOU X7, 96(DI) - MOVOU X10, 112(DI) - MOVO X2, X1 - MOVO X5, X4 - MOVO X8, X7 - MOVO X11, X10 - MOVQ $0x00000080, CX - LEAQ 128(SI), SI - SUBQ $0x80, BX - JMP sealSSE128SealHash - -sealSSE128: - MOVOU ·chacha20Constants<>+0(SB), X0 - MOVOU 16(R8), X3 - MOVOU 32(R8), X6 - MOVOU 48(R8), X9 - MOVO X0, X1 - MOVO X3, X4 - MOVO X6, X7 - MOVO X9, X10 - PADDL ·sseIncMask<>+0(SB), X10 - MOVO X1, X2 - MOVO X4, X5 - MOVO X7, X8 - MOVO X10, X11 - PADDL ·sseIncMask<>+0(SB), X11 - MOVO X3, X13 - MOVO X6, X14 - MOVO X10, X15 - MOVQ $0x0000000a, R9 - -sealSSE128InnerCipherLoop: - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X4 - PXOR X12, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X4 - PXOR X12, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X5 - PXOR X12, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X5 - PXOR X12, X5 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x04 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x0c - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - PADDD X3, X0 - PXOR X0, X9 - ROL16(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X3 - PXOR X12, X3 - PADDD X3, X0 - PXOR X0, X9 - ROL8(X9, X12) - PADDD X9, X6 - PXOR X6, X3 - MOVO X3, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X3 - PXOR X12, X3 - PADDD X4, X1 - PXOR X1, X10 - ROL16(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X4 - PXOR X12, X4 - PADDD X4, X1 - PXOR X1, X10 - ROL8(X10, X12) - PADDD X10, X7 - PXOR X7, X4 - MOVO X4, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X4 - PXOR X12, X4 - PADDD X5, X2 - PXOR X2, X11 - ROL16(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x0c, X12 - PSRLL $0x14, X5 - PXOR X12, X5 - PADDD X5, X2 - PXOR X2, X11 - ROL8(X11, X12) - PADDD X11, X8 - PXOR X8, X5 - MOVO X5, X12 - PSLLL $0x07, X12 - PSRLL $0x19, X5 - PXOR X12, X5 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xe4 - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xed - BYTE $0x0c - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xf6 - BYTE $0x08 - BYTE $0x66 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xff - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc0 - BYTE $0x08 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xc9 - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xd2 - BYTE $0x04 - BYTE $0x66 - BYTE $0x45 - BYTE $0x0f - BYTE $0x3a - BYTE $0x0f - BYTE $0xdb - BYTE $0x04 - DECQ R9 - JNE sealSSE128InnerCipherLoop - - // A0|B0 hold the Poly1305 32-byte key, C0,D0 can be discarded - PADDL ·chacha20Constants<>+0(SB), X0 - PADDL ·chacha20Constants<>+0(SB), X1 - PADDL ·chacha20Constants<>+0(SB), X2 - PADDL X13, X3 - PADDL X13, X4 - PADDL X13, X5 - PADDL X14, X7 - PADDL X14, X8 - PADDL X15, X10 - PADDL ·sseIncMask<>+0(SB), X15 - PADDL X15, X11 - PAND ·polyClampMask<>+0(SB), X0 - MOVOU X0, (BP) - MOVOU X3, 16(BP) - - // Hash - MOVQ ad_len+80(FP), R9 - CALL polyHashADInternal<>(SB) - XORQ CX, CX - -sealSSE128SealHash: - CMPQ CX, $0x10 - JB sealSSE128Seal - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - SUBQ $0x10, CX - ADDQ $0x10, DI - JMP sealSSE128SealHash - -sealSSE128Seal: - CMPQ BX, $0x10 - JB sealSSETail - SUBQ $0x10, BX - - // Load for decryption - MOVOU (SI), X12 - PXOR X12, X1 - MOVOU X1, (DI) - LEAQ 16(SI), SI - LEAQ 16(DI), DI - - // Extract for hashing - MOVQ X1, R13 - PSRLDQ $0x08, X1 - MOVQ X1, R14 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - - // Shift the stream "left" - MOVO X4, X1 - MOVO X7, X4 - MOVO X10, X7 - MOVO X2, X10 - MOVO X5, X2 - MOVO X8, X5 - MOVO X11, X8 - JMP sealSSE128Seal - -sealSSETail: - TESTQ BX, BX - JE sealSSEFinalize - - // We can only load the PT one byte at a time to avoid read after end of buffer - MOVQ BX, R9 - SHLQ $0x04, R9 - LEAQ ·andMask<>+0(SB), R13 - MOVQ BX, CX - LEAQ -1(SI)(BX*1), SI - XORQ R15, R15 - XORQ R8, R8 - XORQ AX, AX - -sealSSETailLoadLoop: - SHLQ $0x08, R15, R8 - SHLQ $0x08, R15 - MOVB (SI), AX - XORQ AX, R15 - LEAQ -1(SI), SI - DECQ CX - JNE sealSSETailLoadLoop - MOVQ R15, 64(BP) - MOVQ R8, 72(BP) - PXOR 64(BP), X1 - MOVOU X1, (DI) - MOVOU -16(R13)(R9*1), X12 - PAND X12, X1 - MOVQ X1, R13 - PSRLDQ $0x08, X1 - MOVQ X1, R14 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - ADDQ BX, DI - -sealSSEFinalize: - // Hash in the buffer lengths - ADDQ ad_len+80(FP), R10 - ADCQ src_len+56(FP), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - - // Final reduce - MOVQ R10, R13 - MOVQ R11, R14 - MOVQ R12, R15 - SUBQ $-5, R10 - SBBQ $-1, R11 - SBBQ $0x03, R12 - CMOVQCS R13, R10 - CMOVQCS R14, R11 - CMOVQCS R15, R12 - - // Add in the "s" part of the key - ADDQ 16(BP), R10 - ADCQ 24(BP), R11 - - // Finally store the tag at the end of the message - MOVQ R10, (DI) - MOVQ R11, 8(DI) - RET - -chacha20Poly1305Seal_AVX2: - VZEROUPPER - VMOVDQU ·chacha20Constants<>+0(SB), Y0 - BYTE $0xc4 - BYTE $0x42 - BYTE $0x7d - BYTE $0x5a - BYTE $0x70 - BYTE $0x10 - BYTE $0xc4 - BYTE $0x42 - BYTE $0x7d - BYTE $0x5a - BYTE $0x60 - BYTE $0x20 - BYTE $0xc4 - BYTE $0xc2 - BYTE $0x7d - BYTE $0x5a - BYTE $0x60 - BYTE $0x30 - VPADDD ·avx2InitMask<>+0(SB), Y4, Y4 - - // Special optimizations, for very short buffers - CMPQ BX, $0x000000c0 - JBE seal192AVX2 - CMPQ BX, $0x00000140 - JBE seal320AVX2 - - // For the general key prepare the key first - as a byproduct we have 64 bytes of cipher stream - VMOVDQA Y0, Y5 - VMOVDQA Y0, Y6 - VMOVDQA Y0, Y7 - VMOVDQA Y14, Y9 - VMOVDQA Y14, Y10 - VMOVDQA Y14, Y11 - VMOVDQA Y14, 32(BP) - VMOVDQA Y12, Y13 - VMOVDQA Y12, Y8 - VMOVDQA Y12, Y15 - VMOVDQA Y12, 64(BP) - VPADDD ·avx2IncMask<>+0(SB), Y4, Y1 - VMOVDQA Y4, 96(BP) - VPADDD ·avx2IncMask<>+0(SB), Y1, Y2 - VMOVDQA Y1, 128(BP) - VPADDD ·avx2IncMask<>+0(SB), Y2, Y3 - VMOVDQA Y2, 160(BP) - VMOVDQA Y3, 192(BP) - MOVQ $0x0000000a, R9 - -sealAVX2IntroLoop: - VMOVDQA Y15, 224(BP) - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y15 - VPSRLD $0x14, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y15 - VPSRLD $0x19, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y15 - VPSRLD $0x14, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y15 - VPSRLD $0x19, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x0c, Y10, Y15 - VPSRLD $0x14, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x07, Y10, Y15 - VPSRLD $0x19, Y10, Y10 - VPXOR Y15, Y10, Y10 - VMOVDQA 224(BP), Y15 - VMOVDQA Y13, 224(BP) - VPADDD Y11, Y7, Y7 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol16<>+0(SB), Y3, Y3 - VPADDD Y3, Y15, Y15 - VPXOR Y15, Y11, Y11 - VPSLLD $0x0c, Y11, Y13 - VPSRLD $0x14, Y11, Y11 - VPXOR Y13, Y11, Y11 - VPADDD Y11, Y7, Y7 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol8<>+0(SB), Y3, Y3 - VPADDD Y3, Y15, Y15 - VPXOR Y15, Y11, Y11 - VPSLLD $0x07, Y11, Y13 - VPSRLD $0x19, Y11, Y11 - VPXOR Y13, Y11, Y11 - VMOVDQA 224(BP), Y13 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPALIGNR $0x04, Y9, Y9, Y9 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x0c, Y1, Y1, Y1 - VPALIGNR $0x04, Y10, Y10, Y10 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x0c, Y2, Y2, Y2 - VPALIGNR $0x04, Y11, Y11, Y11 - VPALIGNR $0x08, Y15, Y15, Y15 - VPALIGNR $0x0c, Y3, Y3, Y3 - VMOVDQA Y15, 224(BP) - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y15 - VPSRLD $0x14, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y15 - VPSRLD $0x19, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y15 - VPSRLD $0x14, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y15 - VPSRLD $0x19, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x0c, Y10, Y15 - VPSRLD $0x14, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x07, Y10, Y15 - VPSRLD $0x19, Y10, Y10 - VPXOR Y15, Y10, Y10 - VMOVDQA 224(BP), Y15 - VMOVDQA Y13, 224(BP) - VPADDD Y11, Y7, Y7 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol16<>+0(SB), Y3, Y3 - VPADDD Y3, Y15, Y15 - VPXOR Y15, Y11, Y11 - VPSLLD $0x0c, Y11, Y13 - VPSRLD $0x14, Y11, Y11 - VPXOR Y13, Y11, Y11 - VPADDD Y11, Y7, Y7 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol8<>+0(SB), Y3, Y3 - VPADDD Y3, Y15, Y15 - VPXOR Y15, Y11, Y11 - VPSLLD $0x07, Y11, Y13 - VPSRLD $0x19, Y11, Y11 - VPXOR Y13, Y11, Y11 - VMOVDQA 224(BP), Y13 - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x04, Y4, Y4, Y4 - VPALIGNR $0x0c, Y9, Y9, Y9 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x04, Y1, Y1, Y1 - VPALIGNR $0x0c, Y10, Y10, Y10 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x04, Y2, Y2, Y2 - VPALIGNR $0x0c, Y11, Y11, Y11 - VPALIGNR $0x08, Y15, Y15, Y15 - VPALIGNR $0x04, Y3, Y3, Y3 - DECQ R9 - JNE sealAVX2IntroLoop - VPADDD ·chacha20Constants<>+0(SB), Y0, Y0 - VPADDD ·chacha20Constants<>+0(SB), Y5, Y5 - VPADDD ·chacha20Constants<>+0(SB), Y6, Y6 - VPADDD ·chacha20Constants<>+0(SB), Y7, Y7 - VPADDD 32(BP), Y14, Y14 - VPADDD 32(BP), Y9, Y9 - VPADDD 32(BP), Y10, Y10 - VPADDD 32(BP), Y11, Y11 - VPADDD 64(BP), Y12, Y12 - VPADDD 64(BP), Y13, Y13 - VPADDD 64(BP), Y8, Y8 - VPADDD 64(BP), Y15, Y15 - VPADDD 96(BP), Y4, Y4 - VPADDD 128(BP), Y1, Y1 - VPADDD 160(BP), Y2, Y2 - VPADDD 192(BP), Y3, Y3 - VPERM2I128 $0x13, Y12, Y4, Y12 - VPERM2I128 $0x02, Y0, Y14, Y4 - VPERM2I128 $0x13, Y0, Y14, Y0 - - // Clamp and store poly key - VPAND ·polyClampMask<>+0(SB), Y4, Y4 - VMOVDQA Y4, (BP) - - // Hash AD - MOVQ ad_len+80(FP), R9 - CALL polyHashADInternal<>(SB) - - // Can store at least 320 bytes - VPXOR (SI), Y0, Y0 - VPXOR 32(SI), Y12, Y12 - VMOVDQU Y0, (DI) - VMOVDQU Y12, 32(DI) - VPERM2I128 $0x02, Y5, Y9, Y0 - VPERM2I128 $0x02, Y13, Y1, Y14 - VPERM2I128 $0x13, Y5, Y9, Y12 - VPERM2I128 $0x13, Y13, Y1, Y4 - VPXOR 64(SI), Y0, Y0 - VPXOR 96(SI), Y14, Y14 - VPXOR 128(SI), Y12, Y12 - VPXOR 160(SI), Y4, Y4 - VMOVDQU Y0, 64(DI) - VMOVDQU Y14, 96(DI) - VMOVDQU Y12, 128(DI) - VMOVDQU Y4, 160(DI) - VPERM2I128 $0x02, Y6, Y10, Y0 - VPERM2I128 $0x02, Y8, Y2, Y14 - VPERM2I128 $0x13, Y6, Y10, Y12 - VPERM2I128 $0x13, Y8, Y2, Y4 - VPXOR 192(SI), Y0, Y0 - VPXOR 224(SI), Y14, Y14 - VPXOR 256(SI), Y12, Y12 - VPXOR 288(SI), Y4, Y4 - VMOVDQU Y0, 192(DI) - VMOVDQU Y14, 224(DI) - VMOVDQU Y12, 256(DI) - VMOVDQU Y4, 288(DI) - MOVQ $0x00000140, CX - SUBQ $0x00000140, BX - LEAQ 320(SI), SI - VPERM2I128 $0x02, Y7, Y11, Y0 - VPERM2I128 $0x02, Y15, Y3, Y14 - VPERM2I128 $0x13, Y7, Y11, Y12 - VPERM2I128 $0x13, Y15, Y3, Y4 - CMPQ BX, $0x80 - JBE sealAVX2SealHash - VPXOR (SI), Y0, Y0 - VPXOR 32(SI), Y14, Y14 - VPXOR 64(SI), Y12, Y12 - VPXOR 96(SI), Y4, Y4 - VMOVDQU Y0, 320(DI) - VMOVDQU Y14, 352(DI) - VMOVDQU Y12, 384(DI) - VMOVDQU Y4, 416(DI) - SUBQ $0x80, BX - LEAQ 128(SI), SI - MOVQ $0x00000008, CX - MOVQ $0x00000002, R9 - CMPQ BX, $0x80 - JBE sealAVX2Tail128 - CMPQ BX, $0x00000100 - JBE sealAVX2Tail256 - CMPQ BX, $0x00000180 - JBE sealAVX2Tail384 - CMPQ BX, $0x00000200 - JBE sealAVX2Tail512 - - // We have 448 bytes to hash, but main loop hashes 512 bytes at a time - perform some rounds, before the main loop - VMOVDQA ·chacha20Constants<>+0(SB), Y0 - VMOVDQA Y0, Y5 - VMOVDQA Y0, Y6 - VMOVDQA Y0, Y7 - VMOVDQA 32(BP), Y14 - VMOVDQA Y14, Y9 - VMOVDQA Y14, Y10 - VMOVDQA Y14, Y11 - VMOVDQA 64(BP), Y12 - VMOVDQA Y12, Y13 - VMOVDQA Y12, Y8 - VMOVDQA Y12, Y15 - VMOVDQA 192(BP), Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y1 - VPADDD ·avx2IncMask<>+0(SB), Y1, Y2 - VPADDD ·avx2IncMask<>+0(SB), Y2, Y3 - VMOVDQA Y4, 96(BP) - VMOVDQA Y1, 128(BP) - VMOVDQA Y2, 160(BP) - VMOVDQA Y3, 192(BP) - VMOVDQA Y15, 224(BP) - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y15 - VPSRLD $0x14, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y15 - VPSRLD $0x19, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y15 - VPSRLD $0x14, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y15 - VPSRLD $0x19, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x0c, Y10, Y15 - VPSRLD $0x14, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x07, Y10, Y15 - VPSRLD $0x19, Y10, Y10 - VPXOR Y15, Y10, Y10 - VMOVDQA 224(BP), Y15 - VMOVDQA Y13, 224(BP) - VPADDD Y11, Y7, Y7 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol16<>+0(SB), Y3, Y3 - VPADDD Y3, Y15, Y15 - VPXOR Y15, Y11, Y11 - VPSLLD $0x0c, Y11, Y13 - VPSRLD $0x14, Y11, Y11 - VPXOR Y13, Y11, Y11 - VPADDD Y11, Y7, Y7 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol8<>+0(SB), Y3, Y3 - VPADDD Y3, Y15, Y15 - VPXOR Y15, Y11, Y11 - VPSLLD $0x07, Y11, Y13 - VPSRLD $0x19, Y11, Y11 - VPXOR Y13, Y11, Y11 - VMOVDQA 224(BP), Y13 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPALIGNR $0x04, Y9, Y9, Y9 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x0c, Y1, Y1, Y1 - VPALIGNR $0x04, Y10, Y10, Y10 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x0c, Y2, Y2, Y2 - VPALIGNR $0x04, Y11, Y11, Y11 - VPALIGNR $0x08, Y15, Y15, Y15 - VPALIGNR $0x0c, Y3, Y3, Y3 - VMOVDQA Y15, 224(BP) - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y15 - VPSRLD $0x14, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y15 - VPSRLD $0x19, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y15 - VPSRLD $0x14, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y15 - VPSRLD $0x19, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x0c, Y10, Y15 - VPSRLD $0x14, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x07, Y10, Y15 - VPSRLD $0x19, Y10, Y10 - VPXOR Y15, Y10, Y10 - VMOVDQA 224(BP), Y15 - VMOVDQA Y13, 224(BP) - VPADDD Y11, Y7, Y7 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol16<>+0(SB), Y3, Y3 - VPADDD Y3, Y15, Y15 - VPXOR Y15, Y11, Y11 - VPSLLD $0x0c, Y11, Y13 - VPSRLD $0x14, Y11, Y11 - VPXOR Y13, Y11, Y11 - VPADDD Y11, Y7, Y7 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol8<>+0(SB), Y3, Y3 - VPADDD Y3, Y15, Y15 - VPXOR Y15, Y11, Y11 - VPSLLD $0x07, Y11, Y13 - VPSRLD $0x19, Y11, Y11 - VPXOR Y13, Y11, Y11 - VMOVDQA 224(BP), Y13 - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x04, Y4, Y4, Y4 - VPALIGNR $0x0c, Y9, Y9, Y9 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x04, Y1, Y1, Y1 - VPALIGNR $0x0c, Y10, Y10, Y10 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x04, Y2, Y2, Y2 - VPALIGNR $0x0c, Y11, Y11, Y11 - VPALIGNR $0x08, Y15, Y15, Y15 - VPALIGNR $0x04, Y3, Y3, Y3 - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y3, Y3 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - VMOVDQA Y15, 224(BP) - VPSLLD $0x0c, Y14, Y15 - VPSRLD $0x14, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x0c, Y9, Y15 - VPSRLD $0x14, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x0c, Y10, Y15 - VPSRLD $0x14, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x0c, Y11, Y15 - VPSRLD $0x14, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - SUBQ $0x10, DI - MOVQ $0x00000009, CX - JMP sealAVX2InternalLoopStart - -sealAVX2MainLoop: - VMOVDQU ·chacha20Constants<>+0(SB), Y0 - VMOVDQA Y0, Y5 - VMOVDQA Y0, Y6 - VMOVDQA Y0, Y7 - VMOVDQA 32(BP), Y14 - VMOVDQA Y14, Y9 - VMOVDQA Y14, Y10 - VMOVDQA Y14, Y11 - VMOVDQA 64(BP), Y12 - VMOVDQA Y12, Y13 - VMOVDQA Y12, Y8 - VMOVDQA Y12, Y15 - VMOVDQA 192(BP), Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y1 - VPADDD ·avx2IncMask<>+0(SB), Y1, Y2 - VPADDD ·avx2IncMask<>+0(SB), Y2, Y3 - VMOVDQA Y4, 96(BP) - VMOVDQA Y1, 128(BP) - VMOVDQA Y2, 160(BP) - VMOVDQA Y3, 192(BP) - MOVQ $0x0000000a, CX - -sealAVX2InternalLoop: - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y3, Y3 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - VMOVDQA Y15, 224(BP) - VPSLLD $0x0c, Y14, Y15 - VPSRLD $0x14, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x0c, Y9, Y15 - VPSRLD $0x14, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x0c, Y10, Y15 - VPSRLD $0x14, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x0c, Y11, Y15 - VPSRLD $0x14, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - -sealAVX2InternalLoopStart: - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y3, Y3 - ADDQ 16(DI), R10 - ADCQ 24(DI), R11 - ADCQ $0x01, R12 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - VMOVDQA Y15, 224(BP) - VPSLLD $0x07, Y14, Y15 - VPSRLD $0x19, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x07, Y9, Y15 - VPSRLD $0x19, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x07, Y10, Y15 - VPSRLD $0x19, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x07, Y11, Y15 - VPSRLD $0x19, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x04, Y9, Y9, Y9 - VPALIGNR $0x04, Y10, Y10, Y10 - VPALIGNR $0x04, Y11, Y11, Y11 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x08, Y15, Y15, Y15 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPALIGNR $0x0c, Y1, Y1, Y1 - VPALIGNR $0x0c, Y2, Y2, Y2 - VPALIGNR $0x0c, Y3, Y3, Y3 - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y3, Y3 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - ADDQ 32(DI), R10 - ADCQ 40(DI), R11 - ADCQ $0x01, R12 - LEAQ 48(DI), DI - VMOVDQA Y15, 224(BP) - VPSLLD $0x0c, Y14, Y15 - VPSRLD $0x14, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x0c, Y9, Y15 - VPSRLD $0x14, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x0c, Y10, Y15 - VPSRLD $0x14, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x0c, Y11, Y15 - VPSRLD $0x14, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y3, Y3 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - VMOVDQA Y15, 224(BP) - VPSLLD $0x07, Y14, Y15 - VPSRLD $0x19, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x07, Y9, Y15 - VPSRLD $0x19, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x07, Y10, Y15 - VPSRLD $0x19, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x07, Y11, Y15 - VPSRLD $0x19, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x0c, Y9, Y9, Y9 - VPALIGNR $0x0c, Y10, Y10, Y10 - VPALIGNR $0x0c, Y11, Y11, Y11 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x08, Y15, Y15, Y15 - VPALIGNR $0x04, Y4, Y4, Y4 - VPALIGNR $0x04, Y1, Y1, Y1 - VPALIGNR $0x04, Y2, Y2, Y2 - VPALIGNR $0x04, Y3, Y3, Y3 - DECQ CX - JNE sealAVX2InternalLoop - VPADDD ·chacha20Constants<>+0(SB), Y0, Y0 - VPADDD ·chacha20Constants<>+0(SB), Y5, Y5 - VPADDD ·chacha20Constants<>+0(SB), Y6, Y6 - VPADDD ·chacha20Constants<>+0(SB), Y7, Y7 - VPADDD 32(BP), Y14, Y14 - VPADDD 32(BP), Y9, Y9 - VPADDD 32(BP), Y10, Y10 - VPADDD 32(BP), Y11, Y11 - VPADDD 64(BP), Y12, Y12 - VPADDD 64(BP), Y13, Y13 - VPADDD 64(BP), Y8, Y8 - VPADDD 64(BP), Y15, Y15 - VPADDD 96(BP), Y4, Y4 - VPADDD 128(BP), Y1, Y1 - VPADDD 160(BP), Y2, Y2 - VPADDD 192(BP), Y3, Y3 - VMOVDQA Y15, 224(BP) - - // We only hashed 480 of the 512 bytes available - hash the remaining 32 here - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 32(DI), DI - VPERM2I128 $0x02, Y0, Y14, Y15 - VPERM2I128 $0x13, Y0, Y14, Y14 - VPERM2I128 $0x02, Y12, Y4, Y0 - VPERM2I128 $0x13, Y12, Y4, Y12 - VPXOR (SI), Y15, Y15 - VPXOR 32(SI), Y0, Y0 - VPXOR 64(SI), Y14, Y14 - VPXOR 96(SI), Y12, Y12 - VMOVDQU Y15, (DI) - VMOVDQU Y0, 32(DI) - VMOVDQU Y14, 64(DI) - VMOVDQU Y12, 96(DI) - VPERM2I128 $0x02, Y5, Y9, Y0 - VPERM2I128 $0x02, Y13, Y1, Y14 - VPERM2I128 $0x13, Y5, Y9, Y12 - VPERM2I128 $0x13, Y13, Y1, Y4 - VPXOR 128(SI), Y0, Y0 - VPXOR 160(SI), Y14, Y14 - VPXOR 192(SI), Y12, Y12 - VPXOR 224(SI), Y4, Y4 - VMOVDQU Y0, 128(DI) - VMOVDQU Y14, 160(DI) - VMOVDQU Y12, 192(DI) - VMOVDQU Y4, 224(DI) - - // and here - ADDQ -16(DI), R10 - ADCQ -8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - VPERM2I128 $0x02, Y6, Y10, Y0 - VPERM2I128 $0x02, Y8, Y2, Y14 - VPERM2I128 $0x13, Y6, Y10, Y12 - VPERM2I128 $0x13, Y8, Y2, Y4 - VPXOR 256(SI), Y0, Y0 - VPXOR 288(SI), Y14, Y14 - VPXOR 320(SI), Y12, Y12 - VPXOR 352(SI), Y4, Y4 - VMOVDQU Y0, 256(DI) - VMOVDQU Y14, 288(DI) - VMOVDQU Y12, 320(DI) - VMOVDQU Y4, 352(DI) - VPERM2I128 $0x02, Y7, Y11, Y0 - VPERM2I128 $0x02, 224(BP), Y3, Y14 - VPERM2I128 $0x13, Y7, Y11, Y12 - VPERM2I128 $0x13, 224(BP), Y3, Y4 - VPXOR 384(SI), Y0, Y0 - VPXOR 416(SI), Y14, Y14 - VPXOR 448(SI), Y12, Y12 - VPXOR 480(SI), Y4, Y4 - VMOVDQU Y0, 384(DI) - VMOVDQU Y14, 416(DI) - VMOVDQU Y12, 448(DI) - VMOVDQU Y4, 480(DI) - LEAQ 512(SI), SI - SUBQ $0x00000200, BX - CMPQ BX, $0x00000200 - JG sealAVX2MainLoop - - // Tail can only hash 480 bytes - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - ADDQ 16(DI), R10 - ADCQ 24(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 32(DI), DI - MOVQ $0x0000000a, CX - MOVQ $0x00000000, R9 - CMPQ BX, $0x80 - JBE sealAVX2Tail128 - CMPQ BX, $0x00000100 - JBE sealAVX2Tail256 - CMPQ BX, $0x00000180 - JBE sealAVX2Tail384 - JMP sealAVX2Tail512 - -seal192AVX2: - VMOVDQA Y0, Y5 - VMOVDQA Y14, Y9 - VMOVDQA Y12, Y13 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y1 - VMOVDQA Y0, Y6 - VMOVDQA Y14, Y10 - VMOVDQA Y12, Y8 - VMOVDQA Y4, Y2 - VMOVDQA Y1, Y15 - MOVQ $0x0000000a, R9 - -sealAVX2192InnerCipherLoop: - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x04, Y9, Y9, Y9 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPALIGNR $0x0c, Y1, Y1, Y1 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x0c, Y9, Y9, Y9 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x04, Y4, Y4, Y4 - VPALIGNR $0x04, Y1, Y1, Y1 - DECQ R9 - JNE sealAVX2192InnerCipherLoop - VPADDD Y6, Y0, Y0 - VPADDD Y6, Y5, Y5 - VPADDD Y10, Y14, Y14 - VPADDD Y10, Y9, Y9 - VPADDD Y8, Y12, Y12 - VPADDD Y8, Y13, Y13 - VPADDD Y2, Y4, Y4 - VPADDD Y15, Y1, Y1 - VPERM2I128 $0x02, Y0, Y14, Y3 - - // Clamp and store poly key - VPAND ·polyClampMask<>+0(SB), Y3, Y3 - VMOVDQA Y3, (BP) - - // Stream for up to 192 bytes - VPERM2I128 $0x13, Y0, Y14, Y0 - VPERM2I128 $0x13, Y12, Y4, Y14 - VPERM2I128 $0x02, Y5, Y9, Y12 - VPERM2I128 $0x02, Y13, Y1, Y4 - VPERM2I128 $0x13, Y5, Y9, Y5 - VPERM2I128 $0x13, Y13, Y1, Y9 - -sealAVX2ShortSeal: - // Hash aad - MOVQ ad_len+80(FP), R9 - CALL polyHashADInternal<>(SB) - XORQ CX, CX - -sealAVX2SealHash: - // itr1 holds the number of bytes encrypted but not yet hashed - CMPQ CX, $0x10 - JB sealAVX2ShortSealLoop - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - SUBQ $0x10, CX - ADDQ $0x10, DI - JMP sealAVX2SealHash - -sealAVX2ShortSealLoop: - CMPQ BX, $0x20 - JB sealAVX2ShortTail32 - SUBQ $0x20, BX - - // Load for encryption - VPXOR (SI), Y0, Y0 - VMOVDQU Y0, (DI) - LEAQ 32(SI), SI - - // Now can hash - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - ADDQ 16(DI), R10 - ADCQ 24(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 32(DI), DI - - // Shift stream left - VMOVDQA Y14, Y0 - VMOVDQA Y12, Y14 - VMOVDQA Y4, Y12 - VMOVDQA Y5, Y4 - VMOVDQA Y9, Y5 - VMOVDQA Y13, Y9 - VMOVDQA Y1, Y13 - VMOVDQA Y6, Y1 - VMOVDQA Y10, Y6 - JMP sealAVX2ShortSealLoop - -sealAVX2ShortTail32: - CMPQ BX, $0x10 - VMOVDQA X0, X1 - JB sealAVX2ShortDone - SUBQ $0x10, BX - - // Load for encryption - VPXOR (SI), X0, X12 - VMOVDQU X12, (DI) - LEAQ 16(SI), SI - - // Hash - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(DI), DI - VPERM2I128 $0x11, Y0, Y0, Y0 - VMOVDQA X0, X1 - -sealAVX2ShortDone: - VZEROUPPER - JMP sealSSETail - -seal320AVX2: - VMOVDQA Y0, Y5 - VMOVDQA Y14, Y9 - VMOVDQA Y12, Y13 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y1 - VMOVDQA Y0, Y6 - VMOVDQA Y14, Y10 - VMOVDQA Y12, Y8 - VPADDD ·avx2IncMask<>+0(SB), Y1, Y2 - VMOVDQA Y14, Y7 - VMOVDQA Y12, Y11 - VMOVDQA Y4, Y15 - MOVQ $0x0000000a, R9 - -sealAVX2320InnerCipherLoop: - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x0c, Y10, Y3 - VPSRLD $0x14, Y10, Y10 - VPXOR Y3, Y10, Y10 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x07, Y10, Y3 - VPSRLD $0x19, Y10, Y10 - VPXOR Y3, Y10, Y10 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x04, Y9, Y9, Y9 - VPALIGNR $0x04, Y10, Y10, Y10 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPALIGNR $0x0c, Y1, Y1, Y1 - VPALIGNR $0x0c, Y2, Y2, Y2 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x0c, Y10, Y3 - VPSRLD $0x14, Y10, Y10 - VPXOR Y3, Y10, Y10 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x07, Y10, Y3 - VPSRLD $0x19, Y10, Y10 - VPXOR Y3, Y10, Y10 - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x0c, Y9, Y9, Y9 - VPALIGNR $0x0c, Y10, Y10, Y10 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x04, Y4, Y4, Y4 - VPALIGNR $0x04, Y1, Y1, Y1 - VPALIGNR $0x04, Y2, Y2, Y2 - DECQ R9 - JNE sealAVX2320InnerCipherLoop - VMOVDQA ·chacha20Constants<>+0(SB), Y3 - VPADDD Y3, Y0, Y0 - VPADDD Y3, Y5, Y5 - VPADDD Y3, Y6, Y6 - VPADDD Y7, Y14, Y14 - VPADDD Y7, Y9, Y9 - VPADDD Y7, Y10, Y10 - VPADDD Y11, Y12, Y12 - VPADDD Y11, Y13, Y13 - VPADDD Y11, Y8, Y8 - VMOVDQA ·avx2IncMask<>+0(SB), Y3 - VPADDD Y15, Y4, Y4 - VPADDD Y3, Y15, Y15 - VPADDD Y15, Y1, Y1 - VPADDD Y3, Y15, Y15 - VPADDD Y15, Y2, Y2 - - // Clamp and store poly key - VPERM2I128 $0x02, Y0, Y14, Y3 - VPAND ·polyClampMask<>+0(SB), Y3, Y3 - VMOVDQA Y3, (BP) - - // Stream for up to 320 bytes - VPERM2I128 $0x13, Y0, Y14, Y0 - VPERM2I128 $0x13, Y12, Y4, Y14 - VPERM2I128 $0x02, Y5, Y9, Y12 - VPERM2I128 $0x02, Y13, Y1, Y4 - VPERM2I128 $0x13, Y5, Y9, Y5 - VPERM2I128 $0x13, Y13, Y1, Y9 - VPERM2I128 $0x02, Y6, Y10, Y13 - VPERM2I128 $0x02, Y8, Y2, Y1 - VPERM2I128 $0x13, Y6, Y10, Y6 - VPERM2I128 $0x13, Y8, Y2, Y10 - JMP sealAVX2ShortSeal - -sealAVX2Tail128: - VMOVDQA ·chacha20Constants<>+0(SB), Y0 - VMOVDQA 32(BP), Y14 - VMOVDQA 64(BP), Y12 - VMOVDQA 192(BP), Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y4 - VMOVDQA Y4, Y1 - -sealAVX2Tail128LoopA: - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(DI), DI - -sealAVX2Tail128LoopB: - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - ADDQ 16(DI), R10 - ADCQ 24(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 32(DI), DI - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x04, Y4, Y4, Y4 - DECQ CX - JG sealAVX2Tail128LoopA - DECQ R9 - JGE sealAVX2Tail128LoopB - VPADDD ·chacha20Constants<>+0(SB), Y0, Y5 - VPADDD 32(BP), Y14, Y9 - VPADDD 64(BP), Y12, Y13 - VPADDD Y1, Y4, Y1 - VPERM2I128 $0x02, Y5, Y9, Y0 - VPERM2I128 $0x02, Y13, Y1, Y14 - VPERM2I128 $0x13, Y5, Y9, Y12 - VPERM2I128 $0x13, Y13, Y1, Y4 - JMP sealAVX2ShortSealLoop - -sealAVX2Tail256: - VMOVDQA ·chacha20Constants<>+0(SB), Y0 - VMOVDQA ·chacha20Constants<>+0(SB), Y5 - VMOVDQA 32(BP), Y14 - VMOVDQA 32(BP), Y9 - VMOVDQA 64(BP), Y12 - VMOVDQA 64(BP), Y13 - VMOVDQA 192(BP), Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y1 - VMOVDQA Y4, Y7 - VMOVDQA Y1, Y11 - -sealAVX2Tail256LoopA: - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(DI), DI - -sealAVX2Tail256LoopB: - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x04, Y9, Y9, Y9 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPALIGNR $0x0c, Y1, Y1, Y1 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - ADDQ 16(DI), R10 - ADCQ 24(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 32(DI), DI - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x0c, Y9, Y9, Y9 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x04, Y4, Y4, Y4 - VPALIGNR $0x04, Y1, Y1, Y1 - DECQ CX - JG sealAVX2Tail256LoopA - DECQ R9 - JGE sealAVX2Tail256LoopB - VPADDD ·chacha20Constants<>+0(SB), Y0, Y0 - VPADDD ·chacha20Constants<>+0(SB), Y5, Y5 - VPADDD 32(BP), Y14, Y14 - VPADDD 32(BP), Y9, Y9 - VPADDD 64(BP), Y12, Y12 - VPADDD 64(BP), Y13, Y13 - VPADDD Y7, Y4, Y4 - VPADDD Y11, Y1, Y1 - VPERM2I128 $0x02, Y0, Y14, Y3 - VPERM2I128 $0x02, Y12, Y4, Y7 - VPERM2I128 $0x13, Y0, Y14, Y11 - VPERM2I128 $0x13, Y12, Y4, Y15 - VPXOR (SI), Y3, Y3 - VPXOR 32(SI), Y7, Y7 - VPXOR 64(SI), Y11, Y11 - VPXOR 96(SI), Y15, Y15 - VMOVDQU Y3, (DI) - VMOVDQU Y7, 32(DI) - VMOVDQU Y11, 64(DI) - VMOVDQU Y15, 96(DI) - MOVQ $0x00000080, CX - LEAQ 128(SI), SI - SUBQ $0x80, BX - VPERM2I128 $0x02, Y5, Y9, Y0 - VPERM2I128 $0x02, Y13, Y1, Y14 - VPERM2I128 $0x13, Y5, Y9, Y12 - VPERM2I128 $0x13, Y13, Y1, Y4 - JMP sealAVX2SealHash - -sealAVX2Tail384: - VMOVDQA ·chacha20Constants<>+0(SB), Y0 - VMOVDQA Y0, Y5 - VMOVDQA Y0, Y6 - VMOVDQA 32(BP), Y14 - VMOVDQA Y14, Y9 - VMOVDQA Y14, Y10 - VMOVDQA 64(BP), Y12 - VMOVDQA Y12, Y13 - VMOVDQA Y12, Y8 - VMOVDQA 192(BP), Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y1 - VPADDD ·avx2IncMask<>+0(SB), Y1, Y2 - VMOVDQA Y4, Y7 - VMOVDQA Y1, Y11 - VMOVDQA Y2, Y15 - -sealAVX2Tail384LoopA: - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(DI), DI - -sealAVX2Tail384LoopB: - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x0c, Y10, Y3 - VPSRLD $0x14, Y10, Y10 - VPXOR Y3, Y10, Y10 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x07, Y10, Y3 - VPSRLD $0x19, Y10, Y10 - VPXOR Y3, Y10, Y10 - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x04, Y9, Y9, Y9 - VPALIGNR $0x04, Y10, Y10, Y10 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPALIGNR $0x0c, Y1, Y1, Y1 - VPALIGNR $0x0c, Y2, Y2, Y2 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x0c, Y14, Y3 - VPSRLD $0x14, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y14, Y0, Y0 - VPXOR Y0, Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPADDD Y4, Y12, Y12 - VPXOR Y12, Y14, Y14 - VPSLLD $0x07, Y14, Y3 - VPSRLD $0x19, Y14, Y14 - VPXOR Y3, Y14, Y14 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x0c, Y9, Y3 - VPSRLD $0x14, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y9, Y5, Y5 - VPXOR Y5, Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPADDD Y1, Y13, Y13 - VPXOR Y13, Y9, Y9 - VPSLLD $0x07, Y9, Y3 - VPSRLD $0x19, Y9, Y9 - VPXOR Y3, Y9, Y9 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x0c, Y10, Y3 - VPSRLD $0x14, Y10, Y10 - VPXOR Y3, Y10, Y10 - VPADDD Y10, Y6, Y6 - VPXOR Y6, Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPADDD Y2, Y8, Y8 - VPXOR Y8, Y10, Y10 - VPSLLD $0x07, Y10, Y3 - VPSRLD $0x19, Y10, Y10 - VPXOR Y3, Y10, Y10 - ADDQ 16(DI), R10 - ADCQ 24(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 32(DI), DI - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x0c, Y9, Y9, Y9 - VPALIGNR $0x0c, Y10, Y10, Y10 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x04, Y4, Y4, Y4 - VPALIGNR $0x04, Y1, Y1, Y1 - VPALIGNR $0x04, Y2, Y2, Y2 - DECQ CX - JG sealAVX2Tail384LoopA - DECQ R9 - JGE sealAVX2Tail384LoopB - VPADDD ·chacha20Constants<>+0(SB), Y0, Y0 - VPADDD ·chacha20Constants<>+0(SB), Y5, Y5 - VPADDD ·chacha20Constants<>+0(SB), Y6, Y6 - VPADDD 32(BP), Y14, Y14 - VPADDD 32(BP), Y9, Y9 - VPADDD 32(BP), Y10, Y10 - VPADDD 64(BP), Y12, Y12 - VPADDD 64(BP), Y13, Y13 - VPADDD 64(BP), Y8, Y8 - VPADDD Y7, Y4, Y4 - VPADDD Y11, Y1, Y1 - VPADDD Y15, Y2, Y2 - VPERM2I128 $0x02, Y0, Y14, Y3 - VPERM2I128 $0x02, Y12, Y4, Y7 - VPERM2I128 $0x13, Y0, Y14, Y11 - VPERM2I128 $0x13, Y12, Y4, Y15 - VPXOR (SI), Y3, Y3 - VPXOR 32(SI), Y7, Y7 - VPXOR 64(SI), Y11, Y11 - VPXOR 96(SI), Y15, Y15 - VMOVDQU Y3, (DI) - VMOVDQU Y7, 32(DI) - VMOVDQU Y11, 64(DI) - VMOVDQU Y15, 96(DI) - VPERM2I128 $0x02, Y5, Y9, Y3 - VPERM2I128 $0x02, Y13, Y1, Y7 - VPERM2I128 $0x13, Y5, Y9, Y11 - VPERM2I128 $0x13, Y13, Y1, Y15 - VPXOR 128(SI), Y3, Y3 - VPXOR 160(SI), Y7, Y7 - VPXOR 192(SI), Y11, Y11 - VPXOR 224(SI), Y15, Y15 - VMOVDQU Y3, 128(DI) - VMOVDQU Y7, 160(DI) - VMOVDQU Y11, 192(DI) - VMOVDQU Y15, 224(DI) - MOVQ $0x00000100, CX - LEAQ 256(SI), SI - SUBQ $0x00000100, BX - VPERM2I128 $0x02, Y6, Y10, Y0 - VPERM2I128 $0x02, Y8, Y2, Y14 - VPERM2I128 $0x13, Y6, Y10, Y12 - VPERM2I128 $0x13, Y8, Y2, Y4 - JMP sealAVX2SealHash - -sealAVX2Tail512: - VMOVDQA ·chacha20Constants<>+0(SB), Y0 - VMOVDQA Y0, Y5 - VMOVDQA Y0, Y6 - VMOVDQA Y0, Y7 - VMOVDQA 32(BP), Y14 - VMOVDQA Y14, Y9 - VMOVDQA Y14, Y10 - VMOVDQA Y14, Y11 - VMOVDQA 64(BP), Y12 - VMOVDQA Y12, Y13 - VMOVDQA Y12, Y8 - VMOVDQA Y12, Y15 - VMOVDQA 192(BP), Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y4 - VPADDD ·avx2IncMask<>+0(SB), Y4, Y1 - VPADDD ·avx2IncMask<>+0(SB), Y1, Y2 - VPADDD ·avx2IncMask<>+0(SB), Y2, Y3 - VMOVDQA Y4, 96(BP) - VMOVDQA Y1, 128(BP) - VMOVDQA Y2, 160(BP) - VMOVDQA Y3, 192(BP) - -sealAVX2Tail512LoopA: - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), AX - MOVQ AX, R15 - MULQ R10 - MOVQ AX, R13 - MOVQ DX, R14 - MOVQ (BP), AX - MULQ R11 - IMULQ R12, R15 - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), AX - MOVQ AX, R8 - MULQ R10 - ADDQ AX, R14 - ADCQ $0x00, DX - MOVQ DX, R10 - MOVQ 8(BP), AX - MULQ R11 - ADDQ AX, R15 - ADCQ $0x00, DX - IMULQ R12, R8 - ADDQ R10, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 16(DI), DI - -sealAVX2Tail512LoopB: - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y3, Y3 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - VMOVDQA Y15, 224(BP) - VPSLLD $0x0c, Y14, Y15 - VPSRLD $0x14, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x0c, Y9, Y15 - VPSRLD $0x14, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x0c, Y10, Y15 - VPSRLD $0x14, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x0c, Y11, Y15 - VPSRLD $0x14, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - ADDQ (DI), R10 - ADCQ 8(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y3, Y3 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - VMOVDQA Y15, 224(BP) - VPSLLD $0x07, Y14, Y15 - VPSRLD $0x19, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x07, Y9, Y15 - VPSRLD $0x19, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x07, Y10, Y15 - VPSRLD $0x19, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x07, Y11, Y15 - VPSRLD $0x19, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - VPALIGNR $0x04, Y14, Y14, Y14 - VPALIGNR $0x04, Y9, Y9, Y9 - VPALIGNR $0x04, Y10, Y10, Y10 - VPALIGNR $0x04, Y11, Y11, Y11 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x08, Y15, Y15, Y15 - VPALIGNR $0x0c, Y4, Y4, Y4 - VPALIGNR $0x0c, Y1, Y1, Y1 - VPALIGNR $0x0c, Y2, Y2, Y2 - VPALIGNR $0x0c, Y3, Y3, Y3 - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol16<>+0(SB), Y4, Y4 - VPSHUFB ·rol16<>+0(SB), Y1, Y1 - VPSHUFB ·rol16<>+0(SB), Y2, Y2 - VPSHUFB ·rol16<>+0(SB), Y3, Y3 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - ADDQ 16(DI), R10 - ADCQ 24(DI), R11 - ADCQ $0x01, R12 - MOVQ (BP), DX - MOVQ DX, R15 - MULXQ R10, R13, R14 - IMULQ R12, R15 - MULXQ R11, AX, DX - ADDQ AX, R14 - ADCQ DX, R15 - MOVQ 8(BP), DX - MULXQ R10, R10, AX - ADDQ R10, R14 - MULXQ R11, R11, R8 - ADCQ R11, R15 - ADCQ $0x00, R8 - IMULQ R12, DX - ADDQ AX, R15 - ADCQ DX, R8 - MOVQ R13, R10 - MOVQ R14, R11 - MOVQ R15, R12 - ANDQ $0x03, R12 - MOVQ R15, R13 - ANDQ $-4, R13 - MOVQ R8, R14 - SHRQ $0x02, R8, R15 - SHRQ $0x02, R8 - ADDQ R13, R10 - ADCQ R14, R11 - ADCQ $0x00, R12 - ADDQ R15, R10 - ADCQ R8, R11 - ADCQ $0x00, R12 - LEAQ 32(DI), DI - VMOVDQA Y15, 224(BP) - VPSLLD $0x0c, Y14, Y15 - VPSRLD $0x14, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x0c, Y9, Y15 - VPSRLD $0x14, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x0c, Y10, Y15 - VPSRLD $0x14, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x0c, Y11, Y15 - VPSRLD $0x14, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - VPADDD Y14, Y0, Y0 - VPADDD Y9, Y5, Y5 - VPADDD Y10, Y6, Y6 - VPADDD Y11, Y7, Y7 - VPXOR Y0, Y4, Y4 - VPXOR Y5, Y1, Y1 - VPXOR Y6, Y2, Y2 - VPXOR Y7, Y3, Y3 - VPSHUFB ·rol8<>+0(SB), Y4, Y4 - VPSHUFB ·rol8<>+0(SB), Y1, Y1 - VPSHUFB ·rol8<>+0(SB), Y2, Y2 - VPSHUFB ·rol8<>+0(SB), Y3, Y3 - VPADDD Y4, Y12, Y12 - VPADDD Y1, Y13, Y13 - VPADDD Y2, Y8, Y8 - VPADDD Y3, Y15, Y15 - VPXOR Y12, Y14, Y14 - VPXOR Y13, Y9, Y9 - VPXOR Y8, Y10, Y10 - VPXOR Y15, Y11, Y11 - VMOVDQA Y15, 224(BP) - VPSLLD $0x07, Y14, Y15 - VPSRLD $0x19, Y14, Y14 - VPXOR Y15, Y14, Y14 - VPSLLD $0x07, Y9, Y15 - VPSRLD $0x19, Y9, Y9 - VPXOR Y15, Y9, Y9 - VPSLLD $0x07, Y10, Y15 - VPSRLD $0x19, Y10, Y10 - VPXOR Y15, Y10, Y10 - VPSLLD $0x07, Y11, Y15 - VPSRLD $0x19, Y11, Y11 - VPXOR Y15, Y11, Y11 - VMOVDQA 224(BP), Y15 - VPALIGNR $0x0c, Y14, Y14, Y14 - VPALIGNR $0x0c, Y9, Y9, Y9 - VPALIGNR $0x0c, Y10, Y10, Y10 - VPALIGNR $0x0c, Y11, Y11, Y11 - VPALIGNR $0x08, Y12, Y12, Y12 - VPALIGNR $0x08, Y13, Y13, Y13 - VPALIGNR $0x08, Y8, Y8, Y8 - VPALIGNR $0x08, Y15, Y15, Y15 - VPALIGNR $0x04, Y4, Y4, Y4 - VPALIGNR $0x04, Y1, Y1, Y1 - VPALIGNR $0x04, Y2, Y2, Y2 - VPALIGNR $0x04, Y3, Y3, Y3 - DECQ CX - JG sealAVX2Tail512LoopA - DECQ R9 - JGE sealAVX2Tail512LoopB - VPADDD ·chacha20Constants<>+0(SB), Y0, Y0 - VPADDD ·chacha20Constants<>+0(SB), Y5, Y5 - VPADDD ·chacha20Constants<>+0(SB), Y6, Y6 - VPADDD ·chacha20Constants<>+0(SB), Y7, Y7 - VPADDD 32(BP), Y14, Y14 - VPADDD 32(BP), Y9, Y9 - VPADDD 32(BP), Y10, Y10 - VPADDD 32(BP), Y11, Y11 - VPADDD 64(BP), Y12, Y12 - VPADDD 64(BP), Y13, Y13 - VPADDD 64(BP), Y8, Y8 - VPADDD 64(BP), Y15, Y15 - VPADDD 96(BP), Y4, Y4 - VPADDD 128(BP), Y1, Y1 - VPADDD 160(BP), Y2, Y2 - VPADDD 192(BP), Y3, Y3 - VMOVDQA Y15, 224(BP) - VPERM2I128 $0x02, Y0, Y14, Y15 - VPXOR (SI), Y15, Y15 - VMOVDQU Y15, (DI) - VPERM2I128 $0x02, Y12, Y4, Y15 - VPXOR 32(SI), Y15, Y15 - VMOVDQU Y15, 32(DI) - VPERM2I128 $0x13, Y0, Y14, Y15 - VPXOR 64(SI), Y15, Y15 - VMOVDQU Y15, 64(DI) - VPERM2I128 $0x13, Y12, Y4, Y15 - VPXOR 96(SI), Y15, Y15 - VMOVDQU Y15, 96(DI) - VPERM2I128 $0x02, Y5, Y9, Y0 - VPERM2I128 $0x02, Y13, Y1, Y14 - VPERM2I128 $0x13, Y5, Y9, Y12 - VPERM2I128 $0x13, Y13, Y1, Y4 - VPXOR 128(SI), Y0, Y0 - VPXOR 160(SI), Y14, Y14 - VPXOR 192(SI), Y12, Y12 - VPXOR 224(SI), Y4, Y4 - VMOVDQU Y0, 128(DI) - VMOVDQU Y14, 160(DI) - VMOVDQU Y12, 192(DI) - VMOVDQU Y4, 224(DI) - VPERM2I128 $0x02, Y6, Y10, Y0 - VPERM2I128 $0x02, Y8, Y2, Y14 - VPERM2I128 $0x13, Y6, Y10, Y12 - VPERM2I128 $0x13, Y8, Y2, Y4 - VPXOR 256(SI), Y0, Y0 - VPXOR 288(SI), Y14, Y14 - VPXOR 320(SI), Y12, Y12 - VPXOR 352(SI), Y4, Y4 - VMOVDQU Y0, 256(DI) - VMOVDQU Y14, 288(DI) - VMOVDQU Y12, 320(DI) - VMOVDQU Y4, 352(DI) - MOVQ $0x00000180, CX - LEAQ 384(SI), SI - SUBQ $0x00000180, BX - VPERM2I128 $0x02, Y7, Y11, Y0 - VPERM2I128 $0x02, 224(BP), Y3, Y14 - VPERM2I128 $0x13, Y7, Y11, Y12 - VPERM2I128 $0x13, 224(BP), Y3, Y4 - JMP sealAVX2SealHash diff --git a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_generic.go b/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_generic.go deleted file mode 100644 index 6313898f..00000000 --- a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_generic.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package chacha20poly1305 - -import ( - "encoding/binary" - - "golang.org/x/crypto/chacha20" - "golang.org/x/crypto/internal/alias" - "golang.org/x/crypto/internal/poly1305" -) - -func writeWithPadding(p *poly1305.MAC, b []byte) { - p.Write(b) - if rem := len(b) % 16; rem != 0 { - var buf [16]byte - padLen := 16 - rem - p.Write(buf[:padLen]) - } -} - -func writeUint64(p *poly1305.MAC, n int) { - var buf [8]byte - binary.LittleEndian.PutUint64(buf[:], uint64(n)) - p.Write(buf[:]) -} - -func (c *chacha20poly1305) sealGeneric(dst, nonce, plaintext, additionalData []byte) []byte { - ret, out := sliceForAppend(dst, len(plaintext)+poly1305.TagSize) - ciphertext, tag := out[:len(plaintext)], out[len(plaintext):] - if alias.InexactOverlap(out, plaintext) { - panic("chacha20poly1305: invalid buffer overlap") - } - - var polyKey [32]byte - s, _ := chacha20.NewUnauthenticatedCipher(c.key[:], nonce) - s.XORKeyStream(polyKey[:], polyKey[:]) - s.SetCounter(1) // set the counter to 1, skipping 32 bytes - s.XORKeyStream(ciphertext, plaintext) - - p := poly1305.New(&polyKey) - writeWithPadding(p, additionalData) - writeWithPadding(p, ciphertext) - writeUint64(p, len(additionalData)) - writeUint64(p, len(plaintext)) - p.Sum(tag[:0]) - - return ret -} - -func (c *chacha20poly1305) openGeneric(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { - tag := ciphertext[len(ciphertext)-16:] - ciphertext = ciphertext[:len(ciphertext)-16] - - var polyKey [32]byte - s, _ := chacha20.NewUnauthenticatedCipher(c.key[:], nonce) - s.XORKeyStream(polyKey[:], polyKey[:]) - s.SetCounter(1) // set the counter to 1, skipping 32 bytes - - p := poly1305.New(&polyKey) - writeWithPadding(p, additionalData) - writeWithPadding(p, ciphertext) - writeUint64(p, len(additionalData)) - writeUint64(p, len(ciphertext)) - - ret, out := sliceForAppend(dst, len(ciphertext)) - if alias.InexactOverlap(out, ciphertext) { - panic("chacha20poly1305: invalid buffer overlap") - } - if !p.Verify(tag) { - for i := range out { - out[i] = 0 - } - return nil, errOpen - } - - s.XORKeyStream(out, ciphertext) - return ret, nil -} diff --git a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_noasm.go b/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_noasm.go deleted file mode 100644 index 34e6ab1d..00000000 --- a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/chacha20poly1305_noasm.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !amd64 || !gc || purego - -package chacha20poly1305 - -func (c *chacha20poly1305) seal(dst, nonce, plaintext, additionalData []byte) []byte { - return c.sealGeneric(dst, nonce, plaintext, additionalData) -} - -func (c *chacha20poly1305) open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { - return c.openGeneric(dst, nonce, ciphertext, additionalData) -} diff --git a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/xchacha20poly1305.go b/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/xchacha20poly1305.go deleted file mode 100644 index 1cebfe94..00000000 --- a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/chacha20poly1305/xchacha20poly1305.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package chacha20poly1305 - -import ( - "crypto/cipher" - "errors" - - "golang.org/x/crypto/chacha20" -) - -type xchacha20poly1305 struct { - key [KeySize]byte -} - -// NewX returns a XChaCha20-Poly1305 AEAD that uses the given 256-bit key. -// -// XChaCha20-Poly1305 is a ChaCha20-Poly1305 variant that takes a longer nonce, -// suitable to be generated randomly without risk of collisions. It should be -// preferred when nonce uniqueness cannot be trivially ensured, or whenever -// nonces are randomly generated. -func NewX(key []byte) (cipher.AEAD, error) { - if len(key) != KeySize { - return nil, errors.New("chacha20poly1305: bad key length") - } - ret := new(xchacha20poly1305) - copy(ret.key[:], key) - return ret, nil -} - -func (*xchacha20poly1305) NonceSize() int { - return NonceSizeX -} - -func (*xchacha20poly1305) Overhead() int { - return Overhead -} - -func (x *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte { - if len(nonce) != NonceSizeX { - panic("chacha20poly1305: bad nonce length passed to Seal") - } - - // XChaCha20-Poly1305 technically supports a 64-bit counter, so there is no - // size limit. However, since we reuse the ChaCha20-Poly1305 implementation, - // the second half of the counter is not available. This is unlikely to be - // an issue because the cipher.AEAD API requires the entire message to be in - // memory, and the counter overflows at 256 GB. - if uint64(len(plaintext)) > (1<<38)-64 { - panic("chacha20poly1305: plaintext too large") - } - - c := new(chacha20poly1305) - hKey, _ := chacha20.HChaCha20(x.key[:], nonce[0:16]) - copy(c.key[:], hKey) - - // The first 4 bytes of the final nonce are unused counter space. - cNonce := make([]byte, NonceSize) - copy(cNonce[4:12], nonce[16:24]) - - return c.seal(dst, cNonce[:], plaintext, additionalData) -} - -func (x *xchacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { - if len(nonce) != NonceSizeX { - panic("chacha20poly1305: bad nonce length passed to Open") - } - if len(ciphertext) < 16 { - return nil, errOpen - } - if uint64(len(ciphertext)) > (1<<38)-48 { - panic("chacha20poly1305: ciphertext too large") - } - - c := new(chacha20poly1305) - hKey, _ := chacha20.HChaCha20(x.key[:], nonce[0:16]) - copy(c.key[:], hKey) - - // The first 4 bytes of the final nonce are unused counter space. - cNonce := make([]byte, NonceSize) - copy(cNonce[4:12], nonce[16:24]) - - return c.open(dst, cNonce[:], ciphertext, additionalData) -} diff --git a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/asn1.go b/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/asn1.go deleted file mode 100644 index 2492f796..00000000 --- a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/asn1.go +++ /dev/null @@ -1,825 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cryptobyte - -import ( - encoding_asn1 "encoding/asn1" - "fmt" - "math/big" - "reflect" - "time" - - "golang.org/x/crypto/cryptobyte/asn1" -) - -// This file contains ASN.1-related methods for String and Builder. - -// Builder - -// AddASN1Int64 appends a DER-encoded ASN.1 INTEGER. -func (b *Builder) AddASN1Int64(v int64) { - b.addASN1Signed(asn1.INTEGER, v) -} - -// AddASN1Int64WithTag appends a DER-encoded ASN.1 INTEGER with the -// given tag. -func (b *Builder) AddASN1Int64WithTag(v int64, tag asn1.Tag) { - b.addASN1Signed(tag, v) -} - -// AddASN1Enum appends a DER-encoded ASN.1 ENUMERATION. -func (b *Builder) AddASN1Enum(v int64) { - b.addASN1Signed(asn1.ENUM, v) -} - -func (b *Builder) addASN1Signed(tag asn1.Tag, v int64) { - b.AddASN1(tag, func(c *Builder) { - length := 1 - for i := v; i >= 0x80 || i < -0x80; i >>= 8 { - length++ - } - - for ; length > 0; length-- { - i := v >> uint((length-1)*8) & 0xff - c.AddUint8(uint8(i)) - } - }) -} - -// AddASN1Uint64 appends a DER-encoded ASN.1 INTEGER. -func (b *Builder) AddASN1Uint64(v uint64) { - b.AddASN1(asn1.INTEGER, func(c *Builder) { - length := 1 - for i := v; i >= 0x80; i >>= 8 { - length++ - } - - for ; length > 0; length-- { - i := v >> uint((length-1)*8) & 0xff - c.AddUint8(uint8(i)) - } - }) -} - -// AddASN1BigInt appends a DER-encoded ASN.1 INTEGER. -func (b *Builder) AddASN1BigInt(n *big.Int) { - if b.err != nil { - return - } - - b.AddASN1(asn1.INTEGER, func(c *Builder) { - if n.Sign() < 0 { - // A negative number has to be converted to two's-complement form. So we - // invert and subtract 1. If the most-significant-bit isn't set then - // we'll need to pad the beginning with 0xff in order to keep the number - // negative. - nMinus1 := new(big.Int).Neg(n) - nMinus1.Sub(nMinus1, bigOne) - bytes := nMinus1.Bytes() - for i := range bytes { - bytes[i] ^= 0xff - } - if len(bytes) == 0 || bytes[0]&0x80 == 0 { - c.add(0xff) - } - c.add(bytes...) - } else if n.Sign() == 0 { - c.add(0) - } else { - bytes := n.Bytes() - if bytes[0]&0x80 != 0 { - c.add(0) - } - c.add(bytes...) - } - }) -} - -// AddASN1OctetString appends a DER-encoded ASN.1 OCTET STRING. -func (b *Builder) AddASN1OctetString(bytes []byte) { - b.AddASN1(asn1.OCTET_STRING, func(c *Builder) { - c.AddBytes(bytes) - }) -} - -const generalizedTimeFormatStr = "20060102150405Z0700" - -// AddASN1GeneralizedTime appends a DER-encoded ASN.1 GENERALIZEDTIME. -func (b *Builder) AddASN1GeneralizedTime(t time.Time) { - if t.Year() < 0 || t.Year() > 9999 { - b.err = fmt.Errorf("cryptobyte: cannot represent %v as a GeneralizedTime", t) - return - } - b.AddASN1(asn1.GeneralizedTime, func(c *Builder) { - c.AddBytes([]byte(t.Format(generalizedTimeFormatStr))) - }) -} - -// AddASN1UTCTime appends a DER-encoded ASN.1 UTCTime. -func (b *Builder) AddASN1UTCTime(t time.Time) { - b.AddASN1(asn1.UTCTime, func(c *Builder) { - // As utilized by the X.509 profile, UTCTime can only - // represent the years 1950 through 2049. - if t.Year() < 1950 || t.Year() >= 2050 { - b.err = fmt.Errorf("cryptobyte: cannot represent %v as a UTCTime", t) - return - } - c.AddBytes([]byte(t.Format(defaultUTCTimeFormatStr))) - }) -} - -// AddASN1BitString appends a DER-encoded ASN.1 BIT STRING. This does not -// support BIT STRINGs that are not a whole number of bytes. -func (b *Builder) AddASN1BitString(data []byte) { - b.AddASN1(asn1.BIT_STRING, func(b *Builder) { - b.AddUint8(0) - b.AddBytes(data) - }) -} - -func (b *Builder) addBase128Int(n int64) { - var length int - if n == 0 { - length = 1 - } else { - for i := n; i > 0; i >>= 7 { - length++ - } - } - - for i := length - 1; i >= 0; i-- { - o := byte(n >> uint(i*7)) - o &= 0x7f - if i != 0 { - o |= 0x80 - } - - b.add(o) - } -} - -func isValidOID(oid encoding_asn1.ObjectIdentifier) bool { - if len(oid) < 2 { - return false - } - - if oid[0] > 2 || (oid[0] <= 1 && oid[1] >= 40) { - return false - } - - for _, v := range oid { - if v < 0 { - return false - } - } - - return true -} - -func (b *Builder) AddASN1ObjectIdentifier(oid encoding_asn1.ObjectIdentifier) { - b.AddASN1(asn1.OBJECT_IDENTIFIER, func(b *Builder) { - if !isValidOID(oid) { - b.err = fmt.Errorf("cryptobyte: invalid OID: %v", oid) - return - } - - b.addBase128Int(int64(oid[0])*40 + int64(oid[1])) - for _, v := range oid[2:] { - b.addBase128Int(int64(v)) - } - }) -} - -func (b *Builder) AddASN1Boolean(v bool) { - b.AddASN1(asn1.BOOLEAN, func(b *Builder) { - if v { - b.AddUint8(0xff) - } else { - b.AddUint8(0) - } - }) -} - -func (b *Builder) AddASN1NULL() { - b.add(uint8(asn1.NULL), 0) -} - -// MarshalASN1 calls encoding_asn1.Marshal on its input and appends the result if -// successful or records an error if one occurred. -func (b *Builder) MarshalASN1(v interface{}) { - // NOTE(martinkr): This is somewhat of a hack to allow propagation of - // encoding_asn1.Marshal errors into Builder.err. N.B. if you call MarshalASN1 with a - // value embedded into a struct, its tag information is lost. - if b.err != nil { - return - } - bytes, err := encoding_asn1.Marshal(v) - if err != nil { - b.err = err - return - } - b.AddBytes(bytes) -} - -// AddASN1 appends an ASN.1 object. The object is prefixed with the given tag. -// Tags greater than 30 are not supported and result in an error (i.e. -// low-tag-number form only). The child builder passed to the -// BuilderContinuation can be used to build the content of the ASN.1 object. -func (b *Builder) AddASN1(tag asn1.Tag, f BuilderContinuation) { - if b.err != nil { - return - } - // Identifiers with the low five bits set indicate high-tag-number format - // (two or more octets), which we don't support. - if tag&0x1f == 0x1f { - b.err = fmt.Errorf("cryptobyte: high-tag number identifier octects not supported: 0x%x", tag) - return - } - b.AddUint8(uint8(tag)) - b.addLengthPrefixed(1, true, f) -} - -// String - -// ReadASN1Boolean decodes an ASN.1 BOOLEAN and converts it to a boolean -// representation into out and advances. It reports whether the read -// was successful. -func (s *String) ReadASN1Boolean(out *bool) bool { - var bytes String - if !s.ReadASN1(&bytes, asn1.BOOLEAN) || len(bytes) != 1 { - return false - } - - switch bytes[0] { - case 0: - *out = false - case 0xff: - *out = true - default: - return false - } - - return true -} - -// ReadASN1Integer decodes an ASN.1 INTEGER into out and advances. If out does -// not point to an integer, to a big.Int, or to a []byte it panics. Only -// positive and zero values can be decoded into []byte, and they are returned as -// big-endian binary values that share memory with s. Positive values will have -// no leading zeroes, and zero will be returned as a single zero byte. -// ReadASN1Integer reports whether the read was successful. -func (s *String) ReadASN1Integer(out interface{}) bool { - switch out := out.(type) { - case *int, *int8, *int16, *int32, *int64: - var i int64 - if !s.readASN1Int64(&i) || reflect.ValueOf(out).Elem().OverflowInt(i) { - return false - } - reflect.ValueOf(out).Elem().SetInt(i) - return true - case *uint, *uint8, *uint16, *uint32, *uint64: - var u uint64 - if !s.readASN1Uint64(&u) || reflect.ValueOf(out).Elem().OverflowUint(u) { - return false - } - reflect.ValueOf(out).Elem().SetUint(u) - return true - case *big.Int: - return s.readASN1BigInt(out) - case *[]byte: - return s.readASN1Bytes(out) - default: - panic("out does not point to an integer type") - } -} - -func checkASN1Integer(bytes []byte) bool { - if len(bytes) == 0 { - // An INTEGER is encoded with at least one octet. - return false - } - if len(bytes) == 1 { - return true - } - if bytes[0] == 0 && bytes[1]&0x80 == 0 || bytes[0] == 0xff && bytes[1]&0x80 == 0x80 { - // Value is not minimally encoded. - return false - } - return true -} - -var bigOne = big.NewInt(1) - -func (s *String) readASN1BigInt(out *big.Int) bool { - var bytes String - if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) { - return false - } - if bytes[0]&0x80 == 0x80 { - // Negative number. - neg := make([]byte, len(bytes)) - for i, b := range bytes { - neg[i] = ^b - } - out.SetBytes(neg) - out.Add(out, bigOne) - out.Neg(out) - } else { - out.SetBytes(bytes) - } - return true -} - -func (s *String) readASN1Bytes(out *[]byte) bool { - var bytes String - if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) { - return false - } - if bytes[0]&0x80 == 0x80 { - return false - } - for len(bytes) > 1 && bytes[0] == 0 { - bytes = bytes[1:] - } - *out = bytes - return true -} - -func (s *String) readASN1Int64(out *int64) bool { - var bytes String - if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) || !asn1Signed(out, bytes) { - return false - } - return true -} - -func asn1Signed(out *int64, n []byte) bool { - length := len(n) - if length > 8 { - return false - } - for i := 0; i < length; i++ { - *out <<= 8 - *out |= int64(n[i]) - } - // Shift up and down in order to sign extend the result. - *out <<= 64 - uint8(length)*8 - *out >>= 64 - uint8(length)*8 - return true -} - -func (s *String) readASN1Uint64(out *uint64) bool { - var bytes String - if !s.ReadASN1(&bytes, asn1.INTEGER) || !checkASN1Integer(bytes) || !asn1Unsigned(out, bytes) { - return false - } - return true -} - -func asn1Unsigned(out *uint64, n []byte) bool { - length := len(n) - if length > 9 || length == 9 && n[0] != 0 { - // Too large for uint64. - return false - } - if n[0]&0x80 != 0 { - // Negative number. - return false - } - for i := 0; i < length; i++ { - *out <<= 8 - *out |= uint64(n[i]) - } - return true -} - -// ReadASN1Int64WithTag decodes an ASN.1 INTEGER with the given tag into out -// and advances. It reports whether the read was successful and resulted in a -// value that can be represented in an int64. -func (s *String) ReadASN1Int64WithTag(out *int64, tag asn1.Tag) bool { - var bytes String - return s.ReadASN1(&bytes, tag) && checkASN1Integer(bytes) && asn1Signed(out, bytes) -} - -// ReadASN1Enum decodes an ASN.1 ENUMERATION into out and advances. It reports -// whether the read was successful. -func (s *String) ReadASN1Enum(out *int) bool { - var bytes String - var i int64 - if !s.ReadASN1(&bytes, asn1.ENUM) || !checkASN1Integer(bytes) || !asn1Signed(&i, bytes) { - return false - } - if int64(int(i)) != i { - return false - } - *out = int(i) - return true -} - -func (s *String) readBase128Int(out *int) bool { - ret := 0 - for i := 0; len(*s) > 0; i++ { - if i == 5 { - return false - } - // Avoid overflowing int on a 32-bit platform. - // We don't want different behavior based on the architecture. - if ret >= 1<<(31-7) { - return false - } - ret <<= 7 - b := s.read(1)[0] - - // ITU-T X.690, section 8.19.2: - // The subidentifier shall be encoded in the fewest possible octets, - // that is, the leading octet of the subidentifier shall not have the value 0x80. - if i == 0 && b == 0x80 { - return false - } - - ret |= int(b & 0x7f) - if b&0x80 == 0 { - *out = ret - return true - } - } - return false // truncated -} - -// ReadASN1ObjectIdentifier decodes an ASN.1 OBJECT IDENTIFIER into out and -// advances. It reports whether the read was successful. -func (s *String) ReadASN1ObjectIdentifier(out *encoding_asn1.ObjectIdentifier) bool { - var bytes String - if !s.ReadASN1(&bytes, asn1.OBJECT_IDENTIFIER) || len(bytes) == 0 { - return false - } - - // In the worst case, we get two elements from the first byte (which is - // encoded differently) and then every varint is a single byte long. - components := make([]int, len(bytes)+1) - - // The first varint is 40*value1 + value2: - // According to this packing, value1 can take the values 0, 1 and 2 only. - // When value1 = 0 or value1 = 1, then value2 is <= 39. When value1 = 2, - // then there are no restrictions on value2. - var v int - if !bytes.readBase128Int(&v) { - return false - } - if v < 80 { - components[0] = v / 40 - components[1] = v % 40 - } else { - components[0] = 2 - components[1] = v - 80 - } - - i := 2 - for ; len(bytes) > 0; i++ { - if !bytes.readBase128Int(&v) { - return false - } - components[i] = v - } - *out = components[:i] - return true -} - -// ReadASN1GeneralizedTime decodes an ASN.1 GENERALIZEDTIME into out and -// advances. It reports whether the read was successful. -func (s *String) ReadASN1GeneralizedTime(out *time.Time) bool { - var bytes String - if !s.ReadASN1(&bytes, asn1.GeneralizedTime) { - return false - } - t := string(bytes) - res, err := time.Parse(generalizedTimeFormatStr, t) - if err != nil { - return false - } - if serialized := res.Format(generalizedTimeFormatStr); serialized != t { - return false - } - *out = res - return true -} - -const defaultUTCTimeFormatStr = "060102150405Z0700" - -// ReadASN1UTCTime decodes an ASN.1 UTCTime into out and advances. -// It reports whether the read was successful. -func (s *String) ReadASN1UTCTime(out *time.Time) bool { - var bytes String - if !s.ReadASN1(&bytes, asn1.UTCTime) { - return false - } - t := string(bytes) - - formatStr := defaultUTCTimeFormatStr - var err error - res, err := time.Parse(formatStr, t) - if err != nil { - // Fallback to minute precision if we can't parse second - // precision. If we are following X.509 or X.690 we shouldn't - // support this, but we do. - formatStr = "0601021504Z0700" - res, err = time.Parse(formatStr, t) - } - if err != nil { - return false - } - - if serialized := res.Format(formatStr); serialized != t { - return false - } - - if res.Year() >= 2050 { - // UTCTime interprets the low order digits 50-99 as 1950-99. - // This only applies to its use in the X.509 profile. - // See https://tools.ietf.org/html/rfc5280#section-4.1.2.5.1 - res = res.AddDate(-100, 0, 0) - } - *out = res - return true -} - -// ReadASN1BitString decodes an ASN.1 BIT STRING into out and advances. -// It reports whether the read was successful. -func (s *String) ReadASN1BitString(out *encoding_asn1.BitString) bool { - var bytes String - if !s.ReadASN1(&bytes, asn1.BIT_STRING) || len(bytes) == 0 || - len(bytes)*8/8 != len(bytes) { - return false - } - - paddingBits := bytes[0] - bytes = bytes[1:] - if paddingBits > 7 || - len(bytes) == 0 && paddingBits != 0 || - len(bytes) > 0 && bytes[len(bytes)-1]&(1< 4 || len(*s) < int(2+lenLen) { - return false - } - - lenBytes := String((*s)[2 : 2+lenLen]) - if !lenBytes.readUnsigned(&len32, int(lenLen)) { - return false - } - - // ITU-T X.690 section 10.1 (DER length forms) requires encoding the length - // with the minimum number of octets. - if len32 < 128 { - // Length should have used short-form encoding. - return false - } - if len32>>((lenLen-1)*8) == 0 { - // Leading octet is 0. Length should have been at least one byte shorter. - return false - } - - headerLen = 2 + uint32(lenLen) - if headerLen+len32 < len32 { - // Overflow. - return false - } - length = headerLen + len32 - } - - if int(length) < 0 || !s.ReadBytes((*[]byte)(out), int(length)) { - return false - } - if skipHeader && !out.Skip(int(headerLen)) { - panic("cryptobyte: internal error") - } - - return true -} diff --git a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/asn1/asn1.go b/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/asn1/asn1.go deleted file mode 100644 index 90ef6a24..00000000 --- a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/asn1/asn1.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package asn1 contains supporting types for parsing and building ASN.1 -// messages with the cryptobyte package. -package asn1 - -// Tag represents an ASN.1 identifier octet, consisting of a tag number -// (indicating a type) and class (such as context-specific or constructed). -// -// Methods in the cryptobyte package only support the low-tag-number form, i.e. -// a single identifier octet with bits 7-8 encoding the class and bits 1-6 -// encoding the tag number. -type Tag uint8 - -const ( - classConstructed = 0x20 - classContextSpecific = 0x80 -) - -// Constructed returns t with the constructed class bit set. -func (t Tag) Constructed() Tag { return t | classConstructed } - -// ContextSpecific returns t with the context-specific class bit set. -func (t Tag) ContextSpecific() Tag { return t | classContextSpecific } - -// The following is a list of standard tag and class combinations. -const ( - BOOLEAN = Tag(1) - INTEGER = Tag(2) - BIT_STRING = Tag(3) - OCTET_STRING = Tag(4) - NULL = Tag(5) - OBJECT_IDENTIFIER = Tag(6) - ENUM = Tag(10) - UTF8String = Tag(12) - SEQUENCE = Tag(16 | classConstructed) - SET = Tag(17 | classConstructed) - PrintableString = Tag(19) - T61String = Tag(20) - IA5String = Tag(22) - UTCTime = Tag(23) - GeneralizedTime = Tag(24) - GeneralString = Tag(27) -) diff --git a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/builder.go b/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/builder.go deleted file mode 100644 index cf254f5f..00000000 --- a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/builder.go +++ /dev/null @@ -1,350 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cryptobyte - -import ( - "errors" - "fmt" -) - -// A Builder builds byte strings from fixed-length and length-prefixed values. -// Builders either allocate space as needed, or are ‘fixed’, which means that -// they write into a given buffer and produce an error if it's exhausted. -// -// The zero value is a usable Builder that allocates space as needed. -// -// Simple values are marshaled and appended to a Builder using methods on the -// Builder. Length-prefixed values are marshaled by providing a -// BuilderContinuation, which is a function that writes the inner contents of -// the value to a given Builder. See the documentation for BuilderContinuation -// for details. -type Builder struct { - err error - result []byte - fixedSize bool - child *Builder - offset int - pendingLenLen int - pendingIsASN1 bool - inContinuation *bool -} - -// NewBuilder creates a Builder that appends its output to the given buffer. -// Like append(), the slice will be reallocated if its capacity is exceeded. -// Use Bytes to get the final buffer. -func NewBuilder(buffer []byte) *Builder { - return &Builder{ - result: buffer, - } -} - -// NewFixedBuilder creates a Builder that appends its output into the given -// buffer. This builder does not reallocate the output buffer. Writes that -// would exceed the buffer's capacity are treated as an error. -func NewFixedBuilder(buffer []byte) *Builder { - return &Builder{ - result: buffer, - fixedSize: true, - } -} - -// SetError sets the value to be returned as the error from Bytes. Writes -// performed after calling SetError are ignored. -func (b *Builder) SetError(err error) { - b.err = err -} - -// Bytes returns the bytes written by the builder or an error if one has -// occurred during building. -func (b *Builder) Bytes() ([]byte, error) { - if b.err != nil { - return nil, b.err - } - return b.result[b.offset:], nil -} - -// BytesOrPanic returns the bytes written by the builder or panics if an error -// has occurred during building. -func (b *Builder) BytesOrPanic() []byte { - if b.err != nil { - panic(b.err) - } - return b.result[b.offset:] -} - -// AddUint8 appends an 8-bit value to the byte string. -func (b *Builder) AddUint8(v uint8) { - b.add(byte(v)) -} - -// AddUint16 appends a big-endian, 16-bit value to the byte string. -func (b *Builder) AddUint16(v uint16) { - b.add(byte(v>>8), byte(v)) -} - -// AddUint24 appends a big-endian, 24-bit value to the byte string. The highest -// byte of the 32-bit input value is silently truncated. -func (b *Builder) AddUint24(v uint32) { - b.add(byte(v>>16), byte(v>>8), byte(v)) -} - -// AddUint32 appends a big-endian, 32-bit value to the byte string. -func (b *Builder) AddUint32(v uint32) { - b.add(byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) -} - -// AddUint48 appends a big-endian, 48-bit value to the byte string. -func (b *Builder) AddUint48(v uint64) { - b.add(byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) -} - -// AddUint64 appends a big-endian, 64-bit value to the byte string. -func (b *Builder) AddUint64(v uint64) { - b.add(byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32), byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) -} - -// AddBytes appends a sequence of bytes to the byte string. -func (b *Builder) AddBytes(v []byte) { - b.add(v...) -} - -// BuilderContinuation is a continuation-passing interface for building -// length-prefixed byte sequences. Builder methods for length-prefixed -// sequences (AddUint8LengthPrefixed etc) will invoke the BuilderContinuation -// supplied to them. The child builder passed to the continuation can be used -// to build the content of the length-prefixed sequence. For example: -// -// parent := cryptobyte.NewBuilder() -// parent.AddUint8LengthPrefixed(func (child *Builder) { -// child.AddUint8(42) -// child.AddUint8LengthPrefixed(func (grandchild *Builder) { -// grandchild.AddUint8(5) -// }) -// }) -// -// It is an error to write more bytes to the child than allowed by the reserved -// length prefix. After the continuation returns, the child must be considered -// invalid, i.e. users must not store any copies or references of the child -// that outlive the continuation. -// -// If the continuation panics with a value of type BuildError then the inner -// error will be returned as the error from Bytes. If the child panics -// otherwise then Bytes will repanic with the same value. -type BuilderContinuation func(child *Builder) - -// BuildError wraps an error. If a BuilderContinuation panics with this value, -// the panic will be recovered and the inner error will be returned from -// Builder.Bytes. -type BuildError struct { - Err error -} - -// AddUint8LengthPrefixed adds a 8-bit length-prefixed byte sequence. -func (b *Builder) AddUint8LengthPrefixed(f BuilderContinuation) { - b.addLengthPrefixed(1, false, f) -} - -// AddUint16LengthPrefixed adds a big-endian, 16-bit length-prefixed byte sequence. -func (b *Builder) AddUint16LengthPrefixed(f BuilderContinuation) { - b.addLengthPrefixed(2, false, f) -} - -// AddUint24LengthPrefixed adds a big-endian, 24-bit length-prefixed byte sequence. -func (b *Builder) AddUint24LengthPrefixed(f BuilderContinuation) { - b.addLengthPrefixed(3, false, f) -} - -// AddUint32LengthPrefixed adds a big-endian, 32-bit length-prefixed byte sequence. -func (b *Builder) AddUint32LengthPrefixed(f BuilderContinuation) { - b.addLengthPrefixed(4, false, f) -} - -func (b *Builder) callContinuation(f BuilderContinuation, arg *Builder) { - if !*b.inContinuation { - *b.inContinuation = true - - defer func() { - *b.inContinuation = false - - r := recover() - if r == nil { - return - } - - if buildError, ok := r.(BuildError); ok { - b.err = buildError.Err - } else { - panic(r) - } - }() - } - - f(arg) -} - -func (b *Builder) addLengthPrefixed(lenLen int, isASN1 bool, f BuilderContinuation) { - // Subsequent writes can be ignored if the builder has encountered an error. - if b.err != nil { - return - } - - offset := len(b.result) - b.add(make([]byte, lenLen)...) - - if b.inContinuation == nil { - b.inContinuation = new(bool) - } - - b.child = &Builder{ - result: b.result, - fixedSize: b.fixedSize, - offset: offset, - pendingLenLen: lenLen, - pendingIsASN1: isASN1, - inContinuation: b.inContinuation, - } - - b.callContinuation(f, b.child) - b.flushChild() - if b.child != nil { - panic("cryptobyte: internal error") - } -} - -func (b *Builder) flushChild() { - if b.child == nil { - return - } - b.child.flushChild() - child := b.child - b.child = nil - - if child.err != nil { - b.err = child.err - return - } - - length := len(child.result) - child.pendingLenLen - child.offset - - if length < 0 { - panic("cryptobyte: internal error") // result unexpectedly shrunk - } - - if child.pendingIsASN1 { - // For ASN.1, we reserved a single byte for the length. If that turned out - // to be incorrect, we have to move the contents along in order to make - // space. - if child.pendingLenLen != 1 { - panic("cryptobyte: internal error") - } - var lenLen, lenByte uint8 - if int64(length) > 0xfffffffe { - b.err = errors.New("pending ASN.1 child too long") - return - } else if length > 0xffffff { - lenLen = 5 - lenByte = 0x80 | 4 - } else if length > 0xffff { - lenLen = 4 - lenByte = 0x80 | 3 - } else if length > 0xff { - lenLen = 3 - lenByte = 0x80 | 2 - } else if length > 0x7f { - lenLen = 2 - lenByte = 0x80 | 1 - } else { - lenLen = 1 - lenByte = uint8(length) - length = 0 - } - - // Insert the initial length byte, make space for successive length bytes, - // and adjust the offset. - child.result[child.offset] = lenByte - extraBytes := int(lenLen - 1) - if extraBytes != 0 { - child.add(make([]byte, extraBytes)...) - childStart := child.offset + child.pendingLenLen - copy(child.result[childStart+extraBytes:], child.result[childStart:]) - } - child.offset++ - child.pendingLenLen = extraBytes - } - - l := length - for i := child.pendingLenLen - 1; i >= 0; i-- { - child.result[child.offset+i] = uint8(l) - l >>= 8 - } - if l != 0 { - b.err = fmt.Errorf("cryptobyte: pending child length %d exceeds %d-byte length prefix", length, child.pendingLenLen) - return - } - - if b.fixedSize && &b.result[0] != &child.result[0] { - panic("cryptobyte: BuilderContinuation reallocated a fixed-size buffer") - } - - b.result = child.result -} - -func (b *Builder) add(bytes ...byte) { - if b.err != nil { - return - } - if b.child != nil { - panic("cryptobyte: attempted write while child is pending") - } - if len(b.result)+len(bytes) < len(bytes) { - b.err = errors.New("cryptobyte: length overflow") - } - if b.fixedSize && len(b.result)+len(bytes) > cap(b.result) { - b.err = errors.New("cryptobyte: Builder is exceeding its fixed-size buffer") - return - } - b.result = append(b.result, bytes...) -} - -// Unwrite rolls back non-negative n bytes written directly to the Builder. -// An attempt by a child builder passed to a continuation to unwrite bytes -// from its parent will panic. -func (b *Builder) Unwrite(n int) { - if b.err != nil { - return - } - if b.child != nil { - panic("cryptobyte: attempted unwrite while child is pending") - } - length := len(b.result) - b.pendingLenLen - b.offset - if length < 0 { - panic("cryptobyte: internal error") - } - if n < 0 { - panic("cryptobyte: attempted to unwrite negative number of bytes") - } - if n > length { - panic("cryptobyte: attempted to unwrite more than was written") - } - b.result = b.result[:len(b.result)-n] -} - -// A MarshalingValue marshals itself into a Builder. -type MarshalingValue interface { - // Marshal is called by Builder.AddValue. It receives a pointer to a builder - // to marshal itself into. It may return an error that occurred during - // marshaling, such as unset or invalid values. - Marshal(b *Builder) error -} - -// AddValue calls Marshal on v, passing a pointer to the builder to append to. -// If Marshal returns an error, it is set on the Builder so that subsequent -// appends don't have an effect. -func (b *Builder) AddValue(v MarshalingValue) { - err := v.Marshal(b) - if err != nil { - b.err = err - } -} diff --git a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/string.go b/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/string.go deleted file mode 100644 index 4b0f8097..00000000 --- a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/cryptobyte/string.go +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package cryptobyte contains types that help with parsing and constructing -// length-prefixed, binary messages, including ASN.1 DER. (The asn1 subpackage -// contains useful ASN.1 constants.) -// -// The String type is for parsing. It wraps a []byte slice and provides helper -// functions for consuming structures, value by value. -// -// The Builder type is for constructing messages. It providers helper functions -// for appending values and also for appending length-prefixed submessages – -// without having to worry about calculating the length prefix ahead of time. -// -// See the documentation and examples for the Builder and String types to get -// started. -package cryptobyte - -// String represents a string of bytes. It provides methods for parsing -// fixed-length and length-prefixed values from it. -type String []byte - -// read advances a String by n bytes and returns them. If less than n bytes -// remain, it returns nil. -func (s *String) read(n int) []byte { - if len(*s) < n || n < 0 { - return nil - } - v := (*s)[:n] - *s = (*s)[n:] - return v -} - -// Skip advances the String by n byte and reports whether it was successful. -func (s *String) Skip(n int) bool { - return s.read(n) != nil -} - -// ReadUint8 decodes an 8-bit value into out and advances over it. -// It reports whether the read was successful. -func (s *String) ReadUint8(out *uint8) bool { - v := s.read(1) - if v == nil { - return false - } - *out = uint8(v[0]) - return true -} - -// ReadUint16 decodes a big-endian, 16-bit value into out and advances over it. -// It reports whether the read was successful. -func (s *String) ReadUint16(out *uint16) bool { - v := s.read(2) - if v == nil { - return false - } - *out = uint16(v[0])<<8 | uint16(v[1]) - return true -} - -// ReadUint24 decodes a big-endian, 24-bit value into out and advances over it. -// It reports whether the read was successful. -func (s *String) ReadUint24(out *uint32) bool { - v := s.read(3) - if v == nil { - return false - } - *out = uint32(v[0])<<16 | uint32(v[1])<<8 | uint32(v[2]) - return true -} - -// ReadUint32 decodes a big-endian, 32-bit value into out and advances over it. -// It reports whether the read was successful. -func (s *String) ReadUint32(out *uint32) bool { - v := s.read(4) - if v == nil { - return false - } - *out = uint32(v[0])<<24 | uint32(v[1])<<16 | uint32(v[2])<<8 | uint32(v[3]) - return true -} - -// ReadUint48 decodes a big-endian, 48-bit value into out and advances over it. -// It reports whether the read was successful. -func (s *String) ReadUint48(out *uint64) bool { - v := s.read(6) - if v == nil { - return false - } - *out = uint64(v[0])<<40 | uint64(v[1])<<32 | uint64(v[2])<<24 | uint64(v[3])<<16 | uint64(v[4])<<8 | uint64(v[5]) - return true -} - -// ReadUint64 decodes a big-endian, 64-bit value into out and advances over it. -// It reports whether the read was successful. -func (s *String) ReadUint64(out *uint64) bool { - v := s.read(8) - if v == nil { - return false - } - *out = uint64(v[0])<<56 | uint64(v[1])<<48 | uint64(v[2])<<40 | uint64(v[3])<<32 | uint64(v[4])<<24 | uint64(v[5])<<16 | uint64(v[6])<<8 | uint64(v[7]) - return true -} - -func (s *String) readUnsigned(out *uint32, length int) bool { - v := s.read(length) - if v == nil { - return false - } - var result uint32 - for i := 0; i < length; i++ { - result <<= 8 - result |= uint32(v[i]) - } - *out = result - return true -} - -func (s *String) readLengthPrefixed(lenLen int, outChild *String) bool { - lenBytes := s.read(lenLen) - if lenBytes == nil { - return false - } - var length uint32 - for _, b := range lenBytes { - length = length << 8 - length = length | uint32(b) - } - v := s.read(int(length)) - if v == nil { - return false - } - *outChild = v - return true -} - -// ReadUint8LengthPrefixed reads the content of an 8-bit length-prefixed value -// into out and advances over it. It reports whether the read was successful. -func (s *String) ReadUint8LengthPrefixed(out *String) bool { - return s.readLengthPrefixed(1, out) -} - -// ReadUint16LengthPrefixed reads the content of a big-endian, 16-bit -// length-prefixed value into out and advances over it. It reports whether the -// read was successful. -func (s *String) ReadUint16LengthPrefixed(out *String) bool { - return s.readLengthPrefixed(2, out) -} - -// ReadUint24LengthPrefixed reads the content of a big-endian, 24-bit -// length-prefixed value into out and advances over it. It reports whether -// the read was successful. -func (s *String) ReadUint24LengthPrefixed(out *String) bool { - return s.readLengthPrefixed(3, out) -} - -// ReadBytes reads n bytes into out and advances over them. It reports -// whether the read was successful. -func (s *String) ReadBytes(out *[]byte, n int) bool { - v := s.read(n) - if v == nil { - return false - } - *out = v - return true -} - -// CopyBytes copies len(out) bytes into out and advances over them. It reports -// whether the copy operation was successful -func (s *String) CopyBytes(out []byte) bool { - n := len(out) - v := s.read(n) - if v == nil { - return false - } - return copy(out, v) == n -} - -// Empty reports whether the string does not contain any bytes. -func (s String) Empty() bool { - return len(s) == 0 -} diff --git a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/ocsp/ocsp.go b/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/ocsp/ocsp.go deleted file mode 100644 index e6c645e7..00000000 --- a/src/code.cloudfoundry.org/vendor/golang.org/x/crypto/ocsp/ocsp.go +++ /dev/null @@ -1,793 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package ocsp parses OCSP responses as specified in RFC 2560. OCSP responses -// are signed messages attesting to the validity of a certificate for a small -// period of time. This is used to manage revocation for X.509 certificates. -package ocsp - -import ( - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - _ "crypto/sha1" - _ "crypto/sha256" - _ "crypto/sha512" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "errors" - "fmt" - "math/big" - "strconv" - "time" -) - -var idPKIXOCSPBasic = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 5, 5, 7, 48, 1, 1}) - -// ResponseStatus contains the result of an OCSP request. See -// https://tools.ietf.org/html/rfc6960#section-2.3 -type ResponseStatus int - -const ( - Success ResponseStatus = 0 - Malformed ResponseStatus = 1 - InternalError ResponseStatus = 2 - TryLater ResponseStatus = 3 - // Status code four is unused in OCSP. See - // https://tools.ietf.org/html/rfc6960#section-4.2.1 - SignatureRequired ResponseStatus = 5 - Unauthorized ResponseStatus = 6 -) - -func (r ResponseStatus) String() string { - switch r { - case Success: - return "success" - case Malformed: - return "malformed" - case InternalError: - return "internal error" - case TryLater: - return "try later" - case SignatureRequired: - return "signature required" - case Unauthorized: - return "unauthorized" - default: - return "unknown OCSP status: " + strconv.Itoa(int(r)) - } -} - -// ResponseError is an error that may be returned by ParseResponse to indicate -// that the response itself is an error, not just that it's indicating that a -// certificate is revoked, unknown, etc. -type ResponseError struct { - Status ResponseStatus -} - -func (r ResponseError) Error() string { - return "ocsp: error from server: " + r.Status.String() -} - -// These are internal structures that reflect the ASN.1 structure of an OCSP -// response. See RFC 2560, section 4.2. - -type certID struct { - HashAlgorithm pkix.AlgorithmIdentifier - NameHash []byte - IssuerKeyHash []byte - SerialNumber *big.Int -} - -// https://tools.ietf.org/html/rfc2560#section-4.1.1 -type ocspRequest struct { - TBSRequest tbsRequest -} - -type tbsRequest struct { - Version int `asn1:"explicit,tag:0,default:0,optional"` - RequestorName pkix.RDNSequence `asn1:"explicit,tag:1,optional"` - RequestList []request -} - -type request struct { - Cert certID -} - -type responseASN1 struct { - Status asn1.Enumerated - Response responseBytes `asn1:"explicit,tag:0,optional"` -} - -type responseBytes struct { - ResponseType asn1.ObjectIdentifier - Response []byte -} - -type basicResponse struct { - TBSResponseData responseData - SignatureAlgorithm pkix.AlgorithmIdentifier - Signature asn1.BitString - Certificates []asn1.RawValue `asn1:"explicit,tag:0,optional"` -} - -type responseData struct { - Raw asn1.RawContent - Version int `asn1:"optional,default:0,explicit,tag:0"` - RawResponderID asn1.RawValue - ProducedAt time.Time `asn1:"generalized"` - Responses []singleResponse -} - -type singleResponse struct { - CertID certID - Good asn1.Flag `asn1:"tag:0,optional"` - Revoked revokedInfo `asn1:"tag:1,optional"` - Unknown asn1.Flag `asn1:"tag:2,optional"` - ThisUpdate time.Time `asn1:"generalized"` - NextUpdate time.Time `asn1:"generalized,explicit,tag:0,optional"` - SingleExtensions []pkix.Extension `asn1:"explicit,tag:1,optional"` -} - -type revokedInfo struct { - RevocationTime time.Time `asn1:"generalized"` - Reason asn1.Enumerated `asn1:"explicit,tag:0,optional"` -} - -var ( - oidSignatureMD2WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2} - oidSignatureMD5WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4} - oidSignatureSHA1WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5} - oidSignatureSHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11} - oidSignatureSHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12} - oidSignatureSHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13} - oidSignatureDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3} - oidSignatureDSAWithSHA256 = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 2} - oidSignatureECDSAWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1} - oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} - oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3} - oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4} -) - -var hashOIDs = map[crypto.Hash]asn1.ObjectIdentifier{ - crypto.SHA1: asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}), - crypto.SHA256: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1}), - crypto.SHA384: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 2}), - crypto.SHA512: asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 3}), -} - -// TODO(rlb): This is also from crypto/x509, so same comment as AGL's below -var signatureAlgorithmDetails = []struct { - algo x509.SignatureAlgorithm - oid asn1.ObjectIdentifier - pubKeyAlgo x509.PublicKeyAlgorithm - hash crypto.Hash -}{ - {x509.MD2WithRSA, oidSignatureMD2WithRSA, x509.RSA, crypto.Hash(0) /* no value for MD2 */}, - {x509.MD5WithRSA, oidSignatureMD5WithRSA, x509.RSA, crypto.MD5}, - {x509.SHA1WithRSA, oidSignatureSHA1WithRSA, x509.RSA, crypto.SHA1}, - {x509.SHA256WithRSA, oidSignatureSHA256WithRSA, x509.RSA, crypto.SHA256}, - {x509.SHA384WithRSA, oidSignatureSHA384WithRSA, x509.RSA, crypto.SHA384}, - {x509.SHA512WithRSA, oidSignatureSHA512WithRSA, x509.RSA, crypto.SHA512}, - {x509.DSAWithSHA1, oidSignatureDSAWithSHA1, x509.DSA, crypto.SHA1}, - {x509.DSAWithSHA256, oidSignatureDSAWithSHA256, x509.DSA, crypto.SHA256}, - {x509.ECDSAWithSHA1, oidSignatureECDSAWithSHA1, x509.ECDSA, crypto.SHA1}, - {x509.ECDSAWithSHA256, oidSignatureECDSAWithSHA256, x509.ECDSA, crypto.SHA256}, - {x509.ECDSAWithSHA384, oidSignatureECDSAWithSHA384, x509.ECDSA, crypto.SHA384}, - {x509.ECDSAWithSHA512, oidSignatureECDSAWithSHA512, x509.ECDSA, crypto.SHA512}, -} - -// TODO(rlb): This is also from crypto/x509, so same comment as AGL's below -func signingParamsForPublicKey(pub interface{}, requestedSigAlgo x509.SignatureAlgorithm) (hashFunc crypto.Hash, sigAlgo pkix.AlgorithmIdentifier, err error) { - var pubType x509.PublicKeyAlgorithm - - switch pub := pub.(type) { - case *rsa.PublicKey: - pubType = x509.RSA - hashFunc = crypto.SHA256 - sigAlgo.Algorithm = oidSignatureSHA256WithRSA - sigAlgo.Parameters = asn1.RawValue{ - Tag: 5, - } - - case *ecdsa.PublicKey: - pubType = x509.ECDSA - - switch pub.Curve { - case elliptic.P224(), elliptic.P256(): - hashFunc = crypto.SHA256 - sigAlgo.Algorithm = oidSignatureECDSAWithSHA256 - case elliptic.P384(): - hashFunc = crypto.SHA384 - sigAlgo.Algorithm = oidSignatureECDSAWithSHA384 - case elliptic.P521(): - hashFunc = crypto.SHA512 - sigAlgo.Algorithm = oidSignatureECDSAWithSHA512 - default: - err = errors.New("x509: unknown elliptic curve") - } - - default: - err = errors.New("x509: only RSA and ECDSA keys supported") - } - - if err != nil { - return - } - - if requestedSigAlgo == 0 { - return - } - - found := false - for _, details := range signatureAlgorithmDetails { - if details.algo == requestedSigAlgo { - if details.pubKeyAlgo != pubType { - err = errors.New("x509: requested SignatureAlgorithm does not match private key type") - return - } - sigAlgo.Algorithm, hashFunc = details.oid, details.hash - if hashFunc == 0 { - err = errors.New("x509: cannot sign with hash function requested") - return - } - found = true - break - } - } - - if !found { - err = errors.New("x509: unknown SignatureAlgorithm") - } - - return -} - -// TODO(agl): this is taken from crypto/x509 and so should probably be exported -// from crypto/x509 or crypto/x509/pkix. -func getSignatureAlgorithmFromOID(oid asn1.ObjectIdentifier) x509.SignatureAlgorithm { - for _, details := range signatureAlgorithmDetails { - if oid.Equal(details.oid) { - return details.algo - } - } - return x509.UnknownSignatureAlgorithm -} - -// TODO(rlb): This is not taken from crypto/x509, but it's of the same general form. -func getHashAlgorithmFromOID(target asn1.ObjectIdentifier) crypto.Hash { - for hash, oid := range hashOIDs { - if oid.Equal(target) { - return hash - } - } - return crypto.Hash(0) -} - -func getOIDFromHashAlgorithm(target crypto.Hash) asn1.ObjectIdentifier { - for hash, oid := range hashOIDs { - if hash == target { - return oid - } - } - return nil -} - -// This is the exposed reflection of the internal OCSP structures. - -// The status values that can be expressed in OCSP. See RFC 6960. -// These are used for the Response.Status field. -const ( - // Good means that the certificate is valid. - Good = 0 - // Revoked means that the certificate has been deliberately revoked. - Revoked = 1 - // Unknown means that the OCSP responder doesn't know about the certificate. - Unknown = 2 - // ServerFailed is unused and was never used (see - // https://go-review.googlesource.com/#/c/18944). ParseResponse will - // return a ResponseError when an error response is parsed. - ServerFailed = 3 -) - -// The enumerated reasons for revoking a certificate. See RFC 5280. -const ( - Unspecified = 0 - KeyCompromise = 1 - CACompromise = 2 - AffiliationChanged = 3 - Superseded = 4 - CessationOfOperation = 5 - CertificateHold = 6 - - RemoveFromCRL = 8 - PrivilegeWithdrawn = 9 - AACompromise = 10 -) - -// Request represents an OCSP request. See RFC 6960. -type Request struct { - HashAlgorithm crypto.Hash - IssuerNameHash []byte - IssuerKeyHash []byte - SerialNumber *big.Int -} - -// Marshal marshals the OCSP request to ASN.1 DER encoded form. -func (req *Request) Marshal() ([]byte, error) { - hashAlg := getOIDFromHashAlgorithm(req.HashAlgorithm) - if hashAlg == nil { - return nil, errors.New("Unknown hash algorithm") - } - return asn1.Marshal(ocspRequest{ - tbsRequest{ - Version: 0, - RequestList: []request{ - { - Cert: certID{ - pkix.AlgorithmIdentifier{ - Algorithm: hashAlg, - Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */}, - }, - req.IssuerNameHash, - req.IssuerKeyHash, - req.SerialNumber, - }, - }, - }, - }, - }) -} - -// Response represents an OCSP response containing a single SingleResponse. See -// RFC 6960. -type Response struct { - Raw []byte - - // Status is one of {Good, Revoked, Unknown} - Status int - SerialNumber *big.Int - ProducedAt, ThisUpdate, NextUpdate, RevokedAt time.Time - RevocationReason int - Certificate *x509.Certificate - // TBSResponseData contains the raw bytes of the signed response. If - // Certificate is nil then this can be used to verify Signature. - TBSResponseData []byte - Signature []byte - SignatureAlgorithm x509.SignatureAlgorithm - - // IssuerHash is the hash used to compute the IssuerNameHash and IssuerKeyHash. - // Valid values are crypto.SHA1, crypto.SHA256, crypto.SHA384, and crypto.SHA512. - // If zero, the default is crypto.SHA1. - IssuerHash crypto.Hash - - // RawResponderName optionally contains the DER-encoded subject of the - // responder certificate. Exactly one of RawResponderName and - // ResponderKeyHash is set. - RawResponderName []byte - // ResponderKeyHash optionally contains the SHA-1 hash of the - // responder's public key. Exactly one of RawResponderName and - // ResponderKeyHash is set. - ResponderKeyHash []byte - - // Extensions contains raw X.509 extensions from the singleExtensions field - // of the OCSP response. When parsing certificates, this can be used to - // extract non-critical extensions that are not parsed by this package. When - // marshaling OCSP responses, the Extensions field is ignored, see - // ExtraExtensions. - Extensions []pkix.Extension - - // ExtraExtensions contains extensions to be copied, raw, into any marshaled - // OCSP response (in the singleExtensions field). Values override any - // extensions that would otherwise be produced based on the other fields. The - // ExtraExtensions field is not populated when parsing certificates, see - // Extensions. - ExtraExtensions []pkix.Extension -} - -// These are pre-serialized error responses for the various non-success codes -// defined by OCSP. The Unauthorized code in particular can be used by an OCSP -// responder that supports only pre-signed responses as a response to requests -// for certificates with unknown status. See RFC 5019. -var ( - MalformedRequestErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x01} - InternalErrorErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x02} - TryLaterErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x03} - SigRequredErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x05} - UnauthorizedErrorResponse = []byte{0x30, 0x03, 0x0A, 0x01, 0x06} -) - -// CheckSignatureFrom checks that the signature in resp is a valid signature -// from issuer. This should only be used if resp.Certificate is nil. Otherwise, -// the OCSP response contained an intermediate certificate that created the -// signature. That signature is checked by ParseResponse and only -// resp.Certificate remains to be validated. -func (resp *Response) CheckSignatureFrom(issuer *x509.Certificate) error { - return issuer.CheckSignature(resp.SignatureAlgorithm, resp.TBSResponseData, resp.Signature) -} - -// ParseError results from an invalid OCSP response. -type ParseError string - -func (p ParseError) Error() string { - return string(p) -} - -// ParseRequest parses an OCSP request in DER form. It only supports -// requests for a single certificate. Signed requests are not supported. -// If a request includes a signature, it will result in a ParseError. -func ParseRequest(bytes []byte) (*Request, error) { - var req ocspRequest - rest, err := asn1.Unmarshal(bytes, &req) - if err != nil { - return nil, err - } - if len(rest) > 0 { - return nil, ParseError("trailing data in OCSP request") - } - - if len(req.TBSRequest.RequestList) == 0 { - return nil, ParseError("OCSP request contains no request body") - } - innerRequest := req.TBSRequest.RequestList[0] - - hashFunc := getHashAlgorithmFromOID(innerRequest.Cert.HashAlgorithm.Algorithm) - if hashFunc == crypto.Hash(0) { - return nil, ParseError("OCSP request uses unknown hash function") - } - - return &Request{ - HashAlgorithm: hashFunc, - IssuerNameHash: innerRequest.Cert.NameHash, - IssuerKeyHash: innerRequest.Cert.IssuerKeyHash, - SerialNumber: innerRequest.Cert.SerialNumber, - }, nil -} - -// ParseResponse parses an OCSP response in DER form. The response must contain -// only one certificate status. To parse the status of a specific certificate -// from a response which may contain multiple statuses, use ParseResponseForCert -// instead. -// -// If the response contains an embedded certificate, then that certificate will -// be used to verify the response signature. If the response contains an -// embedded certificate and issuer is not nil, then issuer will be used to verify -// the signature on the embedded certificate. -// -// If the response does not contain an embedded certificate and issuer is not -// nil, then issuer will be used to verify the response signature. -// -// Invalid responses and parse failures will result in a ParseError. -// Error responses will result in a ResponseError. -func ParseResponse(bytes []byte, issuer *x509.Certificate) (*Response, error) { - return ParseResponseForCert(bytes, nil, issuer) -} - -// ParseResponseForCert acts identically to ParseResponse, except it supports -// parsing responses that contain multiple statuses. If the response contains -// multiple statuses and cert is not nil, then ParseResponseForCert will return -// the first status which contains a matching serial, otherwise it will return an -// error. If cert is nil, then the first status in the response will be returned. -func ParseResponseForCert(bytes []byte, cert, issuer *x509.Certificate) (*Response, error) { - var resp responseASN1 - rest, err := asn1.Unmarshal(bytes, &resp) - if err != nil { - return nil, err - } - if len(rest) > 0 { - return nil, ParseError("trailing data in OCSP response") - } - - if status := ResponseStatus(resp.Status); status != Success { - return nil, ResponseError{status} - } - - if !resp.Response.ResponseType.Equal(idPKIXOCSPBasic) { - return nil, ParseError("bad OCSP response type") - } - - var basicResp basicResponse - rest, err = asn1.Unmarshal(resp.Response.Response, &basicResp) - if err != nil { - return nil, err - } - if len(rest) > 0 { - return nil, ParseError("trailing data in OCSP response") - } - - if n := len(basicResp.TBSResponseData.Responses); n == 0 || cert == nil && n > 1 { - return nil, ParseError("OCSP response contains bad number of responses") - } - - var singleResp singleResponse - if cert == nil { - singleResp = basicResp.TBSResponseData.Responses[0] - } else { - match := false - for _, resp := range basicResp.TBSResponseData.Responses { - if cert.SerialNumber.Cmp(resp.CertID.SerialNumber) == 0 { - singleResp = resp - match = true - break - } - } - if !match { - return nil, ParseError("no response matching the supplied certificate") - } - } - - ret := &Response{ - Raw: bytes, - TBSResponseData: basicResp.TBSResponseData.Raw, - Signature: basicResp.Signature.RightAlign(), - SignatureAlgorithm: getSignatureAlgorithmFromOID(basicResp.SignatureAlgorithm.Algorithm), - Extensions: singleResp.SingleExtensions, - SerialNumber: singleResp.CertID.SerialNumber, - ProducedAt: basicResp.TBSResponseData.ProducedAt, - ThisUpdate: singleResp.ThisUpdate, - NextUpdate: singleResp.NextUpdate, - } - - // Handle the ResponderID CHOICE tag. ResponderID can be flattened into - // TBSResponseData once https://go-review.googlesource.com/34503 has been - // released. - rawResponderID := basicResp.TBSResponseData.RawResponderID - switch rawResponderID.Tag { - case 1: // Name - var rdn pkix.RDNSequence - if rest, err := asn1.Unmarshal(rawResponderID.Bytes, &rdn); err != nil || len(rest) != 0 { - return nil, ParseError("invalid responder name") - } - ret.RawResponderName = rawResponderID.Bytes - case 2: // KeyHash - if rest, err := asn1.Unmarshal(rawResponderID.Bytes, &ret.ResponderKeyHash); err != nil || len(rest) != 0 { - return nil, ParseError("invalid responder key hash") - } - default: - return nil, ParseError("invalid responder id tag") - } - - if len(basicResp.Certificates) > 0 { - // Responders should only send a single certificate (if they - // send any) that connects the responder's certificate to the - // original issuer. We accept responses with multiple - // certificates due to a number responders sending them[1], but - // ignore all but the first. - // - // [1] https://github.com/golang/go/issues/21527 - ret.Certificate, err = x509.ParseCertificate(basicResp.Certificates[0].FullBytes) - if err != nil { - return nil, err - } - - if err := ret.CheckSignatureFrom(ret.Certificate); err != nil { - return nil, ParseError("bad signature on embedded certificate: " + err.Error()) - } - - if issuer != nil { - if err := issuer.CheckSignature(ret.Certificate.SignatureAlgorithm, ret.Certificate.RawTBSCertificate, ret.Certificate.Signature); err != nil { - return nil, ParseError("bad OCSP signature: " + err.Error()) - } - } - } else if issuer != nil { - if err := ret.CheckSignatureFrom(issuer); err != nil { - return nil, ParseError("bad OCSP signature: " + err.Error()) - } - } - - for _, ext := range singleResp.SingleExtensions { - if ext.Critical { - return nil, ParseError("unsupported critical extension") - } - } - - for h, oid := range hashOIDs { - if singleResp.CertID.HashAlgorithm.Algorithm.Equal(oid) { - ret.IssuerHash = h - break - } - } - if ret.IssuerHash == 0 { - return nil, ParseError("unsupported issuer hash algorithm") - } - - switch { - case bool(singleResp.Good): - ret.Status = Good - case bool(singleResp.Unknown): - ret.Status = Unknown - default: - ret.Status = Revoked - ret.RevokedAt = singleResp.Revoked.RevocationTime - ret.RevocationReason = int(singleResp.Revoked.Reason) - } - - return ret, nil -} - -// RequestOptions contains options for constructing OCSP requests. -type RequestOptions struct { - // Hash contains the hash function that should be used when - // constructing the OCSP request. If zero, SHA-1 will be used. - Hash crypto.Hash -} - -func (opts *RequestOptions) hash() crypto.Hash { - if opts == nil || opts.Hash == 0 { - // SHA-1 is nearly universally used in OCSP. - return crypto.SHA1 - } - return opts.Hash -} - -// CreateRequest returns a DER-encoded, OCSP request for the status of cert. If -// opts is nil then sensible defaults are used. -func CreateRequest(cert, issuer *x509.Certificate, opts *RequestOptions) ([]byte, error) { - hashFunc := opts.hash() - - // OCSP seems to be the only place where these raw hash identifiers are - // used. I took the following from - // http://msdn.microsoft.com/en-us/library/ff635603.aspx - _, ok := hashOIDs[hashFunc] - if !ok { - return nil, x509.ErrUnsupportedAlgorithm - } - - if !hashFunc.Available() { - return nil, x509.ErrUnsupportedAlgorithm - } - h := opts.hash().New() - - var publicKeyInfo struct { - Algorithm pkix.AlgorithmIdentifier - PublicKey asn1.BitString - } - if _, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil { - return nil, err - } - - h.Write(publicKeyInfo.PublicKey.RightAlign()) - issuerKeyHash := h.Sum(nil) - - h.Reset() - h.Write(issuer.RawSubject) - issuerNameHash := h.Sum(nil) - - req := &Request{ - HashAlgorithm: hashFunc, - IssuerNameHash: issuerNameHash, - IssuerKeyHash: issuerKeyHash, - SerialNumber: cert.SerialNumber, - } - return req.Marshal() -} - -// CreateResponse returns a DER-encoded OCSP response with the specified contents. -// The fields in the response are populated as follows: -// -// The responder cert is used to populate the responder's name field, and the -// certificate itself is provided alongside the OCSP response signature. -// -// The issuer cert is used to populate the IssuerNameHash and IssuerKeyHash fields. -// -// The template is used to populate the SerialNumber, Status, RevokedAt, -// RevocationReason, ThisUpdate, and NextUpdate fields. -// -// If template.IssuerHash is not set, SHA1 will be used. -// -// The ProducedAt date is automatically set to the current date, to the nearest minute. -func CreateResponse(issuer, responderCert *x509.Certificate, template Response, priv crypto.Signer) ([]byte, error) { - var publicKeyInfo struct { - Algorithm pkix.AlgorithmIdentifier - PublicKey asn1.BitString - } - if _, err := asn1.Unmarshal(issuer.RawSubjectPublicKeyInfo, &publicKeyInfo); err != nil { - return nil, err - } - - if template.IssuerHash == 0 { - template.IssuerHash = crypto.SHA1 - } - hashOID := getOIDFromHashAlgorithm(template.IssuerHash) - if hashOID == nil { - return nil, errors.New("unsupported issuer hash algorithm") - } - - if !template.IssuerHash.Available() { - return nil, fmt.Errorf("issuer hash algorithm %v not linked into binary", template.IssuerHash) - } - h := template.IssuerHash.New() - h.Write(publicKeyInfo.PublicKey.RightAlign()) - issuerKeyHash := h.Sum(nil) - - h.Reset() - h.Write(issuer.RawSubject) - issuerNameHash := h.Sum(nil) - - innerResponse := singleResponse{ - CertID: certID{ - HashAlgorithm: pkix.AlgorithmIdentifier{ - Algorithm: hashOID, - Parameters: asn1.RawValue{Tag: 5 /* ASN.1 NULL */}, - }, - NameHash: issuerNameHash, - IssuerKeyHash: issuerKeyHash, - SerialNumber: template.SerialNumber, - }, - ThisUpdate: template.ThisUpdate.UTC(), - NextUpdate: template.NextUpdate.UTC(), - SingleExtensions: template.ExtraExtensions, - } - - switch template.Status { - case Good: - innerResponse.Good = true - case Unknown: - innerResponse.Unknown = true - case Revoked: - innerResponse.Revoked = revokedInfo{ - RevocationTime: template.RevokedAt.UTC(), - Reason: asn1.Enumerated(template.RevocationReason), - } - } - - rawResponderID := asn1.RawValue{ - Class: 2, // context-specific - Tag: 1, // Name (explicit tag) - IsCompound: true, - Bytes: responderCert.RawSubject, - } - tbsResponseData := responseData{ - Version: 0, - RawResponderID: rawResponderID, - ProducedAt: time.Now().Truncate(time.Minute).UTC(), - Responses: []singleResponse{innerResponse}, - } - - tbsResponseDataDER, err := asn1.Marshal(tbsResponseData) - if err != nil { - return nil, err - } - - hashFunc, signatureAlgorithm, err := signingParamsForPublicKey(priv.Public(), template.SignatureAlgorithm) - if err != nil { - return nil, err - } - - responseHash := hashFunc.New() - responseHash.Write(tbsResponseDataDER) - signature, err := priv.Sign(rand.Reader, responseHash.Sum(nil), hashFunc) - if err != nil { - return nil, err - } - - response := basicResponse{ - TBSResponseData: tbsResponseData, - SignatureAlgorithm: signatureAlgorithm, - Signature: asn1.BitString{ - Bytes: signature, - BitLength: 8 * len(signature), - }, - } - if template.Certificate != nil { - response.Certificates = []asn1.RawValue{ - {FullBytes: template.Certificate.Raw}, - } - } - responseDER, err := asn1.Marshal(response) - if err != nil { - return nil, err - } - - return asn1.Marshal(responseASN1{ - Status: asn1.Enumerated(Success), - Response: responseBytes{ - ResponseType: idPKIXOCSPBasic, - Response: responseDER, - }, - }) -} diff --git a/src/code.cloudfoundry.org/vendor/golang.org/x/time/LICENSE b/src/code.cloudfoundry.org/vendor/golang.org/x/time/LICENSE deleted file mode 100644 index 2a7cf70d..00000000 --- a/src/code.cloudfoundry.org/vendor/golang.org/x/time/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright 2009 The Go Authors. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google LLC nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/code.cloudfoundry.org/vendor/golang.org/x/time/PATENTS b/src/code.cloudfoundry.org/vendor/golang.org/x/time/PATENTS deleted file mode 100644 index 73309904..00000000 --- a/src/code.cloudfoundry.org/vendor/golang.org/x/time/PATENTS +++ /dev/null @@ -1,22 +0,0 @@ -Additional IP Rights Grant (Patents) - -"This implementation" means the copyrightable works distributed by -Google as part of the Go project. - -Google hereby grants to You a perpetual, worldwide, non-exclusive, -no-charge, royalty-free, irrevocable (except as stated in this section) -patent license to make, have made, use, offer to sell, sell, import, -transfer and otherwise run, modify and propagate the contents of this -implementation of Go, where such license applies only to those patent -claims, both currently owned or controlled by Google and acquired in -the future, licensable by Google that are necessarily infringed by this -implementation of Go. This grant does not include claims that would be -infringed only as a consequence of further modification of this -implementation. If you or your agent or exclusive licensee institute or -order or agree to the institution of patent litigation against any -entity (including a cross-claim or counterclaim in a lawsuit) alleging -that this implementation of Go or any code incorporated within this -implementation of Go constitutes direct or contributory patent -infringement, or inducement of patent infringement, then any patent -rights granted to you under this License for this implementation of Go -shall terminate as of the date such litigation is filed. diff --git a/src/code.cloudfoundry.org/vendor/golang.org/x/time/rate/rate.go b/src/code.cloudfoundry.org/vendor/golang.org/x/time/rate/rate.go deleted file mode 100644 index 794b2e32..00000000 --- a/src/code.cloudfoundry.org/vendor/golang.org/x/time/rate/rate.go +++ /dev/null @@ -1,427 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package rate provides a rate limiter. -package rate - -import ( - "context" - "fmt" - "math" - "sync" - "time" -) - -// Limit defines the maximum frequency of some events. -// Limit is represented as number of events per second. -// A zero Limit allows no events. -type Limit float64 - -// Inf is the infinite rate limit; it allows all events (even if burst is zero). -const Inf = Limit(math.MaxFloat64) - -// Every converts a minimum time interval between events to a Limit. -func Every(interval time.Duration) Limit { - if interval <= 0 { - return Inf - } - return 1 / Limit(interval.Seconds()) -} - -// A Limiter controls how frequently events are allowed to happen. -// It implements a "token bucket" of size b, initially full and refilled -// at rate r tokens per second. -// Informally, in any large enough time interval, the Limiter limits the -// rate to r tokens per second, with a maximum burst size of b events. -// As a special case, if r == Inf (the infinite rate), b is ignored. -// See https://en.wikipedia.org/wiki/Token_bucket for more about token buckets. -// -// The zero value is a valid Limiter, but it will reject all events. -// Use NewLimiter to create non-zero Limiters. -// -// Limiter has three main methods, Allow, Reserve, and Wait. -// Most callers should use Wait. -// -// Each of the three methods consumes a single token. -// They differ in their behavior when no token is available. -// If no token is available, Allow returns false. -// If no token is available, Reserve returns a reservation for a future token -// and the amount of time the caller must wait before using it. -// If no token is available, Wait blocks until one can be obtained -// or its associated context.Context is canceled. -// -// The methods AllowN, ReserveN, and WaitN consume n tokens. -// -// Limiter is safe for simultaneous use by multiple goroutines. -type Limiter struct { - mu sync.Mutex - limit Limit - burst int - tokens float64 - // last is the last time the limiter's tokens field was updated - last time.Time - // lastEvent is the latest time of a rate-limited event (past or future) - lastEvent time.Time -} - -// Limit returns the maximum overall event rate. -func (lim *Limiter) Limit() Limit { - lim.mu.Lock() - defer lim.mu.Unlock() - return lim.limit -} - -// Burst returns the maximum burst size. Burst is the maximum number of tokens -// that can be consumed in a single call to Allow, Reserve, or Wait, so higher -// Burst values allow more events to happen at once. -// A zero Burst allows no events, unless limit == Inf. -func (lim *Limiter) Burst() int { - lim.mu.Lock() - defer lim.mu.Unlock() - return lim.burst -} - -// TokensAt returns the number of tokens available at time t. -func (lim *Limiter) TokensAt(t time.Time) float64 { - lim.mu.Lock() - tokens := lim.advance(t) // does not mutate lim - lim.mu.Unlock() - return tokens -} - -// Tokens returns the number of tokens available now. -func (lim *Limiter) Tokens() float64 { - return lim.TokensAt(time.Now()) -} - -// NewLimiter returns a new Limiter that allows events up to rate r and permits -// bursts of at most b tokens. -func NewLimiter(r Limit, b int) *Limiter { - return &Limiter{ - limit: r, - burst: b, - tokens: float64(b), - } -} - -// Allow reports whether an event may happen now. -func (lim *Limiter) Allow() bool { - return lim.AllowN(time.Now(), 1) -} - -// AllowN reports whether n events may happen at time t. -// Use this method if you intend to drop / skip events that exceed the rate limit. -// Otherwise use Reserve or Wait. -func (lim *Limiter) AllowN(t time.Time, n int) bool { - return lim.reserveN(t, n, 0).ok -} - -// A Reservation holds information about events that are permitted by a Limiter to happen after a delay. -// A Reservation may be canceled, which may enable the Limiter to permit additional events. -type Reservation struct { - ok bool - lim *Limiter - tokens int - timeToAct time.Time - // This is the Limit at reservation time, it can change later. - limit Limit -} - -// OK returns whether the limiter can provide the requested number of tokens -// within the maximum wait time. If OK is false, Delay returns InfDuration, and -// Cancel does nothing. -func (r *Reservation) OK() bool { - return r.ok -} - -// Delay is shorthand for DelayFrom(time.Now()). -func (r *Reservation) Delay() time.Duration { - return r.DelayFrom(time.Now()) -} - -// InfDuration is the duration returned by Delay when a Reservation is not OK. -const InfDuration = time.Duration(math.MaxInt64) - -// DelayFrom returns the duration for which the reservation holder must wait -// before taking the reserved action. Zero duration means act immediately. -// InfDuration means the limiter cannot grant the tokens requested in this -// Reservation within the maximum wait time. -func (r *Reservation) DelayFrom(t time.Time) time.Duration { - if !r.ok { - return InfDuration - } - delay := r.timeToAct.Sub(t) - if delay < 0 { - return 0 - } - return delay -} - -// Cancel is shorthand for CancelAt(time.Now()). -func (r *Reservation) Cancel() { - r.CancelAt(time.Now()) -} - -// CancelAt indicates that the reservation holder will not perform the reserved action -// and reverses the effects of this Reservation on the rate limit as much as possible, -// considering that other reservations may have already been made. -func (r *Reservation) CancelAt(t time.Time) { - if !r.ok { - return - } - - r.lim.mu.Lock() - defer r.lim.mu.Unlock() - - if r.lim.limit == Inf || r.tokens == 0 || r.timeToAct.Before(t) { - return - } - - // calculate tokens to restore - // The duration between lim.lastEvent and r.timeToAct tells us how many tokens were reserved - // after r was obtained. These tokens should not be restored. - restoreTokens := float64(r.tokens) - r.limit.tokensFromDuration(r.lim.lastEvent.Sub(r.timeToAct)) - if restoreTokens <= 0 { - return - } - // advance time to now - tokens := r.lim.advance(t) - // calculate new number of tokens - tokens += restoreTokens - if burst := float64(r.lim.burst); tokens > burst { - tokens = burst - } - // update state - r.lim.last = t - r.lim.tokens = tokens - if r.timeToAct == r.lim.lastEvent { - prevEvent := r.timeToAct.Add(r.limit.durationFromTokens(float64(-r.tokens))) - if !prevEvent.Before(t) { - r.lim.lastEvent = prevEvent - } - } -} - -// Reserve is shorthand for ReserveN(time.Now(), 1). -func (lim *Limiter) Reserve() *Reservation { - return lim.ReserveN(time.Now(), 1) -} - -// ReserveN returns a Reservation that indicates how long the caller must wait before n events happen. -// The Limiter takes this Reservation into account when allowing future events. -// The returned Reservation’s OK() method returns false if n exceeds the Limiter's burst size. -// Usage example: -// -// r := lim.ReserveN(time.Now(), 1) -// if !r.OK() { -// // Not allowed to act! Did you remember to set lim.burst to be > 0 ? -// return -// } -// time.Sleep(r.Delay()) -// Act() -// -// Use this method if you wish to wait and slow down in accordance with the rate limit without dropping events. -// If you need to respect a deadline or cancel the delay, use Wait instead. -// To drop or skip events exceeding rate limit, use Allow instead. -func (lim *Limiter) ReserveN(t time.Time, n int) *Reservation { - r := lim.reserveN(t, n, InfDuration) - return &r -} - -// Wait is shorthand for WaitN(ctx, 1). -func (lim *Limiter) Wait(ctx context.Context) (err error) { - return lim.WaitN(ctx, 1) -} - -// WaitN blocks until lim permits n events to happen. -// It returns an error if n exceeds the Limiter's burst size, the Context is -// canceled, or the expected wait time exceeds the Context's Deadline. -// The burst limit is ignored if the rate limit is Inf. -func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) { - // The test code calls lim.wait with a fake timer generator. - // This is the real timer generator. - newTimer := func(d time.Duration) (<-chan time.Time, func() bool, func()) { - timer := time.NewTimer(d) - return timer.C, timer.Stop, func() {} - } - - return lim.wait(ctx, n, time.Now(), newTimer) -} - -// wait is the internal implementation of WaitN. -func (lim *Limiter) wait(ctx context.Context, n int, t time.Time, newTimer func(d time.Duration) (<-chan time.Time, func() bool, func())) error { - lim.mu.Lock() - burst := lim.burst - limit := lim.limit - lim.mu.Unlock() - - if n > burst && limit != Inf { - return fmt.Errorf("rate: Wait(n=%d) exceeds limiter's burst %d", n, burst) - } - // Check if ctx is already cancelled - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - // Determine wait limit - waitLimit := InfDuration - if deadline, ok := ctx.Deadline(); ok { - waitLimit = deadline.Sub(t) - } - // Reserve - r := lim.reserveN(t, n, waitLimit) - if !r.ok { - return fmt.Errorf("rate: Wait(n=%d) would exceed context deadline", n) - } - // Wait if necessary - delay := r.DelayFrom(t) - if delay == 0 { - return nil - } - ch, stop, advance := newTimer(delay) - defer stop() - advance() // only has an effect when testing - select { - case <-ch: - // We can proceed. - return nil - case <-ctx.Done(): - // Context was canceled before we could proceed. Cancel the - // reservation, which may permit other events to proceed sooner. - r.Cancel() - return ctx.Err() - } -} - -// SetLimit is shorthand for SetLimitAt(time.Now(), newLimit). -func (lim *Limiter) SetLimit(newLimit Limit) { - lim.SetLimitAt(time.Now(), newLimit) -} - -// SetLimitAt sets a new Limit for the limiter. The new Limit, and Burst, may be violated -// or underutilized by those which reserved (using Reserve or Wait) but did not yet act -// before SetLimitAt was called. -func (lim *Limiter) SetLimitAt(t time.Time, newLimit Limit) { - lim.mu.Lock() - defer lim.mu.Unlock() - - tokens := lim.advance(t) - - lim.last = t - lim.tokens = tokens - lim.limit = newLimit -} - -// SetBurst is shorthand for SetBurstAt(time.Now(), newBurst). -func (lim *Limiter) SetBurst(newBurst int) { - lim.SetBurstAt(time.Now(), newBurst) -} - -// SetBurstAt sets a new burst size for the limiter. -func (lim *Limiter) SetBurstAt(t time.Time, newBurst int) { - lim.mu.Lock() - defer lim.mu.Unlock() - - tokens := lim.advance(t) - - lim.last = t - lim.tokens = tokens - lim.burst = newBurst -} - -// reserveN is a helper method for AllowN, ReserveN, and WaitN. -// maxFutureReserve specifies the maximum reservation wait duration allowed. -// reserveN returns Reservation, not *Reservation, to avoid allocation in AllowN and WaitN. -func (lim *Limiter) reserveN(t time.Time, n int, maxFutureReserve time.Duration) Reservation { - lim.mu.Lock() - defer lim.mu.Unlock() - - if lim.limit == Inf { - return Reservation{ - ok: true, - lim: lim, - tokens: n, - timeToAct: t, - } - } - - tokens := lim.advance(t) - - // Calculate the remaining number of tokens resulting from the request. - tokens -= float64(n) - - // Calculate the wait duration - var waitDuration time.Duration - if tokens < 0 { - waitDuration = lim.limit.durationFromTokens(-tokens) - } - - // Decide result - ok := n <= lim.burst && waitDuration <= maxFutureReserve - - // Prepare reservation - r := Reservation{ - ok: ok, - lim: lim, - limit: lim.limit, - } - if ok { - r.tokens = n - r.timeToAct = t.Add(waitDuration) - - // Update state - lim.last = t - lim.tokens = tokens - lim.lastEvent = r.timeToAct - } - - return r -} - -// advance calculates and returns an updated number of tokens for lim -// resulting from the passage of time. -// lim is not changed. -// advance requires that lim.mu is held. -func (lim *Limiter) advance(t time.Time) (newTokens float64) { - last := lim.last - if t.Before(last) { - last = t - } - - // Calculate the new number of tokens, due to time that passed. - elapsed := t.Sub(last) - delta := lim.limit.tokensFromDuration(elapsed) - tokens := lim.tokens + delta - if burst := float64(lim.burst); tokens > burst { - tokens = burst - } - return tokens -} - -// durationFromTokens is a unit conversion function from the number of tokens to the duration -// of time it takes to accumulate them at a rate of limit tokens per second. -func (limit Limit) durationFromTokens(tokens float64) time.Duration { - if limit <= 0 { - return InfDuration - } - - duration := (tokens / float64(limit)) * float64(time.Second) - - // Cap the duration to the maximum representable int64 value, to avoid overflow. - if duration > float64(math.MaxInt64) { - return InfDuration - } - - return time.Duration(duration) -} - -// tokensFromDuration is a unit conversion function from a time duration to the number of tokens -// which could be accumulated during that duration at a rate of limit tokens per second. -func (limit Limit) tokensFromDuration(d time.Duration) float64 { - if limit <= 0 { - return 0 - } - return d.Seconds() * float64(limit) -} diff --git a/src/code.cloudfoundry.org/vendor/golang.org/x/time/rate/sometimes.go b/src/code.cloudfoundry.org/vendor/golang.org/x/time/rate/sometimes.go deleted file mode 100644 index 6ba99ddb..00000000 --- a/src/code.cloudfoundry.org/vendor/golang.org/x/time/rate/sometimes.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package rate - -import ( - "sync" - "time" -) - -// Sometimes will perform an action occasionally. The First, Every, and -// Interval fields govern the behavior of Do, which performs the action. -// A zero Sometimes value will perform an action exactly once. -// -// # Example: logging with rate limiting -// -// var sometimes = rate.Sometimes{First: 3, Interval: 10*time.Second} -// func Spammy() { -// sometimes.Do(func() { log.Info("here I am!") }) -// } -type Sometimes struct { - First int // if non-zero, the first N calls to Do will run f. - Every int // if non-zero, every Nth call to Do will run f. - Interval time.Duration // if non-zero and Interval has elapsed since f's last run, Do will run f. - - mu sync.Mutex - count int // number of Do calls - last time.Time // last time f was run -} - -// Do runs the function f as allowed by First, Every, and Interval. -// -// The model is a union (not intersection) of filters. The first call to Do -// always runs f. Subsequent calls to Do run f if allowed by First or Every or -// Interval. -// -// A non-zero First:N causes the first N Do(f) calls to run f. -// -// A non-zero Every:M causes every Mth Do(f) call, starting with the first, to -// run f. -// -// A non-zero Interval causes Do(f) to run f if Interval has elapsed since -// Do last ran f. -// -// Specifying multiple filters produces the union of these execution streams. -// For example, specifying both First:N and Every:M causes the first N Do(f) -// calls and every Mth Do(f) call, starting with the first, to run f. See -// Examples for more. -// -// If Do is called multiple times simultaneously, the calls will block and run -// serially. Therefore, Do is intended for lightweight operations. -// -// Because a call to Do may block until f returns, if f causes Do to be called, -// it will deadlock. -func (s *Sometimes) Do(f func()) { - s.mu.Lock() - defer s.mu.Unlock() - if s.count == 0 || - (s.First > 0 && s.count < s.First) || - (s.Every > 0 && s.count%s.Every == 0) || - (s.Interval > 0 && time.Since(s.last) >= s.Interval) { - f() - s.last = time.Now() - } - s.count++ -} diff --git a/src/code.cloudfoundry.org/vendor/modules.txt b/src/code.cloudfoundry.org/vendor/modules.txt index 909da744..a3fa7d59 100644 --- a/src/code.cloudfoundry.org/vendor/modules.txt +++ b/src/code.cloudfoundry.org/vendor/modules.txt @@ -30,11 +30,6 @@ github.com/google/go-cmp/cmp/internal/diff github.com/google/go-cmp/cmp/internal/flags github.com/google/go-cmp/cmp/internal/function github.com/google/go-cmp/cmp/internal/value -# github.com/google/go-tpm v0.9.3 -## explicit; go 1.22 -github.com/google/go-tpm/legacy/tpm2 -github.com/google/go-tpm/tpmutil -github.com/google/go-tpm/tpmutil/tbs # github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e ## explicit; go 1.23 github.com/google/pprof/profile @@ -42,11 +37,6 @@ github.com/google/pprof/profile ## explicit; go 1.22 github.com/klauspost/compress/flate github.com/klauspost/compress/internal/le -github.com/klauspost/compress/internal/race -github.com/klauspost/compress/s2 -# github.com/minio/highwayhash v1.0.3 -## explicit; go 1.15 -github.com/minio/highwayhash # github.com/nats-io/gnatsd v1.4.1 => github.com/nats-io/gnatsd v1.4.1 ## explicit github.com/nats-io/gnatsd @@ -56,26 +46,6 @@ github.com/nats-io/gnatsd/server github.com/nats-io/gnatsd/server/pse # github.com/nats-io/go-nats v1.7.2 ## explicit -# github.com/nats-io/jwt/v2 v2.7.3 -## explicit; go 1.22 -github.com/nats-io/jwt/v2 -# github.com/nats-io/nats-server/v2 v2.11.0 -## explicit; go 1.23.0 -github.com/nats-io/nats-server/v2 -github.com/nats-io/nats-server/v2/conf -github.com/nats-io/nats-server/v2/internal/fastrand -github.com/nats-io/nats-server/v2/internal/ldap -github.com/nats-io/nats-server/v2/logger -github.com/nats-io/nats-server/v2/server -github.com/nats-io/nats-server/v2/server/avl -github.com/nats-io/nats-server/v2/server/certidp -github.com/nats-io/nats-server/v2/server/certstore -github.com/nats-io/nats-server/v2/server/gsl -github.com/nats-io/nats-server/v2/server/pse -github.com/nats-io/nats-server/v2/server/stree -github.com/nats-io/nats-server/v2/server/sysmem -github.com/nats-io/nats-server/v2/server/thw -github.com/nats-io/nats-server/v2/server/tpm # github.com/nats-io/nats.go v1.40.1 ## explicit; go 1.23.0 github.com/nats-io/nats.go @@ -153,26 +123,17 @@ go.step.sm/crypto/keyutil go.step.sm/crypto/pemutil go.step.sm/crypto/randutil go.step.sm/crypto/x25519 -# go.uber.org/automaxprocs v1.6.0 -## explicit; go 1.20 -go.uber.org/automaxprocs/internal/cgroups -go.uber.org/automaxprocs/internal/runtime -go.uber.org/automaxprocs/maxprocs # golang.org/x/crypto v0.36.0 ## explicit; go 1.23.0 golang.org/x/crypto/bcrypt golang.org/x/crypto/blake2b golang.org/x/crypto/blowfish golang.org/x/crypto/chacha20 -golang.org/x/crypto/chacha20poly1305 -golang.org/x/crypto/cryptobyte -golang.org/x/crypto/cryptobyte/asn1 golang.org/x/crypto/curve25519 golang.org/x/crypto/internal/alias golang.org/x/crypto/internal/poly1305 golang.org/x/crypto/nacl/box golang.org/x/crypto/nacl/secretbox -golang.org/x/crypto/ocsp golang.org/x/crypto/pbkdf2 golang.org/x/crypto/salsa20/salsa golang.org/x/crypto/scrypt @@ -212,9 +173,6 @@ golang.org/x/text/internal/utf8internal golang.org/x/text/language golang.org/x/text/runes golang.org/x/text/transform -# golang.org/x/time v0.11.0 -## explicit; go 1.23.0 -golang.org/x/time/rate # golang.org/x/tools v0.31.0 ## explicit; go 1.23.0 golang.org/x/tools/cover