diff --git a/gateway/Gopkg.lock b/gateway/Gopkg.lock index c7fa1b21..85a48d8d 100644 --- a/gateway/Gopkg.lock +++ b/gateway/Gopkg.lock @@ -54,27 +54,24 @@ version = "v1.0.1" [[projects]] - digest = "1:2ca73053216eb11c8eea2855c8099ad82773638522f91cc0542ec9759163ff3c" - name = "github.com/nats-io/go-nats" + digest = "1:9b8ee267e79a1730cde2e326cc53035913fce52ceef48142c24f3a15e3c65cb3" + name = "github.com/nats-io/jwt" + packages = ["."] + pruneopts = "UT" + revision = "0c3fc7aed8bb2534e7bfdf0968a75890402d48cd" + version = "v0.3.2" + +[[projects]] + digest = "1:2e964212558ab7559cec820aebdf9f04c369f27aafe8581932ae5f0b6826572b" + name = "github.com/nats-io/nats.go" packages = [ ".", "encoders/builtin", "util", ] pruneopts = "UT" - revision = "70fe06cee50d4b6f98248d9675fb55f2a3aa7228" - version = "v1.7.2" - -[[projects]] - digest = "1:25a0458641a719a972fa7850c900aaae6b12e74efa6fe35c9bd7a772543bc635" - name = "github.com/nats-io/go-nats-streaming" - packages = [ - ".", - "pb", - ] - pruneopts = "UT" - revision = "7cd94c239033c0405a43454be55a0810d3a3ca89" - version = "v0.4.4" + revision = "4d5d5d17e7df8f8e3bf3c1426a5ed759012faab8" + version = "v1.9.2" [[projects]] digest = "1:237d85e4f5e91ac6cfc13bad508627570ff29efa28f523d41aac2d39ca15950f" @@ -92,6 +89,17 @@ revision = "4b96681fa6d28dd0ab5fe79bac63b3a493d9ee94" version = "v1.0.1" +[[projects]] + digest = "1:30147647e2ded41b85f4991e306053e478c009a817f4b53ce5e55ee7b5d2fbf6" + name = "github.com/nats-io/stan.go" + packages = [ + ".", + "pb", + ] + pruneopts = "UT" + revision = "e2d4c025a5eea41d9566dc8e978c7ee697470b64" + version = "v0.6.0" + [[projects]] digest = "1:340f4e2e095ead4e0a15b4646da3e4533f8b6520e3a382eaf586e8166f3bbcb5" name = "github.com/openfaas/faas" @@ -112,15 +120,15 @@ version = "0.12.0" [[projects]] - digest = "1:b7c06bdc590871ac5c88f8a85acc9f892c994d6327de311ec5871aaecb6f9489" + digest = "1:1cf86a1a93c110ebcf836468bd917e8c116d6d2fc7612829c15e946b02dbf864" name = "github.com/openfaas/nats-queue-worker" packages = [ "handler", "nats", ] pruneopts = "UT" - revision = "dea1c90b8cc66dc73597b7531a4fd29a32b5f88c" - version = "0.9.0" + revision = "a1835cb71db56e6b814b91df027acf62425a76ad" + version = "0.10.0" [[projects]] digest = "1:eb04f69c8991e52eff33c428bd729e04208bf03235be88e4df0d88497c6861b9" diff --git a/gateway/Gopkg.toml b/gateway/Gopkg.toml index 297182c7..054e9f9d 100644 --- a/gateway/Gopkg.toml +++ b/gateway/Gopkg.toml @@ -12,7 +12,7 @@ [[constraint]] name = "github.com/openfaas/nats-queue-worker" - version = "0.9.0" + version = "0.10.0" [[constraint]] name = "github.com/prometheus/client_golang" diff --git a/gateway/vendor/github.com/gorilla/mux/go.mod b/gateway/vendor/github.com/gorilla/mux/go.mod index ba9be08f..cfc8ede5 100644 --- a/gateway/vendor/github.com/gorilla/mux/go.mod +++ b/gateway/vendor/github.com/gorilla/mux/go.mod @@ -1,3 +1 @@ module github.com/gorilla/mux - -go 1.13 diff --git a/gateway/vendor/github.com/nats-io/go-nats/.travis.yml b/gateway/vendor/github.com/nats-io/go-nats/.travis.yml deleted file mode 100644 index b46060de..00000000 --- a/gateway/vendor/github.com/nats-io/go-nats/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -language: go -sudo: false -go: -- 1.11.x -- 1.10.x -go_import_path: github.com/nats-io/go-nats -install: -- go get -t ./... -- go get github.com/nats-io/gnatsd -- go get github.com/mattn/goveralls -- go get github.com/wadey/gocovmerge -- go get -u honnef.co/go/tools/cmd/staticcheck -- go get -u github.com/client9/misspell/cmd/misspell -before_script: -- $(exit $(go fmt ./... | wc -l)) -- go vet ./... -- misspell -error -locale US . -- staticcheck -ignore "$(cat staticcheck.ignore)" ./... -script: -- go test -i -race ./... -- if [[ "$TRAVIS_GO_VERSION" =~ 1.11 ]]; then ./scripts/cov.sh TRAVIS; else go test -race ./...; fi diff --git a/gateway/vendor/github.com/nats-io/go-nats/staticcheck.ignore b/gateway/vendor/github.com/nats-io/go-nats/staticcheck.ignore deleted file mode 100644 index 25bbf020..00000000 --- a/gateway/vendor/github.com/nats-io/go-nats/staticcheck.ignore +++ /dev/null @@ -1,4 +0,0 @@ -github.com/nats-io/go-nats/*_test.go:SA2002 -github.com/nats-io/go-nats/*/*_test.go:SA2002 -github.com/nats-io/go-nats/test/context_test.go:SA1012 -github.com/nats-io/go-nats/nats.go:SA6000 diff --git a/gateway/vendor/github.com/nats-io/jwt/.gitignore b/gateway/vendor/github.com/nats-io/jwt/.gitignore new file mode 100644 index 00000000..7117a678 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/.gitignore @@ -0,0 +1,16 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# IDE Files +.vscode +.idea/ \ No newline at end of file diff --git a/gateway/vendor/github.com/nats-io/go-nats-streaming/.travis.yml b/gateway/vendor/github.com/nats-io/jwt/.travis.yml similarity index 82% rename from gateway/vendor/github.com/nats-io/go-nats-streaming/.travis.yml rename to gateway/vendor/github.com/nats-io/jwt/.travis.yml index e45c042c..50e27a6b 100644 --- a/gateway/vendor/github.com/nats-io/go-nats-streaming/.travis.yml +++ b/gateway/vendor/github.com/nats-io/jwt/.travis.yml @@ -1,22 +1,22 @@ language: go sudo: false go: -- 1.11.x +- 1.13.x - 1.12.x + install: - go get -t ./... -- go get github.com/nats-io/nats-streaming-server - go get github.com/mattn/goveralls - go get github.com/wadey/gocovmerge - go get -u honnef.co/go/tools/cmd/staticcheck - go get -u github.com/client9/misspell/cmd/misspell + before_script: - $(exit $(go fmt ./... | wc -l)) - go vet ./... - misspell -error -locale US . - staticcheck ./... + script: -- go test -i -race ./... - go test -v -race ./... -after_success: - if [[ "$TRAVIS_GO_VERSION" =~ 1.12 ]]; then ./scripts/cov.sh TRAVIS; fi diff --git a/gateway/vendor/github.com/nats-io/go-nats/LICENSE b/gateway/vendor/github.com/nats-io/jwt/LICENSE similarity index 100% rename from gateway/vendor/github.com/nats-io/go-nats/LICENSE rename to gateway/vendor/github.com/nats-io/jwt/LICENSE diff --git a/gateway/vendor/github.com/nats-io/jwt/Makefile b/gateway/vendor/github.com/nats-io/jwt/Makefile new file mode 100644 index 00000000..95e468e1 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/Makefile @@ -0,0 +1,19 @@ +.PHONY: test cover + +build: + go build + +test: + gofmt -s -w *.go + goimports -w *.go + go vet ./... + go test -v + go test -v --race + staticcheck ./... + +fmt: + gofmt -w -s *.go + +cover: + go test -v -covermode=count -coverprofile=coverage.out + go tool cover -html=coverage.out diff --git a/gateway/vendor/github.com/nats-io/jwt/README.md b/gateway/vendor/github.com/nats-io/jwt/README.md new file mode 100644 index 00000000..d3cb88ac --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/README.md @@ -0,0 +1,54 @@ +# JWT +A [JWT](https://jwt.io/) implementation that uses [nkeys](https://github.com/nats-io/nkeys) to digitally sign JWT tokens. +Nkeys use [Ed25519](https://ed25519.cr.yp.to/) to provide authentication of JWT claims. + + +[![License Apache 2](https://img.shields.io/badge/License-Apache2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) +[![ReportCard](http://goreportcard.com/badge/nats-io/jwt)](http://goreportcard.com/report/nats-io/jwt) +[![Build Status](https://travis-ci.org/nats-io/jwt.svg?branch=master)](http://travis-ci.org/nats-io/jwt) +[![GoDoc](http://godoc.org/github.com/nats-io/jwt?status.png)](http://godoc.org/github.com/nats-io/jwt) +[![Coverage Status](https://coveralls.io/repos/github/nats-io/jwt/badge.svg?branch=master&t=NmEFup)](https://coveralls.io/github/nats-io/jwt?branch=master) + +```go +// Need a private key to sign the claim, nkeys makes it easy to create +kp, err := nkeys.CreateAccount() +if err != nil { + t.Fatal("unable to create account key", err) +} + +pk, err := kp.PublicKey() +if err != nil { + t.Fatal("error getting public key", err) +} + +// create a new claim +claims := NewAccountClaims(pk) +claims.Expires = time.Now().Add(time.Duration(time.Hour)).Unix() + + +// add details by modifying claims.Account + +// serialize the claim to a JWT token +token, err := claims.Encode(kp) +if err != nil { + t.Fatal("error encoding token", err) +} + +// on the receiving side, decode the token +c, err := DecodeAccountClaims(token) +if err != nil { + t.Fatal(err) +} + +// if the token was decoded, it means that it +// validated and it wasn't tampered. the remaining and +// required test is to insure the issuer is trusted +pk, err := kp.PublicKey() +if err != nil { + t.Fatalf("unable to read public key: %v", err) +} + +if c.Issuer != pk { + t.Fatalf("the public key is not trusted") +} +``` \ No newline at end of file diff --git a/gateway/vendor/github.com/nats-io/jwt/ReleaseNotes.md b/gateway/vendor/github.com/nats-io/jwt/ReleaseNotes.md new file mode 100644 index 00000000..500965ea --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/ReleaseNotes.md @@ -0,0 +1,5 @@ +# Release Notes + +## 0.3.0 + +* Removed revocation claims in favor of timestamp-based revocation maps in account and export claims. diff --git a/gateway/vendor/github.com/nats-io/jwt/account_claims.go b/gateway/vendor/github.com/nats-io/jwt/account_claims.go new file mode 100644 index 00000000..945bd987 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/account_claims.go @@ -0,0 +1,222 @@ +/* + * 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 ( + "errors" + "sort" + "time" + + "github.com/nats-io/nkeys" +) + +// NoLimit is used to indicate a limit field is unlimited in value. +const NoLimit = -1 + +// OperatorLimits are used to limit access by an account +type OperatorLimits struct { + Subs int64 `json:"subs,omitempty"` // Max number of subscriptions + Conn int64 `json:"conn,omitempty"` // Max number of active connections + LeafNodeConn int64 `json:"leaf,omitempty"` // Max number of active leaf node connections + Imports int64 `json:"imports,omitempty"` // Max number of imports + Exports int64 `json:"exports,omitempty"` // Max number of exports + Data int64 `json:"data,omitempty"` // Max number of bytes + Payload int64 `json:"payload,omitempty"` // Max message payload + WildcardExports bool `json:"wildcards,omitempty"` // Are wildcards allowed in exports +} + +// IsEmpty returns true if all of the limits are 0/false. +func (o *OperatorLimits) IsEmpty() bool { + return *o == OperatorLimits{} +} + +// IsUnlimited returns true if all limits are +func (o *OperatorLimits) IsUnlimited() bool { + return *o == OperatorLimits{NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, true} +} + +// Validate checks that the operator limits contain valid values +func (o *OperatorLimits) Validate(vr *ValidationResults) { + // negative values mean unlimited, so all numbers are valid +} + +// Account holds account specific claims data +type Account struct { + Imports Imports `json:"imports,omitempty"` + Exports Exports `json:"exports,omitempty"` + Identities []Identity `json:"identity,omitempty"` + Limits OperatorLimits `json:"limits,omitempty"` + SigningKeys StringList `json:"signing_keys,omitempty"` + Revocations RevocationList `json:"revocations,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) + + for _, i := range a.Identities { + i.Validate(vr) + } + + 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") + } + } + } + } + + for _, k := range a.SigningKeys { + if !nkeys.IsValidPublicAccountKey(k) { + vr.AddError("%s is not an account public key", k) + } + } +} + +// 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{} + // Set to unlimited to start. We do it this way so we get compiler + // errors if we add to the OperatorLimits. + c.Limits = OperatorLimits{NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, NoLimit, true} + c.Subject = subject + return c +} + +// Encode converts account claims into a JWT string +func (a *AccountClaims) Encode(pair nkeys.KeyPair) (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.ClaimsData.Type = AccountClaim + return a.ClaimsData.Encode(pair, a) +} + +// DecodeAccountClaims decodes account claims from a JWT string +func DecodeAccountClaims(token string) (*AccountClaims, error) { + v := AccountClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, 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 len(a.Identities) > 0 { + vr.AddWarning("self-signed account JWTs shouldn't contain identity proofs") + } + if !a.Limits.IsEmpty() { + vr.AddWarning("self-signed account JWTs shouldn't contain operator limits") + } + } +} + +// 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 +} + +// DidSign checks the claims against the account's public key and its signing keys +func (a *AccountClaims) DidSign(op Claims) bool { + if op != nil { + issuer := op.Claims().Issuer + if issuer == a.Subject { + return true + } + return a.SigningKeys.Contains(issuer) + } + return false +} + +// Revoke enters a revocation by publickey using time.Now(). +func (a *AccountClaims) Revoke(pubKey string) { + a.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 (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) +} + +// IsRevokedAt 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 time.Now() but other time's can +// be used for testing. +func (a *AccountClaims) IsRevokedAt(pubKey string, timestamp time.Time) bool { + return a.Revocations.IsRevoked(pubKey, timestamp) +} + +// IsRevoked checks if the public key is in the revoked list with time.Now() +func (a *AccountClaims) IsRevoked(pubKey string) bool { + return a.Revocations.IsRevoked(pubKey, time.Now()) +} diff --git a/gateway/vendor/github.com/nats-io/jwt/activation_claims.go b/gateway/vendor/github.com/nats-io/jwt/activation_claims.go new file mode 100644 index 00000000..99228a75 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/activation_claims.go @@ -0,0 +1,166 @@ +/* + * 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 ( + "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:"type,omitempty"` + Limits +} + +// 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 export type: %q", a.ImportType) + } + + if a.IsService() { + if a.ImportSubject.HasWildCards() { + vr.AddError("services cannot have wildcard subject: %q", a.ImportSubject) + } + } + + a.ImportSubject.Validate(vr) + a.Limits.Validate(vr) +} + +// ActivationClaims holds the data specific to an activation JWT +type ActivationClaims struct { + ClaimsData + Activation `json:"nats,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"` +} + +// 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) { + if !nkeys.IsValidPublicAccountKey(a.ClaimsData.Subject) { + return "", errors.New("expected subject to be an account") + } + a.ClaimsData.Type = ActivationClaim + return a.ClaimsData.Encode(pair, a) +} + +// DecodeActivationClaims tries to create an activation claim from a JWT string +func DecodeActivationClaims(token string) (*ActivationClaims, error) { + v := ActivationClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, 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.ClaimsData.Validate(vr) + a.Activation.Validate(vr) + if a.IssuerAccount != "" && !nkeys.IsValidPublicAccountKey(a.IssuerAccount) { + vr.AddError("account_id is not an account public key") + } +} + +// 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/gateway/vendor/github.com/nats-io/jwt/claims.go b/gateway/vendor/github.com/nats-io/jwt/claims.go new file mode 100644 index 00000000..d402bcc5 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/claims.go @@ -0,0 +1,302 @@ +/* + * 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 ( + "crypto/sha512" + "encoding/base32" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "github.com/nats-io/nkeys" +) + +// ClaimType is used to indicate the type of JWT being stored in a Claim +type ClaimType string + +const ( + // AccountClaim is the type of an Account JWT + AccountClaim = "account" + //ActivationClaim is the type of an activation JWT + ActivationClaim = "activation" + //UserClaim is the type of an user JWT + UserClaim = "user" + //ServerClaim is the type of an server JWT + ServerClaim = "server" + //ClusterClaim is the type of an cluster JWT + ClusterClaim = "cluster" + //OperatorClaim is the type of an operator JWT + OperatorClaim = "operator" +) + +// Claims is a JWT claims +type Claims interface { + Claims() *ClaimsData + Encode(kp nkeys.KeyPair) (string, error) + ExpectedPrefixes() []nkeys.PrefixByte + Payload() interface{} + String() string + Validate(vr *ValidationResults) + Verify(payload string, sig []byte) bool +} + +// 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"` + Tags TagList `json:"tags,omitempty"` + Type ClaimType `json:"type,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) (string, error) { + if header == nil { + return "", errors.New("header is required") + } + + if kp == nil { + return "", errors.New("keypair is required") + } + + 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 = string(issuerBytes) + c.IssuedAt = time.Now().UTC().Unix() + + c.ID, err = c.hash() + if err != nil { + return "", err + } + + payload, err := serialize(claim) + if err != nil { + return "", err + } + + sig, err := kp.Sign([]byte(payload)) + if err != nil { + return "", err + } + eSig := encodeToString(sig) + return fmt.Sprintf("%s.%s.%s", h, payload, 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) (string, error) { + return c.doEncode(&Header{TokenTypeJwt, AlgorithmNkey}, kp, payload) +} + +// 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 +} + +// 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, target Claims) error { + // must have 3 chunks + chunks := strings.Split(token, ".") + if len(chunks) != 3 { + return errors.New("expected 3 chunks") + } + + _, err := parseHeaders(chunks[0]) + if err != nil { + return err + } + + if err := parseClaims(chunks[1], target); err != nil { + return err + } + + sig, err := decodeString(chunks[2]) + if err != nil { + return err + } + + if !target.Verify(chunks[1], sig) { + return errors.New("claim failed signature verification") + } + + prefixes := target.ExpectedPrefixes() + if prefixes != nil { + ok := false + issuer := target.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.PrefixByteServer: + if nkeys.IsValidPublicServerKey(issuer) { + ok = true + } + case nkeys.PrefixByteCluster: + if nkeys.IsValidPublicClusterKey(issuer) { + ok = true + } + case nkeys.PrefixByteUser: + if nkeys.IsValidPublicUserKey(issuer) { + ok = true + } + } + } + if !ok { + return fmt.Errorf("unable to validate expected prefixes - %v", prefixes) + } + } + + return nil +} diff --git a/gateway/vendor/github.com/nats-io/jwt/cluster_claims.go b/gateway/vendor/github.com/nats-io/jwt/cluster_claims.go new file mode 100644 index 00000000..bbfcf06f --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/cluster_claims.go @@ -0,0 +1,94 @@ +/* + * 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" + + "github.com/nats-io/nkeys" +) + +// Cluster stores the cluster specific elements of a cluster JWT +type Cluster struct { + Trust []string `json:"identity,omitempty"` + Accounts []string `json:"accts,omitempty"` + AccountURL string `json:"accturl,omitempty"` + OperatorURL string `json:"opurl,omitempty"` +} + +// Validate checks the cluster and permissions for a cluster JWT +func (c *Cluster) Validate(vr *ValidationResults) { + // fixme validate cluster data +} + +// ClusterClaims defines the data in a cluster JWT +type ClusterClaims struct { + ClaimsData + Cluster `json:"nats,omitempty"` +} + +// NewClusterClaims creates a new cluster JWT with the specified subject/public key +func NewClusterClaims(subject string) *ClusterClaims { + if subject == "" { + return nil + } + c := &ClusterClaims{} + c.Subject = subject + return c +} + +// Encode tries to turn the cluster claims into a JWT string +func (c *ClusterClaims) Encode(pair nkeys.KeyPair) (string, error) { + if !nkeys.IsValidPublicClusterKey(c.Subject) { + return "", errors.New("expected subject to be a cluster public key") + } + c.ClaimsData.Type = ClusterClaim + return c.ClaimsData.Encode(pair, c) +} + +// DecodeClusterClaims tries to parse cluster claims from a JWT string +func DecodeClusterClaims(token string) (*ClusterClaims, error) { + v := ClusterClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +func (c *ClusterClaims) String() string { + return c.ClaimsData.String(c) +} + +// Payload returns the cluster specific data +func (c *ClusterClaims) Payload() interface{} { + return &c.Cluster +} + +// Validate checks the generic and cluster data in the cluster claims +func (c *ClusterClaims) Validate(vr *ValidationResults) { + c.ClaimsData.Validate(vr) + c.Cluster.Validate(vr) +} + +// ExpectedPrefixes defines the types that can encode a cluster JWT, operator or cluster +func (c *ClusterClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return []nkeys.PrefixByte{nkeys.PrefixByteOperator, nkeys.PrefixByteCluster} +} + +// Claims returns the generic data +func (c *ClusterClaims) Claims() *ClaimsData { + return &c.ClaimsData +} diff --git a/gateway/vendor/github.com/nats-io/jwt/creds_utils.go b/gateway/vendor/github.com/nats-io/jwt/creds_utils.go new file mode 100644 index 00000000..bb913dc1 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/creds_utils.go @@ -0,0 +1,203 @@ +package jwt + +import ( + "bytes" + "errors" + "fmt" + "regexp" + "strings" + + "github.com/nats-io/nkeys" +) + +// DecorateJWT returns a decorated JWT that describes the kind of JWT +func DecorateJWT(jwtString string) ([]byte, error) { + gc, err := DecodeGeneric(jwtString) + if err != nil { + return nil, err + } + return formatJwt(string(gc.Type), 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,}[^\n]*[-]{3,}\n)(.+)(?:\n\s*[-]{3,}[^\n]*[-]{3,}\n))`) + +// 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 := DecodeGeneric(jwtString) + if err != nil { + return nil, err + } + if gc.Type != UserClaim { + return nil, fmt.Errorf("%q cannot be serialized as a user config", string(gc.Type)) + } + + w := bytes.NewBuffer(nil) + + jd, err := formatJwt(string(gc.Type), 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 +} diff --git a/gateway/vendor/github.com/nats-io/jwt/exports.go b/gateway/vendor/github.com/nats-io/jwt/exports.go new file mode 100644 index 00000000..5578f988 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/exports.go @@ -0,0 +1,236 @@ +/* + * 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 ( + "fmt" + "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/master/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 int `json:"sampling,omitempty"` + Results Subject `json:"results"` +} + +func (sl *ServiceLatency) Validate(vr *ValidationResults) { + 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"` + Latency *ServiceLatency `json:"service_latency,omitempty"` +} + +// 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 resopnse 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.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() && e.ResponseType != "" { + vr.AddError("invalid response type for stream: %q", e.ResponseType) + } + if e.Latency != nil { + if !e.IsService() { + vr.AddError("latency tracking only permitted for services") + } + e.Latency.Validate(vr) + } + e.Subject.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) +} + +// IsRevokedAt 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 time.Now() but other time's can +// be used for testing. +func (e *Export) IsRevokedAt(pubKey string, timestamp time.Time) bool { + return e.Revocations.IsRevoked(pubKey, timestamp) +} + +// IsRevoked checks if the public key is in the revoked list with time.Now() +func (e *Export) IsRevoked(pubKey string) bool { + return e.Revocations.IsRevoked(pubKey, time.Now()) +} + +// 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) error { + var serviceSubjects []Subject + var streamSubjects []Subject + + for _, v := range *e { + 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) + + return nil +} + +// 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/gateway/vendor/github.com/nats-io/jwt/genericlaims.go b/gateway/vendor/github.com/nats-io/jwt/genericlaims.go new file mode 100644 index 00000000..94cd86e0 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/genericlaims.go @@ -0,0 +1,73 @@ +/* + * 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 "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) { + v := GenericClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, 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) +} + +// 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 +} diff --git a/gateway/vendor/github.com/nats-io/jwt/go.mod b/gateway/vendor/github.com/nats-io/jwt/go.mod new file mode 100644 index 00000000..a780dde9 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/go.mod @@ -0,0 +1,3 @@ +module github.com/nats-io/jwt + +require github.com/nats-io/nkeys v0.1.3 diff --git a/gateway/vendor/github.com/nats-io/jwt/go.sum b/gateway/vendor/github.com/nats-io/jwt/go.sum new file mode 100644 index 00000000..9baf67f5 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/go.sum @@ -0,0 +1,9 @@ +github.com/nats-io/nkeys v0.1.3 h1:6JrEfig+HzTH85yxzhSVbjHRJv9cn0p6n3IngIcM5/k= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/gateway/vendor/github.com/nats-io/jwt/header.go b/gateway/vendor/github.com/nats-io/jwt/header.go new file mode 100644 index 00000000..27c65811 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/header.go @@ -0,0 +1,71 @@ +/* + * 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 = "0.3.2" + + // TokenTypeJwt is the JWT token type supported JWT tokens + // encoded and decoded by this library + TokenTypeJwt = "jwt" + + // AlgorithmNkey is the algorithm supported by JWT tokens + // encoded and decoded by this library + AlgorithmNkey = "ed25519" +) + +// 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.ToLower(h.Type) { + return fmt.Errorf("not supported type %q", h.Type) + } + + if AlgorithmNkey != strings.ToLower(h.Algorithm) { + return fmt.Errorf("unexpected %q algorithm", h.Algorithm) + } + return nil +} diff --git a/gateway/vendor/github.com/nats-io/jwt/imports.go b/gateway/vendor/github.com/nats-io/jwt/imports.go new file mode 100644 index 00000000..8cd97479 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/imports.go @@ -0,0 +1,151 @@ +/* + * 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 ( + "io/ioutil" + "net/http" + "net/url" + "time" +) + +// 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"` + // 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"` + Type ExportType `json:"type,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 +} + +// Validate checks if an import is valid for the wrapping account +func (i *Import) Validate(actPubKey string, vr *ValidationResults) { + if !i.IsService() && !i.IsStream() { + vr.AddError("invalid import type: %q", i.Type) + } + + if i.Account == "" { + vr.AddWarning("account to import from is not specified") + } + + i.Subject.Validate(vr) + + if i.IsService() && i.Subject.HasWildCards() { + vr.AddError("services cannot have wildcard subject: %q", i.Subject) + } + if i.IsStream() && i.To.HasWildCards() { + vr.AddError("streams cannot have wildcard to subject: %q", i.Subject) + } + + var act *ActivationClaims + + if i.Token != "" { + // Check to see if its an embedded JWT or a URL. + if url, err := url.Parse(i.Token); err == nil && url.Scheme != "" { + c := &http.Client{Timeout: 5 * time.Second} + resp, err := c.Get(url.String()) + if err != nil { + vr.AddWarning("import %s contains an unreachable token URL %q", i.Subject, i.Token) + } + + if resp != nil { + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + vr.AddWarning("import %s contains an unreadable token URL %q", i.Subject, i.Token) + } else { + act, err = DecodeActivationClaims(string(body)) + if err != nil { + vr.AddWarning("import %s contains a url %q with an invalid activation token", i.Subject, i.Token) + } + } + } + } else { + var err error + act, err = DecodeActivationClaims(i.Token) + if err != nil { + vr.AddWarning("import %q contains an invalid activation token", i.Subject) + } + } + } + + if act != nil { + if act.Issuer != i.Account { + vr.AddWarning("activation token doesn't match account for import %q", i.Subject) + } + + if act.ClaimsData.Subject != actPubKey { + vr.AddWarning("activation token doesn't match account it is being included in, %q", i.Subject) + } + } else { + vr.AddWarning("no activation provided for import %s", 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]bool, len(*i)) + for _, v := range *i { + if v.Type == Service { + if _, ok := toSet[v.To]; ok { + vr.AddError("Duplicate To subjects for %q", v.To) + } + toSet[v.To] = true + } + 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/gateway/vendor/github.com/nats-io/jwt/operator_claims.go b/gateway/vendor/github.com/nats-io/jwt/operator_claims.go new file mode 100644 index 00000000..6a99597b --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/operator_claims.go @@ -0,0 +1,204 @@ +/* + * 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" + "net/url" + "strings" + + "github.com/nats-io/nkeys" +) + +// Operator specific claims +type Operator struct { + // Slice of real identies (like websites) that can be used to identify the operator. + Identities []Identity `json:"identity,omitempty"` + // 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"` +} + +// 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 _, i := range o.Identities { + i.Validate(vr) + } + + for _, k := range o.SigningKeys { + if !nkeys.IsValidPublicOperatorKey(k) { + vr.AddError("%s is not an operator public key", k) + } + } +} + +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 + default: + return fmt.Errorf("operator service url %q - protocol not supported (only 'nats' or 'tls' only)", v) + } +} + +func (o *Operator) validateOperatorServiceURLs() []error { + var errors []error + for _, v := range o.OperatorServiceURLs { + if v != "" { + if err := ValidateOperatorServiceURL(v); err != nil { + errors = append(errors, err) + } + } + } + return errors +} + +// 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 + 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 { + return true + } + return oc.SigningKeys.Contains(issuer) +} + +// Deprecated: AddSigningKey, use claim.SigningKeys.Add() +func (oc *OperatorClaims) AddSigningKey(pk string) { + oc.SigningKeys.Add(pk) +} + +// Encode the claims into a JWT string +func (oc *OperatorClaims) Encode(pair nkeys.KeyPair) (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.ClaimsData.Type = OperatorClaim + return oc.ClaimsData.Encode(pair, oc) +} + +// DecodeOperatorClaims tries to create an operator claims from a JWt string +func DecodeOperatorClaims(token string) (*OperatorClaims, error) { + v := OperatorClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, 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 +} diff --git a/gateway/vendor/github.com/nats-io/jwt/revocation_list.go b/gateway/vendor/github.com/nats-io/jwt/revocation_list.go new file mode 100644 index 00000000..fb1d8367 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/revocation_list.go @@ -0,0 +1,32 @@ +package jwt + +import ( + "time" +) + +// RevocationList is used to store a mapping of public keys to unix timestamps +type RevocationList map[string]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() + if ts, ok := r[pubKey]; ok && ts > newTS { + return + } + + r[pubKey] = newTS +} + +// 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 time.Now() but other time's can +// be used for testing. +func (r RevocationList) IsRevoked(pubKey string, timestamp time.Time) bool { + ts, ok := r[pubKey] + return ok && ts > timestamp.Unix() +} diff --git a/gateway/vendor/github.com/nats-io/jwt/server_claims.go b/gateway/vendor/github.com/nats-io/jwt/server_claims.go new file mode 100644 index 00000000..c18f167f --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/server_claims.go @@ -0,0 +1,94 @@ +/* + * 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" + + "github.com/nats-io/nkeys" +) + +// Server defines the custom part of a server jwt +type Server struct { + Permissions + Cluster string `json:"cluster,omitempty"` +} + +// Validate checks the cluster and permissions for a server JWT +func (s *Server) Validate(vr *ValidationResults) { + if s.Cluster == "" { + vr.AddError("servers can't contain an empty cluster") + } +} + +// ServerClaims defines the data in a server JWT +type ServerClaims struct { + ClaimsData + Server `json:"nats,omitempty"` +} + +// NewServerClaims creates a new server JWT with the specified subject/public key +func NewServerClaims(subject string) *ServerClaims { + if subject == "" { + return nil + } + c := &ServerClaims{} + c.Subject = subject + return c +} + +// Encode tries to turn the server claims into a JWT string +func (s *ServerClaims) Encode(pair nkeys.KeyPair) (string, error) { + if !nkeys.IsValidPublicServerKey(s.Subject) { + return "", errors.New("expected subject to be a server public key") + } + s.ClaimsData.Type = ServerClaim + return s.ClaimsData.Encode(pair, s) +} + +// DecodeServerClaims tries to parse server claims from a JWT string +func DecodeServerClaims(token string) (*ServerClaims, error) { + v := ServerClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +func (s *ServerClaims) String() string { + return s.ClaimsData.String(s) +} + +// Payload returns the server specific data +func (s *ServerClaims) Payload() interface{} { + return &s.Server +} + +// Validate checks the generic and server data in the server claims +func (s *ServerClaims) Validate(vr *ValidationResults) { + s.ClaimsData.Validate(vr) + s.Server.Validate(vr) +} + +// ExpectedPrefixes defines the types that can encode a server JWT, operator or cluster +func (s *ServerClaims) ExpectedPrefixes() []nkeys.PrefixByte { + return []nkeys.PrefixByte{nkeys.PrefixByteOperator, nkeys.PrefixByteCluster} +} + +// Claims returns the generic data +func (s *ServerClaims) Claims() *ClaimsData { + return &s.ClaimsData +} diff --git a/gateway/vendor/github.com/nats-io/jwt/types.go b/gateway/vendor/github.com/nats-io/jwt/types.go new file mode 100644 index 00000000..a1f09fd9 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/types.go @@ -0,0 +1,334 @@ +/* + * 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" + "strings" + "time" +) + +// 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") +} + +// 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") + } + if strings.Contains(v, " ") { + vr.AddError("subject %q cannot have spaces", v) + } +} + +// 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 +} + +// NamedSubject is the combination of a subject and a name for it +type NamedSubject struct { + Name string `json:"name,omitempty"` + Subject Subject `json:"subject,omitempty"` +} + +// Validate checks the subject +func (ns *NamedSubject) Validate(vr *ValidationResults) { + ns.Subject.Validate(vr) +} + +// 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) + } + } +} + +// Limits are used to control acccess for users and importing accounts +// Src is a comma separated list of CIDR specifications +type Limits struct { + Max int64 `json:"max,omitempty"` + Payload int64 `json:"payload,omitempty"` + Src string `json:"src,omitempty"` + Times []TimeRange `json:"times,omitempty"` +} + +// Validate checks the values in a limit struct +func (l *Limits) Validate(vr *ValidationResults) { + if l.Max < 0 { + vr.AddError("limits cannot contain a negative maximum, %d", l.Max) + } + if l.Payload < 0 { + vr.AddError("limits cannot contain a negative payload, %d", l.Payload) + } + + if l.Src != "" { + elements := strings.Split(l.Src, ",") + + for _, cidr := range elements { + cidr = strings.TrimSpace(cidr) + _, ipNet, err := net.ParseCIDR(cidr) + if err != nil || ipNet == nil { + vr.AddError("invalid cidr %q in user src limits", cidr) + } + } + } + + if l.Times != nil && len(l.Times) > 0 { + for _, t := range l.Times { + t.Validate(vr) + } + } +} + +// Permission defines allow/deny subjects +type Permission struct { + Allow StringList `json:"allow,omitempty"` + Deny StringList `json:"deny,omitempty"` +} + +// Validate the allow, deny elements of a permission +func (p *Permission) Validate(vr *ValidationResults) { + for _, subj := range p.Allow { + Subject(subj).Validate(vr) + } + for _, subj := range p.Deny { + Subject(subj).Validate(vr) + } +} + +// 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(vr *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) { + p.Pub.Validate(vr) + p.Sub.Validate(vr) + if p.Resp != nil { + p.Resp.Validate(vr) + } +} + +// 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(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(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(v) + for i, t := range *u { + if t == v { + a := *u + *u = append(a[:i], a[i+1:]...) + break + } + } + } +} + +// Identity is used to associate an account or operator with a real entity +type Identity struct { + ID string `json:"id,omitempty"` + Proof string `json:"proof,omitempty"` +} + +// Validate checks the values in an Identity +func (u *Identity) Validate(vr *ValidationResults) { + //Fixme identity validation +} diff --git a/gateway/vendor/github.com/nats-io/jwt/user_claims.go b/gateway/vendor/github.com/nats-io/jwt/user_claims.go new file mode 100644 index 00000000..0ec1da3f --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/user_claims.go @@ -0,0 +1,99 @@ +/* + * 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 ( + "errors" + + "github.com/nats-io/nkeys" +) + +// User defines the user specific data in a user JWT +type User struct { + Permissions + Limits +} + +// Validate checks the permissions and limits in a User jwt +func (u *User) Validate(vr *ValidationResults) { + u.Permissions.Validate(vr) + u.Limits.Validate(vr) +} + +// UserClaims defines a user JWT +type UserClaims struct { + ClaimsData + User `json:"nats,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"` +} + +// 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 + return c +} + +// Encode tries to turn the user claims into a JWT string +func (u *UserClaims) Encode(pair nkeys.KeyPair) (string, error) { + if !nkeys.IsValidPublicUserKey(u.Subject) { + return "", errors.New("expected subject to be user public key") + } + u.ClaimsData.Type = UserClaim + return u.ClaimsData.Encode(pair, u) +} + +// DecodeUserClaims tries to parse a user claims from a JWT string +func DecodeUserClaims(token string) (*UserClaims, error) { + v := UserClaims{} + if err := Decode(token, &v); err != nil { + return nil, err + } + return &v, nil +} + +// 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) +} diff --git a/gateway/vendor/github.com/nats-io/jwt/validation.go b/gateway/vendor/github.com/nats-io/jwt/validation.go new file mode 100644 index 00000000..c87a9922 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/jwt/validation.go @@ -0,0 +1,107 @@ +/* + * 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 { + 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 +} diff --git a/gateway/vendor/github.com/nats-io/go-nats/.gitignore b/gateway/vendor/github.com/nats-io/nats.go/.gitignore similarity index 95% rename from gateway/vendor/github.com/nats-io/go-nats/.gitignore rename to gateway/vendor/github.com/nats-io/nats.go/.gitignore index 3d5981fa..a9977fce 100644 --- a/gateway/vendor/github.com/nats-io/go-nats/.gitignore +++ b/gateway/vendor/github.com/nats-io/nats.go/.gitignore @@ -37,3 +37,6 @@ _testmain.go .settings/ # bin + +# Goland +.idea \ No newline at end of file diff --git a/gateway/vendor/github.com/nats-io/nats.go/.travis.yml b/gateway/vendor/github.com/nats-io/nats.go/.travis.yml new file mode 100644 index 00000000..b5228774 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/nats.go/.travis.yml @@ -0,0 +1,24 @@ +language: go +sudo: false +go: +- 1.13.x +- 1.12.x +env: +- GO111MODULE=off +go_import_path: github.com/nats-io/nats.go +install: +- go get -t ./... +- go get github.com/nats-io/nats-server +- go get github.com/mattn/goveralls +- go get github.com/wadey/gocovmerge +- go get -u honnef.co/go/tools/cmd/staticcheck +- go get -u github.com/client9/misspell/cmd/misspell +before_script: +- $(exit $(go fmt ./... | wc -l)) +- go vet ./... +- find . -type f -name "*.go" | xargs misspell -error -locale US +- staticcheck ./... +script: +- go test -i -race ./... +- go test -v -run=TestNoRace -p=1 ./... +- if [[ "$TRAVIS_GO_VERSION" =~ 1.13 ]]; then ./scripts/cov.sh TRAVIS; else go test -race -v -p=1 ./... --failfast; fi diff --git a/gateway/vendor/github.com/nats-io/nats.go/CODE-OF-CONDUCT.md b/gateway/vendor/github.com/nats-io/nats.go/CODE-OF-CONDUCT.md new file mode 100644 index 00000000..b850d49e --- /dev/null +++ b/gateway/vendor/github.com/nats-io/nats.go/CODE-OF-CONDUCT.md @@ -0,0 +1,3 @@ +## 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/gateway/vendor/github.com/nats-io/go-nats/GOVERNANCE.md b/gateway/vendor/github.com/nats-io/nats.go/GOVERNANCE.md similarity index 100% rename from gateway/vendor/github.com/nats-io/go-nats/GOVERNANCE.md rename to gateway/vendor/github.com/nats-io/nats.go/GOVERNANCE.md diff --git a/gateway/vendor/github.com/nats-io/nats.go/LICENSE b/gateway/vendor/github.com/nats-io/nats.go/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/gateway/vendor/github.com/nats-io/nats.go/LICENSE @@ -0,0 +1,201 @@ + 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/gateway/vendor/github.com/nats-io/go-nats/MAINTAINERS.md b/gateway/vendor/github.com/nats-io/nats.go/MAINTAINERS.md similarity index 100% rename from gateway/vendor/github.com/nats-io/go-nats/MAINTAINERS.md rename to gateway/vendor/github.com/nats-io/nats.go/MAINTAINERS.md diff --git a/gateway/vendor/github.com/nats-io/go-nats/README.md b/gateway/vendor/github.com/nats-io/nats.go/README.md similarity index 85% rename from gateway/vendor/github.com/nats-io/go-nats/README.md rename to gateway/vendor/github.com/nats-io/nats.go/README.md index a2bbdf9e..c059c253 100644 --- a/gateway/vendor/github.com/nats-io/go-nats/README.md +++ b/gateway/vendor/github.com/nats-io/nats.go/README.md @@ -3,22 +3,36 @@ A [Go](http://golang.org) client for the [NATS messaging system](https://nats.io [![License Apache 2](https://img.shields.io/badge/License-Apache2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fnats-io%2Fgo-nats.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fnats-io%2Fgo-nats?ref=badge_shield) -[![Go Report Card](https://goreportcard.com/badge/github.com/nats-io/go-nats)](https://goreportcard.com/report/github.com/nats-io/go-nats) [![Build Status](https://travis-ci.org/nats-io/go-nats.svg?branch=master)](http://travis-ci.org/nats-io/go-nats) [![GoDoc](https://godoc.org/github.com/nats-io/go-nats?status.svg)](http://godoc.org/github.com/nats-io/go-nats) [![Coverage Status](https://coveralls.io/repos/nats-io/go-nats/badge.svg?branch=master)](https://coveralls.io/r/nats-io/go-nats?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/nats-io/nats.go)](https://goreportcard.com/report/github.com/nats-io/nats.go) [![Build Status](https://travis-ci.org/nats-io/nats.go.svg?branch=master)](http://travis-ci.org/nats-io/nats.go) [![GoDoc](https://godoc.org/github.com/nats-io/nats.go?status.svg)](http://godoc.org/github.com/nats-io/nats.go) [![Coverage Status](https://coveralls.io/repos/nats-io/nats.go/badge.svg?branch=master)](https://coveralls.io/r/nats-io/nats.go?branch=master) ## Installation ```bash # Go client -go get github.com/nats-io/go-nats +go get github.com/nats-io/nats.go/ # Server -go get github.com/nats-io/gnatsd +go get github.com/nats-io/nats-server +``` + +When using or transitioning to Go modules support: + +```bash +# Go client latest or explicit version +go get github.com/nats-io/nats.go/@latest +go get github.com/nats-io/nats.go/@v1.9.2 + +# For latest NATS Server, add /v2 at the end +go get github.com/nats-io/nats-server/v2 + +# NATS Server v1 is installed otherwise +# go get github.com/nats-io/nats-server ``` ## Basic Usage ```go -import nats "github.com/nats-io/go-nats" +import nats "github.com/nats-io/nats.go" // Connect to a server nc, _ := nats.Connect(nats.DefaultURL) @@ -31,6 +45,11 @@ nc.Subscribe("foo", func(m *nats.Msg) { fmt.Printf("Received a message: %s\n", string(m.Data)) }) +// Responding to a request message +nc.Subscribe("request", func(m *nats.Msg) { + m.Respond([]byte("answer is 42")) +}) + // Simple Sync Subscriber sub, err := nc.SubscribeSync("foo") m, err := sub.NextMsg(timeout) @@ -50,7 +69,7 @@ sub.Drain() msg, err := nc.Request("help", []byte("help me"), 10*time.Millisecond) // Replies -nc.Subscribe("help", func(m *Msg) { +nc.Subscribe("help", func(m *nats.Msg) { nc.Publish(m.Reply, []byte("I can help!")) }) @@ -97,12 +116,12 @@ c.Publish("hello", me) // Unsubscribe sub, err := c.Subscribe("foo", nil) -... +// ... sub.Unsubscribe() // Requests var response string -err := c.Request("help", "help me", &response, 10*time.Millisecond) +err = c.Request("help", "help me", &response, 10*time.Millisecond) if err != nil { fmt.Printf("Request failed: %v\n", err) } @@ -122,21 +141,21 @@ This requires server with version >= 2.0.0 NATS servers have a new security and authentication mechanism to authenticate with user credentials and Nkeys. The simplest form is to use the helper method UserCredentials(credsFilepath). ```go -nc, err := nats.Connect(url, UserCredentials("user.creds")) +nc, err := nats.Connect(url, nats.UserCredentials("user.creds")) ``` -The helper methos creates two callback handlers to present the user JWT and sign the nonce challenge from the server. +The helper methods creates two callback handlers to present the user JWT and sign the nonce challenge from the server. The core client library never has direct access to your private key and simply performs the callback for signing the server challenge. The helper will load and wipe and erase memory it uses for each connect or reconnect. The helper also can take two entries, one for the JWT and one for the NKey seed file. ```go -nc, err := nats.Connect(url, UserCredentials("user.jwt", "user.nk")) +nc, err := nats.Connect(url, nats.UserCredentials("user.jwt", "user.nk")) ``` You can also set the callback handlers directly and manage challenge signing directly. ```go -nc, err := nats.Connect(url, UserJWT(jwtCB, sigCB)) +nc, err := nats.Connect(url, nats.UserJWT(jwtCB, sigCB)) ``` Bare Nkeys are also supported. The nkey seed should be in a read only file, e.g. seed.txt @@ -155,7 +174,7 @@ opt, err := nats.NkeyOptionFromSeed("seed.txt") nc, err := nats.Connect(serverUrl, opt) // Direct -nc, err := nats.Connect(serverUrl, Nkey(pubNkey, sigCB)) +nc, err := nats.Connect(serverUrl, nats.Nkey(pubNkey, sigCB)) ``` ## TLS @@ -311,8 +330,8 @@ nc, err = nats.Connect(servers, nats.DontRandomize()) // Setup callbacks to be notified on disconnects, reconnects and connection closed. nc, err = nats.Connect(servers, - nats.DisconnectHandler(func(nc *nats.Conn) { - fmt.Printf("Got disconnected!\n") + nats.DisconnectErrHandler(func(nc *nats.Conn, err error) { + fmt.Printf("Got disconnected! Reason: %q\n", err) }), nats.ReconnectHandler(func(nc *nats.Conn) { fmt.Printf("Got reconnected to %v!\n", nc.ConnectedUrl()) @@ -339,9 +358,9 @@ nc, err = nats.Connect("nats://localhost:4222", nats.Token("S3cretT0ken")) // Note that if credentials are specified in the initial URLs, they take -// precedence on the credentials specfied through the options. +// precedence on the credentials specified through the options. // For instance, in the connect call below, the client library will use -// the user "my" and password "pwd" to connect to locahost:4222, however, +// the user "my" and password "pwd" to connect to localhost:4222, however, // it will use username "foo" and password "bar" when (re)connecting to // a different server URL that it got as part of the auto-discovery. nc, err = nats.Connect("nats://my:pwd@localhost:4222", nats.UserInfo("foo", "bar")) diff --git a/gateway/vendor/github.com/nats-io/go-nats/TODO.md b/gateway/vendor/github.com/nats-io/nats.go/TODO.md similarity index 100% rename from gateway/vendor/github.com/nats-io/go-nats/TODO.md rename to gateway/vendor/github.com/nats-io/nats.go/TODO.md diff --git a/gateway/vendor/github.com/nats-io/go-nats/context.go b/gateway/vendor/github.com/nats-io/nats.go/context.go similarity index 87% rename from gateway/vendor/github.com/nats-io/go-nats/context.go rename to gateway/vendor/github.com/nats-io/nats.go/context.go index ee5576f5..c921d6be 100644 --- a/gateway/vendor/github.com/nats-io/go-nats/context.go +++ b/gateway/vendor/github.com/nats-io/nats.go/context.go @@ -43,29 +43,7 @@ func (nc *Conn) RequestWithContext(ctx context.Context, subj string, data []byte return nc.oldRequestWithContext(ctx, subj, data) } - // Do setup for the new style. - if nc.respMap == nil { - nc.initNewResp() - } - // Create literal Inbox and map to a chan msg. - mch := make(chan *Msg, RequestChanLen) - respInbox := nc.newRespInbox() - token := respToken(respInbox) - nc.respMap[token] = mch - createSub := nc.respMux == nil - ginbox := nc.respSub - nc.mu.Unlock() - - if createSub { - // Make sure scoped subscription is setup only once. - var err error - nc.respSetup.Do(func() { err = nc.createRespMux(ginbox) }) - if err != nil { - return nil, err - } - } - - err := nc.PublishRequest(subj, respInbox, data) + mch, token, err := nc.createNewRequestAndSend(subj, data) if err != nil { return nil, err } @@ -93,7 +71,7 @@ func (nc *Conn) oldRequestWithContext(ctx context.Context, subj string, data []b inbox := NewInbox() ch := make(chan *Msg, RequestChanLen) - s, err := nc.subscribe(inbox, _EMPTY_, nil, ch) + s, err := nc.subscribe(inbox, _EMPTY_, nil, ch, true) if err != nil { return nil, err } @@ -140,7 +118,7 @@ func (s *Subscription) NextMsgWithContext(ctx context.Context) (*Msg, error) { select { case msg, ok = <-mch: if !ok { - return nil, ErrConnectionClosed + return nil, s.getNextMsgErr() } if err := s.processNextMsgDelivered(msg); err != nil { return nil, err @@ -153,7 +131,7 @@ func (s *Subscription) NextMsgWithContext(ctx context.Context) (*Msg, error) { select { case msg, ok = <-mch: if !ok { - return nil, ErrConnectionClosed + return nil, s.getNextMsgErr() } if err := s.processNextMsgDelivered(msg); err != nil { return nil, err diff --git a/gateway/vendor/github.com/nats-io/go-nats/enc.go b/gateway/vendor/github.com/nats-io/nats.go/enc.go similarity index 97% rename from gateway/vendor/github.com/nats-io/go-nats/enc.go rename to gateway/vendor/github.com/nats-io/nats.go/enc.go index 9a0dd2c0..0ed71a2c 100644 --- a/gateway/vendor/github.com/nats-io/go-nats/enc.go +++ b/gateway/vendor/github.com/nats-io/nats.go/enc.go @@ -1,4 +1,4 @@ -// Copyright 2012-2018 The NATS Authors +// Copyright 2012-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 @@ -21,7 +21,7 @@ import ( "time" // Default Encoders - "github.com/nats-io/go-nats/encoders/builtin" + "github.com/nats-io/nats.go/encoders/builtin" ) // Encoder interface is for all register encoders @@ -33,7 +33,7 @@ type Encoder interface { var encMap map[string]Encoder var encLock sync.Mutex -// Indexe names into the Registered Encoders. +// Indexed names into the Registered Encoders. const ( JSON_ENCODER = "json" GOB_ENCODER = "gob" @@ -109,7 +109,7 @@ func (c *EncodedConn) PublishRequest(subject, reply string, v interface{}) error // Request will create an Inbox and perform a Request() call // with the Inbox reply for the data v. A response will be -// decoded into the vPtrResponse. +// decoded into the vPtr Response. func (c *EncodedConn) Request(subject string, v interface{}, vPtr interface{}, timeout time.Duration) error { b, err := c.Enc.Encode(subject, v) if err != nil { @@ -234,7 +234,7 @@ func (c *EncodedConn) subscribe(subject, queue string, cb Handler) (*Subscriptio cbValue.Call(oV) } - return c.Conn.subscribe(subject, queue, natsCB, nil) + return c.Conn.subscribe(subject, queue, natsCB, nil, false) } // FlushTimeout allows a Flush operation to have an associated timeout. diff --git a/gateway/vendor/github.com/nats-io/go-nats/encoders/builtin/default_enc.go b/gateway/vendor/github.com/nats-io/nats.go/encoders/builtin/default_enc.go similarity index 100% rename from gateway/vendor/github.com/nats-io/go-nats/encoders/builtin/default_enc.go rename to gateway/vendor/github.com/nats-io/nats.go/encoders/builtin/default_enc.go diff --git a/gateway/vendor/github.com/nats-io/go-nats/encoders/builtin/gob_enc.go b/gateway/vendor/github.com/nats-io/nats.go/encoders/builtin/gob_enc.go similarity index 100% rename from gateway/vendor/github.com/nats-io/go-nats/encoders/builtin/gob_enc.go rename to gateway/vendor/github.com/nats-io/nats.go/encoders/builtin/gob_enc.go diff --git a/gateway/vendor/github.com/nats-io/go-nats/encoders/builtin/json_enc.go b/gateway/vendor/github.com/nats-io/nats.go/encoders/builtin/json_enc.go similarity index 100% rename from gateway/vendor/github.com/nats-io/go-nats/encoders/builtin/json_enc.go rename to gateway/vendor/github.com/nats-io/nats.go/encoders/builtin/json_enc.go diff --git a/gateway/vendor/github.com/nats-io/nats.go/go.mod b/gateway/vendor/github.com/nats-io/nats.go/go.mod new file mode 100644 index 00000000..bd7d44a2 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/nats.go/go.mod @@ -0,0 +1,7 @@ +module github.com/nats-io/nats.go + +require ( + github.com/nats-io/jwt v0.3.2 + github.com/nats-io/nkeys v0.1.4 + github.com/nats-io/nuid v1.0.1 +) diff --git a/gateway/vendor/github.com/nats-io/nats.go/go.sum b/gateway/vendor/github.com/nats-io/nats.go/go.sum new file mode 100644 index 00000000..70e81d40 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/nats.go/go.sum @@ -0,0 +1,15 @@ +github.com/nats-io/jwt v0.3.2 h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.4 h1:aEsHIssIk6ETN5m2/MD8Y4B2X7FfXrBAUdkyRvbVYzA= +github.com/nats-io/nkeys v0.1.4/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/gateway/vendor/github.com/nats-io/go-nats/nats.go b/gateway/vendor/github.com/nats-io/nats.go/nats.go similarity index 87% rename from gateway/vendor/github.com/nats-io/go-nats/nats.go rename to gateway/vendor/github.com/nats-io/nats.go/nats.go index ec43708a..f741894b 100644 --- a/gateway/vendor/github.com/nats-io/go-nats/nats.go +++ b/gateway/vendor/github.com/nats-io/nats.go/nats.go @@ -28,7 +28,8 @@ import ( "math/rand" "net" "net/url" - "regexp" + "os" + "path/filepath" "runtime" "strconv" "strings" @@ -36,15 +37,16 @@ import ( "sync/atomic" "time" - "github.com/nats-io/go-nats/util" + "github.com/nats-io/jwt" + "github.com/nats-io/nats.go/util" "github.com/nats-io/nkeys" "github.com/nats-io/nuid" ) // Default Constants const ( - Version = "1.7.2" - DefaultURL = "nats://localhost:4222" + Version = "1.9.2" + DefaultURL = "nats://127.0.0.1:4222" DefaultPort = 4222 DefaultMaxReconnect = 60 DefaultReconnectWait = 2 * time.Second @@ -67,6 +69,9 @@ const ( // AUTHORIZATION_ERR is for when nats server user authorization has failed. AUTHORIZATION_ERR = "authorization violation" + + // AUTHENTICATION_EXPIRED_ERR is for when nats server user authorization has expired. + AUTHENTICATION_EXPIRED_ERR = "user authentication expired" ) // Errors @@ -80,10 +85,12 @@ var ( ErrBadSubscription = errors.New("nats: invalid subscription") ErrTypeSubscription = errors.New("nats: invalid subscription type") ErrBadSubject = errors.New("nats: invalid subject") + ErrBadQueueName = errors.New("nats: invalid queue name") ErrSlowConsumer = errors.New("nats: slow consumer, messages dropped") ErrTimeout = errors.New("nats: timeout") ErrBadTimeout = errors.New("nats: timeout invalid") ErrAuthorization = errors.New("nats: authorization violation") + ErrAuthExpired = errors.New("nats: authentication expired") ErrNoServers = errors.New("nats: no servers available for connection") ErrJsonParse = errors.New("nats: connect message, json parse error") ErrChanArg = errors.New("nats: argument needs to be a channel type") @@ -107,8 +114,14 @@ var ( ErrNkeysNotSupported = errors.New("nats: nkeys not supported by the server") ErrStaleConnection = errors.New("nats: " + STALE_CONNECTION) ErrTokenAlreadySet = errors.New("nats: token and token handler both set") + ErrMsgNotBound = errors.New("nats: message is not bound to subscription/connection") + ErrMsgNoReply = errors.New("nats: message does not have a reply") ) +func init() { + rand.Seed(time.Now().UnixNano()) +} + // GetDefaultOptions returns default configuration options for the client. func GetDefaultOptions() Options { return Options{ @@ -146,6 +159,10 @@ const ( // disconnected and closed connections. type ConnHandler func(*Conn) +// ConnErrHandler is used to process asynchronous events like +// disconnected connection with the error (if any). +type ConnErrHandler func(*Conn, error) + // ErrHandler is used to process asynchronous errors encountered // while processing inbound messages. type ErrHandler func(*Conn, *Subscription, error) @@ -156,7 +173,8 @@ type UserJWTHandler func() (string, error) // SignatureHandler is used to sign a nonce from the server while // authenticating with nkeys. The user should sign the nonce and -// return the base64 encoded signature. +// return the raw signature. The client will base64 encode this to +// send to the server. type SignatureHandler func([]byte) ([]byte, error) // AuthTokenHandler is used to generate a new token. @@ -262,8 +280,17 @@ type Options struct { // DisconnectedCB sets the disconnected handler that is called // whenever the connection is disconnected. + // Will not be called if DisconnectedErrCB is set + // DEPRECATED. Use DisconnectedErrCB which passes error that caused + // the disconnect event. DisconnectedCB ConnHandler + // DisconnectedErrCB sets the disconnected error handler that is called + // whenever the connection is disconnected. + // Disconnected error could be nil, for instance when user explicitly closes the connection. + // DisconnectedCB will not be called if DisconnectedErrCB is set + DisconnectedErrCB ConnErrHandler + // ReconnectedCB sets the reconnected handler called whenever // the connection is successfully reconnected. ReconnectedCB ConnHandler @@ -320,6 +347,11 @@ type Options struct { // UseOldRequestStyle forces the old method of Requests that utilize // a new Inbox and a new Subscription for each request. UseOldRequestStyle bool + + // NoCallbacksAfterClientClose allows preventing the invocation of + // callbacks after Close() is called. Client won't receive notifications + // when Close is invoked by user code. Default is to invoke the callbacks. + NoCallbacksAfterClientClose bool } const ( @@ -330,7 +362,7 @@ const ( defaultBufSize = 32768 // The buffered size of the flush "kick" channel - flushChanSize = 1024 + flushChanSize = 1 // Default server pool size srvPoolSize = 4 @@ -344,13 +376,14 @@ const ( // A Conn represents a bare connection to a nats-server. // It can send and receive []byte payloads. +// The connection is safe to use in multiple Go routines concurrently. type Conn struct { // Keep all members for which we use atomic at the beginning of the // struct and make sure they are all 64bits (or use padding if necessary). // atomic.* functions crash on 32bit machines if operand is not aligned // at 64bit. See https://github.com/golang/go/issues/599 Statistics - mu sync.Mutex + mu sync.RWMutex // Opts holds the configuration of the Conn. // Modifying the configuration of a running Conn is a race. Opts Options @@ -375,13 +408,14 @@ type Conn struct { ps *parseState ptmr *time.Timer pout int + ar bool // abort reconnect // New style response handler respSub string // The wildcard subject + respScanf string // The scanf template to extract mux token respMux *Subscription // A single response subscription respMap map[string]chan *Msg // Request map for the response msg channels - respSetup sync.Once // Ensures response subscription occurs once - respRand *rand.Rand // Used for generating suffix. + respRand *rand.Rand // Used for generating suffix } // A Subscription represents interest in a given subject. @@ -456,6 +490,7 @@ type srv struct { didConnect bool reconnects int lastAttempt time.Time + lastErr error isImplicit bool tlsName string } @@ -683,7 +718,16 @@ func DrainTimeout(t time.Duration) Option { } } +// DisconnectErrHandler is an Option to set the disconnected error handler. +func DisconnectErrHandler(cb ConnErrHandler) Option { + return func(o *Options) error { + o.DisconnectedErrCB = cb + return nil + } +} + // DisconnectHandler is an Option to set the disconnected handler. +// DEPRECATED: Use DisconnectErrHandler. func DisconnectHandler(cb ConnHandler) Option { return func(o *Options) error { o.DisconnectedCB = cb @@ -844,9 +888,20 @@ func UseOldRequestStyle() Option { } } +// NoCallbacksAfterClientClose is an Option to disable callbacks when user code +// calls Close(). If close is initiated by any other condition, callbacks +// if any will be invoked. +func NoCallbacksAfterClientClose() Option { + return func(o *Options) error { + o.NoCallbacksAfterClientClose = true + return nil + } +} + // Handler processing // SetDisconnectHandler will set the disconnect event handler. +// DEPRECATED: Use SetDisconnectErrHandler func (nc *Conn) SetDisconnectHandler(dcb ConnHandler) { if nc == nil { return @@ -856,6 +911,16 @@ func (nc *Conn) SetDisconnectHandler(dcb ConnHandler) { nc.Opts.DisconnectedCB = dcb } +// SetDisconnectErrHandler will set the disconnect event handler. +func (nc *Conn) SetDisconnectErrHandler(dcb ConnErrHandler) { + if nc == nil { + return + } + nc.mu.Lock() + defer nc.mu.Unlock() + nc.Opts.DisconnectedErrCB = dcb +} + // SetReconnectHandler will set the reconnect event handler. func (nc *Conn) SetReconnectHandler(rcb ConnHandler) { if nc == nil { @@ -1189,18 +1254,18 @@ func (nc *Conn) createConn() (err error) { } // We will auto-expand host names if they resolve to multiple IPs - hosts := map[string]struct{}{} + hosts := []string{} u := nc.current.url if net.ParseIP(u.Hostname()) == nil { addrs, _ := net.LookupHost(u.Hostname()) for _, addr := range addrs { - hosts[net.JoinHostPort(addr, u.Port())] = struct{}{} + hosts = append(hosts, net.JoinHostPort(addr, u.Port())) } } // Fall back to what we were given. if len(hosts) == 0 { - hosts[u.Host] = struct{}{} + hosts = append(hosts, u.Host) } // CustomDialer takes precedence. If not set, use Opts.Dialer which @@ -1214,7 +1279,12 @@ func (nc *Conn) createConn() (err error) { dialer = ©Dialer } - for host := range hosts { + if len(hosts) > 1 && !nc.Opts.NoRandomize { + rand.Shuffle(len(hosts), func(i, j int) { + hosts[i], hosts[j] = hosts[j], hosts[i] + }) + } + for _, host := range hosts { nc.conn, err = dialer.Dial("tcp", host) if err == nil { break @@ -1224,12 +1294,6 @@ func (nc *Conn) createConn() (err error) { return err } - // No clue why, but this stalls and kills performance on Mac (Mavericks). - // https://code.google.com/p/go/issues/detail?id=6930 - //if ip, ok := nc.conn.(*net.TCPConn); ok { - // ip.SetReadBuffer(defaultBufSize) - //} - if nc.pending != nil && nc.bw != nil { // Move to pending buffer. nc.bw.Flush() @@ -1283,8 +1347,10 @@ func (nc *Conn) ConnectedUrl() string { if nc == nil { return _EMPTY_ } - nc.mu.Lock() - defer nc.mu.Unlock() + + nc.mu.RLock() + defer nc.mu.RUnlock() + if nc.status != CONNECTED { return _EMPTY_ } @@ -1296,8 +1362,10 @@ func (nc *Conn) ConnectedAddr() string { if nc == nil { return _EMPTY_ } - nc.mu.Lock() - defer nc.mu.Unlock() + + nc.mu.RLock() + defer nc.mu.RUnlock() + if nc.status != CONNECTED { return _EMPTY_ } @@ -1309,8 +1377,10 @@ func (nc *Conn) ConnectedServerId() string { if nc == nil { return _EMPTY_ } - nc.mu.Lock() - defer nc.mu.Unlock() + + nc.mu.RLock() + defer nc.mu.RUnlock() + if nc.status != CONNECTED { return _EMPTY_ } @@ -1395,12 +1465,13 @@ func (nc *Conn) connect() error { if err == nil { nc.srvPool[i].didConnect = true nc.srvPool[i].reconnects = 0 + nc.current.lastErr = nil returnedErr = nil break } else { returnedErr = err nc.mu.Unlock() - nc.close(DISCONNECTED, false) + nc.close(DISCONNECTED, false, err) nc.mu.Lock() nc.current = nil } @@ -1413,11 +1484,10 @@ func (nc *Conn) connect() error { } } nc.initc = false - defer nc.mu.Unlock() - if returnedErr == nil && nc.status != CONNECTED { returnedErr = ErrNoServers } + nc.mu.Unlock() return returnedErr } @@ -1565,7 +1635,6 @@ func normalizeErr(line string) string { // applicable. Will wait for a flush to return from the server for error // processing. func (nc *Conn) sendConnect() error { - // Construct the CONNECT protocol string cProto, err := nc.connectProto() if err != nil { @@ -1619,6 +1688,17 @@ func (nc *Conn) sendConnect() error { if strings.HasPrefix(proto, _ERR_OP_) { // Remove -ERR, trim spaces and quotes, and convert to lower case. proto = normalizeErr(proto) + + // Check if this is an auth error + if authErr := checkAuthError(strings.ToLower(proto)); authErr != nil { + // This will schedule an async error if we are in reconnect, + // and keep track of the auth error for the current server. + // If we have got the same error twice, this sets nc.ar to true to + // indicate that the reconnect should be aborted (will be checked + // in doReconnect()). + nc.processAuthError(authErr) + } + return errors.New("nats: " + proto) } @@ -1705,7 +1785,7 @@ func (nc *Conn) stopPingTimer() { // Try to reconnect using the option parameters. // This function assumes we are allowed to reconnect. -func (nc *Conn) doReconnect() { +func (nc *Conn) doReconnect(err error) { // We want to make sure we have the other watchers shutdown properly // here before we proceed past this point. nc.waitForExits() @@ -1724,7 +1804,10 @@ func (nc *Conn) doReconnect() { // Clear any errors. nc.err = nil // Perform appropriate callback if needed for a disconnect. - if nc.Opts.DisconnectedCB != nil { + // DisconnectedErrCB has priority over deprecated DisconnectedCB + if nc.Opts.DisconnectedErrCB != nil { + nc.ach.push(func() { nc.Opts.DisconnectedErrCB(nc, err) }) + } else if nc.Opts.DisconnectedCB != nil { nc.ach.push(func() { nc.Opts.DisconnectedCB(nc) }) } @@ -1789,6 +1872,11 @@ func (nc *Conn) doReconnect() { // Process connect logic if nc.err = nc.processConnectInit(); nc.err != nil { + // Check if we should abort reconnect. If so, break out + // of the loop and connection will be closed. + if nc.ar { + break + } nc.status = RECONNECTING // Reset the buffered writer to the pending buffer // (was set to a buffered writer on nc.conn in createConn) @@ -1796,6 +1884,10 @@ func (nc *Conn) doReconnect() { continue } + // Clear possible lastErr under the connection lock after + // a successful processConnectInit(). + nc.current.lastErr = nil + // Clear out server stats for the server we connected to.. cur.didConnect = true cur.reconnects = 0 @@ -1831,6 +1923,7 @@ func (nc *Conn) doReconnect() { if nc.Opts.ReconnectedCB != nil { nc.ach.push(func() { nc.Opts.ReconnectedCB(nc) }) } + // Release lock here, we will return below. nc.mu.Unlock() @@ -1845,7 +1938,7 @@ func (nc *Conn) doReconnect() { nc.err = ErrNoServers } nc.mu.Unlock() - nc.Close() + nc.close(CLOSED, true, nil) } // processOpErr handles errors from reading or parsing the protocol. @@ -1872,7 +1965,7 @@ func (nc *Conn) processOpErr(err error) { nc.pending = new(bytes.Buffer) nc.bw.Reset(nc.pending) - go nc.doReconnect() + go nc.doReconnect(err) nc.mu.Unlock() return } @@ -1880,7 +1973,7 @@ func (nc *Conn) processOpErr(err error) { nc.status = DISCONNECTED nc.err = err nc.mu.Unlock() - nc.Close() + nc.close(CLOSED, true, nil) } // dispatch is responsible for calling any async callbacks @@ -1956,31 +2049,21 @@ func (nc *Conn) readLoop() { if nc.ps == nil { nc.ps = &parseState{} } + conn := nc.conn nc.mu.Unlock() + if conn == nil { + return + } + // Stack based buffer. b := make([]byte, defaultBufSize) for { - // FIXME(dlc): RWLock here? - nc.mu.Lock() - sb := nc.isClosed() || nc.isReconnecting() - if sb { - nc.ps = &parseState{} - } - conn := nc.conn - nc.mu.Unlock() - - if sb || conn == nil { - break - } - - n, err := conn.Read(b) - if err != nil { + if n, err := conn.Read(b); err != nil { nc.processOpErr(err) break - } - if err := nc.parse(b[:n]); err != nil { + } else if err = nc.parse(b[:n]); err != nil { nc.processOpErr(err) break } @@ -2080,8 +2163,8 @@ func (nc *Conn) processMsg(data []byte) { nc.subsMu.RLock() // Stats - nc.InMsgs++ - nc.InBytes += uint64(len(data)) + atomic.AddUint64(&nc.InMsgs, 1) + atomic.AddUint64(&nc.InBytes, uint64(len(data))) sub := nc.subs[nc.ps.ma.sid] if sub == nil { @@ -2187,15 +2270,24 @@ func (nc *Conn) processPermissionsViolation(err string) { nc.mu.Unlock() } -// processAuthorizationViolation is called when the server signals a user -// authorization violation. -func (nc *Conn) processAuthorizationViolation(err string) { - nc.mu.Lock() - nc.err = ErrAuthorization - if nc.Opts.AsyncErrorCB != nil { - nc.ach.push(func() { nc.Opts.AsyncErrorCB(nc, nil, ErrAuthorization) }) +// processAuthError generally processing for auth errors. We want to do retries +// unless we get the same error again. This allows us for instance to swap credentials +// and have the app reconnect, but if nothing is changing we should bail. +// This function will return true if the connection should be closed, false otherwise. +// Connection lock is held on entry +func (nc *Conn) processAuthError(err error) bool { + nc.err = err + if !nc.initc && nc.Opts.AsyncErrorCB != nil { + nc.ach.push(func() { nc.Opts.AsyncErrorCB(nc, nil, err) }) } - nc.mu.Unlock() + // We should give up if we tried twice on this server and got the + // same error. + if nc.current.lastErr == err { + nc.ar = true + } else { + nc.current.lastErr = err + } + return nc.ar } // flusher is a separate Go routine that will process flush requests for the write @@ -2276,6 +2368,7 @@ func (nc *Conn) processInfo(info string) error { if err := json.Unmarshal([]byte(info), &ncInfo); err != nil { return err } + // Copy content into connection's info structure. nc.info = ncInfo // The array could be empty/not present on initial connect, @@ -2322,10 +2415,8 @@ func (nc *Conn) processInfo(info string) error { } } // Figure out if we should save off the current non-IP hostname if we encounter a bare IP. - var saveTLS bool - if nc.current != nil && nc.Opts.Secure && !hostIsIP(nc.current.url) { - saveTLS = true - } + saveTLS := nc.current != nil && !hostIsIP(nc.current.url) + // If there are any left in the tmp map, these are new (or restarted) servers // and need to be added to the pool. for curl := range tmp { @@ -2360,12 +2451,24 @@ func (nc *Conn) LastError() error { if nc == nil { return ErrInvalidConnection } - nc.mu.Lock() + nc.mu.RLock() err := nc.err - nc.mu.Unlock() + nc.mu.RUnlock() return err } +// Check if the given error string is an auth error, and if so returns +// the corresponding ErrXXX error, nil otherwise +func checkAuthError(e string) error { + if strings.HasPrefix(e, AUTHORIZATION_ERR) { + return ErrAuthorization + } + if strings.HasPrefix(e, AUTHENTICATION_EXPIRED_ERR) { + return ErrAuthExpired + } + return nil +} + // processErr processes any error messages from the server and // sets the connection's lastError. func (nc *Conn) processErr(ie string) { @@ -2374,18 +2477,25 @@ func (nc *Conn) processErr(ie string) { // convert to lower case. e := strings.ToLower(ne) + close := false + // FIXME(dlc) - process Slow Consumer signals special. if e == STALE_CONNECTION { nc.processOpErr(ErrStaleConnection) } else if strings.HasPrefix(e, PERMISSIONS_ERR) { nc.processPermissionsViolation(ne) - } else if strings.HasPrefix(e, AUTHORIZATION_ERR) { - nc.processAuthorizationViolation(ne) + } else if authErr := checkAuthError(e); authErr != nil { + nc.mu.Lock() + close = nc.processAuthError(authErr) + nc.mu.Unlock() } else { + close = true nc.mu.Lock() nc.err = errors.New("nats: " + ne) nc.mu.Unlock() - nc.Close() + } + if close { + nc.close(CLOSED, true, nil) } } @@ -2521,21 +2631,32 @@ func (nc *Conn) publish(subj, reply string, data []byte) error { // the appropriate channel based on the last token and place // the message on the channel if possible. func (nc *Conn) respHandler(m *Msg) { - rt := respToken(m.Subject) - nc.mu.Lock() + // Just return if closed. if nc.isClosed() { nc.mu.Unlock() return } + var mch chan *Msg + // Grab mch - mch := nc.respMap[rt] - // Delete the key regardless, one response only. - // FIXME(dlc) - should we track responses past 1 - // just statistics wise? - delete(nc.respMap, rt) + rt := nc.respToken(m.Subject) + if rt != _EMPTY_ { + mch = nc.respMap[rt] + // Delete the key regardless, one response only. + delete(nc.respMap, rt) + } else if len(nc.respMap) == 1 { + // If the server has rewritten the subject, the response token (rt) + // will not match (could be the case with JetStream). If that is the + // case and there is a single entry, use that. + for k, v := range nc.respMap { + mch = v + delete(nc.respMap, k) + break + } + } nc.mu.Unlock() // Don't block, let Request timeout instead, mch is @@ -2548,20 +2669,36 @@ func (nc *Conn) respHandler(m *Msg) { } } -// Create the response subscription we will use for all -// new style responses. This will be on an _INBOX with an -// additional terminal token. The subscription will be on -// a wildcard. Caller is responsible for ensuring this is -// only called once. -func (nc *Conn) createRespMux(respSub string) error { - s, err := nc.Subscribe(respSub, nc.respHandler) - if err != nil { - return err +// Helper to setup and send new request style requests. Return the chan to receive the response. +func (nc *Conn) createNewRequestAndSend(subj string, data []byte) (chan *Msg, string, error) { + // Do setup for the new style if needed. + if nc.respMap == nil { + nc.initNewResp() + } + // Create new literal Inbox and map to a chan msg. + mch := make(chan *Msg, RequestChanLen) + respInbox := nc.newRespInbox() + token := respInbox[respInboxPrefixLen:] + nc.respMap[token] = mch + if nc.respMux == nil { + // Create the response subscription we will use for all new style responses. + // This will be on an _INBOX with an additional terminal token. The subscription + // will be on a wildcard. + s, err := nc.subscribeLocked(nc.respSub, _EMPTY_, nc.respHandler, nil, false) + if err != nil { + nc.mu.Unlock() + return nil, token, err + } + nc.respScanf = strings.Replace(nc.respSub, "*", "%s", -1) + nc.respMux = s } - nc.mu.Lock() - nc.respMux = s nc.mu.Unlock() - return nil + + if err := nc.PublishRequest(subj, respInbox, data); err != nil { + return nil, token, err + } + + return mch, token, nil } // Request will send a request payload and deliver the response message, @@ -2578,29 +2715,8 @@ func (nc *Conn) Request(subj string, data []byte, timeout time.Duration) (*Msg, return nc.oldRequest(subj, data, timeout) } - // Do setup for the new style. - if nc.respMap == nil { - nc.initNewResp() - } - // Create literal Inbox and map to a chan msg. - mch := make(chan *Msg, RequestChanLen) - respInbox := nc.newRespInbox() - token := respToken(respInbox) - nc.respMap[token] = mch - createSub := nc.respMux == nil - ginbox := nc.respSub - nc.mu.Unlock() - - if createSub { - // Make sure scoped subscription is setup only once. - var err error - nc.respSetup.Do(func() { err = nc.createRespMux(ginbox) }) - if err != nil { - return nil, err - } - } - - if err := nc.PublishRequest(subj, respInbox, data); err != nil { + mch, token, err := nc.createNewRequestAndSend(subj, data) + if err != nil { return nil, err } @@ -2632,7 +2748,7 @@ func (nc *Conn) oldRequest(subj string, data []byte, timeout time.Duration) (*Ms inbox := NewInbox() ch := make(chan *Msg, RequestChanLen) - s, err := nc.subscribe(inbox, _EMPTY_, nil, ch) + s, err := nc.subscribe(inbox, _EMPTY_, nil, ch, false) if err != nil { return nil, err } @@ -2703,23 +2819,30 @@ func (nc *Conn) NewRespInbox() string { } // respToken will return the last token of a literal response inbox -// which we use for the message channel lookup. -func respToken(respInbox string) string { - return respInbox[respInboxPrefixLen:] +// which we use for the message channel lookup. This needs to do a +// scan to protect itself against the server changing the subject. +// Lock should be held. +func (nc *Conn) respToken(respInbox string) string { + var token string + n, err := fmt.Sscanf(respInbox, nc.respScanf, &token) + if err != nil || n != 1 { + return "" + } + return token } // Subscribe will express interest in the given subject. The subject // can have wildcards (partial:*, full:>). Messages will be delivered // to the associated MsgHandler. func (nc *Conn) Subscribe(subj string, cb MsgHandler) (*Subscription, error) { - return nc.subscribe(subj, _EMPTY_, cb, nil) + return nc.subscribe(subj, _EMPTY_, cb, nil, false) } // ChanSubscribe will express interest in the given subject and place // all messages received on the channel. // You should not close the channel until sub.Unsubscribe() has been called. func (nc *Conn) ChanSubscribe(subj string, ch chan *Msg) (*Subscription, error) { - return nc.subscribe(subj, _EMPTY_, nil, ch) + return nc.subscribe(subj, _EMPTY_, nil, ch, false) } // ChanQueueSubscribe will express interest in the given subject. @@ -2729,7 +2852,7 @@ func (nc *Conn) ChanSubscribe(subj string, ch chan *Msg) (*Subscription, error) // You should not close the channel until sub.Unsubscribe() has been called. // Note: This is the same than QueueSubscribeSyncWithChan. func (nc *Conn) ChanQueueSubscribe(subj, group string, ch chan *Msg) (*Subscription, error) { - return nc.subscribe(subj, group, nil, ch) + return nc.subscribe(subj, group, nil, ch, false) } // SubscribeSync will express interest on the given subject. Messages will @@ -2739,10 +2862,7 @@ func (nc *Conn) SubscribeSync(subj string) (*Subscription, error) { return nil, ErrInvalidConnection } mch := make(chan *Msg, nc.Opts.SubChanLen) - s, e := nc.subscribe(subj, _EMPTY_, nil, mch) - if s != nil { - s.typ = SyncSubscription - } + s, e := nc.subscribe(subj, _EMPTY_, nil, mch, true) return s, e } @@ -2751,7 +2871,7 @@ func (nc *Conn) SubscribeSync(subj string) (*Subscription, error) { // only one member of the group will be selected to receive any given // message asynchronously. func (nc *Conn) QueueSubscribe(subj, queue string, cb MsgHandler) (*Subscription, error) { - return nc.subscribe(subj, queue, cb, nil) + return nc.subscribe(subj, queue, cb, nil, false) } // QueueSubscribeSync creates a synchronous queue subscriber on the given @@ -2760,10 +2880,7 @@ func (nc *Conn) QueueSubscribe(subj, queue string, cb MsgHandler) (*Subscription // given message synchronously using Subscription.NextMsg(). func (nc *Conn) QueueSubscribeSync(subj, queue string) (*Subscription, error) { mch := make(chan *Msg, nc.Opts.SubChanLen) - s, e := nc.subscribe(subj, queue, nil, mch) - if s != nil { - s.typ = SyncSubscription - } + s, e := nc.subscribe(subj, queue, nil, mch, true) return s, e } @@ -2774,17 +2891,50 @@ func (nc *Conn) QueueSubscribeSync(subj, queue string) (*Subscription, error) { // You should not close the channel until sub.Unsubscribe() has been called. // Note: This is the same than ChanQueueSubscribe. func (nc *Conn) QueueSubscribeSyncWithChan(subj, queue string, ch chan *Msg) (*Subscription, error) { - return nc.subscribe(subj, queue, nil, ch) + return nc.subscribe(subj, queue, nil, ch, false) +} + +// badSubject will do quick test on whether a subject is acceptable. +// Spaces are not allowed and all tokens should be > 0 in len. +func badSubject(subj string) bool { + if strings.ContainsAny(subj, " \t\r\n") { + return true + } + tokens := strings.Split(subj, ".") + for _, t := range tokens { + if len(t) == 0 { + return true + } + } + return false +} + +// badQueue will check a queue name for whitespace. +func badQueue(qname string) bool { + return strings.ContainsAny(qname, " \t\r\n") } // subscribe is the internal subscribe function that indicates interest in a subject. -func (nc *Conn) subscribe(subj, queue string, cb MsgHandler, ch chan *Msg) (*Subscription, error) { +func (nc *Conn) subscribe(subj, queue string, cb MsgHandler, ch chan *Msg, isSync bool) (*Subscription, error) { if nc == nil { return nil, ErrInvalidConnection } nc.mu.Lock() - // ok here, but defer is generally expensive - defer nc.mu.Unlock() + s, err := nc.subscribeLocked(subj, queue, cb, ch, isSync) + nc.mu.Unlock() + return s, err +} + +func (nc *Conn) subscribeLocked(subj, queue string, cb MsgHandler, ch chan *Msg, isSync bool) (*Subscription, error) { + if nc == nil { + return nil, ErrInvalidConnection + } + if badSubject(subj) { + return nil, ErrBadSubject + } + if queue != "" && badQueue(queue) { + return nil, ErrBadQueueName + } // Check for some error conditions. if nc.isClosed() { @@ -2809,9 +2959,12 @@ func (nc *Conn) subscribe(subj, queue string, cb MsgHandler, ch chan *Msg) (*Sub sub.typ = AsyncSubscription sub.pCond = sync.NewCond(&sub.mu) go nc.waitForMsgs(sub) - } else { + } else if !isSync { sub.typ = ChanSubscription sub.mch = ch + } else { // Sync Subscription + sub.typ = SyncSubscription + sub.mch = ch } nc.subsMu.Lock() @@ -2835,8 +2988,8 @@ func (nc *Conn) subscribe(subj, queue string, cb MsgHandler, ch chan *Msg) (*Sub // NumSubscriptions returns active number of subscriptions. func (nc *Conn) NumSubscriptions() int { - nc.mu.Lock() - defer nc.mu.Unlock() + nc.mu.RLock() + defer nc.mu.RUnlock() return len(nc.subs) } @@ -2854,7 +3007,6 @@ func (nc *Conn) removeSub(s *Subscription) { s.mch = nil // Mark as invalid - s.conn = nil s.closed = true if s.pCond != nil { s.pCond.Broadcast() @@ -2891,7 +3043,7 @@ func (s *Subscription) IsValid() bool { } s.mu.Lock() defer s.mu.Unlock() - return s.conn != nil + return s.conn != nil && !s.closed } // Drain will remove interest but continue callbacks until all messages @@ -2916,8 +3068,12 @@ func (s *Subscription) Unsubscribe() error { } s.mu.Lock() conn := s.conn + closed := s.closed s.mu.Unlock() - if conn == nil { + if conn == nil || conn.IsClosed() { + return ErrConnectionClosed + } + if closed { return ErrBadSubscription } if conn.IsDraining() { @@ -2973,8 +3129,9 @@ func (s *Subscription) AutoUnsubscribe(max int) error { } s.mu.Lock() conn := s.conn + closed := s.closed s.mu.Unlock() - if conn == nil { + if conn == nil || closed { return ErrBadSubscription } return conn.unsubscribe(s, max, false) @@ -3021,8 +3178,8 @@ func (nc *Conn) unsubscribe(sub *Subscription, max int, drainMode bool) error { } // NextMsg will return the next message available to a synchronous subscriber -// or block until one is available. A timeout can be used to return when no -// message has been delivered. +// or block until one is available. An error is returned if the subscription is invalid (ErrBadSubscription), +// the connection is closed (ErrConnectionClosed), or the timeout is reached (ErrTimeout). func (s *Subscription) NextMsg(timeout time.Duration) (*Msg, error) { if s == nil { return nil, ErrBadSubscription @@ -3046,7 +3203,7 @@ func (s *Subscription) NextMsg(timeout time.Duration) (*Msg, error) { select { case msg, ok = <-mch: if !ok { - return nil, ErrConnectionClosed + return nil, s.getNextMsgErr() } if err := s.processNextMsgDelivered(msg); err != nil { return nil, err @@ -3065,7 +3222,7 @@ func (s *Subscription) NextMsg(timeout time.Duration) (*Msg, error) { select { case msg, ok = <-mch: if !ok { - return nil, ErrConnectionClosed + return nil, s.getNextMsgErr() } if err := s.processNextMsgDelivered(msg); err != nil { return nil, err @@ -3102,6 +3259,18 @@ func (s *Subscription) validateNextMsgState() error { return nil } +// This is called when the sync channel has been closed. +// The error returned will be either connection or subscription +// closed depending on what caused NextMsg() to fail. +func (s *Subscription) getNextMsgErr() error { + s.mu.Lock() + defer s.mu.Unlock() + if s.connClosed { + return ErrConnectionClosed + } + return ErrBadSubscription +} + // processNextMsgDelivered takes a message and applies the needed // accounting to the stats from the subscription, returning an // error in case we have the maximum number of messages have been @@ -3149,7 +3318,7 @@ func (s *Subscription) Pending() (int, int, error) { } s.mu.Lock() defer s.mu.Unlock() - if s.conn == nil { + if s.conn == nil || s.closed { return -1, -1, ErrBadSubscription } if s.typ == ChanSubscription { @@ -3165,7 +3334,7 @@ func (s *Subscription) MaxPending() (int, int, error) { } s.mu.Lock() defer s.mu.Unlock() - if s.conn == nil { + if s.conn == nil || s.closed { return -1, -1, ErrBadSubscription } if s.typ == ChanSubscription { @@ -3181,7 +3350,7 @@ func (s *Subscription) ClearMaxPending() error { } s.mu.Lock() defer s.mu.Unlock() - if s.conn == nil { + if s.conn == nil || s.closed { return ErrBadSubscription } if s.typ == ChanSubscription { @@ -3206,7 +3375,7 @@ func (s *Subscription) PendingLimits() (int, int, error) { } s.mu.Lock() defer s.mu.Unlock() - if s.conn == nil { + if s.conn == nil || s.closed { return -1, -1, ErrBadSubscription } if s.typ == ChanSubscription { @@ -3223,7 +3392,7 @@ func (s *Subscription) SetPendingLimits(msgLimit, bytesLimit int) error { } s.mu.Lock() defer s.mu.Unlock() - if s.conn == nil { + if s.conn == nil || s.closed { return ErrBadSubscription } if s.typ == ChanSubscription { @@ -3243,7 +3412,7 @@ func (s *Subscription) Delivered() (int64, error) { } s.mu.Lock() defer s.mu.Unlock() - if s.conn == nil { + if s.conn == nil || s.closed { return -1, ErrBadSubscription } return int64(s.delivered), nil @@ -3259,12 +3428,27 @@ func (s *Subscription) Dropped() (int, error) { } s.mu.Lock() defer s.mu.Unlock() - if s.conn == nil { + if s.conn == nil || s.closed { return -1, ErrBadSubscription } return s.dropped, nil } +// Respond allows a convenient way to respond to requests in service based subscriptions. +func (m *Msg) Respond(data []byte) error { + if m == nil || m.Sub == nil { + return ErrMsgNotBound + } + if m.Reply == "" { + return ErrMsgNoReply + } + m.Sub.mu.Lock() + nc := m.Sub.conn + m.Sub.mu.Unlock() + // No need to check the connection here since the call to publish will do all the checking. + return nc.Publish(m.Reply, data) +} + // FIXME: This is a hack // removeFlushEntry is needed when we need to discard queued up responses // for our pings as part of a flush call. This happens when we have a flush @@ -3366,8 +3550,8 @@ func (nc *Conn) Flush() error { // Buffered will return the number of bytes buffered to be sent to the server. // FIXME(dlc) take into account disconnected state. func (nc *Conn) Buffered() (int, error) { - nc.mu.Lock() - defer nc.mu.Unlock() + nc.mu.RLock() + defer nc.mu.RUnlock() if nc.isClosed() || nc.bw == nil { return -1, ErrConnectionClosed } @@ -3441,7 +3625,7 @@ func (nc *Conn) clearPendingRequestCalls() { // desired status. Also controls whether user defined callbacks // will be triggered. The lock should not be held entering this // function. This function will handle the locking manually. -func (nc *Conn) close(status Status, doCBs bool) { +func (nc *Conn) close(status Status, doCBs bool, err error) { nc.mu.Lock() if nc.isClosed() { nc.status = status @@ -3466,8 +3650,14 @@ func (nc *Conn) close(status Status, doCBs bool) { nc.stopPingTimer() nc.ptmr = nil - // Go ahead and make sure we have flushed the outbound - if nc.conn != nil { + // Need to close and set tcp conn to nil if reconnect loop has stopped, + // otherwise we would incorrectly invoke Disconnect handler (if set) + // down below. + if nc.ar && nc.conn != nil { + nc.conn.Close() + nc.conn = nil + } else if nc.conn != nil { + // Go ahead and make sure we have flushed the outbound nc.bw.Flush() defer nc.conn.Close() } @@ -3501,12 +3691,20 @@ func (nc *Conn) close(status Status, doCBs bool) { // Perform appropriate callback if needed for a disconnect. if doCBs { - if nc.Opts.DisconnectedCB != nil && nc.conn != nil { - nc.ach.push(func() { nc.Opts.DisconnectedCB(nc) }) + if nc.conn != nil { + if nc.Opts.DisconnectedErrCB != nil { + nc.ach.push(func() { nc.Opts.DisconnectedErrCB(nc, err) }) + } else if nc.Opts.DisconnectedCB != nil { + nc.ach.push(func() { nc.Opts.DisconnectedCB(nc) }) + } } if nc.Opts.ClosedCB != nil { nc.ach.push(func() { nc.Opts.ClosedCB(nc) }) } + } + // If this is terminal, then we have to notify the asyncCB handler that + // it can exit once all async cbs have been dispatched. + if status == CLOSED { nc.ach.close() } nc.mu.Unlock() @@ -3515,27 +3713,29 @@ func (nc *Conn) close(status Status, doCBs bool) { // Close will close the connection to the server. This call will release // all blocking calls, such as Flush() and NextMsg() func (nc *Conn) Close() { - nc.close(CLOSED, true) + if nc != nil { + nc.close(CLOSED, !nc.Opts.NoCallbacksAfterClientClose, nil) + } } // IsClosed tests if a Conn has been closed. func (nc *Conn) IsClosed() bool { - nc.mu.Lock() - defer nc.mu.Unlock() + nc.mu.RLock() + defer nc.mu.RUnlock() return nc.isClosed() } // IsReconnecting tests if a Conn is reconnecting. func (nc *Conn) IsReconnecting() bool { - nc.mu.Lock() - defer nc.mu.Unlock() + nc.mu.RLock() + defer nc.mu.RUnlock() return nc.isReconnecting() } // IsConnected tests if a Conn is connected. func (nc *Conn) IsConnected() bool { - nc.mu.Lock() - defer nc.mu.Unlock() + nc.mu.RLock() + defer nc.mu.RUnlock() return nc.isConnected() } @@ -3544,6 +3744,19 @@ func (nc *Conn) IsConnected() bool { func (nc *Conn) drainConnection() { // Snapshot subs list. nc.mu.Lock() + + // Check again here if we are in a state to not process. + if nc.isClosed() { + nc.mu.Unlock() + return + } + if nc.isConnecting() || nc.isReconnecting() { + nc.mu.Unlock() + // Move to closed state. + nc.close(CLOSED, true, nil) + return + } + subs := make([]*Subscription, 0, len(nc.subs)) for _, s := range nc.subs { subs = append(subs, s) @@ -3590,15 +3803,15 @@ func (nc *Conn) drainConnection() { nc.mu.Unlock() // Do publish drain via Flush() call. - err := nc.Flush() + err := nc.FlushTimeout(5 * time.Second) if err != nil { pushErr(err) - nc.Close() + nc.close(CLOSED, true, nil) return } // Move to closed state. - nc.Close() + nc.close(CLOSED, true, nil) } // Drain will put a connection into a drain state. All subscriptions will @@ -3608,27 +3821,30 @@ func (nc *Conn) drainConnection() { // option to know when the connection has moved from draining to closed. func (nc *Conn) Drain() error { nc.mu.Lock() - defer nc.mu.Unlock() - if nc.isClosed() { + nc.mu.Unlock() return ErrConnectionClosed } if nc.isConnecting() || nc.isReconnecting() { + nc.mu.Unlock() + nc.close(CLOSED, true, nil) return ErrConnectionReconnecting } if nc.isDraining() { + nc.mu.Unlock() return nil } - nc.status = DRAINING_SUBS go nc.drainConnection() + nc.mu.Unlock() + return nil } // IsDraining tests if a Conn is in the draining state. func (nc *Conn) IsDraining() bool { - nc.mu.Lock() - defer nc.mu.Unlock() + nc.mu.RLock() + defer nc.mu.RUnlock() return nc.isDraining() } @@ -3651,8 +3867,8 @@ func (nc *Conn) getServers(implicitOnly bool) []string { // authentication is enabled, use UserInfo or Token when connecting with // these urls. func (nc *Conn) Servers() []string { - nc.mu.Lock() - defer nc.mu.Unlock() + nc.mu.RLock() + defer nc.mu.RUnlock() return nc.getServers(false) } @@ -3660,15 +3876,15 @@ func (nc *Conn) Servers() []string { // after a connection has been established. If authentication is enabled, // use UserInfo or Token when connecting with these urls. func (nc *Conn) DiscoveredServers() []string { - nc.mu.Lock() - defer nc.mu.Unlock() + nc.mu.RLock() + defer nc.mu.RUnlock() return nc.getServers(true) } // Status returns the current state of the connection. func (nc *Conn) Status() Status { - nc.mu.Lock() - defer nc.mu.Unlock() + nc.mu.RLock() + defer nc.mu.RUnlock() return nc.status } @@ -3704,18 +3920,16 @@ func (nc *Conn) isDrainingPubs() bool { // Stats will return a race safe copy of the Statistics section for the connection. func (nc *Conn) Stats() Statistics { - // Stats are updated either under connection's mu or subsMu mutexes. - // Lock both to safely get them. + // Stats are updated either under connection's mu or with atomic operations + // for inbound stats in processMsg(). nc.mu.Lock() - nc.subsMu.RLock() stats := Statistics{ - InMsgs: nc.InMsgs, - InBytes: nc.InBytes, + InMsgs: atomic.LoadUint64(&nc.InMsgs), + InBytes: atomic.LoadUint64(&nc.InBytes), OutMsgs: nc.OutMsgs, OutBytes: nc.OutBytes, Reconnects: nc.Reconnects, } - nc.subsMu.RUnlock() nc.mu.Unlock() return stats } @@ -3724,22 +3938,22 @@ func (nc *Conn) Stats() Statistics { // This is set by the server configuration and delivered to the client // upon connect. func (nc *Conn) MaxPayload() int64 { - nc.mu.Lock() - defer nc.mu.Unlock() + nc.mu.RLock() + defer nc.mu.RUnlock() return nc.info.MaxPayload } // AuthRequired will return if the connected server requires authorization. func (nc *Conn) AuthRequired() bool { - nc.mu.Lock() - defer nc.mu.Unlock() + nc.mu.RLock() + defer nc.mu.RUnlock() return nc.info.AuthRequired } // TLSRequired will return if the connected server requires TLS connections. func (nc *Conn) TLSRequired() bool { - nc.mu.Lock() - defer nc.mu.Unlock() + nc.mu.RLock() + defer nc.mu.RUnlock() return nc.info.TLSRequired } @@ -3797,8 +4011,8 @@ func (nc *Conn) Barrier(f func()) error { // This function returns ErrNoClientIDReturned if the server is of a // version prior to 1.2.0. func (nc *Conn) GetClientID() (uint64, error) { - nc.mu.Lock() - defer nc.mu.Unlock() + nc.mu.RLock() + defer nc.mu.RUnlock() if nc.isClosed() { return 0, ErrConnectionClosed } @@ -3833,70 +4047,74 @@ func NkeyOptionFromSeed(seedFile string) (Option, error) { return Nkey(string(pub), sigCB), nil } -// This is a regex to match decorated jwts in keys/seeds. -// .e.g. -// -----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------ - -var nscDecoratedRe = regexp.MustCompile(`\s*(?:(?:[-]{3,}[^\n]*[-]{3,}\n)(.+)(?:\n\s*[-]{3,}[^\n]*[-]{3,}\n))`) +// Just wipe slice with 'x', for clearing contents of creds or nkey seed file. +func wipeSlice(buf []byte) { + for i := range buf { + buf[i] = 'x' + } +} func userFromFile(userFile string) (string, error) { - contents, err := ioutil.ReadFile(userFile) + path, err := expandPath(userFile) + if err != nil { + return _EMPTY_, fmt.Errorf("nats: %v", err) + } + + contents, err := ioutil.ReadFile(path) if err != nil { return _EMPTY_, fmt.Errorf("nats: %v", err) } defer wipeSlice(contents) + return jwt.ParseDecoratedJWT(contents) +} - items := nscDecoratedRe.FindAllSubmatch(contents, -1) - if len(items) == 0 { - return string(contents), nil +func homeDir() (string, error) { + if runtime.GOOS == "windows" { + homeDrive, homePath := os.Getenv("HOMEDRIVE"), os.Getenv("HOMEPATH") + userProfile := os.Getenv("USERPROFILE") + + var home string + if homeDrive == "" || homePath == "" { + if userProfile == "" { + return _EMPTY_, errors.New("nats: failed to get home dir, require %HOMEDRIVE% and %HOMEPATH% or %USERPROFILE%") + } + home = userProfile + } else { + home = filepath.Join(homeDrive, homePath) + } + + return home, 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 + + home := os.Getenv("HOME") + if home == "" { + return _EMPTY_, errors.New("nats: 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 } func nkeyPairFromSeedFile(seedFile string) (nkeys.KeyPair, error) { - var seed []byte contents, err := ioutil.ReadFile(seedFile) if err != nil { return nil, fmt.Errorf("nats: %v", err) } defer wipeSlice(contents) - - items := nscDecoratedRe.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("SU")) { - seed = line - break - } - } - } - - if seed == nil { - return nil, fmt.Errorf("nats: No nkey user seed found in %q", seedFile) - } - kp, err := nkeys.FromSeed(seed) - if err != nil { - return nil, err - } - return kp, nil + return jwt.ParseDecoratedNKey(contents) } // Sign authentication challenges from the server. @@ -3913,13 +4131,6 @@ func sigHandler(nonce []byte, seedFile string) ([]byte, error) { return sig, nil } -// Just wipe slice with 'x', for clearing contents of nkey seed file. -func wipeSlice(buf []byte) { - for i := range buf { - buf[i] = 'x' - } -} - type timeoutWriter struct { timeout time.Duration conn net.Conn diff --git a/gateway/vendor/github.com/nats-io/go-nats/netchan.go b/gateway/vendor/github.com/nats-io/nats.go/netchan.go similarity index 98% rename from gateway/vendor/github.com/nats-io/go-nats/netchan.go rename to gateway/vendor/github.com/nats-io/nats.go/netchan.go index add3cba5..fd86d065 100644 --- a/gateway/vendor/github.com/nats-io/go-nats/netchan.go +++ b/gateway/vendor/github.com/nats-io/nats.go/netchan.go @@ -107,5 +107,5 @@ func (c *EncodedConn) bindRecvChan(subject, queue string, channel interface{}) ( chVal.Send(oPtr) } - return c.Conn.subscribe(subject, queue, cb, nil) + return c.Conn.subscribe(subject, queue, cb, nil, false) } diff --git a/gateway/vendor/github.com/nats-io/go-nats/parser.go b/gateway/vendor/github.com/nats-io/nats.go/parser.go similarity index 100% rename from gateway/vendor/github.com/nats-io/go-nats/parser.go rename to gateway/vendor/github.com/nats-io/nats.go/parser.go diff --git a/gateway/vendor/github.com/nats-io/go-nats/timer.go b/gateway/vendor/github.com/nats-io/nats.go/timer.go similarity index 100% rename from gateway/vendor/github.com/nats-io/go-nats/timer.go rename to gateway/vendor/github.com/nats-io/nats.go/timer.go diff --git a/gateway/vendor/github.com/nats-io/go-nats/util/tls.go b/gateway/vendor/github.com/nats-io/nats.go/util/tls.go similarity index 100% rename from gateway/vendor/github.com/nats-io/go-nats/util/tls.go rename to gateway/vendor/github.com/nats-io/nats.go/util/tls.go diff --git a/gateway/vendor/github.com/nats-io/go-nats/util/tls_go17.go b/gateway/vendor/github.com/nats-io/nats.go/util/tls_go17.go similarity index 100% rename from gateway/vendor/github.com/nats-io/go-nats/util/tls_go17.go rename to gateway/vendor/github.com/nats-io/nats.go/util/tls_go17.go diff --git a/gateway/vendor/github.com/nats-io/go-nats-streaming/.gitignore b/gateway/vendor/github.com/nats-io/stan.go/.gitignore similarity index 98% rename from gateway/vendor/github.com/nats-io/go-nats-streaming/.gitignore rename to gateway/vendor/github.com/nats-io/stan.go/.gitignore index f9b9d14c..0a5fc725 100644 --- a/gateway/vendor/github.com/nats-io/go-nats-streaming/.gitignore +++ b/gateway/vendor/github.com/nats-io/stan.go/.gitignore @@ -26,3 +26,4 @@ _testmain.go # Eclipse stuff .project .settings/ +.idea \ No newline at end of file diff --git a/gateway/vendor/github.com/nats-io/stan.go/.travis.yml b/gateway/vendor/github.com/nats-io/stan.go/.travis.yml new file mode 100644 index 00000000..56635d46 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/stan.go/.travis.yml @@ -0,0 +1,23 @@ +language: go +sudo: false +go: +- 1.12.x +- 1.13.x +env: +- GO111MODULE=off +go_import_path: github.com/nats-io/stan.go +install: +- go get -t -v ./... +- go get github.com/nats-io/nats-streaming-server +- go get -u honnef.co/go/tools/cmd/staticcheck +- go get -u github.com/client9/misspell/cmd/misspell +before_script: +- $(exit $(go fmt ./... | wc -l)) +- go vet ./... +- find . -type f -name "*.go" | grep -v "/pb/" | xargs misspell -error -locale US +- staticcheck ./... +script: +- go test -i -v ./... +- go test -v -race ./... +after_success: +- if [[ "$TRAVIS_GO_VERSION" =~ 1.13 ]]; then ./scripts/cov.sh TRAVIS; fi diff --git a/gateway/vendor/github.com/nats-io/go-nats-streaming/CODE-OF-CONDUCT.md b/gateway/vendor/github.com/nats-io/stan.go/CODE-OF-CONDUCT.md similarity index 100% rename from gateway/vendor/github.com/nats-io/go-nats-streaming/CODE-OF-CONDUCT.md rename to gateway/vendor/github.com/nats-io/stan.go/CODE-OF-CONDUCT.md diff --git a/gateway/vendor/github.com/nats-io/go-nats-streaming/GOVERNANCE.md b/gateway/vendor/github.com/nats-io/stan.go/GOVERNANCE.md similarity index 100% rename from gateway/vendor/github.com/nats-io/go-nats-streaming/GOVERNANCE.md rename to gateway/vendor/github.com/nats-io/stan.go/GOVERNANCE.md diff --git a/gateway/vendor/github.com/nats-io/go-nats-streaming/LICENSE b/gateway/vendor/github.com/nats-io/stan.go/LICENSE similarity index 100% rename from gateway/vendor/github.com/nats-io/go-nats-streaming/LICENSE rename to gateway/vendor/github.com/nats-io/stan.go/LICENSE diff --git a/gateway/vendor/github.com/nats-io/go-nats-streaming/MAINTAINERS.md b/gateway/vendor/github.com/nats-io/stan.go/MAINTAINERS.md similarity index 100% rename from gateway/vendor/github.com/nats-io/go-nats-streaming/MAINTAINERS.md rename to gateway/vendor/github.com/nats-io/stan.go/MAINTAINERS.md diff --git a/gateway/vendor/github.com/nats-io/go-nats-streaming/README.md b/gateway/vendor/github.com/nats-io/stan.go/README.md similarity index 96% rename from gateway/vendor/github.com/nats-io/go-nats-streaming/README.md rename to gateway/vendor/github.com/nats-io/stan.go/README.md index 7af264d8..d1cebff0 100644 --- a/gateway/vendor/github.com/nats-io/go-nats-streaming/README.md +++ b/gateway/vendor/github.com/nats-io/stan.go/README.md @@ -3,9 +3,9 @@ NATS Streaming is an extremely performant, lightweight reliable streaming platform powered by [NATS](https://nats.io). [![License Apache 2](https://img.shields.io/badge/License-Apache2-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) -[![Build Status](https://travis-ci.org/nats-io/go-nats-streaming.svg?branch=master)](http://travis-ci.org/nats-io/go-nats-streaming) -[![Coverage Status](https://coveralls.io/repos/nats-io/go-nats-streaming/badge.svg?branch=master)](https://coveralls.io/r/nats-io/go-nats-streaming?branch=master) -[![GoDoc](https://godoc.org/github.com/nats-io/go-nats-streaming?status.svg)](http://godoc.org/github.com/nats-io/go-nats-streaming) +[![Build Status](https://travis-ci.org/nats-io/stan.go.svg?branch=master)](http://travis-ci.org/nats-io/stan.go) +[![Coverage Status](https://coveralls.io/repos/nats-io/stan.go/badge.svg?branch=master)](https://coveralls.io/r/nats-io/stan.go?branch=master) +[![GoDoc](https://godoc.org/github.com/nats-io/stan.go?status.svg)](http://godoc.org/github.com/nats-io/stan.go) NATS Streaming provides the following high-level feature set: - Log based persistence @@ -16,19 +16,27 @@ NATS Streaming provides the following high-level feature set: ## Notes -- Please raise questions/issues via the [Issue Tracker](https://github.com/nats-io/go-nats-streaming/issues). +- Please raise questions/issues via the [Issue Tracker](https://github.com/nats-io/stan.go/issues). ## Installation ```bash # Go client -go get github.com/nats-io/go-nats-streaming +go get github.com/nats-io/stan.go/ +``` + +When using or transitioning to Go modules support: + +```bash +# Go client latest or explicit version +go get github.com/nats-io/stan.go/@latest +go get github.com/nats-io/stan.go/@v0.6.0 ``` ## Basic Usage ```go -import stan "github.com/nats-io/go-nats-streaming" +import stan "github.com/nats-io/stan.go" sc, _ := stan.Connect(clusterID, clientID) diff --git a/gateway/vendor/github.com/nats-io/stan.go/go.mod b/gateway/vendor/github.com/nats-io/stan.go/go.mod new file mode 100644 index 00000000..600561d3 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/stan.go/go.mod @@ -0,0 +1,7 @@ +module github.com/nats-io/stan.go + +require ( + github.com/gogo/protobuf v1.2.1 + github.com/nats-io/nats.go v1.9.1 + github.com/nats-io/nuid v1.0.1 +) diff --git a/gateway/vendor/github.com/nats-io/stan.go/go.sum b/gateway/vendor/github.com/nats-io/stan.go/go.sum new file mode 100644 index 00000000..296a7401 --- /dev/null +++ b/gateway/vendor/github.com/nats-io/stan.go/go.sum @@ -0,0 +1,20 @@ +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/nats-io/jwt v0.3.0 h1:xdnzwFETV++jNc4W1mw//qFyJGb2ABOombmZJQS4+Qo= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/nats.go v1.9.1 h1:ik3HbLhZ0YABLto7iX80pZLPw/6dx3T+++MZJwLnMrQ= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0 h1:qMd4+pRHgdr1nAClu+2h/2a5F2TmKcCzjCDazVgRoX4= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/gateway/vendor/github.com/nats-io/go-nats-streaming/pb/protocol.pb.go b/gateway/vendor/github.com/nats-io/stan.go/pb/protocol.pb.go similarity index 92% rename from gateway/vendor/github.com/nats-io/go-nats-streaming/pb/protocol.pb.go rename to gateway/vendor/github.com/nats-io/stan.go/pb/protocol.pb.go index 2182dc93..064c8c1e 100644 --- a/gateway/vendor/github.com/nats-io/go-nats-streaming/pb/protocol.pb.go +++ b/gateway/vendor/github.com/nats-io/stan.go/pb/protocol.pb.go @@ -103,13 +103,14 @@ func (*PubAck) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []i // Msg struct. Sequence is assigned for global ordering by // the cluster after the publisher has been acknowledged. type MsgProto struct { - Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` - Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` - Reply string `protobuf:"bytes,3,opt,name=reply,proto3" json:"reply,omitempty"` - Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"` - Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"` - Redelivered bool `protobuf:"varint,6,opt,name=redelivered,proto3" json:"redelivered,omitempty"` - CRC32 uint32 `protobuf:"varint,10,opt,name=CRC32,proto3" json:"CRC32,omitempty"` + Sequence uint64 `protobuf:"varint,1,opt,name=sequence,proto3" json:"sequence,omitempty"` + Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` + Reply string `protobuf:"bytes,3,opt,name=reply,proto3" json:"reply,omitempty"` + Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"` + Timestamp int64 `protobuf:"varint,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Redelivered bool `protobuf:"varint,6,opt,name=redelivered,proto3" json:"redelivered,omitempty"` + RedeliveryCount uint32 `protobuf:"varint,7,opt,name=redeliveryCount,proto3" json:"redeliveryCount,omitempty"` + CRC32 uint32 `protobuf:"varint,10,opt,name=CRC32,proto3" json:"CRC32,omitempty"` } func (m *MsgProto) Reset() { *m = MsgProto{} } @@ -405,6 +406,11 @@ func (m *MsgProto) MarshalTo(dAtA []byte) (int, error) { } i++ } + if m.RedeliveryCount != 0 { + dAtA[i] = 0x38 + i++ + i = encodeVarintProtocol(dAtA, i, uint64(m.RedeliveryCount)) + } if m.CRC32 != 0 { dAtA[i] = 0x50 i++ @@ -898,6 +904,9 @@ func (m *MsgProto) Size() (n int) { if m.Redelivered { n += 2 } + if m.RedeliveryCount != 0 { + n += 1 + sovProtocol(uint64(m.RedeliveryCount)) + } if m.CRC32 != 0 { n += 1 + sovProtocol(uint64(m.CRC32)) } @@ -1664,6 +1673,25 @@ func (m *MsgProto) Unmarshal(dAtA []byte) error { } } m.Redelivered = bool(v != 0) + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RedeliveryCount", wireType) + } + m.RedeliveryCount = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RedeliveryCount |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } case 10: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field CRC32", wireType) @@ -3327,57 +3355,58 @@ var ( func init() { proto.RegisterFile("protocol.proto", fileDescriptorProtocol) } var fileDescriptorProtocol = []byte{ - // 823 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0xc1, 0x8e, 0xe3, 0x44, - 0x10, 0x1d, 0xc7, 0x76, 0x26, 0x53, 0x93, 0x84, 0x6c, 0x13, 0xad, 0xac, 0x08, 0x45, 0x23, 0x6b, - 0x41, 0xab, 0x91, 0xc8, 0x4a, 0x59, 0x01, 0x07, 0x4e, 0x90, 0xd1, 0x42, 0x04, 0xb3, 0x1b, 0x39, - 0x20, 0xae, 0xb4, 0x9d, 0x5e, 0xa7, 0x19, 0xa7, 0xed, 0x75, 0xb7, 0x87, 0xcc, 0x11, 0xbe, 0x80, - 0x0f, 0xe1, 0x23, 0x38, 0xee, 0x81, 0x03, 0x9f, 0x00, 0xc3, 0x8d, 0xaf, 0x40, 0x5d, 0x76, 0x9c, - 0x76, 0x86, 0x1d, 0x90, 0xb8, 0xf5, 0x7b, 0xa9, 0x6e, 0xd7, 0x7b, 0xaf, 0x2a, 0xd0, 0xcf, 0xf2, - 0x54, 0xa5, 0x51, 0x9a, 0x4c, 0xf0, 0x40, 0x5a, 0x59, 0x38, 0x7a, 0x3f, 0xe6, 0x6a, 0x5d, 0x84, - 0x93, 0x28, 0xdd, 0x3c, 0x89, 0xd3, 0x38, 0x7d, 0x82, 0x3f, 0x85, 0xc5, 0x4b, 0x44, 0x08, 0xf0, - 0x54, 0x5e, 0xf1, 0x7f, 0xb6, 0xa0, 0xbd, 0x28, 0xc2, 0x4b, 0x19, 0x93, 0x11, 0x74, 0xa2, 0x84, - 0x33, 0xa1, 0xe6, 0x17, 0x9e, 0x75, 0x66, 0x3d, 0x3e, 0x09, 0x6a, 0x4c, 0x08, 0x38, 0x71, 0xc1, - 0x57, 0x5e, 0x0b, 0x79, 0x3c, 0x13, 0x0f, 0x8e, 0x65, 0x11, 0x7e, 0xc7, 0x22, 0xe5, 0xd9, 0x48, - 0xef, 0x20, 0x19, 0x82, 0x9b, 0xb3, 0x2c, 0xb9, 0xf1, 0x1c, 0xe4, 0x4b, 0xa0, 0xdf, 0x58, 0x51, - 0x45, 0x3d, 0xf7, 0xcc, 0x7a, 0xdc, 0x0d, 0xf0, 0x4c, 0x1e, 0x42, 0x3b, 0x4a, 0x85, 0x98, 0x5f, - 0x78, 0x6d, 0x64, 0x2b, 0xa4, 0x79, 0xb9, 0xa6, 0xd3, 0x0f, 0x3e, 0xf4, 0xa0, 0xe4, 0x4b, 0xe4, - 0x4f, 0xb1, 0xdb, 0x4f, 0xa2, 0xab, 0xba, 0x23, 0xcb, 0xe8, 0x68, 0x08, 0x2e, 0xcb, 0xf3, 0x34, - 0xaf, 0xda, 0x2c, 0x81, 0xff, 0x8b, 0x05, 0x9d, 0x4b, 0x19, 0x2f, 0xd0, 0xa2, 0x11, 0x74, 0x24, - 0x7b, 0x55, 0x30, 0x11, 0x31, 0xbc, 0xea, 0x04, 0x35, 0x36, 0x05, 0xb5, 0xde, 0x20, 0xc8, 0xfe, - 0x27, 0x41, 0x8e, 0x21, 0xe8, 0x1d, 0x38, 0x51, 0x7c, 0xc3, 0xa4, 0xa2, 0x9b, 0x0c, 0x95, 0xda, - 0xc1, 0x9e, 0x20, 0x67, 0x70, 0x9a, 0xb3, 0x15, 0x4b, 0xf8, 0x35, 0xcb, 0xd9, 0x0a, 0x35, 0x77, - 0x02, 0x93, 0xd2, 0x5f, 0x9a, 0x05, 0xb3, 0xa7, 0x53, 0xd4, 0xdd, 0x0b, 0x4a, 0xe0, 0x7f, 0x0c, - 0xb6, 0xd6, 0x6c, 0x34, 0x68, 0x35, 0x1b, 0x34, 0x65, 0xb5, 0x9a, 0xb2, 0xfc, 0x5f, 0x2d, 0xe8, - 0xcf, 0x52, 0x21, 0x58, 0xa4, 0x02, 0xcd, 0x49, 0x75, 0x6f, 0xd4, 0xef, 0x41, 0x7f, 0xcd, 0x68, - 0xae, 0x42, 0x46, 0xd5, 0x5c, 0x84, 0xe9, 0xb6, 0x32, 0xe3, 0x80, 0xd5, 0x6f, 0xec, 0xc6, 0x0f, - 0x6d, 0x71, 0x83, 0x1a, 0x1b, 0xb1, 0x3a, 0x8d, 0x58, 0x7d, 0xe8, 0x66, 0x5c, 0xc4, 0x73, 0xa1, - 0x58, 0x7e, 0x4d, 0x13, 0x34, 0xc8, 0x0d, 0x1a, 0x1c, 0x19, 0x03, 0x68, 0x7c, 0x49, 0xb7, 0x2f, - 0x0a, 0x85, 0x16, 0xb9, 0x81, 0xc1, 0xf8, 0x3f, 0xd8, 0xf0, 0x56, 0x2d, 0x47, 0x66, 0xa9, 0x90, - 0x4c, 0xbb, 0x9e, 0x15, 0xe1, 0x22, 0x67, 0x2f, 0xf9, 0xb6, 0x12, 0xb4, 0x27, 0xb4, 0xeb, 0xb2, - 0x08, 0x2b, 0xed, 0xb2, 0x92, 0x63, 0x52, 0xe4, 0x11, 0xf4, 0x0a, 0x61, 0xd6, 0x94, 0x39, 0x37, - 0x49, 0x5d, 0x15, 0x25, 0xa9, 0x64, 0x75, 0x55, 0x39, 0xde, 0x4d, 0x72, 0x3f, 0x84, 0xae, 0x31, - 0x84, 0xe4, 0x1c, 0x06, 0xb2, 0x08, 0x67, 0x8d, 0xeb, 0x6d, 0x2c, 0xb8, 0xc3, 0xef, 0x5c, 0xaa, - 0xeb, 0x8e, 0xb1, 0xae, 0xc1, 0xdd, 0x71, 0xb2, 0xf3, 0xaf, 0x4e, 0x9e, 0x1c, 0x3a, 0xd9, 0x48, - 0x10, 0x0e, 0x12, 0x2c, 0x1d, 0x4d, 0x78, 0xf4, 0x05, 0xbb, 0xf1, 0x56, 0xb5, 0xa3, 0x25, 0xe1, - 0x8f, 0xc1, 0x59, 0x70, 0x11, 0x1b, 0x39, 0x5b, 0x66, 0xce, 0xfe, 0x23, 0xe8, 0x2e, 0xb0, 0xdb, - 0x2a, 0x9f, 0xda, 0x13, 0xcb, 0x5c, 0xcc, 0xbf, 0x5a, 0xf0, 0xf6, 0xb2, 0x08, 0x65, 0x94, 0xf3, - 0x4c, 0xf1, 0x54, 0xfc, 0x97, 0xe9, 0x7c, 0xf3, 0x8e, 0x3e, 0x84, 0xf6, 0xab, 0xcf, 0xf2, 0xb4, - 0xc8, 0xaa, 0xf0, 0x2a, 0xa4, 0xbf, 0xcd, 0x71, 0x8c, 0xab, 0x3f, 0x23, 0x04, 0x7a, 0x26, 0x36, - 0x74, 0x3b, 0x17, 0xcf, 0x12, 0x1e, 0xaf, 0x55, 0x35, 0x88, 0x26, 0xa5, 0xd3, 0xa6, 0xd1, 0xd5, - 0x37, 0x94, 0xab, 0xb9, 0x58, 0xb2, 0x48, 0x56, 0xa3, 0xd8, 0x24, 0xf5, 0x3b, 0xab, 0x22, 0xa7, - 0x61, 0xc2, 0x9e, 0xd3, 0x0d, 0xab, 0xa2, 0x32, 0x29, 0xf2, 0x11, 0xf4, 0xa4, 0xa2, 0xb9, 0x5a, - 0xa4, 0x92, 0x6b, 0x95, 0x68, 0x75, 0x7f, 0xfa, 0x60, 0x92, 0x85, 0x93, 0xa5, 0xf9, 0x43, 0xd0, - 0xac, 0xd3, 0x0d, 0x20, 0xb1, 0xdc, 0x2d, 0xf6, 0x29, 0x2e, 0x76, 0x93, 0xd4, 0xeb, 0x8a, 0xc4, - 0x57, 0x7c, 0xc3, 0x2e, 0x58, 0xa2, 0xa8, 0xd7, 0xc5, 0x7f, 0x9d, 0x03, 0xd6, 0xff, 0x1c, 0x86, - 0x4d, 0xaf, 0xab, 0x68, 0x46, 0xd0, 0xa1, 0xd1, 0x95, 0xb9, 0xe8, 0x35, 0xde, 0xc7, 0x66, 0x9b, - 0xb1, 0xfd, 0x68, 0x01, 0xf9, 0x5a, 0x2f, 0x86, 0x7e, 0x2c, 0x64, 0xff, 0x2f, 0xb5, 0x3a, 0x1d, - 0xfb, 0x20, 0x1d, 0xd3, 0x55, 0xe7, 0x8e, 0xab, 0xfe, 0x39, 0x74, 0xcd, 0xa5, 0xb9, 0xef, 0xeb, - 0xfe, 0xbb, 0xd0, 0xab, 0x6a, 0xef, 0x1b, 0xc7, 0xf3, 0x6f, 0xa1, 0xd7, 0xc8, 0x83, 0x9c, 0xc2, - 0xf1, 0x73, 0xf6, 0xfd, 0x0b, 0x91, 0xdc, 0x0c, 0x8e, 0xc8, 0x00, 0xba, 0x5f, 0x52, 0xa9, 0x02, - 0x16, 0x31, 0x7e, 0xcd, 0x56, 0x03, 0x8b, 0x10, 0xe8, 0xd7, 0xf6, 0xe2, 0xc5, 0x41, 0x8b, 0x3c, - 0x80, 0xde, 0x2e, 0x99, 0x92, 0xb2, 0xc9, 0x09, 0xb8, 0xcf, 0x78, 0x2e, 0xd5, 0xc0, 0xf9, 0x74, - 0xf8, 0xfa, 0x8f, 0xf1, 0xd1, 0xeb, 0xdb, 0xb1, 0xf5, 0xdb, 0xed, 0xd8, 0xfa, 0xfd, 0x76, 0x6c, - 0xfd, 0xf4, 0xe7, 0xf8, 0x28, 0x6c, 0xe3, 0xd2, 0x3d, 0xfd, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x66, - 0xcd, 0xb9, 0x27, 0xce, 0x07, 0x00, 0x00, + // 840 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x94, 0xd1, 0x6e, 0xe3, 0x44, + 0x14, 0x86, 0x3b, 0x89, 0x9d, 0xa6, 0xa7, 0x49, 0x36, 0x3b, 0x54, 0x2b, 0xab, 0x42, 0x51, 0x65, + 0x2d, 0xa8, 0xaa, 0x44, 0x57, 0xea, 0x0a, 0xb8, 0xe0, 0x0a, 0x52, 0x2d, 0x44, 0xd0, 0xdd, 0xc8, + 0x05, 0x71, 0xcb, 0xd8, 0x99, 0x75, 0x87, 0x3a, 0x33, 0x5e, 0xcf, 0xb8, 0xb4, 0x97, 0xf0, 0x04, + 0x3c, 0x08, 0x0f, 0xb2, 0x17, 0x5c, 0xf0, 0x08, 0x50, 0xee, 0x78, 0x0a, 0x34, 0xc7, 0x8e, 0x33, + 0x4e, 0x69, 0x41, 0xe2, 0xce, 0xff, 0x97, 0xe3, 0xc9, 0x9c, 0xff, 0x3f, 0xc7, 0x30, 0xca, 0x0b, + 0x65, 0x54, 0xa2, 0xb2, 0x63, 0x7c, 0xa0, 0x9d, 0x3c, 0xde, 0xff, 0x20, 0x15, 0xe6, 0xa2, 0x8c, + 0x8f, 0x13, 0xb5, 0x7c, 0x96, 0xaa, 0x54, 0x3d, 0xc3, 0x9f, 0xe2, 0xf2, 0x35, 0x2a, 0x14, 0xf8, + 0x54, 0xbd, 0x12, 0xfe, 0x42, 0xa0, 0x37, 0x2f, 0xe3, 0x33, 0x9d, 0xd2, 0x7d, 0xe8, 0x27, 0x99, + 0xe0, 0xd2, 0xcc, 0x4e, 0x03, 0x72, 0x40, 0x0e, 0x77, 0xa2, 0x46, 0x53, 0x0a, 0x5e, 0x5a, 0x8a, + 0x45, 0xd0, 0x41, 0x8e, 0xcf, 0x34, 0x80, 0x6d, 0x5d, 0xc6, 0xdf, 0xf3, 0xc4, 0x04, 0x5d, 0xc4, + 0x2b, 0x49, 0xf7, 0xc0, 0x2f, 0x78, 0x9e, 0xdd, 0x04, 0x1e, 0xf2, 0x4a, 0xd8, 0x33, 0x16, 0xcc, + 0xb0, 0xc0, 0x3f, 0x20, 0x87, 0x83, 0x08, 0x9f, 0xe9, 0x13, 0xe8, 0x25, 0x4a, 0xca, 0xd9, 0x69, + 0xd0, 0x43, 0x5a, 0x2b, 0xcb, 0xf5, 0x05, 0x3b, 0xf9, 0xf0, 0xa3, 0x00, 0x2a, 0x5e, 0xa9, 0xf0, + 0x04, 0x6f, 0xfb, 0x69, 0x72, 0xd9, 0xdc, 0x88, 0x38, 0x37, 0xda, 0x03, 0x9f, 0x17, 0x85, 0x2a, + 0xea, 0x6b, 0x56, 0x22, 0xfc, 0x8b, 0x40, 0xff, 0x4c, 0xa7, 0x73, 0xb4, 0x68, 0x1f, 0xfa, 0x9a, + 0xbf, 0x29, 0xb9, 0x4c, 0x38, 0xbe, 0xea, 0x45, 0x8d, 0x76, 0x1b, 0xea, 0xdc, 0xd3, 0x50, 0xf7, + 0x9f, 0x1a, 0xf2, 0x9c, 0x86, 0xde, 0x85, 0x1d, 0x23, 0x96, 0x5c, 0x1b, 0xb6, 0xcc, 0xb1, 0xd3, + 0x6e, 0xb4, 0x06, 0xf4, 0x00, 0x76, 0x0b, 0xbe, 0xe0, 0x99, 0xb8, 0xe2, 0x05, 0x5f, 0x60, 0xcf, + 0xfd, 0xc8, 0x45, 0xf4, 0x10, 0x1e, 0x35, 0xf2, 0x66, 0xaa, 0x4a, 0x69, 0x82, 0xed, 0x03, 0x72, + 0x38, 0x8c, 0x36, 0xb1, 0xbd, 0xd3, 0x34, 0x9a, 0x3e, 0x3f, 0x41, 0x87, 0x86, 0x51, 0x25, 0xc2, + 0x4f, 0xa0, 0x6b, 0xdd, 0x71, 0x5a, 0x21, 0xed, 0x56, 0x5c, 0x03, 0x3a, 0x6d, 0x03, 0xc2, 0x5f, + 0x09, 0x8c, 0xa6, 0x4a, 0x4a, 0x9e, 0x98, 0xc8, 0x32, 0x6d, 0x1e, 0x1c, 0x8a, 0xf7, 0x61, 0x74, + 0xc1, 0x59, 0x61, 0x62, 0xce, 0xcc, 0x4c, 0xc6, 0xea, 0xba, 0xb6, 0x6d, 0x83, 0xda, 0x33, 0x56, + 0x83, 0x8a, 0x06, 0xfa, 0x51, 0xa3, 0x9d, 0x01, 0xf0, 0x5a, 0x03, 0x10, 0xc2, 0x20, 0x17, 0x32, + 0x9d, 0x49, 0xc3, 0x8b, 0x2b, 0x96, 0xa1, 0x95, 0x7e, 0xd4, 0x62, 0x74, 0x02, 0x60, 0xf5, 0x19, + 0xbb, 0x7e, 0x55, 0x1a, 0x34, 0xd3, 0x8f, 0x1c, 0x12, 0xfe, 0xd8, 0x85, 0x47, 0x4d, 0x3b, 0x3a, + 0x57, 0x52, 0x73, 0x9b, 0x4f, 0x5e, 0xc6, 0xf3, 0x82, 0xbf, 0x16, 0xd7, 0x75, 0x43, 0x6b, 0x60, + 0xf3, 0xd1, 0x65, 0x5c, 0xf7, 0xae, 0xeb, 0x76, 0x5c, 0x44, 0x9f, 0xc2, 0xb0, 0x94, 0x6e, 0x4d, + 0x35, 0x11, 0x6d, 0x68, 0xab, 0x92, 0x4c, 0x69, 0xde, 0x54, 0x55, 0x8b, 0xd0, 0x86, 0xeb, 0x71, + 0xf5, 0x9d, 0x71, 0xa5, 0x47, 0x30, 0xd6, 0x65, 0x3c, 0x6d, 0xbd, 0xde, 0xc3, 0x82, 0x3b, 0x7c, + 0xe5, 0x52, 0x53, 0xb7, 0x8d, 0x75, 0x2d, 0x76, 0xc7, 0xc9, 0xfe, 0xbf, 0x3a, 0xb9, 0xb3, 0xe9, + 0x64, 0x2b, 0x41, 0xd8, 0x48, 0xb0, 0x72, 0x34, 0x13, 0xc9, 0x97, 0xfc, 0x26, 0x58, 0x34, 0x8e, + 0x56, 0x20, 0x9c, 0x80, 0x37, 0x17, 0x32, 0x75, 0x72, 0x26, 0x6e, 0xce, 0xe1, 0x53, 0x18, 0xcc, + 0xf1, 0xb6, 0x75, 0x3e, 0x8d, 0x27, 0xa4, 0xb5, 0xc2, 0x1d, 0x78, 0xe7, 0xbc, 0x8c, 0x75, 0x52, + 0x88, 0xdc, 0x08, 0x25, 0xff, 0xcb, 0x74, 0xde, 0xbf, 0xcd, 0x4f, 0xa0, 0xf7, 0xe6, 0xf3, 0x42, + 0x95, 0x79, 0x1d, 0x5e, 0xad, 0xec, 0x7f, 0x0b, 0x1c, 0xe3, 0xfa, 0xb3, 0x85, 0xc2, 0xce, 0xc4, + 0x92, 0x5d, 0xcf, 0xe4, 0x8b, 0x4c, 0xa4, 0x17, 0xa6, 0x1e, 0x44, 0x17, 0xd9, 0xb4, 0x59, 0x72, + 0xf9, 0x2d, 0x13, 0x66, 0x26, 0xcf, 0x79, 0xa2, 0xeb, 0x51, 0x6c, 0x43, 0x7b, 0xce, 0xa2, 0x2c, + 0x58, 0x9c, 0xf1, 0x97, 0x6c, 0xc9, 0xeb, 0xa8, 0x5c, 0x44, 0x3f, 0x86, 0xa1, 0x36, 0xac, 0x30, + 0x73, 0xa5, 0x85, 0xed, 0x12, 0xad, 0x1e, 0x9d, 0x3c, 0x3e, 0xce, 0xe3, 0xe3, 0x73, 0xf7, 0x87, + 0xa8, 0x5d, 0x67, 0x2f, 0x80, 0xe0, 0x7c, 0xb5, 0xd8, 0xbb, 0xb8, 0xd8, 0x6d, 0x68, 0xd7, 0x15, + 0xc1, 0xd7, 0x62, 0xc9, 0x4f, 0x79, 0x66, 0x58, 0x30, 0xc0, 0xef, 0xd3, 0x06, 0x0d, 0xbf, 0x80, + 0xbd, 0xb6, 0xd7, 0x75, 0x34, 0xfb, 0xd0, 0x67, 0xc9, 0xa5, 0xbb, 0xe8, 0x8d, 0x5e, 0xc7, 0xd6, + 0x75, 0x63, 0xfb, 0x89, 0x00, 0xfd, 0xc6, 0x2e, 0x86, 0x3d, 0x2c, 0xe6, 0xff, 0x2f, 0xb5, 0x26, + 0x9d, 0xee, 0x46, 0x3a, 0xae, 0xab, 0xde, 0x1d, 0x57, 0xc3, 0x23, 0x18, 0xb8, 0x4b, 0xf3, 0xd0, + 0xbf, 0x87, 0xef, 0xc1, 0xb0, 0xae, 0x7d, 0x68, 0x1c, 0x8f, 0xbe, 0x83, 0x61, 0x2b, 0x0f, 0xba, + 0x0b, 0xdb, 0x2f, 0xf9, 0x0f, 0xaf, 0x64, 0x76, 0x33, 0xde, 0xa2, 0x63, 0x18, 0x7c, 0xc5, 0xb4, + 0x89, 0x78, 0xc2, 0xc5, 0x15, 0x5f, 0x8c, 0x09, 0xa5, 0x30, 0x6a, 0xec, 0xc5, 0x17, 0xc7, 0x1d, + 0xfa, 0x18, 0x86, 0xab, 0x64, 0x2a, 0xd4, 0xa5, 0x3b, 0xe0, 0xbf, 0x10, 0x85, 0x36, 0x63, 0xef, + 0xb3, 0xbd, 0xb7, 0x7f, 0x4c, 0xb6, 0xde, 0xde, 0x4e, 0xc8, 0x6f, 0xb7, 0x13, 0xf2, 0xfb, 0xed, + 0x84, 0xfc, 0xfc, 0xe7, 0x64, 0x2b, 0xee, 0xe1, 0xd2, 0x3d, 0xff, 0x3b, 0x00, 0x00, 0xff, 0xff, + 0x89, 0xd0, 0xaf, 0x8e, 0xf8, 0x07, 0x00, 0x00, } diff --git a/gateway/vendor/github.com/nats-io/go-nats-streaming/pb/protocol.proto b/gateway/vendor/github.com/nats-io/stan.go/pb/protocol.proto similarity index 90% rename from gateway/vendor/github.com/nats-io/go-nats-streaming/pb/protocol.proto rename to gateway/vendor/github.com/nats-io/stan.go/pb/protocol.proto index 20da0e8c..f5c63f7d 100644 --- a/gateway/vendor/github.com/nats-io/go-nats-streaming/pb/protocol.proto +++ b/gateway/vendor/github.com/nats-io/stan.go/pb/protocol.proto @@ -45,14 +45,15 @@ message PubAck { // Msg struct. Sequence is assigned for global ordering by // the cluster after the publisher has been acknowledged. message MsgProto { - uint64 sequence = 1; // globally ordered sequence number for the subject's channel - string subject = 2; // subject - string reply = 3; // optional reply - bytes data = 4; // payload - int64 timestamp = 5; // received timestamp - bool redelivered = 6; // Flag specifying if the message is being redelivered + uint64 sequence = 1; // globally ordered sequence number for the subject's channel + string subject = 2; // subject + string reply = 3; // optional reply + bytes data = 4; // payload + int64 timestamp = 5; // received timestamp + bool redelivered = 6; // Flag specifying if the message is being redelivered + uint32 redeliveryCount = 7; // Number of times the message has been redelivered (count currently not persisted) - uint32 CRC32 = 10; // optional IEEE CRC32 + uint32 CRC32 = 10; // optional IEEE CRC32 } // Ack will deliver an ack for a delivered msg. diff --git a/gateway/vendor/github.com/nats-io/go-nats-streaming/stan.go b/gateway/vendor/github.com/nats-io/stan.go/stan.go similarity index 83% rename from gateway/vendor/github.com/nats-io/go-nats-streaming/stan.go rename to gateway/vendor/github.com/nats-io/stan.go/stan.go index 5856588e..1f7f7fbc 100644 --- a/gateway/vendor/github.com/nats-io/go-nats-streaming/stan.go +++ b/gateway/vendor/github.com/nats-io/stan.go/stan.go @@ -20,17 +20,17 @@ import ( "sync" "time" - "github.com/nats-io/go-nats" - "github.com/nats-io/go-nats-streaming/pb" + "github.com/nats-io/nats.go" "github.com/nats-io/nuid" + "github.com/nats-io/stan.go/pb" ) // Version is the NATS Streaming Go Client version -const Version = "0.4.4" +const Version = "0.6.0" const ( // DefaultNatsURL is the default URL the client connects to - DefaultNatsURL = "nats://localhost:4222" + DefaultNatsURL = "nats://127.0.0.1:4222" // DefaultConnectWait is the default timeout used for the connect operation DefaultConnectWait = 2 * time.Second // DefaultDiscoverPrefix is the prefix subject used to connect to the NATS Streaming server @@ -48,6 +48,7 @@ const ( // Conn represents a connection to the NATS Streaming subsystem. It can Publish and // Subscribe to messages within the NATS Streaming cluster. +// The connection is safe to use in multiple Go routines concurrently. type Conn interface { // Publish will publish to the cluster and wait for an ACK. Publish(subject string, data []byte) error @@ -99,7 +100,7 @@ const ( // Errors var ( - ErrConnectReqTimeout = errors.New("stan: connect request timeout") + ErrConnectReqTimeout = errors.New("stan: connect request timeout (possibly wrong cluster ID?)") ErrCloseReqTimeout = errors.New("stan: close request timeout") ErrSubReqTimeout = errors.New("stan: subscribe request timeout") ErrUnsubReqTimeout = errors.New("stan: unsubscribe request timeout") @@ -156,9 +157,12 @@ type Options struct { // calls block. MaxPubAcksInflight int + // DEPRECATED: Please use PingInterval instead + PingIterval int + // PingInterval is the interval at which client sends PINGs to the server // to detect the loss of a connection. - PingIterval int + PingInterval int // PingMaxOut specifies the maximum number of PINGs without a corresponding // PONG before declaring the connection permanently lost. @@ -169,17 +173,25 @@ type Options struct { ConnectionLostCB ConnectionLostHandler } -// DefaultOptions are the NATS Streaming client's default options -var DefaultOptions = Options{ - NatsURL: DefaultNatsURL, - ConnectTimeout: DefaultConnectWait, - AckTimeout: DefaultAckWait, - DiscoverPrefix: DefaultDiscoverPrefix, - MaxPubAcksInflight: DefaultMaxPubAcksInflight, - PingIterval: DefaultPingInterval, - PingMaxOut: DefaultPingMaxOut, +// GetDefaultOptions returns default configuration options for the client. +func GetDefaultOptions() Options { + return Options{ + NatsURL: DefaultNatsURL, + ConnectTimeout: DefaultConnectWait, + AckTimeout: DefaultAckWait, + DiscoverPrefix: DefaultDiscoverPrefix, + MaxPubAcksInflight: DefaultMaxPubAcksInflight, + PingInterval: DefaultPingInterval, + PingMaxOut: DefaultPingMaxOut, + } } +// DEPRECATED: Use GetDefaultOptions() instead. +// DefaultOptions is not safe for use by multiple clients. +// For details see https://github.com/nats-io/nats.go/issues/308. +// DefaultOptions are the NATS Streaming client's default options +var DefaultOptions = GetDefaultOptions() + // Option is a function on the options for a connection. type Option func(*Options) error @@ -242,11 +254,11 @@ func Pings(interval, maxOut int) Option { // by the library as milliseconds. If this test boolean is set, // do not check values. if !testAllowMillisecInPings { - if interval < 1 || maxOut <= 2 { + if interval < 1 || maxOut < 2 { return fmt.Errorf("invalid ping values: interval=%v (min>0) maxOut=%v (min=2)", interval, maxOut) } } - o.PingIterval = interval + o.PingInterval = interval o.PingMaxOut = maxOut return nil } @@ -279,21 +291,27 @@ type conn struct { subMap map[string]*subscription pubAckMap map[string]*ack pubAckChan chan (struct{}) + pubAckCloseChan chan (struct{}) opts Options nc *nats.Conn ncOwned bool // NATS Streaming created the connection, so needs to close it. pubNUID *nuid.NUID // NUID generator for published messages. connLostCB ConnectionLostHandler + closed bool + ping pingInfo +} - pingMu sync.Mutex - pingSub *nats.Subscription - pingTimer *time.Timer - pingBytes []byte - pingRequests string - pingInbox string - pingInterval time.Duration - pingMaxOut int - pingOut int +// Holds all field related to the client-to-server pings +type pingInfo struct { + mu sync.Mutex + sub *nats.Subscription + timer *time.Timer + proto []byte + requests string + inbox string + interval time.Duration + maxOut int + out int } // Closure for ack contexts. @@ -307,7 +325,15 @@ type ack struct { // Note that clientID can contain only alphanumeric and `-` or `_` characters. func Connect(stanClusterID, clientID string, options ...Option) (Conn, error) { // Process Options - c := conn{clientID: clientID, opts: DefaultOptions, connID: []byte(nuid.Next()), pubNUID: nuid.New()} + c := conn{ + clientID: clientID, + opts: DefaultOptions, + connID: []byte(nuid.Next()), + pubNUID: nuid.New(), + pubAckMap: make(map[string]*ack), + pubAckCloseChan: make(chan struct{}), + subMap: make(map[string]*subscription), + } for _, opt := range options { if err := opt(&c.opts); err != nil { return nil, err @@ -347,7 +373,8 @@ func Connect(stanClusterID, clientID string, options ...Option) (Conn, error) { // Prepare a subscription on ping responses, even if we are not // going to need it, so that if that fails, it fails before initiating // a connection. - if c.pingSub, err = c.nc.Subscribe(nats.NewInbox(), c.processPingResponse); err != nil { + p := &c.ping + if p.sub, err = c.nc.Subscribe(nats.NewInbox(), c.processPingResponse); err != nil { c.failConnect(err) return nil, err } @@ -359,7 +386,7 @@ func Connect(stanClusterID, clientID string, options ...Option) (Conn, error) { HeartbeatInbox: hbInbox, ConnID: c.connID, Protocol: protocolOne, - PingInterval: int32(c.opts.PingIterval), + PingInterval: int32(c.opts.PingInterval), PingMaxOut: int32(c.opts.PingMaxOut), } b, _ := req.Marshal() @@ -399,11 +426,7 @@ func Connect(stanClusterID, clientID string, options ...Option) (Conn, error) { c.Close() return nil, err } - c.ackSubscription.SetPendingLimits(1024*1024, 32*1024*1024) - c.pubAckMap = make(map[string]*ack) - - // Create Subscription map - c.subMap = make(map[string]*subscription) + c.ackSubscription.SetPendingLimits(-1, -1) c.pubAckChan = make(chan struct{}, c.opts.MaxPubAcksInflight) @@ -423,28 +446,28 @@ func Connect(stanClusterID, clientID string, options ...Option) (Conn, error) { unsubPingSub = false // These will be immutable. - c.pingRequests = cr.PingRequests - c.pingInbox = c.pingSub.Subject + p.requests = cr.PingRequests + p.inbox = p.sub.Subject // In test, it is possible that we get a negative value // to represent milliseconds. if testAllowMillisecInPings && cr.PingInterval < 0 { - c.pingInterval = time.Duration(cr.PingInterval*-1) * time.Millisecond + p.interval = time.Duration(cr.PingInterval*-1) * time.Millisecond } else { // PingInterval is otherwise assumed to be in seconds. - c.pingInterval = time.Duration(cr.PingInterval) * time.Second + p.interval = time.Duration(cr.PingInterval) * time.Second } - c.pingMaxOut = int(cr.PingMaxOut) - c.pingBytes, _ = (&pb.Ping{ConnID: c.connID}).Marshal() + p.maxOut = int(cr.PingMaxOut) + p.proto, _ = (&pb.Ping{ConnID: c.connID}).Marshal() // Set the timer now that we are set. Use lock to create // synchronization point. - c.pingMu.Lock() - c.pingTimer = time.AfterFunc(c.pingInterval, c.pingServer) - c.pingMu.Unlock() + p.mu.Lock() + p.timer = time.AfterFunc(p.interval, c.pingServer) + p.mu.Unlock() } } if unsubPingSub { - c.pingSub.Unsubscribe() - c.pingSub = nil + p.sub.Unsubscribe() + p.sub = nil } return &c, nil @@ -466,24 +489,24 @@ func (sc *conn) failConnect(err error) { // If the total number is > than the PingMaxOut option, then the connection // is closed, and connection error callback invoked if one was specified. func (sc *conn) pingServer() { - sc.pingMu.Lock() + p := &sc.ping + p.mu.Lock() // In case the timer fired while we were stopping it. - if sc.pingTimer == nil { - sc.pingMu.Unlock() + if p.timer == nil { + p.mu.Unlock() return } - sc.pingOut++ - if sc.pingOut > sc.pingMaxOut { - sc.pingMu.Unlock() + p.out++ + if p.out > p.maxOut { + p.mu.Unlock() sc.closeDueToPing(ErrMaxPings) return } - sc.pingTimer.Reset(sc.pingInterval) - nc := sc.nc - sc.pingMu.Unlock() - // Send the PING now. If the NATS connection is reported closed, - // we are done. - if err := nc.PublishRequest(sc.pingRequests, sc.pingInbox, sc.pingBytes); err == nats.ErrConnectionClosed { + p.timer.Reset(p.interval) + p.mu.Unlock() + // Send the PING now. If the NATS connection is reported closed, we are done. + // sc.nc is immutable and never nil, even if connection is closed. + if err := sc.nc.PublishRequest(p.requests, p.inbox, p.proto); err == nats.ErrConnectionClosed { sc.closeDueToPing(err) } } @@ -509,16 +532,17 @@ func (sc *conn) processPingResponse(m *nats.Msg) { } } // Do not attempt to decrement, simply reset to 0. - sc.pingMu.Lock() - sc.pingOut = 0 - sc.pingMu.Unlock() + p := &sc.ping + p.mu.Lock() + p.out = 0 + p.mu.Unlock() } // Closes a connection and invoke the connection error callback if one // was registered when the connection was created. func (sc *conn) closeDueToPing(err error) { sc.Lock() - if sc.nc == nil { + if sc.closed { sc.Unlock() return } @@ -529,10 +553,8 @@ func (sc *conn) closeDueToPing(err error) { if sc.ncOwned && !sc.nc.IsClosed() { sc.nc.Close() } - // Mark this streaming connection as closed. Do this under pingMu lock. - sc.pingMu.Lock() - sc.nc = nil - sc.pingMu.Unlock() + // Mark this streaming connection as closed. + sc.closed = true // Capture callback (even though this is immutable). cb := sc.connLostCB sc.Unlock() @@ -545,12 +567,13 @@ func (sc *conn) closeDueToPing(err error) { // Do some cleanup when connection is lost or closed. // Connection lock is held on entry, and sc.nc is guaranteed not to be nil. func (sc *conn) cleanupOnClose(err error) { - sc.pingMu.Lock() - if sc.pingTimer != nil { - sc.pingTimer.Stop() - sc.pingTimer = nil + p := &sc.ping + p.mu.Lock() + if p.timer != nil { + p.timer.Stop() + p.timer = nil } - sc.pingMu.Unlock() + p.mu.Unlock() // Unsubscribe only if the NATS connection is not already closed // and we don't own it (otherwise connection is going to be closed @@ -559,8 +582,8 @@ func (sc *conn) cleanupOnClose(err error) { if sc.hbSubscription != nil { sc.hbSubscription.Unsubscribe() } - if sc.pingSub != nil { - sc.pingSub.Unsubscribe() + if p.sub != nil { + p.sub.Unsubscribe() } if sc.ackSubscription != nil { sc.ackSubscription.Unsubscribe() @@ -568,20 +591,34 @@ func (sc *conn) cleanupOnClose(err error) { } // Fail all pending pubs - for guid, pubAck := range sc.pubAckMap { - if pubAck.t != nil { - pubAck.t.Stop() + if len(sc.pubAckMap) > 0 { + // Collect only the ones that have a timer that can be stopped. + // All others will be handled either in publishAsync() or their + // timer has already fired. + acks := map[string]*ack{} + for guid, pubAck := range sc.pubAckMap { + if pubAck.t != nil && pubAck.t.Stop() { + delete(sc.pubAckMap, guid) + acks[guid] = pubAck + } } - if pubAck.ah != nil { - pubAck.ah(guid, err) - } else if pubAck.ch != nil { - pubAck.ch <- err - } - delete(sc.pubAckMap, guid) - if len(sc.pubAckChan) > 0 { - <-sc.pubAckChan + // If we collected any, start a go routine that will do the job. + // We can't do it in place in case user's ackHandler uses the connection. + if len(acks) > 0 { + go func() { + for guid, a := range acks { + if a.ah != nil { + a.ah(guid, ErrConnectionClosed) + } else if a.ch != nil { + a.ch <- ErrConnectionClosed + } + } + }() } } + // Prevent publish calls that have passed the connection close check but + // not yet send to pubAckChan to be possibly blocked. + close(sc.pubAckCloseChan) } // Close a connection to the stan system. @@ -589,30 +626,24 @@ func (sc *conn) Close() error { sc.Lock() defer sc.Unlock() - if sc.nc == nil { + if sc.closed { // We are already closed. return nil } + // Signals we are closed. + sc.closed = true // Capture for NATS calls below. - nc := sc.nc if sc.ncOwned { - defer nc.Close() + defer sc.nc.Close() } // Now close ourselves. sc.cleanupOnClose(ErrConnectionClosed) - // Signals we are closed. - // Do this also under pingMu lock so that we don't need - // to grab sc's lock in pingServer. - sc.pingMu.Lock() - sc.nc = nil - sc.pingMu.Unlock() - req := &pb.CloseRequest{ClientID: sc.clientID} b, _ := req.Marshal() - reply, err := nc.Request(sc.closeRequests, b, sc.opts.ConnectTimeout) + reply, err := sc.nc.Request(sc.closeRequests, b, sc.opts.ConnectTimeout) if err != nil { if err == nats.ErrTimeout { return ErrCloseReqTimeout @@ -636,6 +667,9 @@ func (sc *conn) Close() error { func (sc *conn) NatsConn() *nats.Conn { sc.RLock() nc := sc.nc + if sc.closed { + nc = nil + } sc.RUnlock() return nc } @@ -643,12 +677,8 @@ func (sc *conn) NatsConn() *nats.Conn { // Process a heartbeat from the NATS Streaming cluster func (sc *conn) processHeartBeat(m *nats.Msg) { // No payload assumed, just reply. - sc.RLock() - nc := sc.nc - sc.RUnlock() - if nc != nil { - nc.Publish(m.Reply, nil) - } + // sc.nc is immutable and never nil, even if connection is closed. + sc.nc.Publish(m.Reply, nil) } // Process an ack from the NATS Streaming cluster @@ -698,7 +728,7 @@ func (sc *conn) PublishAsync(subject string, data []byte, ah AckHandler) (string func (sc *conn) publishAsync(subject string, data []byte, ah AckHandler, ch chan error) (string, error) { a := &ack{ah: ah, ch: ch} sc.Lock() - if sc.nc == nil { + if sc.closed { sc.Unlock() return "", ErrConnectionClosed } @@ -717,18 +747,28 @@ func (sc *conn) publishAsync(subject string, data []byte, ah AckHandler, ch chan // snapshot ackSubject := sc.ackSubject ackTimeout := sc.opts.AckTimeout - pac := sc.pubAckChan - nc := sc.nc sc.Unlock() // Use the buffered channel to control the number of outstanding acks. - pac <- struct{}{} + select { + case sc.pubAckChan <- struct{}{}: + default: + // It seems faster to first try to send to pubAckChan and only if + // it fails to retry with the check on pubAckCloseChan than having + // simply only the select with the 2 cases. + select { + case sc.pubAckChan <- struct{}{}: + case <-sc.pubAckCloseChan: + return "", ErrConnectionClosed + } + } - err := nc.PublishRequest(subj, ackSubject, b) + // sc.nc is immutable and never nil once connection is created. + err := sc.nc.PublishRequest(subj, ackSubject, b) // Setup the timer for expiration. sc.Lock() - if err != nil || sc.nc == nil { + if err != nil || sc.closed { sc.Unlock() // If we got and error on publish or the connection has been closed, // we need to return an error only if: @@ -797,8 +837,7 @@ func (sc *conn) processMsg(raw *nats.Msg) { var sub *subscription // Lookup the subscription sc.RLock() - nc := sc.nc - isClosed := nc == nil + isClosed := sc.closed if !isClosed { sub = sc.subMap[raw.Subject] } @@ -825,10 +864,11 @@ func (sc *conn) processMsg(raw *nats.Msg) { } // Process auto-ack - if !isManualAck && nc != nil { + if !isManualAck { ack := &pb.Ack{Subject: msg.Subject, Sequence: msg.Sequence} b, _ := ack.Marshal() // FIXME(dlc) - Async error handler? Retry? - nc.Publish(ackSubject, b) + // sc.nc is immutable and never nil once connection is created. + sc.nc.Publish(ackSubject, b) } } diff --git a/gateway/vendor/github.com/nats-io/go-nats-streaming/sub.go b/gateway/vendor/github.com/nats-io/stan.go/sub.go similarity index 94% rename from gateway/vendor/github.com/nats-io/go-nats-streaming/sub.go rename to gateway/vendor/github.com/nats-io/stan.go/sub.go index ddda7fbc..454a8c24 100644 --- a/gateway/vendor/github.com/nats-io/go-nats-streaming/sub.go +++ b/gateway/vendor/github.com/nats-io/stan.go/sub.go @@ -11,7 +11,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package stan is a Go client for the NATS Streaming messaging system (https://nats.io). package stan import ( @@ -19,8 +18,8 @@ import ( "sync" "time" - "github.com/nats-io/go-nats" - "github.com/nats-io/go-nats-streaming/pb" + "github.com/nats-io/nats.go" + "github.com/nats-io/stan.go/pb" ) const ( @@ -40,6 +39,7 @@ type Msg struct { // Subscription represents a subscription within the NATS Streaming cluster. Subscriptions // will be rate matched and follow at-least once delivery semantics. +// The subscription is safe to use in multiple Go routines concurrently. type Subscription interface { // Unsubscribe removes interest in the subscription. // For durables, it means that the durable interest is also removed from @@ -56,7 +56,7 @@ type Subscription interface { // These functions have been added for expert-users that need to get details // about the low level NATS Subscription used internally to receive messages // for this streaming subscription. They are documented in the Go client - // library: https://godoc.org/github.com/nats-io/go-nats#Subscription.ClearMaxPending + // library: https://godoc.org/github.com/nats-io/nats.go#Subscription.ClearMaxPending // ClearMaxPending resets the maximums seen so far. ClearMaxPending() error @@ -238,25 +238,27 @@ func (sc *conn) subscribe(subject, qgroup string, cb MsgHandler, options ...Subs } } sc.Lock() - if sc.nc == nil { + if sc.closed { sc.Unlock() return nil, ErrConnectionClosed } // Register subscription. sc.subMap[sub.inbox] = sub - nc := sc.nc sc.Unlock() // Hold lock throughout. sub.Lock() defer sub.Unlock() + // sc.nc is immutable and never nil once connection is created. + // Listen for actual messages. - nsub, err := nc.Subscribe(sub.inbox, sc.processMsg) + nsub, err := sc.nc.Subscribe(sub.inbox, sc.processMsg) if err != nil { return nil, err } + nsub.SetPendingLimits(-1, -1) sub.inboxSub = nsub // Create a subscription request @@ -281,7 +283,7 @@ func (sc *conn) subscribe(subject, qgroup string, cb MsgHandler, options ...Subs } b, _ := sr.Marshal() - reply, err := nc.Request(sc.subRequests, b, sc.opts.ConnectTimeout) + reply, err := sc.nc.Request(sc.subRequests, b, sc.opts.ConnectTimeout) if err != nil { sub.inboxSub.Unsubscribe() if err == nats.ErrTimeout { @@ -407,7 +409,7 @@ func (sub *subscription) closeOrUnsubscribe(doClose bool) error { sub.Unlock() sc.Lock() - if sc.nc == nil { + if sc.closed { sc.Unlock() return ErrConnectionClosed } @@ -421,19 +423,17 @@ func (sub *subscription) closeOrUnsubscribe(doClose bool) error { return ErrNoServerSupport } } - - // Snapshot connection to avoid data race, since the connection may be - // closing while we try to send the request - nc := sc.nc sc.Unlock() + // sc.nc is immutable and never nil once connection is created. + usr := &pb.UnsubscribeRequest{ ClientID: sc.clientID, Subject: sub.subject, Inbox: sub.ackInbox, } b, _ := usr.Marshal() - reply, err := nc.Request(reqSubject, b, sc.opts.ConnectTimeout) + reply, err := sc.nc.Request(reqSubject, b, sc.opts.ConnectTimeout) if err != nil { if err == nats.ErrTimeout { if doClose { @@ -485,16 +485,15 @@ func (msg *Msg) Ack() error { if sc == nil { return ErrBadSubscription } - // Get nc from the connection (needs locking to avoid race) - sc.RLock() - nc := sc.nc - sc.RUnlock() - if nc == nil { - return ErrBadConnection - } + + // sc.nc is immutable and never nil once connection is created. // Ack here. ack := &pb.Ack{Subject: msg.Subject, Sequence: msg.Sequence} b, _ := ack.Marshal() - return nc.Publish(ackSubject, b) + err := sc.nc.Publish(ackSubject, b) + if err == nats.ErrConnectionClosed { + return ErrBadConnection + } + return err } diff --git a/gateway/vendor/github.com/openfaas/nats-queue-worker/handler/nats_queue.go b/gateway/vendor/github.com/openfaas/nats-queue-worker/handler/nats_queue.go index 13df6fdb..1526c992 100644 --- a/gateway/vendor/github.com/openfaas/nats-queue-worker/handler/nats_queue.go +++ b/gateway/vendor/github.com/openfaas/nats-queue-worker/handler/nats_queue.go @@ -7,7 +7,7 @@ import ( "sync" "time" - stan "github.com/nats-io/go-nats-streaming" + stan "github.com/nats-io/stan.go" "github.com/openfaas/faas/gateway/queue" )