Various updates

- Updated all dependencies
- Updated geoindex Box api
This commit is contained in:
tidwall 2021-02-03 14:30:55 -07:00
parent 263bbd9dcc
commit 3ed048242e
711 changed files with 92237 additions and 95383 deletions

38
go.mod
View File

@ -1,35 +1,35 @@
module github.com/tidwall/tile38 module github.com/tidwall/tile38
go 1.13 go 1.15
require ( require (
github.com/Shopify/sarama v1.26.4 github.com/Shopify/sarama v1.27.2
github.com/aws/aws-sdk-go v1.34.29 github.com/aws/aws-sdk-go v1.37.3
github.com/cespare/xxhash v1.1.0 // indirect github.com/eclipse/paho.mqtt.golang v1.3.1
github.com/eclipse/paho.mqtt.golang v1.2.0 github.com/golang/protobuf v1.4.3
github.com/golang/protobuf v1.4.2 github.com/gomodule/redigo v1.8.3
github.com/gomodule/redigo v1.8.2
github.com/mmcloughlin/geohash v0.10.0 github.com/mmcloughlin/geohash v0.10.0
github.com/nats-io/go-nats v1.6.0 github.com/nats-io/nats-server/v2 v2.1.9 // indirect
github.com/nats-io/nuid v1.0.1 // indirect github.com/nats-io/nats.go v1.10.0
github.com/peterh/liner v1.2.0 github.com/peterh/liner v1.2.1
github.com/streadway/amqp v1.0.0 github.com/streadway/amqp v1.0.0
github.com/tidwall/btree v0.3.0 github.com/tidwall/btree v0.3.0
github.com/tidwall/buntdb v1.1.8 github.com/tidwall/buntdb v1.1.8
github.com/tidwall/geoindex v1.1.0 github.com/tidwall/cities v0.1.0 // indirect
github.com/tidwall/geoindex v1.4.0
github.com/tidwall/geojson v1.2.3 github.com/tidwall/geojson v1.2.3
github.com/tidwall/gjson v1.6.7 github.com/tidwall/gjson v1.6.8
github.com/tidwall/match v1.0.3 github.com/tidwall/match v1.0.3
github.com/tidwall/pretty v1.0.2 github.com/tidwall/pretty v1.0.2
github.com/tidwall/rbang v1.2.2 github.com/tidwall/rbang v1.2.2
github.com/tidwall/redbench v0.1.0 github.com/tidwall/redbench v0.1.0
github.com/tidwall/redcon v1.3.2 github.com/tidwall/redcon v1.4.0
github.com/tidwall/resp v0.1.0 github.com/tidwall/resp v0.1.0
github.com/tidwall/rhh v1.1.0 github.com/tidwall/rhh v1.1.1
github.com/tidwall/sjson v1.1.1 github.com/tidwall/sjson v1.1.5
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 golang.org/x/net v0.0.0-20210119194325-5f4716e94777
google.golang.org/grpc v1.32.0 google.golang.org/grpc v1.35.0
layeh.com/gopher-json v0.0.0-20190114024228-97fed8db8427 layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf
) )

165
go.sum
View File

@ -2,12 +2,12 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/sarama v1.26.4 h1:+17TxUq/PJEAfZAll0T7XJjSgQWCpaQSoki/x5yN8o8= github.com/Shopify/sarama v1.27.2 h1:1EyY1dsxNDUQEv0O/4TsjosHI2CgB1uo9H/v56xzTxc=
github.com/Shopify/sarama v1.26.4/go.mod h1:NbSGBSSndYaIhRcBtY9V0U7AyH+x71bG668AuWys/yU= github.com/Shopify/sarama v1.27.2/go.mod h1:g5s5osgELxgM+Md9Qni9rzo7Rbt+vvFQI4bt/Mc93II=
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/aws/aws-sdk-go v1.34.29 h1:4Yw8eC4nCXiIVmHJO5PD4oh0vI/df5o6cYTVzFV7vWA= github.com/aws/aws-sdk-go v1.37.3 h1:1f0groABc4AuapskpHf6EBRaG2tqw0Sx3ebCMwfp1Ys=
github.com/aws/aws-sdk-go v1.34.29/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/aws/aws-sdk-go v1.37.3/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@ -15,7 +15,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -25,37 +26,43 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0= github.com/eclipse/paho.mqtt.golang v1.3.1 h1:6F5FYb1hxVSZS+p0ji5xBQamc5ltOolTYRy5R15uVmI=
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/eclipse/paho.mqtt.golang v1.3.1/go.mod h1:eTzb4gxwwyWpqBUHGQZ4ABAV7+Jgm1PklsYT/eo8Hcc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk= github.com/frankban/quicktest v1.10.2 h1:19ARM85nVi4xH7xPXuc5eM/udya5ieh7b/Sv+d844Tk=
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= github.com/gomodule/redigo v1.8.3 h1:HR0kYDX2RJZvAup8CsiJwxB4dTCSC0AaUq6S4SiLwUc=
github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= github.com/gomodule/redigo v1.8.3/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
@ -64,57 +71,65 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/klauspost/compress v1.9.8 h1:VMAMUUOh+gaxKTMk+zqbjsSjsIcUcL/LF4o63i82QyA= github.com/klauspost/compress v1.11.0 h1:wJbzvpYMVGG9iTI9VxpnNZfd4DzMPoCWze3GgSqz8yg=
github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4= github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mmcloughlin/geohash v0.10.0 h1:9w1HchfDfdeLc+jFEf/04D27KP7E2QmpDu52wPbJWRE= github.com/mmcloughlin/geohash v0.10.0 h1:9w1HchfDfdeLc+jFEf/04D27KP7E2QmpDu52wPbJWRE=
github.com/mmcloughlin/geohash v0.10.0/go.mod h1:oNZxQo5yWJh0eMQEP/8hwQuVx9Z9tjwFUqcTB1SmG0c= github.com/mmcloughlin/geohash v0.10.0/go.mod h1:oNZxQo5yWJh0eMQEP/8hwQuVx9Z9tjwFUqcTB1SmG0c=
github.com/nats-io/gnatsd v1.4.1 h1:RconcfDeWpKCD6QIIwiVFcvForlXpWeJP7i5/lDLy44= github.com/nats-io/jwt v0.3.2 h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI=
github.com/nats-io/gnatsd v1.4.1/go.mod h1:nqco77VO78hLCJpIcVfygDP2rPGfsEHkGTUk94uh5DQ= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/go-nats v1.6.0 h1:FznPwMfrVwGnSCh7JTXyJDRW0TIkD4Tr+M1LPJt9T70= github.com/nats-io/jwt v1.1.0 h1:+vOlgtM0ZsF46GbmUoadq0/2rChNS45gtxHEa3H1gqM=
github.com/nats-io/go-nats v1.6.0/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0= github.com/nats-io/jwt v1.1.0/go.mod h1:n3cvmLfBfnpV4JJRN7lRYCyZnw48ksGsbThGXEk4w9M=
github.com/nats-io/nuid v1.0.0 h1:44QGdhbiANq8ZCbUkdn6W5bqtg+mHuDE4wOUuxxndFs= github.com/nats-io/nats-server/v2 v2.1.9 h1:Sxr2zpaapgpBT9ElTxTVe62W+qjnhPcKY/8W5cnA/Qk=
github.com/nats-io/nuid v1.0.0/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nats-io/nats-server/v2 v2.1.9/go.mod h1:9qVyoewoYXzG1ME9ox0HwkkzyYvnlBDugfR4Gg/8uHU=
github.com/nats-io/nats.go v1.10.0 h1:L8qnKaofSfNFbXg0C5F71LdjPRnmQwSsA4ukmkt1TvY=
github.com/nats-io/nats.go v1.10.0/go.mod h1:AjGArbfyR50+afOUotNX2Xs5SYHf+CoOa5HH1eEl2HE=
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 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/peterh/liner v1.2.0 h1:w/UPXyl5GfahFxcTOz2j9wCIHNI+pUPr2laqpojKNCg= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/peterh/liner v1.2.0/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pierrec/lz4 v2.4.1+incompatible h1:mFe7ttWaflA46Mhqh+jUfjp2qTbPYxLB2/OyBppH9dg= github.com/peterh/liner v1.2.1 h1:O4BlKaq/LWu6VRWmol4ByWfzx6MfXc5Op5HETyIy5yg=
github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/peterh/liner v1.2.1/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo= github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo=
github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tidwall/btree v0.2.2/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
github.com/tidwall/btree v0.3.0 h1:LcwmLI5kv+AaH/xnBgOuKfbu5eLBWVdWTpD2o+qSRdU= github.com/tidwall/btree v0.3.0 h1:LcwmLI5kv+AaH/xnBgOuKfbu5eLBWVdWTpD2o+qSRdU=
github.com/tidwall/btree v0.3.0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= github.com/tidwall/btree v0.3.0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
github.com/tidwall/buntdb v1.1.8 h1:3rHHTlR4uFACKLbC+ws9jH0Ytk1Pd+n/cMEERtbMVKk= github.com/tidwall/buntdb v1.1.8 h1:3rHHTlR4uFACKLbC+ws9jH0Ytk1Pd+n/cMEERtbMVKk=
github.com/tidwall/buntdb v1.1.8/go.mod h1:a/Xp8Hsllzxex0WTNdANz2+hOwGaYDHW4xBK9dMp30w= github.com/tidwall/buntdb v1.1.8/go.mod h1:a/Xp8Hsllzxex0WTNdANz2+hOwGaYDHW4xBK9dMp30w=
github.com/tidwall/cities v0.1.0 h1:CVNkmMf7NEC9Bvokf5GoSsArHCKRMTgLuubRTHnH0mE= github.com/tidwall/cities v0.1.0 h1:CVNkmMf7NEC9Bvokf5GoSsArHCKRMTgLuubRTHnH0mE=
github.com/tidwall/cities v0.1.0/go.mod h1:lV/HDp2gCcRcHJWqgt6Di54GiDrTZwh1aG2ZUPNbqa4= github.com/tidwall/cities v0.1.0/go.mod h1:lV/HDp2gCcRcHJWqgt6Di54GiDrTZwh1aG2ZUPNbqa4=
github.com/tidwall/geoindex v1.1.0 h1:d/pGCgKUonfQINd1235kKqx9gWBU4N7GjDS9WvbPvLY= github.com/tidwall/geoindex v1.4.0 h1:6O8bwdRrNDwLzPSb6AhuyBW27PSa24Pa4I70fnoiBGI=
github.com/tidwall/geoindex v1.1.0/go.mod h1:3gTa91BW+eiVIipuR6aU1Y9Sa0q75b1teE/NP2vfsTc= github.com/tidwall/geoindex v1.4.0/go.mod h1:3gTa91BW+eiVIipuR6aU1Y9Sa0q75b1teE/NP2vfsTc=
github.com/tidwall/geojson v1.2.3 h1:z//3h73oC+EkQ1TQr0oLbk7fKtGqAYkhSILtFZi1j8w= github.com/tidwall/geojson v1.2.3 h1:z//3h73oC+EkQ1TQr0oLbk7fKtGqAYkhSILtFZi1j8w=
github.com/tidwall/geojson v1.2.3/go.mod h1:tBjfxeALRFLc25LLpjtWzy2nIrNmW1ze1EAhLtd8+QQ= github.com/tidwall/geojson v1.2.3/go.mod h1:tBjfxeALRFLc25LLpjtWzy2nIrNmW1ze1EAhLtd8+QQ=
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/gjson v1.6.7 h1:Mb1M9HZCRWEcXQ8ieJo7auYyyiSux6w9XN3AdTpxJrE=
github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= github.com/tidwall/gjson v1.6.7/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w=
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/grect v0.1.0 h1:ICcKWD5uu5A5fmxApGIa0QRvfGnSWKRd07POT08CQSA= github.com/tidwall/grect v0.1.0 h1:ICcKWD5uu5A5fmxApGIa0QRvfGnSWKRd07POT08CQSA=
github.com/tidwall/grect v0.1.0/go.mod h1:sa5O42oP6jWfTShL9ka6Sgmg3TgIK649veZe05B7+J8= github.com/tidwall/grect v0.1.0/go.mod h1:sa5O42oP6jWfTShL9ka6Sgmg3TgIK649veZe05B7+J8=
github.com/tidwall/lotsa v1.0.1 h1:w4gpDvI7RdkgbMC0q5ndKqG2ffrwCgerUY/gM2TYkH4= github.com/tidwall/lotsa v1.0.1 h1:w4gpDvI7RdkgbMC0q5ndKqG2ffrwCgerUY/gM2TYkH4=
@ -122,24 +137,22 @@ github.com/tidwall/lotsa v1.0.1/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UM
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/rbang v1.2.2 h1:j5JZDSsybpGzCabqFpabaQNU5MCmIrlThXVUF7LD99I= github.com/tidwall/rbang v1.2.2 h1:j5JZDSsybpGzCabqFpabaQNU5MCmIrlThXVUF7LD99I=
github.com/tidwall/rbang v1.2.2/go.mod h1:aMGOM1Wj50tooEO/0aO9j+7gyHUs3bUW0t4Q+xiuOjg= github.com/tidwall/rbang v1.2.2/go.mod h1:aMGOM1Wj50tooEO/0aO9j+7gyHUs3bUW0t4Q+xiuOjg=
github.com/tidwall/redbench v0.1.0 h1:UZYUMhwMMObQRq5xU4SA3lmlJRztXzqtushDii+AmPo= github.com/tidwall/redbench v0.1.0 h1:UZYUMhwMMObQRq5xU4SA3lmlJRztXzqtushDii+AmPo=
github.com/tidwall/redbench v0.1.0/go.mod h1:zxcRGCq/JcqV48YjK9WxBNJL7JSpMzbLXaHvMcnanKQ= github.com/tidwall/redbench v0.1.0/go.mod h1:zxcRGCq/JcqV48YjK9WxBNJL7JSpMzbLXaHvMcnanKQ=
github.com/tidwall/redcon v1.3.2 h1:8INx/Nm3VSUbDUT16TH1rMgYQsbXNqy9xcX70edHXbo= github.com/tidwall/redcon v1.4.0 h1:y2PmDD55STRdy4S98qP/Dn+gZG+cPVvIDi9BJV2aOwA=
github.com/tidwall/redcon v1.3.2/go.mod h1:bdYBm4rlcWpst2XMwKVzWDF9CoUxEbUmM7CQrKeOZas= github.com/tidwall/redcon v1.4.0/go.mod h1:IGzxyoKE3Ea5AWIXo/ZHP+hzY8sWXaMKr7KlFgcWSZU=
github.com/tidwall/resp v0.1.0 h1:zZ6Hq+2cY4QqhZ4LqrV05T5yLOSPspj+l+DgAoJ25Ak= github.com/tidwall/resp v0.1.0 h1:zZ6Hq+2cY4QqhZ4LqrV05T5yLOSPspj+l+DgAoJ25Ak=
github.com/tidwall/resp v0.1.0/go.mod h1:18xEj855iMY2bK6tNF2A4x+nZy5gWO1iO7OOl3jETKw= github.com/tidwall/resp v0.1.0/go.mod h1:18xEj855iMY2bK6tNF2A4x+nZy5gWO1iO7OOl3jETKw=
github.com/tidwall/rhh v1.1.0 h1:U+3RGzEB6VoBBkLlsAaF3ThjwZ0JGibplJhDin5Ub/Y= github.com/tidwall/rhh v1.1.1 h1:8zDpMKcK1pA1zU+Jyuo1UdzTFvME8pH3Sx/MdYgM5sE=
github.com/tidwall/rhh v1.1.0/go.mod h1:37/ybjMQQ4nDztczc//g/WBd2X51vogKHWL3xC8kY24= github.com/tidwall/rhh v1.1.1/go.mod h1:DmqiIRtSnlVEi5CSKqNaX6m3YTa3YNSYrGB4FlfdLUU=
github.com/tidwall/rtree v0.1.0 h1:w/RoiV3WvrKF/EzLaK6CT0MXAV6KveGy3EOyeJACoFA= github.com/tidwall/rtree v0.1.0 h1:w/RoiV3WvrKF/EzLaK6CT0MXAV6KveGy3EOyeJACoFA=
github.com/tidwall/rtree v0.1.0/go.mod h1:xsujlpupsrYVnWaok1pqGtxhj9TKHSbMbY8eKPatQ4U= github.com/tidwall/rtree v0.1.0/go.mod h1:xsujlpupsrYVnWaok1pqGtxhj9TKHSbMbY8eKPatQ4U=
github.com/tidwall/sjson v1.1.1 h1:7h1vk049Jnd5EH9NyzNiEuwYW4b5qgreBbqRC19AS3U= github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE=
github.com/tidwall/sjson v1.1.1/go.mod h1:yvVuSnpEQv5cYIrO+AT6kw4QVfd5SDZoGIS7/5+fZFs= github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE= github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
@ -147,8 +160,12 @@ github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg= github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5ukMK0AMWEhFaL/lrEOaephfuoiARg=
github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 h1:+ELyKg6m8UBf0nPFSqD0mi7zUfwPyXo23HNjMnXPz7w= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -158,8 +175,11 @@ golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -167,38 +187,52 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw= gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM= gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
@ -212,7 +246,10 @@ gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLv
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
layeh.com/gopher-json v0.0.0-20190114024228-97fed8db8427 h1:RZkKxMR3jbQxdCEcglq3j7wY3PRJIopAwBlx1RE71X0= layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf h1:rRz0YsF7VXj9fXRF6yQgFI7DzST+hsI3TeFSGupntu0=
layeh.com/gopher-json v0.0.0-20190114024228-97fed8db8427/go.mod h1:ivKkcY8Zxw5ba0jldhZCYYQfGdb2K6u9tbYK1AwMIBc= layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf/go.mod h1:ivKkcY8Zxw5ba0jldhZCYYQfGdb2K6u9tbYK1AwMIBc=

View File

@ -5,6 +5,7 @@ import (
"github.com/tidwall/btree" "github.com/tidwall/btree"
"github.com/tidwall/geoindex" "github.com/tidwall/geoindex"
"github.com/tidwall/geoindex/algo"
"github.com/tidwall/geojson" "github.com/tidwall/geojson"
"github.com/tidwall/geojson/geo" "github.com/tidwall/geojson/geo"
"github.com/tidwall/geojson/geometry" "github.com/tidwall/geojson/geometry"
@ -573,19 +574,19 @@ func (c *Collection) geoSparseInner(
w := rect.Max.X - rect.Min.X w := rect.Max.X - rect.Min.X
h := rect.Max.Y - rect.Min.Y h := rect.Max.Y - rect.Min.Y
quads := [4]geometry.Rect{ quads := [4]geometry.Rect{
geometry.Rect{ {
Min: geometry.Point{X: rect.Min.X, Y: rect.Min.Y + h/2}, Min: geometry.Point{X: rect.Min.X, Y: rect.Min.Y + h/2},
Max: geometry.Point{X: rect.Min.X + w/2, Y: rect.Max.Y}, Max: geometry.Point{X: rect.Min.X + w/2, Y: rect.Max.Y},
}, },
geometry.Rect{ {
Min: geometry.Point{X: rect.Min.X + w/2, Y: rect.Min.Y + h/2}, Min: geometry.Point{X: rect.Min.X + w/2, Y: rect.Min.Y + h/2},
Max: geometry.Point{X: rect.Max.X, Y: rect.Max.Y}, Max: geometry.Point{X: rect.Max.X, Y: rect.Max.Y},
}, },
geometry.Rect{ {
Min: geometry.Point{X: rect.Min.X, Y: rect.Min.Y}, Min: geometry.Point{X: rect.Min.X, Y: rect.Min.Y},
Max: geometry.Point{X: rect.Min.X + w/2, Y: rect.Min.Y + h/2}, Max: geometry.Point{X: rect.Min.X + w/2, Y: rect.Min.Y + h/2},
}, },
geometry.Rect{ {
Min: geometry.Point{X: rect.Min.X + w/2, Y: rect.Min.Y}, Min: geometry.Point{X: rect.Min.X + w/2, Y: rect.Min.Y},
Max: geometry.Point{X: rect.Max.X, Y: rect.Min.Y + h/2}, Max: geometry.Point{X: rect.Max.X, Y: rect.Min.Y + h/2},
}, },
@ -745,10 +746,10 @@ func (c *Collection) Nearby(
cursor.Step(offset) cursor.Step(offset)
} }
c.index.Nearby( c.index.Nearby(
geoindex.SimpleBoxAlgo( algo.Box(
[2]float64{center.X, center.Y}, [2]float64{center.X, center.Y},
[2]float64{center.X, center.Y}, [2]float64{center.X, center.Y},
), false, nil),
func(_, _ [2]float64, itemv interface{}, _ float64) bool { func(_, _ [2]float64, itemv interface{}, _ float64) bool {
count++ count++
if count <= offset { if count <= offset {

View File

@ -5,7 +5,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/nats-io/go-nats" "github.com/nats-io/nats.go"
) )
const natsExpiresAfter = time.Second * 30 const natsExpiresAfter = time.Second * 30

View File

@ -25,3 +25,5 @@ _testmain.go
coverage.txt coverage.txt
profile.out profile.out
simplest-uncommitted-msg-0.1-jar-with-dependencies.jar

View File

@ -66,9 +66,12 @@ linters:
- varcheck - varcheck
- whitespace - whitespace
# - goconst # - goconst
# - gochecknoinits - gochecknoinits
issues: issues:
exclude: exclude:
- consider giving a name to these results - consider giving a name to these results
- include an explanation for nolint directive - include an explanation for nolint directive
- Potential Integer overflow made by strconv.Atoi result conversion to int16/32
- Use of weak random number generator
- TLS MinVersion too low

View File

@ -2,6 +2,99 @@
#### Unreleased #### Unreleased
#### Version 1.27.2 (2020-10-21)
# Improvements
#1750 - @krantideep95 Adds missing mock responses for mocking consumer group
# Fixes
#1817 - reverts #1785 - Add private method to Client interface to prevent implementation
#### Version 1.27.1 (2020-10-07)
# Improvements
#1775 - @d1egoaz - Adds a Producer Interceptor example
#1781 - @justin-chen - Refresh brokers given list of seed brokers
#1784 - @justin-chen - Add randomize seed broker method
#1790 - @d1egoaz - remove example binary
#1798 - @bai - Test against Go 1.15
#1785 - @justin-chen - Add private method to Client interface to prevent implementation
#1802 - @uvw - Support Go 1.13 error unwrapping
# Fixes
#1791 - @stanislavkozlovski - bump default version to 1.0.0
#### Version 1.27.0 (2020-08-11)
# Improvements
#1466 - @rubenvp8510 - Expose kerberos fast negotiation configuration
#1695 - @KJTsanaktsidis - Use docker-compose to run the functional tests
#1699 - @wclaeys - Consumer group support for manually comitting offsets
#1714 - @bai - Bump Go to version 1.14.3, golangci-lint to 1.27.0
#1726 - @d1egoaz - Include zstd on the functional tests
#1730 - @d1egoaz - KIP-42 Add producer and consumer interceptors
#1738 - @varun06 - fixed variable names that are named same as some std lib package names
#1741 - @varun06 - updated zstd dependency to latest v1.10.10
#1743 - @varun06 - Fixed declaration dependencies and other lint issues in code base
#1763 - @alrs - remove deprecated tls options from test
#1769 - @bai - Add support for Kafka 2.6.0
# Fixes
#1697 - @kvch - Use gofork for encoding/asn1 to fix ASN errors during Kerberos authentication
#1744 - @alrs - Fix isBalanced Function Signature
#### Version 1.26.4 (2020-05-19)
# Fixes
- #1701 - @d1egoaz - Set server name only for the current broker
- #1694 - @dnwe - testfix: set KAFKA_HEAP_OPTS for zk and kafka
#### Version 1.26.3 (2020-05-07)
# Fixes
- #1692 - @d1egoaz - Set tls ServerName to fix issue: either ServerName or InsecureSkipVerify must be specified in the tls.Config
#### Version 1.26.2 (2020-05-06)
# ⚠️ Known Issues
This release has been marked as not ready for production and may be unstable, please use v1.26.4.
# Improvements
- #1560 - @iyacontrol - add sync pool for gzip 1-9
- #1605 - @dnwe - feat: protocol support for V11 fetch w/ rackID
- #1617 - @sladkoff / @dwi-di / @random-dwi - Add support for alter/list partition reassignements APIs
- #1632 - @bai - Add support for Go 1.14
- #1640 - @random-dwi - Feature/fix list partition reassignments
- #1646 - @mimaison - Add DescribeLogDirs to admin client
- #1667 - @bai - Add support for kafka 2.5.0
# Fixes
- #1594 - @sladkoff - Sets ConfigEntry.Default flag in addition to the ConfigEntry.Source for Kafka versions > V1_1_0_0
- #1601 - @alrs - fix: remove use of testing.T.FailNow() inside goroutine
- #1602 - @d1egoaz - adds a note about consumer groups Consume method
- #1607 - @darklore - Fix memory leak when Broker.Open and Broker.Close called repeatedly
- #1613 - @wblakecaldwell - Updated "retrying" log message when BackoffFunc implemented
- #1614 - @alrs - produce_response.go: Remove Unused Functions
- #1619 - @alrs - tools/kafka-producer-performance: prune unused flag variables
- #1639 - @agriffaut - Handle errors with no message but error code
- #1643 - @kzinglzy - fix `config.net.keepalive`
- #1644 - @KJTsanaktsidis - Fix brokers continually allocating new Session IDs
- #1645 - @Stephan14 - Remove broker(s) which no longer exist in metadata
- #1650 - @lavoiesl - Return the response error in heartbeatLoop
- #1661 - @KJTsanaktsidis - Fix "broker received out of order sequence" when brokers die
- #1666 - @KevinJCross - Bugfix: Allow TLS connections to work over socks proxy.
#### Version 1.26.1 (2020-02-04) #### Version 1.26.1 (2020-02-04)
Improvements: Improvements:

View File

@ -21,7 +21,11 @@ fmt:
gofmt -s -l -w $(FILES) $(TESTS) gofmt -s -l -w $(FILES) $(TESTS)
lint: lint:
golangci-lint run GOFLAGS="-tags=functional" golangci-lint run
test: test:
$(GOTEST) ./... $(GOTEST) ./...
.PHONY: test_functional
test_functional:
$(GOTEST) -tags=functional ./...

View File

@ -20,7 +20,7 @@ You might also want to look at the [Frequently Asked Questions](https://github.c
Sarama provides a "2 releases + 2 months" compatibility guarantee: we support Sarama provides a "2 releases + 2 months" compatibility guarantee: we support
the two latest stable releases of Kafka and Go, and we provide a two month the two latest stable releases of Kafka and Go, and we provide a two month
grace period for older releases. This means we currently officially support grace period for older releases. This means we currently officially support
Go 1.12 through 1.14, and Kafka 2.1 through 2.4, although older releases are Go 1.13 through 1.14, and Kafka 2.4 through 2.6, although older releases are
still likely to work. still likely to work.
Sarama follows semantic versioning and provides API stability via the gopkg.in service. Sarama follows semantic versioning and provides API stability via the gopkg.in service.

View File

@ -802,7 +802,7 @@ func (ca *clusterAdmin) ListConsumerGroups() (allGroups map[string]string, err e
// Query brokers in parallel, since we have to query *all* brokers // Query brokers in parallel, since we have to query *all* brokers
brokers := ca.client.Brokers() brokers := ca.client.Brokers()
groupMaps := make(chan map[string]string, len(brokers)) groupMaps := make(chan map[string]string, len(brokers))
errors := make(chan error, len(brokers)) errChan := make(chan error, len(brokers))
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
for _, b := range brokers { for _, b := range brokers {
@ -813,7 +813,7 @@ func (ca *clusterAdmin) ListConsumerGroups() (allGroups map[string]string, err e
response, err := b.ListGroups(&ListGroupsRequest{}) response, err := b.ListGroups(&ListGroupsRequest{})
if err != nil { if err != nil {
errors <- err errChan <- err
return return
} }
@ -828,7 +828,7 @@ func (ca *clusterAdmin) ListConsumerGroups() (allGroups map[string]string, err e
wg.Wait() wg.Wait()
close(groupMaps) close(groupMaps)
close(errors) close(errChan)
for groupMap := range groupMaps { for groupMap := range groupMaps {
for group, protocolType := range groupMap { for group, protocolType := range groupMap {
@ -837,7 +837,7 @@ func (ca *clusterAdmin) ListConsumerGroups() (allGroups map[string]string, err e
} }
// Intentionally return only the first error for simplicity // Intentionally return only the first error for simplicity
err = <-errors err = <-errChan
return return
} }
@ -893,7 +893,7 @@ func (ca *clusterAdmin) DescribeLogDirs(brokerIds []int32) (allLogDirs map[int32
// Query brokers in parallel, since we may have to query multiple brokers // Query brokers in parallel, since we may have to query multiple brokers
logDirsMaps := make(chan map[int32][]DescribeLogDirsResponseDirMetadata, len(brokerIds)) logDirsMaps := make(chan map[int32][]DescribeLogDirsResponseDirMetadata, len(brokerIds))
errors := make(chan error, len(brokerIds)) errChan := make(chan error, len(brokerIds))
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
for _, b := range brokerIds { for _, b := range brokerIds {
@ -909,7 +909,7 @@ func (ca *clusterAdmin) DescribeLogDirs(brokerIds []int32) (allLogDirs map[int32
response, err := b.DescribeLogDirs(&DescribeLogDirsRequest{}) response, err := b.DescribeLogDirs(&DescribeLogDirsRequest{})
if err != nil { if err != nil {
errors <- err errChan <- err
return return
} }
logDirs := make(map[int32][]DescribeLogDirsResponseDirMetadata) logDirs := make(map[int32][]DescribeLogDirsResponseDirMetadata)
@ -920,7 +920,7 @@ func (ca *clusterAdmin) DescribeLogDirs(brokerIds []int32) (allLogDirs map[int32
wg.Wait() wg.Wait()
close(logDirsMaps) close(logDirsMaps)
close(errors) close(errChan)
for logDirsMap := range logDirsMaps { for logDirsMap := range logDirsMaps {
for id, logDirs := range logDirsMap { for id, logDirs := range logDirsMap {
@ -929,6 +929,6 @@ func (ca *clusterAdmin) DescribeLogDirs(brokerIds []int32) (allLogDirs map[int32
} }
// Intentionally return only the first error for simplicity // Intentionally return only the first error for simplicity
err = <-errors err = <-errChan
return return
} }

View File

@ -267,6 +267,10 @@ func (pe ProducerError) Error() string {
return fmt.Sprintf("kafka: Failed to produce message to topic %s: %s", pe.Msg.Topic, pe.Err) return fmt.Sprintf("kafka: Failed to produce message to topic %s: %s", pe.Msg.Topic, pe.Err)
} }
func (pe ProducerError) Unwrap() error {
return pe.Err
}
// ProducerErrors is a type that wraps a batch of "ProducerError"s and implements the Error interface. // ProducerErrors is a type that wraps a batch of "ProducerError"s and implements the Error interface.
// It can be returned from the Producer's Close method to avoid the need to manually drain the Errors channel // It can be returned from the Producer's Close method to avoid the need to manually drain the Errors channel
// when closing a producer. // when closing a producer.
@ -348,6 +352,10 @@ func (p *asyncProducer) dispatcher() {
p.inFlight.Add(1) p.inFlight.Add(1)
} }
for _, interceptor := range p.conf.Producer.Interceptors {
msg.safelyApplyInterceptor(interceptor)
}
version := 1 version := 1
if p.conf.Version.IsAtLeast(V0_11_0_0) { if p.conf.Version.IsAtLeast(V0_11_0_0) {
version = 2 version = 2

View File

@ -373,8 +373,8 @@ func getBalanceScore(assignment map[string][]topicPartitionAssignment) int {
} }
// Determine whether the current assignment plan is balanced. // Determine whether the current assignment plan is balanced.
func isBalanced(currentAssignment map[string][]topicPartitionAssignment, sortedCurrentSubscriptions []string, allSubscriptions map[string][]topicPartitionAssignment) bool { func isBalanced(currentAssignment map[string][]topicPartitionAssignment, allSubscriptions map[string][]topicPartitionAssignment) bool {
sortedCurrentSubscriptions = sortMemberIDsByPartitionAssignments(currentAssignment) sortedCurrentSubscriptions := sortMemberIDsByPartitionAssignments(currentAssignment)
min := len(currentAssignment[sortedCurrentSubscriptions[0]]) min := len(currentAssignment[sortedCurrentSubscriptions[0]])
max := len(currentAssignment[sortedCurrentSubscriptions[len(sortedCurrentSubscriptions)-1]]) max := len(currentAssignment[sortedCurrentSubscriptions[len(sortedCurrentSubscriptions)-1]])
if min >= max-1 { if min >= max-1 {
@ -430,7 +430,7 @@ func (s *stickyBalanceStrategy) performReassignments(reassignablePartitions []to
// reassign all reassignable partitions (starting from the partition with least potential consumers and if needed) // reassign all reassignable partitions (starting from the partition with least potential consumers and if needed)
// until the full list is processed or a balance is achieved // until the full list is processed or a balance is achieved
for _, partition := range reassignablePartitions { for _, partition := range reassignablePartitions {
if isBalanced(currentAssignment, sortedCurrentSubscriptions, consumer2AllPotentialPartitions) { if isBalanced(currentAssignment, consumer2AllPotentialPartitions) {
break break
} }
@ -690,11 +690,11 @@ func sortPartitionsByPotentialConsumerAssignments(partition2AllPotentialConsumer
} }
func deepCopyAssignment(assignment map[string][]topicPartitionAssignment) map[string][]topicPartitionAssignment { func deepCopyAssignment(assignment map[string][]topicPartitionAssignment) map[string][]topicPartitionAssignment {
copy := make(map[string][]topicPartitionAssignment, len(assignment)) m := make(map[string][]topicPartitionAssignment, len(assignment))
for memberID, subscriptions := range assignment { for memberID, subscriptions := range assignment {
copy[memberID] = append(subscriptions[:0:0], subscriptions...) m[memberID] = append(subscriptions[:0:0], subscriptions...)
} }
return copy return m
} }
func areSubscriptionsIdentical(partition2AllPotentialConsumers map[topicPartitionAssignment][]string, consumer2AllPotentialPartitions map[string][]topicPartitionAssignment) bool { func areSubscriptionsIdentical(partition2AllPotentialConsumers map[topicPartitionAssignment][]string, consumer2AllPotentialPartitions map[string][]topicPartitionAssignment) bool {

View File

@ -13,7 +13,7 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
metrics "github.com/rcrowley/go-metrics" "github.com/rcrowley/go-metrics"
) )
// Broker represents a single Kafka broker connection. All operations on this object are entirely concurrency-safe. // Broker represents a single Kafka broker connection. All operations on this object are entirely concurrency-safe.
@ -1027,7 +1027,7 @@ func (b *Broker) sendAndReceiveV0SASLPlainAuth() error {
length := len(b.conf.Net.SASL.AuthIdentity) + 1 + len(b.conf.Net.SASL.User) + 1 + len(b.conf.Net.SASL.Password) length := len(b.conf.Net.SASL.AuthIdentity) + 1 + len(b.conf.Net.SASL.User) + 1 + len(b.conf.Net.SASL.Password)
authBytes := make([]byte, length+4) //4 byte length header + auth data authBytes := make([]byte, length+4) //4 byte length header + auth data
binary.BigEndian.PutUint32(authBytes, uint32(length)) binary.BigEndian.PutUint32(authBytes, uint32(length))
copy(authBytes[4:], []byte(b.conf.Net.SASL.AuthIdentity+"\x00"+b.conf.Net.SASL.User+"\x00"+b.conf.Net.SASL.Password)) copy(authBytes[4:], b.conf.Net.SASL.AuthIdentity+"\x00"+b.conf.Net.SASL.User+"\x00"+b.conf.Net.SASL.Password)
requestTime := time.Now() requestTime := time.Now()
// Will be decremented in updateIncomingCommunicationMetrics (except error) // Will be decremented in updateIncomingCommunicationMetrics (except error)

View File

@ -56,6 +56,11 @@ type Client interface {
// partition. Offline replicas are replicas which are offline // partition. Offline replicas are replicas which are offline
OfflineReplicas(topic string, partitionID int32) ([]int32, error) OfflineReplicas(topic string, partitionID int32) ([]int32, error)
// RefreshBrokers takes a list of addresses to be used as seed brokers.
// Existing broker connections are closed and the updated list of seed brokers
// will be used for the next metadata fetch.
RefreshBrokers(addrs []string) error
// RefreshMetadata takes a list of topics and queries the cluster to refresh the // RefreshMetadata takes a list of topics and queries the cluster to refresh the
// available metadata for those topics. If no topics are provided, it will refresh // available metadata for those topics. If no topics are provided, it will refresh
// metadata for all topics. // metadata for all topics.
@ -155,10 +160,7 @@ func NewClient(addrs []string, conf *Config) (Client, error) {
coordinators: make(map[string]int32), coordinators: make(map[string]int32),
} }
random := rand.New(rand.NewSource(time.Now().UnixNano())) client.randomizeSeedBrokers(addrs)
for _, index := range random.Perm(len(addrs)) {
client.seedBrokers = append(client.seedBrokers, NewBroker(addrs[index]))
}
if conf.Metadata.Full { if conf.Metadata.Full {
// do an initial fetch of all cluster metadata by specifying an empty list of topics // do an initial fetch of all cluster metadata by specifying an empty list of topics
@ -429,6 +431,27 @@ func (client *client) Leader(topic string, partitionID int32) (*Broker, error) {
return leader, err return leader, err
} }
func (client *client) RefreshBrokers(addrs []string) error {
if client.Closed() {
return ErrClosedClient
}
client.lock.Lock()
defer client.lock.Unlock()
for _, broker := range client.brokers {
_ = broker.Close()
delete(client.brokers, broker.ID())
}
client.seedBrokers = nil
client.deadSeeds = nil
client.randomizeSeedBrokers(addrs)
return nil
}
func (client *client) RefreshMetadata(topics ...string) error { func (client *client) RefreshMetadata(topics ...string) error {
if client.Closed() { if client.Closed() {
return ErrClosedClient return ErrClosedClient
@ -562,6 +585,13 @@ func (client *client) RefreshCoordinator(consumerGroup string) error {
// private broker management helpers // private broker management helpers
func (client *client) randomizeSeedBrokers(addrs []string) {
random := rand.New(rand.NewSource(time.Now().UnixNano()))
for _, index := range random.Perm(len(addrs)) {
client.seedBrokers = append(client.seedBrokers, NewBroker(addrs[index]))
}
}
func (client *client) updateBroker(brokers []*Broker) { func (client *client) updateBroker(brokers []*Broker) {
var currentBroker = make(map[int32]*Broker, len(brokers)) var currentBroker = make(map[int32]*Broker, len(brokers))
@ -788,7 +818,7 @@ func (client *client) backgroundMetadataUpdater() {
} }
func (client *client) refreshMetadata() error { func (client *client) refreshMetadata() error {
topics := []string{} var topics []string
if !client.conf.Metadata.Full { if !client.conf.Metadata.Full {
if specificTopics, err := client.MetadataTopics(); err != nil { if specificTopics, err := client.MetadataTopics(); err != nil {

View File

@ -229,6 +229,14 @@ type Config struct {
// `Backoff` if set. // `Backoff` if set.
BackoffFunc func(retries, maxRetries int) time.Duration BackoffFunc func(retries, maxRetries int) time.Duration
} }
// Interceptors to be called when the producer dispatcher reads the
// message for the first time. Interceptors allows to intercept and
// possible mutate the message before they are published to Kafka
// cluster. *ProducerMessage modified by the first interceptor's
// OnSend() is passed to the second interceptor OnSend(), and so on in
// the interceptor chain.
Interceptors []ProducerInterceptor
} }
// Consumer is the namespace for configuration related to consuming messages, // Consumer is the namespace for configuration related to consuming messages,
@ -391,6 +399,14 @@ type Config struct {
// - use `ReadUncommitted` (default) to consume and return all messages in message channel // - use `ReadUncommitted` (default) to consume and return all messages in message channel
// - use `ReadCommitted` to hide messages that are part of an aborted transaction // - use `ReadCommitted` to hide messages that are part of an aborted transaction
IsolationLevel IsolationLevel IsolationLevel IsolationLevel
// Interceptors to be called just before the record is sent to the
// messages channel. Interceptors allows to intercept and possible
// mutate the message before they are returned to the client.
// *ConsumerMessage modified by the first interceptor's OnConsume() is
// passed to the second interceptor OnConsume(), and so on in the
// interceptor chain.
Interceptors []ConsumerInterceptor
} }
// A user-provided string sent with every request to the brokers for logging, // A user-provided string sent with every request to the brokers for logging,
@ -470,7 +486,7 @@ func NewConfig() *Config {
c.ClientID = defaultClientID c.ClientID = defaultClientID
c.ChannelBufferSize = 256 c.ChannelBufferSize = 256
c.Version = MinVersion c.Version = DefaultVersion
c.MetricRegistry = metrics.NewRegistry() c.MetricRegistry = metrics.NewRegistry()
return c return c

View File

@ -35,6 +35,10 @@ func (ce ConsumerError) Error() string {
return fmt.Sprintf("kafka: error while consuming %s/%d: %s", ce.Topic, ce.Partition, ce.Err) return fmt.Sprintf("kafka: error while consuming %s/%d: %s", ce.Topic, ce.Partition, ce.Err)
} }
func (ce ConsumerError) Unwrap() error {
return ce.Err
}
// ConsumerErrors is a type that wraps a batch of errors and implements the Error interface. // ConsumerErrors is a type that wraps a batch of errors and implements the Error interface.
// It can be returned from the PartitionConsumer's Close methods to avoid the need to manually drain errors // It can be returned from the PartitionConsumer's Close methods to avoid the need to manually drain errors
// when stopping. // when stopping.
@ -422,13 +426,13 @@ func (child *partitionConsumer) AsyncClose() {
func (child *partitionConsumer) Close() error { func (child *partitionConsumer) Close() error {
child.AsyncClose() child.AsyncClose()
var errors ConsumerErrors var consumerErrors ConsumerErrors
for err := range child.errors { for err := range child.errors {
errors = append(errors, err) consumerErrors = append(consumerErrors, err)
} }
if len(errors) > 0 { if len(consumerErrors) > 0 {
return errors return consumerErrors
} }
return nil return nil
} }
@ -451,6 +455,9 @@ feederLoop:
} }
for i, msg := range msgs { for i, msg := range msgs {
for _, interceptor := range child.conf.Consumer.Interceptors {
msg.safelyApplyInterceptor(interceptor)
}
messageSelect: messageSelect:
select { select {
case <-child.dying: case <-child.dying:
@ -623,7 +630,7 @@ func (child *partitionConsumer) parseResponse(response *FetchResponse) ([]*Consu
abortedProducerIDs := make(map[int64]struct{}, len(block.AbortedTransactions)) abortedProducerIDs := make(map[int64]struct{}, len(block.AbortedTransactions))
abortedTransactions := block.getAbortedTransactions() abortedTransactions := block.getAbortedTransactions()
messages := []*ConsumerMessage{} var messages []*ConsumerMessage
for _, records := range block.RecordsSet { for _, records := range block.RecordsSet {
switch records.recordsType { switch records.recordsType {
case legacyRecords: case legacyRecords:

View File

@ -255,36 +255,36 @@ func (c *consumerGroup) newSession(ctx context.Context, topics []string, handler
} }
// Sync consumer group // Sync consumer group
sync, err := c.syncGroupRequest(coordinator, plan, join.GenerationId) groupRequest, err := c.syncGroupRequest(coordinator, plan, join.GenerationId)
if err != nil { if err != nil {
_ = coordinator.Close() _ = coordinator.Close()
return nil, err return nil, err
} }
switch sync.Err { switch groupRequest.Err {
case ErrNoError: case ErrNoError:
case ErrUnknownMemberId, ErrIllegalGeneration: // reset member ID and retry immediately case ErrUnknownMemberId, ErrIllegalGeneration: // reset member ID and retry immediately
c.memberID = "" c.memberID = ""
return c.newSession(ctx, topics, handler, retries) return c.newSession(ctx, topics, handler, retries)
case ErrNotCoordinatorForConsumer: // retry after backoff with coordinator refresh case ErrNotCoordinatorForConsumer: // retry after backoff with coordinator refresh
if retries <= 0 { if retries <= 0 {
return nil, sync.Err return nil, groupRequest.Err
} }
return c.retryNewSession(ctx, topics, handler, retries, true) return c.retryNewSession(ctx, topics, handler, retries, true)
case ErrRebalanceInProgress: // retry after backoff case ErrRebalanceInProgress: // retry after backoff
if retries <= 0 { if retries <= 0 {
return nil, sync.Err return nil, groupRequest.Err
} }
return c.retryNewSession(ctx, topics, handler, retries, false) return c.retryNewSession(ctx, topics, handler, retries, false)
default: default:
return nil, sync.Err return nil, groupRequest.Err
} }
// Retrieve and sort claims // Retrieve and sort claims
var claims map[string][]int32 var claims map[string][]int32
if len(sync.MemberAssignment) > 0 { if len(groupRequest.MemberAssignment) > 0 {
members, err := sync.GetMemberAssignment() members, err := groupRequest.GetMemberAssignment()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -513,6 +513,11 @@ type ConsumerGroupSession interface {
// message twice, and your processing should ideally be idempotent. // message twice, and your processing should ideally be idempotent.
MarkOffset(topic string, partition int32, offset int64, metadata string) MarkOffset(topic string, partition int32, offset int64, metadata string)
// Commit the offset to the backend
//
// Note: calling Commit performs a blocking synchronous operation.
Commit()
// ResetOffset resets to the provided offset, alongside a metadata string that // ResetOffset resets to the provided offset, alongside a metadata string that
// represents the state of the partition consumer at that point in time. Reset // represents the state of the partition consumer at that point in time. Reset
// acts as a counterpart to MarkOffset, the difference being that it allows to // acts as a counterpart to MarkOffset, the difference being that it allows to
@ -624,6 +629,10 @@ func (s *consumerGroupSession) MarkOffset(topic string, partition int32, offset
} }
} }
func (s *consumerGroupSession) Commit() {
s.offsets.Commit()
}
func (s *consumerGroupSession) ResetOffset(topic string, partition int32, offset int64, metadata string) { func (s *consumerGroupSession) ResetOffset(topic string, partition int32, offset int64, metadata string) {
if pom := s.offsets.findPOM(topic, partition); pom != nil { if pom := s.offsets.findPOM(topic, partition); pom != nil {
pom.ResetOffset(offset, metadata) pom.ResetOffset(offset, metadata)

View File

@ -2,7 +2,7 @@ name: sarama
up: up:
- go: - go:
version: '1.14' version: '1.15.2'
commands: commands:
test: test:

134
vendor/github.com/Shopify/sarama/docker-compose.yml generated vendored Normal file
View File

@ -0,0 +1,134 @@
version: '3.7'
services:
zookeeper-1:
image: 'confluentinc/cp-zookeeper:${CONFLUENT_PLATFORM_VERSION:-5.5.0}'
restart: always
environment:
ZOOKEEPER_SERVER_ID: '1'
ZOOKEEPER_SERVERS: 'zookeeper-1:2888:3888;zookeeper-2:2888:3888;zookeeper-3:2888:3888'
ZOOKEEPER_CLIENT_PORT: '2181'
ZOOKEEPER_PEER_PORT: '2888'
ZOOKEEPER_LEADER_PORT: '3888'
ZOOKEEPER_INIT_LIMIT: '10'
ZOOKEEPER_SYNC_LIMIT: '5'
ZOOKEEPER_MAX_CLIENT_CONNS: '0'
zookeeper-2:
image: 'confluentinc/cp-zookeeper:${CONFLUENT_PLATFORM_VERSION:-5.5.0}'
restart: always
environment:
ZOOKEEPER_SERVER_ID: '2'
ZOOKEEPER_SERVERS: 'zookeeper-1:2888:3888;zookeeper-2:2888:3888;zookeeper-3:2888:3888'
ZOOKEEPER_CLIENT_PORT: '2181'
ZOOKEEPER_PEER_PORT: '2888'
ZOOKEEPER_LEADER_PORT: '3888'
ZOOKEEPER_INIT_LIMIT: '10'
ZOOKEEPER_SYNC_LIMIT: '5'
ZOOKEEPER_MAX_CLIENT_CONNS: '0'
zookeeper-3:
image: 'confluentinc/cp-zookeeper:${CONFLUENT_PLATFORM_VERSION:-5.5.0}'
restart: always
environment:
ZOOKEEPER_SERVER_ID: '3'
ZOOKEEPER_SERVERS: 'zookeeper-1:2888:3888;zookeeper-2:2888:3888;zookeeper-3:2888:3888'
ZOOKEEPER_CLIENT_PORT: '2181'
ZOOKEEPER_PEER_PORT: '2888'
ZOOKEEPER_LEADER_PORT: '3888'
ZOOKEEPER_INIT_LIMIT: '10'
ZOOKEEPER_SYNC_LIMIT: '5'
ZOOKEEPER_MAX_CLIENT_CONNS: '0'
kafka-1:
image: 'confluentinc/cp-kafka:${CONFLUENT_PLATFORM_VERSION:-5.5.0}'
restart: always
environment:
KAFKA_ZOOKEEPER_CONNECT: 'zookeeper-1:2181,zookeeper-2:2181,zookeeper-3:2181'
KAFKA_LISTENERS: 'LISTENER_INTERNAL://:9091,LISTENER_LOCAL://:29091'
KAFKA_ADVERTISED_LISTENERS: 'LISTENER_INTERNAL://kafka-1:9091,LISTENER_LOCAL://localhost:29091'
KAFKA_INTER_BROKER_LISTENER_NAME: 'LISTENER_INTERNAL'
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'LISTENER_INTERNAL:PLAINTEXT,LISTENER_LOCAL:PLAINTEXT'
KAFKA_DEFAULT_REPLICATION_FACTOR: '2'
KAFKA_BROKER_ID: '1'
KAFKA_BROKER_RACK: '1'
KAFKA_ZOOKEEPER_SESSION_TIMEOUT_MS: '3000'
KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: '3000'
KAFKA_REPLICA_SELECTOR_CLASS: 'org.apache.kafka.common.replica.RackAwareReplicaSelector'
KAFKA_DELETE_TOPIC_ENABLE: 'true'
KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'false'
kafka-2:
image: 'confluentinc/cp-kafka:${CONFLUENT_PLATFORM_VERSION:-5.5.0}'
restart: always
environment:
KAFKA_ZOOKEEPER_CONNECT: 'zookeeper-1:2181,zookeeper-2:2181,zookeeper-3:2181'
KAFKA_LISTENERS: 'LISTENER_INTERNAL://:9091,LISTENER_LOCAL://:29092'
KAFKA_ADVERTISED_LISTENERS: 'LISTENER_INTERNAL://kafka-2:9091,LISTENER_LOCAL://localhost:29092'
KAFKA_INTER_BROKER_LISTENER_NAME: 'LISTENER_INTERNAL'
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'LISTENER_INTERNAL:PLAINTEXT,LISTENER_LOCAL:PLAINTEXT'
KAFKA_DEFAULT_REPLICATION_FACTOR: '2'
KAFKA_BROKER_ID: '2'
KAFKA_BROKER_RACK: '2'
KAFKA_ZOOKEEPER_SESSION_TIMEOUT_MS: '3000'
KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: '3000'
KAFKA_REPLICA_SELECTOR_CLASS: 'org.apache.kafka.common.replica.RackAwareReplicaSelector'
KAFKA_DELETE_TOPIC_ENABLE: 'true'
KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'false'
kafka-3:
image: 'confluentinc/cp-kafka:${CONFLUENT_PLATFORM_VERSION:-5.5.0}'
restart: always
environment:
KAFKA_ZOOKEEPER_CONNECT: 'zookeeper-1:2181,zookeeper-2:2181,zookeeper-3:2181'
KAFKA_LISTENERS: 'LISTENER_INTERNAL://:9091,LISTENER_LOCAL://:29093'
KAFKA_ADVERTISED_LISTENERS: 'LISTENER_INTERNAL://kafka-3:9091,LISTENER_LOCAL://localhost:29093'
KAFKA_INTER_BROKER_LISTENER_NAME: 'LISTENER_INTERNAL'
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'LISTENER_INTERNAL:PLAINTEXT,LISTENER_LOCAL:PLAINTEXT'
KAFKA_DEFAULT_REPLICATION_FACTOR: '2'
KAFKA_BROKER_ID: '3'
KAFKA_BROKER_RACK: '3'
KAFKA_ZOOKEEPER_SESSION_TIMEOUT_MS: '3000'
KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: '3000'
KAFKA_REPLICA_SELECTOR_CLASS: 'org.apache.kafka.common.replica.RackAwareReplicaSelector'
KAFKA_DELETE_TOPIC_ENABLE: 'true'
KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'false'
kafka-4:
image: 'confluentinc/cp-kafka:${CONFLUENT_PLATFORM_VERSION:-5.5.0}'
restart: always
environment:
KAFKA_ZOOKEEPER_CONNECT: 'zookeeper-1:2181,zookeeper-2:2181,zookeeper-3:2181'
KAFKA_LISTENERS: 'LISTENER_INTERNAL://:9091,LISTENER_LOCAL://:29094'
KAFKA_ADVERTISED_LISTENERS: 'LISTENER_INTERNAL://kafka-4:9091,LISTENER_LOCAL://localhost:29094'
KAFKA_INTER_BROKER_LISTENER_NAME: 'LISTENER_INTERNAL'
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'LISTENER_INTERNAL:PLAINTEXT,LISTENER_LOCAL:PLAINTEXT'
KAFKA_DEFAULT_REPLICATION_FACTOR: '2'
KAFKA_BROKER_ID: '4'
KAFKA_BROKER_RACK: '4'
KAFKA_ZOOKEEPER_SESSION_TIMEOUT_MS: '3000'
KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: '3000'
KAFKA_REPLICA_SELECTOR_CLASS: 'org.apache.kafka.common.replica.RackAwareReplicaSelector'
KAFKA_DELETE_TOPIC_ENABLE: 'true'
KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'false'
kafka-5:
image: 'confluentinc/cp-kafka:${CONFLUENT_PLATFORM_VERSION:-5.5.0}'
restart: always
environment:
KAFKA_ZOOKEEPER_CONNECT: 'zookeeper-1:2181,zookeeper-2:2181,zookeeper-3:2181'
KAFKA_LISTENERS: 'LISTENER_INTERNAL://:9091,LISTENER_LOCAL://:29095'
KAFKA_ADVERTISED_LISTENERS: 'LISTENER_INTERNAL://kafka-5:9091,LISTENER_LOCAL://localhost:29095'
KAFKA_INTER_BROKER_LISTENER_NAME: 'LISTENER_INTERNAL'
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'LISTENER_INTERNAL:PLAINTEXT,LISTENER_LOCAL:PLAINTEXT'
KAFKA_DEFAULT_REPLICATION_FACTOR: '2'
KAFKA_BROKER_ID: '5'
KAFKA_BROKER_RACK: '5'
KAFKA_ZOOKEEPER_SESSION_TIMEOUT_MS: '3000'
KAFKA_ZOOKEEPER_CONNECTION_TIMEOUT_MS: '3000'
KAFKA_REPLICA_SELECTOR_CLASS: 'org.apache.kafka.common.replica.RackAwareReplicaSelector'
KAFKA_DELETE_TOPIC_ENABLE: 'true'
KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'false'
toxiproxy:
image: 'shopify/toxiproxy:2.1.4'
ports:
# The tests themselves actually start the proies on these ports
- '29091:29091'
- '29092:29092'
- '29093:29093'
- '29094:29094'
- '29095:29095'
# This is the toxiproxy API port
- '8474:8474'

View File

@ -9,26 +9,27 @@ require (
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21
github.com/eapache/queue v1.1.0 github.com/eapache/queue v1.1.0
github.com/fortytw2/leaktest v1.3.0 github.com/fortytw2/leaktest v1.3.0
github.com/frankban/quicktest v1.7.2 // indirect github.com/frankban/quicktest v1.10.2 // indirect
github.com/golang/snappy v0.0.1 // indirect github.com/golang/snappy v0.0.1 // indirect
github.com/google/go-cmp v0.4.0 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/jcmturner/gofork v1.0.0 // indirect github.com/jcmturner/gofork v1.0.0
github.com/klauspost/compress v1.9.8 github.com/klauspost/compress v1.11.0
github.com/kr/pretty v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/pierrec/lz4 v2.4.1+incompatible github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 github.com/pierrec/lz4 v2.5.2+incompatible
github.com/stretchr/testify v1.4.0 github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0
github.com/stretchr/testify v1.6.1
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c
github.com/xdg/stringprep v1.0.0 // indirect github.com/xdg/stringprep v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 // indirect golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 golang.org/x/net v0.0.0-20200904194848-62affa334b73
golang.org/x/text v0.3.2 // indirect golang.org/x/text v0.3.3 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect gopkg.in/jcmturner/aescts.v1 v1.0.1 // indirect
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect gopkg.in/jcmturner/dnsutils.v1 v1.0.1 // indirect
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 gopkg.in/jcmturner/gokrb5.v7 v7.5.0
gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect gopkg.in/jcmturner/rpc.v1 v1.1.0 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
) )

View File

@ -1,5 +1,6 @@
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -11,60 +12,65 @@ github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk= github.com/frankban/quicktest v1.10.2 h1:19ARM85nVi4xH7xPXuc5eM/udya5ieh7b/Sv+d844Tk=
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/klauspost/compress v1.9.8 h1:VMAMUUOh+gaxKTMk+zqbjsSjsIcUcL/LF4o63i82QyA= github.com/klauspost/compress v1.11.0 h1:wJbzvpYMVGG9iTI9VxpnNZfd4DzMPoCWze3GgSqz8yg=
github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pierrec/lz4 v2.4.1+incompatible h1:mFe7ttWaflA46Mhqh+jUfjp2qTbPYxLB2/OyBppH9dg= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 h1:+ELyKg6m8UBf0nPFSqD0mi7zUfwPyXo23HNjMnXPz7w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw= gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM= gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
@ -75,7 +81,7 @@ gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlI
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,13 +1,13 @@
package sarama package sarama
import ( import (
"encoding/asn1"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
"strings" "strings"
"time" "time"
"github.com/jcmturner/gofork/encoding/asn1"
"gopkg.in/jcmturner/gokrb5.v7/asn1tools" "gopkg.in/jcmturner/gokrb5.v7/asn1tools"
"gopkg.in/jcmturner/gokrb5.v7/gssapi" "gopkg.in/jcmturner/gokrb5.v7/gssapi"
"gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype" "gopkg.in/jcmturner/gokrb5.v7/iana/chksumtype"
@ -34,6 +34,7 @@ type GSSAPIConfig struct {
Username string Username string
Password string Password string
Realm string Realm string
DisablePAFXFAST bool
} }
type GSSAPIKerberosAuth struct { type GSSAPIKerberosAuth struct {

43
vendor/github.com/Shopify/sarama/interceptors.go generated vendored Normal file
View File

@ -0,0 +1,43 @@
package sarama
// ProducerInterceptor allows you to intercept (and possibly mutate) the records
// received by the producer before they are published to the Kafka cluster.
// https://cwiki.apache.org/confluence/display/KAFKA/KIP-42%3A+Add+Producer+and+Consumer+Interceptors#KIP42:AddProducerandConsumerInterceptors-Motivation
type ProducerInterceptor interface {
// OnSend is called when the producer message is intercepted. Please avoid
// modifying the message until it's safe to do so, as this is _not_ a copy
// of the message.
OnSend(*ProducerMessage)
}
// ConsumerInterceptor allows you to intercept (and possibly mutate) the records
// received by the consumer before they are sent to the messages channel.
// https://cwiki.apache.org/confluence/display/KAFKA/KIP-42%3A+Add+Producer+and+Consumer+Interceptors#KIP42:AddProducerandConsumerInterceptors-Motivation
type ConsumerInterceptor interface {
// OnConsume is called when the consumed message is intercepted. Please
// avoid modifying the message until it's safe to do so, as this is _not_ a
// copy of the message.
OnConsume(*ConsumerMessage)
}
func (msg *ProducerMessage) safelyApplyInterceptor(interceptor ProducerInterceptor) {
defer func() {
if r := recover(); r != nil {
Logger.Printf("Error when calling producer interceptor: %s, %w\n", interceptor, r)
}
}()
interceptor.OnSend(msg)
}
func (msg *ConsumerMessage) safelyApplyInterceptor(interceptor ConsumerInterceptor) {
defer func() {
if r := recover(); r != nil {
Logger.Printf("Error when calling consumer interceptor: %s, %w\n", interceptor, r)
}
}()
interceptor.OnConsume(msg)
}

View File

@ -19,14 +19,9 @@ func (c *KerberosGoKrb5Client) CName() types.PrincipalName {
return c.Credentials.CName() return c.Credentials.CName()
} }
/* // NewKerberosClient creates kerberos client used to obtain TGT and TGS tokens.
* // It uses pure go Kerberos 5 solution (RFC-4121 and RFC-4120).
* Create kerberos client used to obtain TGT and TGS tokens // uses gokrb5 library underlying which is a pure go kerberos client with some GSS-API capabilities.
* used gokrb5 library, which is a pure go kerberos client with
* some GSS-API capabilities, and SPNEGO support. Kafka does not use SPNEGO
* it uses pure Kerberos 5 solution (RFC-4121 and RFC-4120).
*
*/
func NewKerberosClient(config *GSSAPIConfig) (KerberosClient, error) { func NewKerberosClient(config *GSSAPIConfig) (KerberosClient, error) {
cfg, err := krb5config.Load(config.KerberosConfigPath) cfg, err := krb5config.Load(config.KerberosConfigPath)
if err != nil { if err != nil {
@ -42,10 +37,10 @@ func createClient(config *GSSAPIConfig, cfg *krb5config.Config) (KerberosClient,
if err != nil { if err != nil {
return nil, err return nil, err
} }
client = krb5client.NewClientWithKeytab(config.Username, config.Realm, kt, cfg) client = krb5client.NewClientWithKeytab(config.Username, config.Realm, kt, cfg, krb5client.DisablePAFXFAST(config.DisablePAFXFAST))
} else { } else {
client = krb5client.NewClientWithPassword(config.Username, client = krb5client.NewClientWithPassword(config.Username,
config.Realm, config.Password, cfg) config.Realm, config.Password, cfg, krb5client.DisablePAFXFAST(config.DisablePAFXFAST))
} }
return &KerberosGoKrb5Client{*client}, nil return &KerberosGoKrb5Client{*client}, nil
} }

View File

@ -177,8 +177,8 @@ func (mmr *MockMetadataResponse) For(reqBody versionedDecoder) encoderWithHeader
} }
// Generate set of replicas // Generate set of replicas
replicas := []int32{} var replicas []int32
offlineReplicas := []int32{} var offlineReplicas []int32
for _, brokerID := range mmr.brokers { for _, brokerID := range mmr.brokers {
replicas = append(replicas, brokerID) replicas = append(replicas, brokerID)
} }
@ -772,8 +772,8 @@ func (mr *MockDescribeConfigsResponse) For(reqBody versionedDecoder) encoderWith
Version: req.Version, Version: req.Version,
} }
includeSynonyms := (req.Version > 0) includeSynonyms := req.Version > 0
includeSource := (req.Version > 0) includeSource := req.Version > 0
for _, r := range req.Resources { for _, r := range req.Resources {
var configEntries []*ConfigEntry var configEntries []*ConfigEntry
@ -1078,6 +1078,154 @@ func (m *MockDeleteGroupsResponse) For(reqBody versionedDecoder) encoderWithHead
return resp return resp
} }
type MockJoinGroupResponse struct {
t TestReporter
ThrottleTime int32
Err KError
GenerationId int32
GroupProtocol string
LeaderId string
MemberId string
Members map[string][]byte
}
func NewMockJoinGroupResponse(t TestReporter) *MockJoinGroupResponse {
return &MockJoinGroupResponse{
t: t,
Members: make(map[string][]byte),
}
}
func (m *MockJoinGroupResponse) For(reqBody versionedDecoder) encoderWithHeader {
req := reqBody.(*JoinGroupRequest)
resp := &JoinGroupResponse{
Version: req.Version,
ThrottleTime: m.ThrottleTime,
Err: m.Err,
GenerationId: m.GenerationId,
GroupProtocol: m.GroupProtocol,
LeaderId: m.LeaderId,
MemberId: m.MemberId,
Members: m.Members,
}
return resp
}
func (m *MockJoinGroupResponse) SetThrottleTime(t int32) *MockJoinGroupResponse {
m.ThrottleTime = t
return m
}
func (m *MockJoinGroupResponse) SetError(kerr KError) *MockJoinGroupResponse {
m.Err = kerr
return m
}
func (m *MockJoinGroupResponse) SetGenerationId(id int32) *MockJoinGroupResponse {
m.GenerationId = id
return m
}
func (m *MockJoinGroupResponse) SetGroupProtocol(proto string) *MockJoinGroupResponse {
m.GroupProtocol = proto
return m
}
func (m *MockJoinGroupResponse) SetLeaderId(id string) *MockJoinGroupResponse {
m.LeaderId = id
return m
}
func (m *MockJoinGroupResponse) SetMemberId(id string) *MockJoinGroupResponse {
m.MemberId = id
return m
}
func (m *MockJoinGroupResponse) SetMember(id string, meta *ConsumerGroupMemberMetadata) *MockJoinGroupResponse {
bin, err := encode(meta, nil)
if err != nil {
panic(fmt.Sprintf("error encoding member metadata: %v", err))
}
m.Members[id] = bin
return m
}
type MockLeaveGroupResponse struct {
t TestReporter
Err KError
}
func NewMockLeaveGroupResponse(t TestReporter) *MockLeaveGroupResponse {
return &MockLeaveGroupResponse{t: t}
}
func (m *MockLeaveGroupResponse) For(reqBody versionedDecoder) encoderWithHeader {
resp := &LeaveGroupResponse{
Err: m.Err,
}
return resp
}
func (m *MockLeaveGroupResponse) SetError(kerr KError) *MockLeaveGroupResponse {
m.Err = kerr
return m
}
type MockSyncGroupResponse struct {
t TestReporter
Err KError
MemberAssignment []byte
}
func NewMockSyncGroupResponse(t TestReporter) *MockSyncGroupResponse {
return &MockSyncGroupResponse{t: t}
}
func (m *MockSyncGroupResponse) For(reqBody versionedDecoder) encoderWithHeader {
resp := &SyncGroupResponse{
Err: m.Err,
MemberAssignment: m.MemberAssignment,
}
return resp
}
func (m *MockSyncGroupResponse) SetError(kerr KError) *MockSyncGroupResponse {
m.Err = kerr
return m
}
func (m *MockSyncGroupResponse) SetMemberAssignment(assignment *ConsumerGroupMemberAssignment) *MockSyncGroupResponse {
bin, err := encode(assignment, nil)
if err != nil {
panic(fmt.Sprintf("error encoding member assignment: %v", err))
}
m.MemberAssignment = bin
return m
}
type MockHeartbeatResponse struct {
t TestReporter
Err KError
}
func NewMockHeartbeatResponse(t TestReporter) *MockHeartbeatResponse {
return &MockHeartbeatResponse{t: t}
}
func (m *MockHeartbeatResponse) For(reqBody versionedDecoder) encoderWithHeader {
resp := &HeartbeatResponse{}
return resp
}
func (m *MockHeartbeatResponse) SetError(kerr KError) *MockHeartbeatResponse {
m.Err = kerr
return m
}
type MockDescribeLogDirsResponse struct { type MockDescribeLogDirsResponse struct {
t TestReporter t TestReporter
logDirs []DescribeLogDirsResponseDirMetadata logDirs []DescribeLogDirsResponseDirMetadata
@ -1088,9 +1236,9 @@ func NewMockDescribeLogDirsResponse(t TestReporter) *MockDescribeLogDirsResponse
} }
func (m *MockDescribeLogDirsResponse) SetLogDirs(logDirPath string, topicPartitions map[string]int) *MockDescribeLogDirsResponse { func (m *MockDescribeLogDirsResponse) SetLogDirs(logDirPath string, topicPartitions map[string]int) *MockDescribeLogDirsResponse {
topics := []DescribeLogDirsResponseTopic{} var topics []DescribeLogDirsResponseTopic
for topic := range topicPartitions { for topic := range topicPartitions {
partitions := []DescribeLogDirsResponsePartition{} var partitions []DescribeLogDirsResponsePartition
for i := 0; i < topicPartitions[topic]; i++ { for i := 0; i < topicPartitions[topic]; i++ {
partitions = append(partitions, DescribeLogDirsResponsePartition{ partitions = append(partitions, DescribeLogDirsResponsePartition{
PartitionID: int32(i), PartitionID: int32(i),

View File

@ -19,6 +19,10 @@ type OffsetManager interface {
// will otherwise leak memory. You must call this after all the // will otherwise leak memory. You must call this after all the
// PartitionOffsetManagers are closed. // PartitionOffsetManagers are closed.
Close() error Close() error
// Commit commits the offsets. This method can be used if AutoCommit.Enable is
// set to false.
Commit()
} }
type offsetManager struct { type offsetManager struct {
@ -58,7 +62,6 @@ func newOffsetManagerFromClient(group, memberID string, generation int32, client
client: client, client: client,
conf: conf, conf: conf,
group: group, group: group,
ticker: time.NewTicker(conf.Consumer.Offsets.AutoCommit.Interval),
poms: make(map[string]map[int32]*partitionOffsetManager), poms: make(map[string]map[int32]*partitionOffsetManager),
memberID: memberID, memberID: memberID,
@ -67,7 +70,10 @@ func newOffsetManagerFromClient(group, memberID string, generation int32, client
closing: make(chan none), closing: make(chan none),
closed: make(chan none), closed: make(chan none),
} }
go withRecover(om.mainLoop) if conf.Consumer.Offsets.AutoCommit.Enable {
om.ticker = time.NewTicker(conf.Consumer.Offsets.AutoCommit.Interval)
go withRecover(om.mainLoop)
}
return om, nil return om, nil
} }
@ -99,7 +105,9 @@ func (om *offsetManager) Close() error {
om.closeOnce.Do(func() { om.closeOnce.Do(func() {
// exit the mainLoop // exit the mainLoop
close(om.closing) close(om.closing)
<-om.closed if om.conf.Consumer.Offsets.AutoCommit.Enable {
<-om.closed
}
// mark all POMs as closed // mark all POMs as closed
om.asyncClosePOMs() om.asyncClosePOMs()
@ -225,20 +233,19 @@ func (om *offsetManager) mainLoop() {
for { for {
select { select {
case <-om.ticker.C: case <-om.ticker.C:
om.flushToBroker() om.Commit()
om.releasePOMs(false)
case <-om.closing: case <-om.closing:
return return
} }
} }
} }
// flushToBroker is ignored if auto-commit offsets is disabled func (om *offsetManager) Commit() {
func (om *offsetManager) flushToBroker() { om.flushToBroker()
if !om.conf.Consumer.Offsets.AutoCommit.Enable { om.releasePOMs(false)
return }
}
func (om *offsetManager) flushToBroker() {
req := om.constructRequest() req := om.constructRequest()
if req == nil { if req == nil {
return return

View File

@ -161,6 +161,7 @@ var (
V2_3_0_0 = newKafkaVersion(2, 3, 0, 0) V2_3_0_0 = newKafkaVersion(2, 3, 0, 0)
V2_4_0_0 = newKafkaVersion(2, 4, 0, 0) V2_4_0_0 = newKafkaVersion(2, 4, 0, 0)
V2_5_0_0 = newKafkaVersion(2, 5, 0, 0) V2_5_0_0 = newKafkaVersion(2, 5, 0, 0)
V2_6_0_0 = newKafkaVersion(2, 6, 0, 0)
SupportedVersions = []KafkaVersion{ SupportedVersions = []KafkaVersion{
V0_8_2_0, V0_8_2_0,
@ -187,15 +188,17 @@ var (
V2_3_0_0, V2_3_0_0,
V2_4_0_0, V2_4_0_0,
V2_5_0_0, V2_5_0_0,
V2_6_0_0,
} }
MinVersion = V0_8_2_0 MinVersion = V0_8_2_0
MaxVersion = V2_5_0_0 MaxVersion = V2_6_0_0
DefaultVersion = V1_0_0_0
) )
//ParseKafkaVersion parses and returns kafka version or error from a string //ParseKafkaVersion parses and returns kafka version or error from a string
func ParseKafkaVersion(s string) (KafkaVersion, error) { func ParseKafkaVersion(s string) (KafkaVersion, error) {
if len(s) < 5 { if len(s) < 5 {
return MinVersion, fmt.Errorf("invalid version `%s`", s) return DefaultVersion, fmt.Errorf("invalid version `%s`", s)
} }
var major, minor, veryMinor, patch uint var major, minor, veryMinor, patch uint
var err error var err error
@ -205,7 +208,7 @@ func ParseKafkaVersion(s string) (KafkaVersion, error) {
err = scanKafkaVersion(s, `^\d+\.\d+\.\d+$`, "%d.%d.%d", [3]*uint{&major, &minor, &veryMinor}) err = scanKafkaVersion(s, `^\d+\.\d+\.\d+$`, "%d.%d.%d", [3]*uint{&major, &minor, &veryMinor})
} }
if err != nil { if err != nil {
return MinVersion, err return DefaultVersion, err
} }
return newKafkaVersion(major, minor, veryMinor, patch), nil return newKafkaVersion(major, minor, veryMinor, patch), nil
} }

View File

@ -438,13 +438,6 @@ func (c *Config) WithDisableEndpointHostPrefix(t bool) *Config {
return c return c
} }
// MergeIn merges the passed in configs into the existing config object.
func (c *Config) MergeIn(cfgs ...*Config) {
for _, other := range cfgs {
mergeInConfig(c, other)
}
}
// WithSTSRegionalEndpoint will set whether or not to use regional endpoint flag // WithSTSRegionalEndpoint will set whether or not to use regional endpoint flag
// when resolving the endpoint for a service // when resolving the endpoint for a service
func (c *Config) WithSTSRegionalEndpoint(sre endpoints.STSRegionalEndpoint) *Config { func (c *Config) WithSTSRegionalEndpoint(sre endpoints.STSRegionalEndpoint) *Config {
@ -459,6 +452,27 @@ func (c *Config) WithS3UsEast1RegionalEndpoint(sre endpoints.S3UsEast1RegionalEn
return c return c
} }
// WithLowerCaseHeaderMaps sets a config LowerCaseHeaderMaps value
// returning a Config pointer for chaining.
func (c *Config) WithLowerCaseHeaderMaps(t bool) *Config {
c.LowerCaseHeaderMaps = &t
return c
}
// WithDisableRestProtocolURICleaning sets a config DisableRestProtocolURICleaning value
// returning a Config pointer for chaining.
func (c *Config) WithDisableRestProtocolURICleaning(t bool) *Config {
c.DisableRestProtocolURICleaning = &t
return c
}
// MergeIn merges the passed in configs into the existing config object.
func (c *Config) MergeIn(cfgs ...*Config) {
for _, other := range cfgs {
mergeInConfig(c, other)
}
}
func mergeInConfig(dst *Config, other *Config) { func mergeInConfig(dst *Config, other *Config) {
if other == nil { if other == nil {
return return
@ -571,6 +585,10 @@ func mergeInConfig(dst *Config, other *Config) {
if other.S3UsEast1RegionalEndpoint != endpoints.UnsetS3UsEast1Endpoint { if other.S3UsEast1RegionalEndpoint != endpoints.UnsetS3UsEast1Endpoint {
dst.S3UsEast1RegionalEndpoint = other.S3UsEast1RegionalEndpoint dst.S3UsEast1RegionalEndpoint = other.S3UsEast1RegionalEndpoint
} }
if other.LowerCaseHeaderMaps != nil {
dst.LowerCaseHeaderMaps = other.LowerCaseHeaderMaps
}
} }
// Copy will return a shallow copy of the Config object. If any additional // Copy will return a shallow copy of the Config object. If any additional

View File

@ -50,7 +50,7 @@ package credentials
import ( import (
"fmt" "fmt"
"sync/atomic" "sync"
"time" "time"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
@ -173,7 +173,9 @@ type Expiry struct {
// the expiration time given to ensure no requests are made with expired // the expiration time given to ensure no requests are made with expired
// tokens. // tokens.
func (e *Expiry) SetExpiration(expiration time.Time, window time.Duration) { func (e *Expiry) SetExpiration(expiration time.Time, window time.Duration) {
e.expiration = expiration // Passed in expirations should have the monotonic clock values stripped.
// This ensures time comparisons will be based on wall-time.
e.expiration = expiration.Round(0)
if window > 0 { if window > 0 {
e.expiration = e.expiration.Add(-window) e.expiration = e.expiration.Add(-window)
} }
@ -205,9 +207,10 @@ func (e *Expiry) ExpiresAt() time.Time {
// first instance of the credentials Value. All calls to Get() after that // first instance of the credentials Value. All calls to Get() after that
// will return the cached credentials Value until IsExpired() returns true. // will return the cached credentials Value until IsExpired() returns true.
type Credentials struct { type Credentials struct {
creds atomic.Value sf singleflight.Group
sf singleflight.Group
m sync.RWMutex
creds Value
provider Provider provider Provider
} }
@ -216,7 +219,6 @@ func NewCredentials(provider Provider) *Credentials {
c := &Credentials{ c := &Credentials{
provider: provider, provider: provider,
} }
c.creds.Store(Value{})
return c return c
} }
@ -233,8 +235,17 @@ func NewCredentials(provider Provider) *Credentials {
// //
// Passed in Context is equivalent to aws.Context, and context.Context. // Passed in Context is equivalent to aws.Context, and context.Context.
func (c *Credentials) GetWithContext(ctx Context) (Value, error) { func (c *Credentials) GetWithContext(ctx Context) (Value, error) {
if curCreds := c.creds.Load(); !c.isExpired(curCreds) { // Check if credentials are cached, and not expired.
return curCreds.(Value), nil select {
case curCreds, ok := <-c.asyncIsExpired():
// ok will only be true, of the credentials were not expired. ok will
// be false and have no value if the credentials are expired.
if ok {
return curCreds, nil
}
case <-ctx.Done():
return Value{}, awserr.New("RequestCanceled",
"request context canceled", ctx.Err())
} }
// Cannot pass context down to the actual retrieve, because the first // Cannot pass context down to the actual retrieve, because the first
@ -252,18 +263,23 @@ func (c *Credentials) GetWithContext(ctx Context) (Value, error) {
} }
} }
func (c *Credentials) singleRetrieve(ctx Context) (creds interface{}, err error) { func (c *Credentials) singleRetrieve(ctx Context) (interface{}, error) {
if curCreds := c.creds.Load(); !c.isExpired(curCreds) { c.m.Lock()
return curCreds.(Value), nil defer c.m.Unlock()
if curCreds := c.creds; !c.isExpiredLocked(curCreds) {
return curCreds, nil
} }
var creds Value
var err error
if p, ok := c.provider.(ProviderWithContext); ok { if p, ok := c.provider.(ProviderWithContext); ok {
creds, err = p.RetrieveWithContext(ctx) creds, err = p.RetrieveWithContext(ctx)
} else { } else {
creds, err = c.provider.Retrieve() creds, err = c.provider.Retrieve()
} }
if err == nil { if err == nil {
c.creds.Store(creds) c.creds = creds
} }
return creds, err return creds, err
@ -288,7 +304,10 @@ func (c *Credentials) Get() (Value, error) {
// This will override the Provider's expired state, and force Credentials // This will override the Provider's expired state, and force Credentials
// to call the Provider's Retrieve(). // to call the Provider's Retrieve().
func (c *Credentials) Expire() { func (c *Credentials) Expire() {
c.creds.Store(Value{}) c.m.Lock()
defer c.m.Unlock()
c.creds = Value{}
} }
// IsExpired returns if the credentials are no longer valid, and need // IsExpired returns if the credentials are no longer valid, and need
@ -297,11 +316,32 @@ func (c *Credentials) Expire() {
// If the Credentials were forced to be expired with Expire() this will // If the Credentials were forced to be expired with Expire() this will
// reflect that override. // reflect that override.
func (c *Credentials) IsExpired() bool { func (c *Credentials) IsExpired() bool {
return c.isExpired(c.creds.Load()) c.m.RLock()
defer c.m.RUnlock()
return c.isExpiredLocked(c.creds)
} }
// isExpired helper method wrapping the definition of expired credentials. // asyncIsExpired returns a channel of credentials Value. If the channel is
func (c *Credentials) isExpired(creds interface{}) bool { // closed the credentials are expired and credentials value are not empty.
func (c *Credentials) asyncIsExpired() <-chan Value {
ch := make(chan Value, 1)
go func() {
c.m.RLock()
defer c.m.RUnlock()
if curCreds := c.creds; !c.isExpiredLocked(curCreds) {
ch <- curCreds
}
close(ch)
}()
return ch
}
// isExpiredLocked helper method wrapping the definition of expired credentials.
func (c *Credentials) isExpiredLocked(creds interface{}) bool {
return creds == nil || creds.(Value) == Value{} || c.provider.IsExpired() return creds == nil || creds.(Value) == Value{} || c.provider.IsExpired()
} }
@ -309,13 +349,17 @@ func (c *Credentials) isExpired(creds interface{}) bool {
// the underlying Provider, if it supports that interface. Otherwise, it returns // the underlying Provider, if it supports that interface. Otherwise, it returns
// an error. // an error.
func (c *Credentials) ExpiresAt() (time.Time, error) { func (c *Credentials) ExpiresAt() (time.Time, error) {
c.m.RLock()
defer c.m.RUnlock()
expirer, ok := c.provider.(Expirer) expirer, ok := c.provider.(Expirer)
if !ok { if !ok {
return time.Time{}, awserr.New("ProviderNotExpirer", return time.Time{}, awserr.New("ProviderNotExpirer",
fmt.Sprintf("provider %s does not support ExpiresAt()", c.creds.Load().(Value).ProviderName), fmt.Sprintf("provider %s does not support ExpiresAt()",
c.creds.ProviderName),
nil) nil)
} }
if c.creds.Load().(Value) == (Value{}) { if c.creds == (Value{}) {
// set expiration time to the distant past // set expiration time to the distant past
return time.Time{}, nil return time.Time{}, nil
} }

View File

@ -0,0 +1,60 @@
// Package ssocreds provides a credential provider for retrieving temporary AWS credentials using an SSO access token.
//
// IMPORTANT: The provider in this package does not initiate or perform the AWS SSO login flow. The SDK provider
// expects that you have already performed the SSO login flow using AWS CLI using the "aws sso login" command, or by
// some other mechanism. The provider must find a valid non-expired access token for the AWS SSO user portal URL in
// ~/.aws/sso/cache. If a cached token is not found, it is expired, or the file is malformed an error will be returned.
//
// Loading AWS SSO credentials with the AWS shared configuration file
//
// You can use configure AWS SSO credentials from the AWS shared configuration file by
// providing the specifying the required keys in the profile:
//
// sso_account_id
// sso_region
// sso_role_name
// sso_start_url
//
// For example, the following defines a profile "devsso" and specifies the AWS SSO parameters that defines the target
// account, role, sign-on portal, and the region where the user portal is located. Note: all SSO arguments must be
// provided, or an error will be returned.
//
// [profile devsso]
// sso_start_url = https://my-sso-portal.awsapps.com/start
// sso_role_name = SSOReadOnlyRole
// sso_region = us-east-1
// sso_account_id = 123456789012
//
// Using the config module, you can load the AWS SDK shared configuration, and specify that this profile be used to
// retrieve credentials. For example:
//
// sess, err := session.NewSessionWithOptions(session.Options{
// SharedConfigState: session.SharedConfigEnable,
// Profile: "devsso",
// })
// if err != nil {
// return err
// }
//
// Programmatically loading AWS SSO credentials directly
//
// You can programmatically construct the AWS SSO Provider in your application, and provide the necessary information
// to load and retrieve temporary credentials using an access token from ~/.aws/sso/cache.
//
// svc := sso.New(sess, &aws.Config{
// Region: aws.String("us-west-2"), // Client Region must correspond to the AWS SSO user portal region
// })
//
// provider := ssocreds.NewCredentialsWithClient(svc, "123456789012", "SSOReadOnlyRole", "https://my-sso-portal.awsapps.com/start")
//
// credentials, err := provider.Get()
// if err != nil {
// return err
// }
//
// Additional Resources
//
// Configuring the AWS CLI to use AWS Single Sign-On: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html
//
// AWS Single Sign-On User Guide: https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html
package ssocreds

View File

@ -0,0 +1,9 @@
// +build !windows
package ssocreds
import "os"
func getHomeDirectory() string {
return os.Getenv("HOME")
}

View File

@ -0,0 +1,7 @@
package ssocreds
import "os"
func getHomeDirectory() string {
return os.Getenv("USERPROFILE")
}

View File

@ -0,0 +1,180 @@
package ssocreds
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/service/sso"
"github.com/aws/aws-sdk-go/service/sso/ssoiface"
)
// ErrCodeSSOProviderInvalidToken is the code type that is returned if loaded token has expired or is otherwise invalid.
// To refresh the SSO session run aws sso login with the corresponding profile.
const ErrCodeSSOProviderInvalidToken = "SSOProviderInvalidToken"
const invalidTokenMessage = "the SSO session has expired or is invalid"
func init() {
nowTime = time.Now
defaultCacheLocation = defaultCacheLocationImpl
}
var nowTime func() time.Time
// ProviderName is the name of the provider used to specify the source of credentials.
const ProviderName = "SSOProvider"
var defaultCacheLocation func() string
func defaultCacheLocationImpl() string {
return filepath.Join(getHomeDirectory(), ".aws", "sso", "cache")
}
// Provider is an AWS credential provider that retrieves temporary AWS credentials by exchanging an SSO login token.
type Provider struct {
credentials.Expiry
// The Client which is configured for the AWS Region where the AWS SSO user portal is located.
Client ssoiface.SSOAPI
// The AWS account that is assigned to the user.
AccountID string
// The role name that is assigned to the user.
RoleName string
// The URL that points to the organization's AWS Single Sign-On (AWS SSO) user portal.
StartURL string
}
// NewCredentials returns a new AWS Single Sign-On (AWS SSO) credential provider. The ConfigProvider is expected to be configured
// for the AWS Region where the AWS SSO user portal is located.
func NewCredentials(configProvider client.ConfigProvider, accountID, roleName, startURL string, optFns ...func(provider *Provider)) *credentials.Credentials {
return NewCredentialsWithClient(sso.New(configProvider), accountID, roleName, startURL, optFns...)
}
// NewCredentialsWithClient returns a new AWS Single Sign-On (AWS SSO) credential provider. The provided client is expected to be configured
// for the AWS Region where the AWS SSO user portal is located.
func NewCredentialsWithClient(client ssoiface.SSOAPI, accountID, roleName, startURL string, optFns ...func(provider *Provider)) *credentials.Credentials {
p := &Provider{
Client: client,
AccountID: accountID,
RoleName: roleName,
StartURL: startURL,
}
for _, fn := range optFns {
fn(p)
}
return credentials.NewCredentials(p)
}
// Retrieve retrieves temporary AWS credentials from the configured Amazon Single Sign-On (AWS SSO) user portal
// by exchanging the accessToken present in ~/.aws/sso/cache.
func (p *Provider) Retrieve() (credentials.Value, error) {
return p.RetrieveWithContext(aws.BackgroundContext())
}
// RetrieveWithContext retrieves temporary AWS credentials from the configured Amazon Single Sign-On (AWS SSO) user portal
// by exchanging the accessToken present in ~/.aws/sso/cache.
func (p *Provider) RetrieveWithContext(ctx credentials.Context) (credentials.Value, error) {
tokenFile, err := loadTokenFile(p.StartURL)
if err != nil {
return credentials.Value{}, err
}
output, err := p.Client.GetRoleCredentialsWithContext(ctx, &sso.GetRoleCredentialsInput{
AccessToken: &tokenFile.AccessToken,
AccountId: &p.AccountID,
RoleName: &p.RoleName,
})
if err != nil {
return credentials.Value{}, err
}
expireTime := time.Unix(0, aws.Int64Value(output.RoleCredentials.Expiration)*int64(time.Millisecond)).UTC()
p.SetExpiration(expireTime, 0)
return credentials.Value{
AccessKeyID: aws.StringValue(output.RoleCredentials.AccessKeyId),
SecretAccessKey: aws.StringValue(output.RoleCredentials.SecretAccessKey),
SessionToken: aws.StringValue(output.RoleCredentials.SessionToken),
ProviderName: ProviderName,
}, nil
}
func getCacheFileName(url string) (string, error) {
hash := sha1.New()
_, err := hash.Write([]byte(url))
if err != nil {
return "", err
}
return strings.ToLower(hex.EncodeToString(hash.Sum(nil))) + ".json", nil
}
type rfc3339 time.Time
func (r *rfc3339) UnmarshalJSON(bytes []byte) error {
var value string
if err := json.Unmarshal(bytes, &value); err != nil {
return err
}
parse, err := time.Parse(time.RFC3339, value)
if err != nil {
return fmt.Errorf("expected RFC3339 timestamp: %v", err)
}
*r = rfc3339(parse)
return nil
}
type token struct {
AccessToken string `json:"accessToken"`
ExpiresAt rfc3339 `json:"expiresAt"`
Region string `json:"region,omitempty"`
StartURL string `json:"startUrl,omitempty"`
}
func (t token) Expired() bool {
return nowTime().Round(0).After(time.Time(t.ExpiresAt))
}
func loadTokenFile(startURL string) (t token, err error) {
key, err := getCacheFileName(startURL)
if err != nil {
return token{}, awserr.New(ErrCodeSSOProviderInvalidToken, invalidTokenMessage, err)
}
fileBytes, err := ioutil.ReadFile(filepath.Join(defaultCacheLocation(), key))
if err != nil {
return token{}, awserr.New(ErrCodeSSOProviderInvalidToken, invalidTokenMessage, err)
}
if err := json.Unmarshal(fileBytes, &t); err != nil {
return token{}, awserr.New(ErrCodeSSOProviderInvalidToken, invalidTokenMessage, err)
}
if len(t.AccessToken) == 0 {
return token{}, awserr.New(ErrCodeSSOProviderInvalidToken, invalidTokenMessage, nil)
}
if t.Expired() {
return token{}, awserr.New(ErrCodeSSOProviderInvalidToken, invalidTokenMessage, nil)
}
return t, nil
}

View File

@ -244,9 +244,11 @@ type AssumeRoleProvider struct {
MaxJitterFrac float64 MaxJitterFrac float64
} }
// NewCredentials returns a pointer to a new Credentials object wrapping the // NewCredentials returns a pointer to a new Credentials value wrapping the
// AssumeRoleProvider. The credentials will expire every 15 minutes and the // AssumeRoleProvider. The credentials will expire every 15 minutes and the
// role will be named after a nanosecond timestamp of this operation. // role will be named after a nanosecond timestamp of this operation. The
// Credentials value will attempt to refresh the credentials using the provider
// when Credentials.Get is called, if the cached credentials are expiring.
// //
// Takes a Config provider to create the STS client. The ConfigProvider is // Takes a Config provider to create the STS client. The ConfigProvider is
// satisfied by the session.Session type. // satisfied by the session.Session type.
@ -268,9 +270,11 @@ func NewCredentials(c client.ConfigProvider, roleARN string, options ...func(*As
return credentials.NewCredentials(p) return credentials.NewCredentials(p)
} }
// NewCredentialsWithClient returns a pointer to a new Credentials object wrapping the // NewCredentialsWithClient returns a pointer to a new Credentials value wrapping the
// AssumeRoleProvider. The credentials will expire every 15 minutes and the // AssumeRoleProvider. The credentials will expire every 15 minutes and the
// role will be named after a nanosecond timestamp of this operation. // role will be named after a nanosecond timestamp of this operation. The
// Credentials value will attempt to refresh the credentials using the provider
// when Credentials.Get is called, if the cached credentials are expiring.
// //
// Takes an AssumeRoler which can be satisfied by the STS client. // Takes an AssumeRoler which can be satisfied by the STS client.
// //

View File

@ -87,6 +87,7 @@ func (t *tokenProvider) enableTokenProviderHandler(r *request.Request) {
// If the error code status is 401, we enable the token provider // If the error code status is 401, we enable the token provider
if e, ok := r.Error.(awserr.RequestFailure); ok && e != nil && if e, ok := r.Error.(awserr.RequestFailure); ok && e != nil &&
e.StatusCode() == http.StatusUnauthorized { e.StatusCode() == http.StatusUnauthorized {
t.token.Store(ec2Token{})
atomic.StoreUint32(&t.disabled, 0) atomic.StoreUint32(&t.disabled, 0)
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@ import (
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/processcreds" "github.com/aws/aws-sdk-go/aws/credentials/processcreds"
"github.com/aws/aws-sdk-go/aws/credentials/ssocreds"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds" "github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/defaults" "github.com/aws/aws-sdk-go/aws/defaults"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
@ -100,6 +101,9 @@ func resolveCredsFromProfile(cfg *aws.Config,
sharedCfg.Creds, sharedCfg.Creds,
) )
case sharedCfg.hasSSOConfiguration():
creds, err = resolveSSOCredentials(cfg, sharedCfg, handlers)
case len(sharedCfg.CredentialProcess) != 0: case len(sharedCfg.CredentialProcess) != 0:
// Get credentials from CredentialProcess // Get credentials from CredentialProcess
creds = processcreds.NewCredentials(sharedCfg.CredentialProcess) creds = processcreds.NewCredentials(sharedCfg.CredentialProcess)
@ -151,6 +155,25 @@ func resolveCredsFromProfile(cfg *aws.Config,
return creds, nil return creds, nil
} }
func resolveSSOCredentials(cfg *aws.Config, sharedCfg sharedConfig, handlers request.Handlers) (*credentials.Credentials, error) {
if err := sharedCfg.validateSSOConfiguration(); err != nil {
return nil, err
}
cfgCopy := cfg.Copy()
cfgCopy.Region = &sharedCfg.SSORegion
return ssocreds.NewCredentials(
&Session{
Config: cfgCopy,
Handlers: handlers.Copy(),
},
sharedCfg.SSOAccountID,
sharedCfg.SSORoleName,
sharedCfg.SSOStartURL,
), nil
}
// valid credential source values // valid credential source values
const ( const (
credSourceEc2Metadata = "Ec2InstanceMetadata" credSourceEc2Metadata = "Ec2InstanceMetadata"

View File

@ -0,0 +1,27 @@
// +build go1.13
package session
import (
"net"
"net/http"
"time"
)
// Transport that should be used when a custom CA bundle is specified with the
// SDK.
func getCustomTransport() *http.Transport {
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
}

View File

@ -1,4 +1,4 @@
// +build go1.7 // +build !go1.13,go1.7
package session package session
@ -10,7 +10,7 @@ import (
// Transport that should be used when a custom CA bundle is specified with the // Transport that should be used when a custom CA bundle is specified with the
// SDK. // SDK.
func getCABundleTransport() *http.Transport { func getCustomTransport() *http.Transport {
return &http.Transport{ return &http.Transport{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{ DialContext: (&net.Dialer{

View File

@ -10,7 +10,7 @@ import (
// Transport that should be used when a custom CA bundle is specified with the // Transport that should be used when a custom CA bundle is specified with the
// SDK. // SDK.
func getCABundleTransport() *http.Transport { func getCustomTransport() *http.Transport {
return &http.Transport{ return &http.Transport{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{ Dial: (&net.Dialer{

View File

@ -10,7 +10,7 @@ import (
// Transport that should be used when a custom CA bundle is specified with the // Transport that should be used when a custom CA bundle is specified with the
// SDK. // SDK.
func getCABundleTransport() *http.Transport { func getCustomTransport() *http.Transport {
return &http.Transport{ return &http.Transport{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{ Dial: (&net.Dialer{

View File

@ -208,6 +208,8 @@ env values as well.
AWS_SDK_LOAD_CONFIG=1 AWS_SDK_LOAD_CONFIG=1
Custom Shared Config and Credential Files
Shared credentials file path can be set to instruct the SDK to use an alternative Shared credentials file path can be set to instruct the SDK to use an alternative
file for the shared credentials. If not set the file will be loaded from file for the shared credentials. If not set the file will be loaded from
$HOME/.aws/credentials on Linux/Unix based systems, and $HOME/.aws/credentials on Linux/Unix based systems, and
@ -222,6 +224,8 @@ $HOME/.aws/config on Linux/Unix based systems, and
AWS_CONFIG_FILE=$HOME/my_shared_config AWS_CONFIG_FILE=$HOME/my_shared_config
Custom CA Bundle
Path to a custom Credentials Authority (CA) bundle PEM file that the SDK Path to a custom Credentials Authority (CA) bundle PEM file that the SDK
will use instead of the default system's root CA bundle. Use this only will use instead of the default system's root CA bundle. Use this only
if you want to replace the CA bundle the SDK uses for TLS requests. if you want to replace the CA bundle the SDK uses for TLS requests.
@ -242,6 +246,29 @@ Setting a custom HTTPClient in the aws.Config options will override this setting
To use this option and custom HTTP client, the HTTP client needs to be provided To use this option and custom HTTP client, the HTTP client needs to be provided
when creating the session. Not the service client. when creating the session. Not the service client.
Custom Client TLS Certificate
The SDK supports the environment and session option being configured with
Client TLS certificates that are sent as a part of the client's TLS handshake
for client authentication. If used, both Cert and Key values are required. If
one is missing, or either fail to load the contents of the file an error will
be returned.
HTTP Client's Transport concrete implementation must be a http.Transport
or creating the session will fail.
AWS_SDK_GO_CLIENT_TLS_KEY=$HOME/my_client_key
AWS_SDK_GO_CLIENT_TLS_CERT=$HOME/my_client_cert
This can also be configured via the session.Options ClientTLSCert and ClientTLSKey.
sess, err := session.NewSessionWithOptions(session.Options{
ClientTLSCert: myCertFile,
ClientTLSKey: myKeyFile,
})
Custom EC2 IMDS Endpoint
The endpoint of the EC2 IMDS client can be configured via the environment The endpoint of the EC2 IMDS client can be configured via the environment
variable, AWS_EC2_METADATA_SERVICE_ENDPOINT when creating the client with a variable, AWS_EC2_METADATA_SERVICE_ENDPOINT when creating the client with a
Session. See Options.EC2IMDSEndpoint for more details. Session. See Options.EC2IMDSEndpoint for more details.

View File

@ -101,6 +101,18 @@ type envConfig struct {
// AWS_CA_BUNDLE=$HOME/my_custom_ca_bundle // AWS_CA_BUNDLE=$HOME/my_custom_ca_bundle
CustomCABundle string CustomCABundle string
// Sets the TLC client certificate that should be used by the SDK's HTTP transport
// when making requests. The certificate must be paired with a TLS client key file.
//
// AWS_SDK_GO_CLIENT_TLS_CERT=$HOME/my_client_cert
ClientTLSCert string
// Sets the TLC client key that should be used by the SDK's HTTP transport
// when making requests. The key must be paired with a TLS client certificate file.
//
// AWS_SDK_GO_CLIENT_TLS_KEY=$HOME/my_client_key
ClientTLSKey string
csmEnabled string csmEnabled string
CSMEnabled *bool CSMEnabled *bool
CSMPort string CSMPort string
@ -219,6 +231,15 @@ var (
ec2IMDSEndpointEnvKey = []string{ ec2IMDSEndpointEnvKey = []string{
"AWS_EC2_METADATA_SERVICE_ENDPOINT", "AWS_EC2_METADATA_SERVICE_ENDPOINT",
} }
useCABundleKey = []string{
"AWS_CA_BUNDLE",
}
useClientTLSCert = []string{
"AWS_SDK_GO_CLIENT_TLS_CERT",
}
useClientTLSKey = []string{
"AWS_SDK_GO_CLIENT_TLS_KEY",
}
) )
// loadEnvConfig retrieves the SDK's environment configuration. // loadEnvConfig retrieves the SDK's environment configuration.
@ -302,7 +323,9 @@ func envConfigLoad(enableSharedConfig bool) (envConfig, error) {
cfg.SharedConfigFile = defaults.SharedConfigFilename() cfg.SharedConfigFile = defaults.SharedConfigFilename()
} }
cfg.CustomCABundle = os.Getenv("AWS_CA_BUNDLE") setFromEnvVal(&cfg.CustomCABundle, useCABundleKey)
setFromEnvVal(&cfg.ClientTLSCert, useClientTLSCert)
setFromEnvVal(&cfg.ClientTLSKey, useClientTLSKey)
var err error var err error
// STS Regional Endpoint variable // STS Regional Endpoint variable

View File

@ -25,11 +25,18 @@ const (
// ErrCodeSharedConfig represents an error that occurs in the shared // ErrCodeSharedConfig represents an error that occurs in the shared
// configuration logic // configuration logic
ErrCodeSharedConfig = "SharedConfigErr" ErrCodeSharedConfig = "SharedConfigErr"
// ErrCodeLoadCustomCABundle error code for unable to load custom CA bundle.
ErrCodeLoadCustomCABundle = "LoadCustomCABundleError"
// ErrCodeLoadClientTLSCert error code for unable to load client TLS
// certificate or key
ErrCodeLoadClientTLSCert = "LoadClientTLSCertError"
) )
// ErrSharedConfigSourceCollision will be returned if a section contains both // ErrSharedConfigSourceCollision will be returned if a section contains both
// source_profile and credential_source // source_profile and credential_source
var ErrSharedConfigSourceCollision = awserr.New(ErrCodeSharedConfig, "only source profile or credential source can be specified, not both", nil) var ErrSharedConfigSourceCollision = awserr.New(ErrCodeSharedConfig, "only one credential type may be specified per profile: source profile, credential source, credential process, web identity token, or sso", nil)
// ErrSharedConfigECSContainerEnvVarEmpty will be returned if the environment // ErrSharedConfigECSContainerEnvVarEmpty will be returned if the environment
// variables are empty and Environment was set as the credential source // variables are empty and Environment was set as the credential source
@ -229,17 +236,46 @@ type Options struct {
// the SDK will use instead of the default system's root CA bundle. Use this // the SDK will use instead of the default system's root CA bundle. Use this
// only if you want to replace the CA bundle the SDK uses for TLS requests. // only if you want to replace the CA bundle the SDK uses for TLS requests.
// //
// Enabling this option will attempt to merge the Transport into the SDK's HTTP // HTTP Client's Transport concrete implementation must be a http.Transport
// client. If the client's Transport is not a http.Transport an error will be // or creating the session will fail.
// returned. If the Transport's TLS config is set this option will cause the SDK //
// If the Transport's TLS config is set this option will cause the SDK
// to overwrite the Transport's TLS config's RootCAs value. If the CA // to overwrite the Transport's TLS config's RootCAs value. If the CA
// bundle reader contains multiple certificates all of them will be loaded. // bundle reader contains multiple certificates all of them will be loaded.
// //
// The Session option CustomCABundle is also available when creating sessions // Can also be specified via the environment variable:
// to also enable this feature. CustomCABundle session option field has priority //
// over the AWS_CA_BUNDLE environment variable, and will be used if both are set. // AWS_CA_BUNDLE=$HOME/ca_bundle
//
// Can also be specified via the shared config field:
//
// ca_bundle = $HOME/ca_bundle
CustomCABundle io.Reader CustomCABundle io.Reader
// Reader for the TLC client certificate that should be used by the SDK's
// HTTP transport when making requests. The certificate must be paired with
// a TLS client key file. Will be ignored if both are not provided.
//
// HTTP Client's Transport concrete implementation must be a http.Transport
// or creating the session will fail.
//
// Can also be specified via the environment variable:
//
// AWS_SDK_GO_CLIENT_TLS_CERT=$HOME/my_client_cert
ClientTLSCert io.Reader
// Reader for the TLC client key that should be used by the SDK's HTTP
// transport when making requests. The key must be paired with a TLS client
// certificate file. Will be ignored if both are not provided.
//
// HTTP Client's Transport concrete implementation must be a http.Transport
// or creating the session will fail.
//
// Can also be specified via the environment variable:
//
// AWS_SDK_GO_CLIENT_TLS_KEY=$HOME/my_client_key
ClientTLSKey io.Reader
// The handlers that the session and all API clients will be created with. // The handlers that the session and all API clients will be created with.
// This must be a complete set of handlers. Use the defaults.Handlers() // This must be a complete set of handlers. Use the defaults.Handlers()
// function to initialize this value before changing the handlers to be // function to initialize this value before changing the handlers to be
@ -319,17 +355,6 @@ func NewSessionWithOptions(opts Options) (*Session, error) {
envCfg.EnableSharedConfig = true envCfg.EnableSharedConfig = true
} }
// Only use AWS_CA_BUNDLE if session option is not provided.
if len(envCfg.CustomCABundle) != 0 && opts.CustomCABundle == nil {
f, err := os.Open(envCfg.CustomCABundle)
if err != nil {
return nil, awserr.New("LoadCustomCABundleError",
"failed to open custom CA bundle PEM file", err)
}
defer f.Close()
opts.CustomCABundle = f
}
return newSession(opts, envCfg, &opts.Config) return newSession(opts, envCfg, &opts.Config)
} }
@ -460,6 +485,10 @@ func newSession(opts Options, envCfg envConfig, cfgs ...*aws.Config) (*Session,
return nil, err return nil, err
} }
if err := setTLSOptions(&opts, cfg, envCfg, sharedCfg); err != nil {
return nil, err
}
s := &Session{ s := &Session{
Config: cfg, Config: cfg,
Handlers: handlers, Handlers: handlers,
@ -479,13 +508,6 @@ func newSession(opts Options, envCfg envConfig, cfgs ...*aws.Config) (*Session,
} }
} }
// Setup HTTP client with custom cert bundle if enabled
if opts.CustomCABundle != nil {
if err := loadCustomCABundle(s, opts.CustomCABundle); err != nil {
return nil, err
}
}
return s, nil return s, nil
} }
@ -529,22 +551,83 @@ func loadCSMConfig(envCfg envConfig, cfgFiles []string) (csmConfig, error) {
return csmConfig{}, nil return csmConfig{}, nil
} }
func loadCustomCABundle(s *Session, bundle io.Reader) error { func setTLSOptions(opts *Options, cfg *aws.Config, envCfg envConfig, sharedCfg sharedConfig) error {
// CA Bundle can be specified in both environment variable shared config file.
var caBundleFilename = envCfg.CustomCABundle
if len(caBundleFilename) == 0 {
caBundleFilename = sharedCfg.CustomCABundle
}
// Only use environment value if session option is not provided.
customTLSOptions := map[string]struct {
filename string
field *io.Reader
errCode string
}{
"custom CA bundle PEM": {filename: caBundleFilename, field: &opts.CustomCABundle, errCode: ErrCodeLoadCustomCABundle},
"custom client TLS cert": {filename: envCfg.ClientTLSCert, field: &opts.ClientTLSCert, errCode: ErrCodeLoadClientTLSCert},
"custom client TLS key": {filename: envCfg.ClientTLSKey, field: &opts.ClientTLSKey, errCode: ErrCodeLoadClientTLSCert},
}
for name, v := range customTLSOptions {
if len(v.filename) != 0 && *v.field == nil {
f, err := os.Open(v.filename)
if err != nil {
return awserr.New(v.errCode, fmt.Sprintf("failed to open %s file", name), err)
}
defer f.Close()
*v.field = f
}
}
// Setup HTTP client with custom cert bundle if enabled
if opts.CustomCABundle != nil {
if err := loadCustomCABundle(cfg.HTTPClient, opts.CustomCABundle); err != nil {
return err
}
}
// Setup HTTP client TLS certificate and key for client TLS authentication.
if opts.ClientTLSCert != nil && opts.ClientTLSKey != nil {
if err := loadClientTLSCert(cfg.HTTPClient, opts.ClientTLSCert, opts.ClientTLSKey); err != nil {
return err
}
} else if opts.ClientTLSCert == nil && opts.ClientTLSKey == nil {
// Do nothing if neither values are available.
} else {
return awserr.New(ErrCodeLoadClientTLSCert,
fmt.Sprintf("client TLS cert(%t) and key(%t) must both be provided",
opts.ClientTLSCert != nil, opts.ClientTLSKey != nil), nil)
}
return nil
}
func getHTTPTransport(client *http.Client) (*http.Transport, error) {
var t *http.Transport var t *http.Transport
switch v := s.Config.HTTPClient.Transport.(type) { switch v := client.Transport.(type) {
case *http.Transport: case *http.Transport:
t = v t = v
default: default:
if s.Config.HTTPClient.Transport != nil { if client.Transport != nil {
return awserr.New("LoadCustomCABundleError", return nil, fmt.Errorf("unsupported transport, %T", client.Transport)
"unable to load custom CA bundle, HTTPClient's transport unsupported type", nil)
} }
} }
if t == nil { if t == nil {
// Nil transport implies `http.DefaultTransport` should be used. Since // Nil transport implies `http.DefaultTransport` should be used. Since
// the SDK cannot modify, nor copy the `DefaultTransport` specifying // the SDK cannot modify, nor copy the `DefaultTransport` specifying
// the values the next closest behavior. // the values the next closest behavior.
t = getCABundleTransport() t = getCustomTransport()
}
return t, nil
}
func loadCustomCABundle(client *http.Client, bundle io.Reader) error {
t, err := getHTTPTransport(client)
if err != nil {
return awserr.New(ErrCodeLoadCustomCABundle,
"unable to load custom CA bundle, HTTPClient's transport unsupported type", err)
} }
p, err := loadCertPool(bundle) p, err := loadCertPool(bundle)
@ -556,7 +639,7 @@ func loadCustomCABundle(s *Session, bundle io.Reader) error {
} }
t.TLSClientConfig.RootCAs = p t.TLSClientConfig.RootCAs = p
s.Config.HTTPClient.Transport = t client.Transport = t
return nil return nil
} }
@ -564,19 +647,57 @@ func loadCustomCABundle(s *Session, bundle io.Reader) error {
func loadCertPool(r io.Reader) (*x509.CertPool, error) { func loadCertPool(r io.Reader) (*x509.CertPool, error) {
b, err := ioutil.ReadAll(r) b, err := ioutil.ReadAll(r)
if err != nil { if err != nil {
return nil, awserr.New("LoadCustomCABundleError", return nil, awserr.New(ErrCodeLoadCustomCABundle,
"failed to read custom CA bundle PEM file", err) "failed to read custom CA bundle PEM file", err)
} }
p := x509.NewCertPool() p := x509.NewCertPool()
if !p.AppendCertsFromPEM(b) { if !p.AppendCertsFromPEM(b) {
return nil, awserr.New("LoadCustomCABundleError", return nil, awserr.New(ErrCodeLoadCustomCABundle,
"failed to load custom CA bundle PEM file", err) "failed to load custom CA bundle PEM file", err)
} }
return p, nil return p, nil
} }
func loadClientTLSCert(client *http.Client, certFile, keyFile io.Reader) error {
t, err := getHTTPTransport(client)
if err != nil {
return awserr.New(ErrCodeLoadClientTLSCert,
"unable to get usable HTTP transport from client", err)
}
cert, err := ioutil.ReadAll(certFile)
if err != nil {
return awserr.New(ErrCodeLoadClientTLSCert,
"unable to get read client TLS cert file", err)
}
key, err := ioutil.ReadAll(keyFile)
if err != nil {
return awserr.New(ErrCodeLoadClientTLSCert,
"unable to get read client TLS key file", err)
}
clientCert, err := tls.X509KeyPair(cert, key)
if err != nil {
return awserr.New(ErrCodeLoadClientTLSCert,
"unable to load x509 key pair from client cert", err)
}
tlsCfg := t.TLSClientConfig
if tlsCfg == nil {
tlsCfg = &tls.Config{}
}
tlsCfg.Certificates = append(tlsCfg.Certificates, clientCert)
t.TLSClientConfig = tlsCfg
client.Transport = t
return nil
}
func mergeConfigSrcs(cfg, userCfg *aws.Config, func mergeConfigSrcs(cfg, userCfg *aws.Config,
envCfg envConfig, sharedCfg sharedConfig, envCfg envConfig, sharedCfg sharedConfig,
handlers request.Handlers, handlers request.Handlers,

View File

@ -2,6 +2,7 @@ package session
import ( import (
"fmt" "fmt"
"strings"
"time" "time"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
@ -25,6 +26,12 @@ const (
roleSessionNameKey = `role_session_name` // optional roleSessionNameKey = `role_session_name` // optional
roleDurationSecondsKey = "duration_seconds" // optional roleDurationSecondsKey = "duration_seconds" // optional
// AWS Single Sign-On (AWS SSO) group
ssoAccountIDKey = "sso_account_id"
ssoRegionKey = "sso_region"
ssoRoleNameKey = "sso_role_name"
ssoStartURL = "sso_start_url"
// CSM options // CSM options
csmEnabledKey = `csm_enabled` csmEnabledKey = `csm_enabled`
csmHostKey = `csm_host` csmHostKey = `csm_host`
@ -34,6 +41,9 @@ const (
// Additional Config fields // Additional Config fields
regionKey = `region` regionKey = `region`
// custom CA Bundle filename
customCABundleKey = `ca_bundle`
// endpoint discovery group // endpoint discovery group
enableEndpointDiscoveryKey = `endpoint_discovery_enabled` // optional enableEndpointDiscoveryKey = `endpoint_discovery_enabled` // optional
@ -60,6 +70,8 @@ const (
// sharedConfig represents the configuration fields of the SDK config files. // sharedConfig represents the configuration fields of the SDK config files.
type sharedConfig struct { type sharedConfig struct {
Profile string
// Credentials values from the config file. Both aws_access_key_id and // Credentials values from the config file. Both aws_access_key_id and
// aws_secret_access_key must be provided together in the same file to be // aws_secret_access_key must be provided together in the same file to be
// considered valid. The values will be ignored if not a complete group. // considered valid. The values will be ignored if not a complete group.
@ -75,6 +87,11 @@ type sharedConfig struct {
CredentialProcess string CredentialProcess string
WebIdentityTokenFile string WebIdentityTokenFile string
SSOAccountID string
SSORegion string
SSORoleName string
SSOStartURL string
RoleARN string RoleARN string
RoleSessionName string RoleSessionName string
ExternalID string ExternalID string
@ -90,6 +107,15 @@ type sharedConfig struct {
// region // region
Region string Region string
// CustomCABundle is the file path to a PEM file the SDK will read and
// use to configure the HTTP transport with additional CA certs that are
// not present in the platforms default CA store.
//
// This value will be ignored if the file does not exist.
//
// ca_bundle
CustomCABundle string
// EnableEndpointDiscovery can be enabled in the shared config by setting // EnableEndpointDiscovery can be enabled in the shared config by setting
// endpoint_discovery_enabled to true // endpoint_discovery_enabled to true
// //
@ -177,6 +203,8 @@ func loadSharedConfigIniFiles(filenames []string) ([]sharedConfigFile, error) {
} }
func (cfg *sharedConfig) setFromIniFiles(profiles map[string]struct{}, profile string, files []sharedConfigFile, exOpts bool) error { func (cfg *sharedConfig) setFromIniFiles(profiles map[string]struct{}, profile string, files []sharedConfigFile, exOpts bool) error {
cfg.Profile = profile
// Trim files from the list that don't exist. // Trim files from the list that don't exist.
var skippedFiles int var skippedFiles int
var profileNotFoundErr error var profileNotFoundErr error
@ -205,9 +233,9 @@ func (cfg *sharedConfig) setFromIniFiles(profiles map[string]struct{}, profile s
cfg.clearAssumeRoleOptions() cfg.clearAssumeRoleOptions()
} else { } else {
// First time a profile has been seen, It must either be a assume role // First time a profile has been seen, It must either be a assume role
// or credentials. Assert if the credential type requires a role ARN, // credentials, or SSO. Assert if the credential type requires a role ARN,
// the ARN is also set. // the ARN is also set, or validate that the SSO configuration is complete.
if err := cfg.validateCredentialsRequireARN(profile); err != nil { if err := cfg.validateCredentialsConfig(profile); err != nil {
return err return err
} }
} }
@ -276,6 +304,7 @@ func (cfg *sharedConfig) setFromIniFile(profile string, file sharedConfigFile, e
updateString(&cfg.SourceProfileName, section, sourceProfileKey) updateString(&cfg.SourceProfileName, section, sourceProfileKey)
updateString(&cfg.CredentialSource, section, credentialSourceKey) updateString(&cfg.CredentialSource, section, credentialSourceKey)
updateString(&cfg.Region, section, regionKey) updateString(&cfg.Region, section, regionKey)
updateString(&cfg.CustomCABundle, section, customCABundleKey)
if section.Has(roleDurationSecondsKey) { if section.Has(roleDurationSecondsKey) {
d := time.Duration(section.Int(roleDurationSecondsKey)) * time.Second d := time.Duration(section.Int(roleDurationSecondsKey)) * time.Second
@ -299,6 +328,12 @@ func (cfg *sharedConfig) setFromIniFile(profile string, file sharedConfigFile, e
} }
cfg.S3UsEast1RegionalEndpoint = sre cfg.S3UsEast1RegionalEndpoint = sre
} }
// AWS Single Sign-On (AWS SSO)
updateString(&cfg.SSOAccountID, section, ssoAccountIDKey)
updateString(&cfg.SSORegion, section, ssoRegionKey)
updateString(&cfg.SSORoleName, section, ssoRoleNameKey)
updateString(&cfg.SSOStartURL, section, ssoStartURL)
} }
updateString(&cfg.CredentialProcess, section, credentialProcessKey) updateString(&cfg.CredentialProcess, section, credentialProcessKey)
@ -329,6 +364,14 @@ func (cfg *sharedConfig) setFromIniFile(profile string, file sharedConfigFile, e
return nil return nil
} }
func (cfg *sharedConfig) validateCredentialsConfig(profile string) error {
if err := cfg.validateCredentialsRequireARN(profile); err != nil {
return err
}
return nil
}
func (cfg *sharedConfig) validateCredentialsRequireARN(profile string) error { func (cfg *sharedConfig) validateCredentialsRequireARN(profile string) error {
var credSource string var credSource string
@ -358,6 +401,7 @@ func (cfg *sharedConfig) validateCredentialType() error {
len(cfg.CredentialSource) != 0, len(cfg.CredentialSource) != 0,
len(cfg.CredentialProcess) != 0, len(cfg.CredentialProcess) != 0,
len(cfg.WebIdentityTokenFile) != 0, len(cfg.WebIdentityTokenFile) != 0,
cfg.hasSSOConfiguration(),
) { ) {
return ErrSharedConfigSourceCollision return ErrSharedConfigSourceCollision
} }
@ -365,12 +409,43 @@ func (cfg *sharedConfig) validateCredentialType() error {
return nil return nil
} }
func (cfg *sharedConfig) validateSSOConfiguration() error {
if !cfg.hasSSOConfiguration() {
return nil
}
var missing []string
if len(cfg.SSOAccountID) == 0 {
missing = append(missing, ssoAccountIDKey)
}
if len(cfg.SSORegion) == 0 {
missing = append(missing, ssoRegionKey)
}
if len(cfg.SSORoleName) == 0 {
missing = append(missing, ssoRoleNameKey)
}
if len(cfg.SSOStartURL) == 0 {
missing = append(missing, ssoStartURL)
}
if len(missing) > 0 {
return fmt.Errorf("profile %q is configured to use SSO but is missing required configuration: %s",
cfg.Profile, strings.Join(missing, ", "))
}
return nil
}
func (cfg *sharedConfig) hasCredentials() bool { func (cfg *sharedConfig) hasCredentials() bool {
switch { switch {
case len(cfg.SourceProfileName) != 0: case len(cfg.SourceProfileName) != 0:
case len(cfg.CredentialSource) != 0: case len(cfg.CredentialSource) != 0:
case len(cfg.CredentialProcess) != 0: case len(cfg.CredentialProcess) != 0:
case len(cfg.WebIdentityTokenFile) != 0: case len(cfg.WebIdentityTokenFile) != 0:
case cfg.hasSSOConfiguration():
case cfg.Creds.HasKeys(): case cfg.Creds.HasKeys():
default: default:
return false return false
@ -394,6 +469,18 @@ func (cfg *sharedConfig) clearAssumeRoleOptions() {
cfg.SourceProfileName = "" cfg.SourceProfileName = ""
} }
func (cfg *sharedConfig) hasSSOConfiguration() bool {
switch {
case len(cfg.SSOAccountID) != 0:
case len(cfg.SSORegion) != 0:
case len(cfg.SSORoleName) != 0:
case len(cfg.SSOStartURL) != 0:
default:
return false
}
return true
}
func oneOrNone(bs ...bool) bool { func oneOrNone(bs ...bool) bool {
var count int var count int

View File

@ -5,4 +5,4 @@ package aws
const SDKName = "aws-sdk-go" const SDKName = "aws-sdk-go"
// SDKVersion is the version of this SDK // SDKVersion is the version of this SDK
const SDKVersion = "1.34.29" const SDKVersion = "1.37.3"

View File

@ -63,9 +63,10 @@ var parseTable = map[ASTKind]map[TokenType]int{
TokenNone: MarkCompleteState, TokenNone: MarkCompleteState,
}, },
ASTKindEqualExpr: map[TokenType]int{ ASTKindEqualExpr: map[TokenType]int{
TokenLit: ValueState, TokenLit: ValueState,
TokenWS: SkipTokenState, TokenWS: SkipTokenState,
TokenNL: SkipState, TokenNL: SkipState,
TokenNone: SkipState,
}, },
ASTKindStatement: map[TokenType]int{ ASTKindStatement: map[TokenType]int{
TokenLit: SectionState, TokenLit: SectionState,

View File

@ -1,9 +1,10 @@
package protocol package protocol
import ( import (
"strings"
"github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/request"
"net"
"strconv"
"strings"
) )
// ValidateEndpointHostHandler is a request handler that will validate the // ValidateEndpointHostHandler is a request handler that will validate the
@ -22,8 +23,26 @@ var ValidateEndpointHostHandler = request.NamedHandler{
// 3986 host. Returns error if the host is not valid. // 3986 host. Returns error if the host is not valid.
func ValidateEndpointHost(opName, host string) error { func ValidateEndpointHost(opName, host string) error {
paramErrs := request.ErrInvalidParams{Context: opName} paramErrs := request.ErrInvalidParams{Context: opName}
labels := strings.Split(host, ".")
var hostname string
var port string
var err error
if strings.Contains(host, ":") {
hostname, port, err = net.SplitHostPort(host)
if err != nil {
paramErrs.Add(request.NewErrParamFormat("endpoint", err.Error(), host))
}
if !ValidPortNumber(port) {
paramErrs.Add(request.NewErrParamFormat("endpoint port number", "[0-65535]", port))
}
} else {
hostname = host
}
labels := strings.Split(hostname, ".")
for i, label := range labels { for i, label := range labels {
if i == len(labels)-1 && len(label) == 0 { if i == len(labels)-1 && len(label) == 0 {
// Allow trailing dot for FQDN hosts. // Allow trailing dot for FQDN hosts.
@ -36,7 +55,11 @@ func ValidateEndpointHost(opName, host string) error {
} }
} }
if len(host) > 255 { if len(hostname) == 0 {
paramErrs.Add(request.NewErrParamMinLen("endpoint host", 1))
}
if len(hostname) > 255 {
paramErrs.Add(request.NewErrParamMaxLen( paramErrs.Add(request.NewErrParamMaxLen(
"endpoint host", 255, host, "endpoint host", 255, host,
)) ))
@ -66,3 +89,16 @@ func ValidHostLabel(label string) bool {
return true return true
} }
// ValidPortNumber return if the port is valid RFC 3986 port
func ValidPortNumber(port string) bool {
i, err := strconv.Atoi(port)
if err != nil {
return false
}
if i < 0 || i > 65535 {
return false
}
return true
}

View File

@ -0,0 +1,88 @@
// Package jsonrpc provides JSON RPC utilities for serialization of AWS
// requests and responses.
package jsonrpc
//go:generate go run -tags codegen ../../../private/model/cli/gen-protocol-tests ../../../models/protocol_tests/input/json.json build_test.go
//go:generate go run -tags codegen ../../../private/model/cli/gen-protocol-tests ../../../models/protocol_tests/output/json.json unmarshal_test.go
import (
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/protocol/json/jsonutil"
"github.com/aws/aws-sdk-go/private/protocol/rest"
)
var emptyJSON = []byte("{}")
// BuildHandler is a named request handler for building jsonrpc protocol
// requests
var BuildHandler = request.NamedHandler{
Name: "awssdk.jsonrpc.Build",
Fn: Build,
}
// UnmarshalHandler is a named request handler for unmarshaling jsonrpc
// protocol requests
var UnmarshalHandler = request.NamedHandler{
Name: "awssdk.jsonrpc.Unmarshal",
Fn: Unmarshal,
}
// UnmarshalMetaHandler is a named request handler for unmarshaling jsonrpc
// protocol request metadata
var UnmarshalMetaHandler = request.NamedHandler{
Name: "awssdk.jsonrpc.UnmarshalMeta",
Fn: UnmarshalMeta,
}
// Build builds a JSON payload for a JSON RPC request.
func Build(req *request.Request) {
var buf []byte
var err error
if req.ParamsFilled() {
buf, err = jsonutil.BuildJSON(req.Params)
if err != nil {
req.Error = awserr.New(request.ErrCodeSerialization, "failed encoding JSON RPC request", err)
return
}
} else {
buf = emptyJSON
}
if req.ClientInfo.TargetPrefix != "" || string(buf) != "{}" {
req.SetBufferBody(buf)
}
if req.ClientInfo.TargetPrefix != "" {
target := req.ClientInfo.TargetPrefix + "." + req.Operation.Name
req.HTTPRequest.Header.Add("X-Amz-Target", target)
}
// Only set the content type if one is not already specified and an
// JSONVersion is specified.
if ct, v := req.HTTPRequest.Header.Get("Content-Type"), req.ClientInfo.JSONVersion; len(ct) == 0 && len(v) != 0 {
jsonVersion := req.ClientInfo.JSONVersion
req.HTTPRequest.Header.Set("Content-Type", "application/x-amz-json-"+jsonVersion)
}
}
// Unmarshal unmarshals a response for a JSON RPC service.
func Unmarshal(req *request.Request) {
defer req.HTTPResponse.Body.Close()
if req.DataFilled() {
err := jsonutil.UnmarshalJSON(req.Data, req.HTTPResponse.Body)
if err != nil {
req.Error = awserr.NewRequestFailure(
awserr.New(request.ErrCodeSerialization, "failed decoding JSON RPC response", err),
req.HTTPResponse.StatusCode,
req.RequestID,
)
}
}
return
}
// UnmarshalMeta unmarshals headers from a response for a JSON RPC service.
func UnmarshalMeta(req *request.Request) {
rest.UnmarshalMeta(req)
}

View File

@ -0,0 +1,107 @@
package jsonrpc
import (
"bytes"
"io"
"io/ioutil"
"net/http"
"strings"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/protocol"
"github.com/aws/aws-sdk-go/private/protocol/json/jsonutil"
)
// UnmarshalTypedError provides unmarshaling errors API response errors
// for both typed and untyped errors.
type UnmarshalTypedError struct {
exceptions map[string]func(protocol.ResponseMetadata) error
}
// NewUnmarshalTypedError returns an UnmarshalTypedError initialized for the
// set of exception names to the error unmarshalers
func NewUnmarshalTypedError(exceptions map[string]func(protocol.ResponseMetadata) error) *UnmarshalTypedError {
return &UnmarshalTypedError{
exceptions: exceptions,
}
}
// UnmarshalError attempts to unmarshal the HTTP response error as a known
// error type. If unable to unmarshal the error type, the generic SDK error
// type will be used.
func (u *UnmarshalTypedError) UnmarshalError(
resp *http.Response,
respMeta protocol.ResponseMetadata,
) (error, error) {
var buf bytes.Buffer
var jsonErr jsonErrorResponse
teeReader := io.TeeReader(resp.Body, &buf)
err := jsonutil.UnmarshalJSONError(&jsonErr, teeReader)
if err != nil {
return nil, err
}
body := ioutil.NopCloser(&buf)
// Code may be separated by hash(#), with the last element being the code
// used by the SDK.
codeParts := strings.SplitN(jsonErr.Code, "#", 2)
code := codeParts[len(codeParts)-1]
msg := jsonErr.Message
if fn, ok := u.exceptions[code]; ok {
// If exception code is know, use associated constructor to get a value
// for the exception that the JSON body can be unmarshaled into.
v := fn(respMeta)
err := jsonutil.UnmarshalJSONCaseInsensitive(v, body)
if err != nil {
return nil, err
}
return v, nil
}
// fallback to unmodeled generic exceptions
return awserr.NewRequestFailure(
awserr.New(code, msg, nil),
respMeta.StatusCode,
respMeta.RequestID,
), nil
}
// UnmarshalErrorHandler is a named request handler for unmarshaling jsonrpc
// protocol request errors
var UnmarshalErrorHandler = request.NamedHandler{
Name: "awssdk.jsonrpc.UnmarshalError",
Fn: UnmarshalError,
}
// UnmarshalError unmarshals an error response for a JSON RPC service.
func UnmarshalError(req *request.Request) {
defer req.HTTPResponse.Body.Close()
var jsonErr jsonErrorResponse
err := jsonutil.UnmarshalJSONError(&jsonErr, req.HTTPResponse.Body)
if err != nil {
req.Error = awserr.NewRequestFailure(
awserr.New(request.ErrCodeSerialization,
"failed to unmarshal error message", err),
req.HTTPResponse.StatusCode,
req.RequestID,
)
return
}
codes := strings.SplitN(jsonErr.Code, "#", 2)
req.Error = awserr.NewRequestFailure(
awserr.New(codes[len(codes)-1], jsonErr.Message, nil),
req.HTTPResponse.StatusCode,
req.RequestID,
)
}
type jsonErrorResponse struct {
Code string `json:"__type"`
Message string `json:"message"`
}

View File

@ -0,0 +1,59 @@
// Package restjson provides RESTful JSON serialization of AWS
// requests and responses.
package restjson
//go:generate go run -tags codegen ../../../private/model/cli/gen-protocol-tests ../../../models/protocol_tests/input/rest-json.json build_test.go
//go:generate go run -tags codegen ../../../private/model/cli/gen-protocol-tests ../../../models/protocol_tests/output/rest-json.json unmarshal_test.go
import (
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/protocol/jsonrpc"
"github.com/aws/aws-sdk-go/private/protocol/rest"
)
// BuildHandler is a named request handler for building restjson protocol
// requests
var BuildHandler = request.NamedHandler{
Name: "awssdk.restjson.Build",
Fn: Build,
}
// UnmarshalHandler is a named request handler for unmarshaling restjson
// protocol requests
var UnmarshalHandler = request.NamedHandler{
Name: "awssdk.restjson.Unmarshal",
Fn: Unmarshal,
}
// UnmarshalMetaHandler is a named request handler for unmarshaling restjson
// protocol request metadata
var UnmarshalMetaHandler = request.NamedHandler{
Name: "awssdk.restjson.UnmarshalMeta",
Fn: UnmarshalMeta,
}
// Build builds a request for the REST JSON protocol.
func Build(r *request.Request) {
rest.Build(r)
if t := rest.PayloadType(r.Params); t == "structure" || t == "" {
if v := r.HTTPRequest.Header.Get("Content-Type"); len(v) == 0 {
r.HTTPRequest.Header.Set("Content-Type", "application/json")
}
jsonrpc.Build(r)
}
}
// Unmarshal unmarshals a response body for the REST JSON protocol.
func Unmarshal(r *request.Request) {
if t := rest.PayloadType(r.Data); t == "structure" || t == "" {
jsonrpc.Unmarshal(r)
} else {
rest.Unmarshal(r)
}
}
// UnmarshalMeta unmarshals response headers for the REST JSON protocol.
func UnmarshalMeta(r *request.Request) {
rest.UnmarshalMeta(r)
}

View File

@ -0,0 +1,134 @@
package restjson
import (
"bytes"
"io"
"io/ioutil"
"net/http"
"strings"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/protocol"
"github.com/aws/aws-sdk-go/private/protocol/json/jsonutil"
"github.com/aws/aws-sdk-go/private/protocol/rest"
)
const (
errorTypeHeader = "X-Amzn-Errortype"
errorMessageHeader = "X-Amzn-Errormessage"
)
// UnmarshalTypedError provides unmarshaling errors API response errors
// for both typed and untyped errors.
type UnmarshalTypedError struct {
exceptions map[string]func(protocol.ResponseMetadata) error
}
// NewUnmarshalTypedError returns an UnmarshalTypedError initialized for the
// set of exception names to the error unmarshalers
func NewUnmarshalTypedError(exceptions map[string]func(protocol.ResponseMetadata) error) *UnmarshalTypedError {
return &UnmarshalTypedError{
exceptions: exceptions,
}
}
// UnmarshalError attempts to unmarshal the HTTP response error as a known
// error type. If unable to unmarshal the error type, the generic SDK error
// type will be used.
func (u *UnmarshalTypedError) UnmarshalError(
resp *http.Response,
respMeta protocol.ResponseMetadata,
) (error, error) {
code := resp.Header.Get(errorTypeHeader)
msg := resp.Header.Get(errorMessageHeader)
body := resp.Body
if len(code) == 0 {
// If unable to get code from HTTP headers have to parse JSON message
// to determine what kind of exception this will be.
var buf bytes.Buffer
var jsonErr jsonErrorResponse
teeReader := io.TeeReader(resp.Body, &buf)
err := jsonutil.UnmarshalJSONError(&jsonErr, teeReader)
if err != nil {
return nil, err
}
body = ioutil.NopCloser(&buf)
code = jsonErr.Code
msg = jsonErr.Message
}
// If code has colon separators remove them so can compare against modeled
// exception names.
code = strings.SplitN(code, ":", 2)[0]
if fn, ok := u.exceptions[code]; ok {
// If exception code is know, use associated constructor to get a value
// for the exception that the JSON body can be unmarshaled into.
v := fn(respMeta)
if err := jsonutil.UnmarshalJSONCaseInsensitive(v, body); err != nil {
return nil, err
}
if err := rest.UnmarshalResponse(resp, v, true); err != nil {
return nil, err
}
return v, nil
}
// fallback to unmodeled generic exceptions
return awserr.NewRequestFailure(
awserr.New(code, msg, nil),
respMeta.StatusCode,
respMeta.RequestID,
), nil
}
// UnmarshalErrorHandler is a named request handler for unmarshaling restjson
// protocol request errors
var UnmarshalErrorHandler = request.NamedHandler{
Name: "awssdk.restjson.UnmarshalError",
Fn: UnmarshalError,
}
// UnmarshalError unmarshals a response error for the REST JSON protocol.
func UnmarshalError(r *request.Request) {
defer r.HTTPResponse.Body.Close()
var jsonErr jsonErrorResponse
err := jsonutil.UnmarshalJSONError(&jsonErr, r.HTTPResponse.Body)
if err != nil {
r.Error = awserr.NewRequestFailure(
awserr.New(request.ErrCodeSerialization,
"failed to unmarshal response error", err),
r.HTTPResponse.StatusCode,
r.RequestID,
)
return
}
code := r.HTTPResponse.Header.Get(errorTypeHeader)
if code == "" {
code = jsonErr.Code
}
msg := r.HTTPResponse.Header.Get(errorMessageHeader)
if msg == "" {
msg = jsonErr.Message
}
code = strings.SplitN(code, ":", 2)[0]
r.Error = awserr.NewRequestFailure(
awserr.New(code, jsonErr.Message, nil),
r.HTTPResponse.StatusCode,
r.RequestID,
)
}
type jsonErrorResponse struct {
Code string `json:"code"`
Message string `json:"message"`
}

View File

@ -86,7 +86,7 @@ func (c *SQS) AddPermissionRequest(input *AddPermissionInput) (req *request.Requ
// &AttributeName.2=second // &AttributeName.2=second
// //
// Cross-account permissions don't apply to this action. For more information, // Cross-account permissions don't apply to this action. For more information,
// see Grant Cross-Account Permissions to a Role and a User Name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name) // see Grant cross-account permissions to a role and a user name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name)
// in the Amazon Simple Queue Service Developer Guide. // in the Amazon Simple Queue Service Developer Guide.
// //
// Returns awserr.Error for service API and SDK errors. Use runtime type assertions // Returns awserr.Error for service API and SDK errors. Use runtime type assertions
@ -449,7 +449,7 @@ func (c *SQS) CreateQueueRequest(input *CreateQueueInput) (req *request.Request,
// &AttributeName.2=second // &AttributeName.2=second
// //
// Cross-account permissions don't apply to this action. For more information, // Cross-account permissions don't apply to this action. For more information,
// see Grant Cross-Account Permissions to a Role and a User Name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name) // see Grant cross-account permissions to a role and a user name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name)
// in the Amazon Simple Queue Service Developer Guide. // in the Amazon Simple Queue Service Developer Guide.
// //
// Returns awserr.Error for service API and SDK errors. Use runtime type assertions // Returns awserr.Error for service API and SDK errors. Use runtime type assertions
@ -754,7 +754,7 @@ func (c *SQS) DeleteQueueRequest(input *DeleteQueueInput) (req *request.Request,
// a queue with the same name. // a queue with the same name.
// //
// Cross-account permissions don't apply to this action. For more information, // Cross-account permissions don't apply to this action. For more information,
// see Grant Cross-Account Permissions to a Role and a User Name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name) // see Grant cross-account permissions to a role and a user name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name)
// in the Amazon Simple Queue Service Developer Guide. // in the Amazon Simple Queue Service Developer Guide.
// //
// Returns awserr.Error for service API and SDK errors. Use runtime type assertions // Returns awserr.Error for service API and SDK errors. Use runtime type assertions
@ -1152,7 +1152,7 @@ func (c *SQS) ListQueueTagsRequest(input *ListQueueTagsInput) (req *request.Requ
// in the Amazon Simple Queue Service Developer Guide. // in the Amazon Simple Queue Service Developer Guide.
// //
// Cross-account permissions don't apply to this action. For more information, // Cross-account permissions don't apply to this action. For more information,
// see Grant Cross-Account Permissions to a Role and a User Name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name) // see Grant cross-account permissions to a role and a user name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name)
// in the Amazon Simple Queue Service Developer Guide. // in the Amazon Simple Queue Service Developer Guide.
// //
// Returns awserr.Error for service API and SDK errors. Use runtime type assertions // Returns awserr.Error for service API and SDK errors. Use runtime type assertions
@ -1246,7 +1246,7 @@ func (c *SQS) ListQueuesRequest(input *ListQueuesInput) (req *request.Request, o
// request to listQueues to receive the next page of results. // request to listQueues to receive the next page of results.
// //
// Cross-account permissions don't apply to this action. For more information, // Cross-account permissions don't apply to this action. For more information,
// see Grant Cross-Account Permissions to a Role and a User Name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name) // see Grant cross-account permissions to a role and a user name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name)
// in the Amazon Simple Queue Service Developer Guide. // in the Amazon Simple Queue Service Developer Guide.
// //
// Returns awserr.Error for service API and SDK errors. Use runtime type assertions // Returns awserr.Error for service API and SDK errors. Use runtime type assertions
@ -1605,7 +1605,7 @@ func (c *SQS) RemovePermissionRequest(input *RemovePermissionInput) (req *reques
// * Only the owner of a queue can remove permissions from it. // * Only the owner of a queue can remove permissions from it.
// //
// * Cross-account permissions don't apply to this action. For more information, // * Cross-account permissions don't apply to this action. For more information,
// see Grant Cross-Account Permissions to a Role and a User Name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name) // see Grant cross-account permissions to a role and a user name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name)
// in the Amazon Simple Queue Service Developer Guide. // in the Amazon Simple Queue Service Developer Guide.
// //
// * To remove the ability to change queue permissions, you must deny permission // * To remove the ability to change queue permissions, you must deny permission
@ -1909,7 +1909,7 @@ func (c *SQS) SetQueueAttributesRequest(input *SetQueueAttributesInput) (req *re
// can handle new attributes gracefully. // can handle new attributes gracefully.
// //
// * Cross-account permissions don't apply to this action. For more information, // * Cross-account permissions don't apply to this action. For more information,
// see Grant Cross-Account Permissions to a Role and a User Name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name) // see Grant cross-account permissions to a role and a user name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name)
// in the Amazon Simple Queue Service Developer Guide. // in the Amazon Simple Queue Service Developer Guide.
// //
// * To remove the ability to change queue permissions, you must deny permission // * To remove the ability to change queue permissions, you must deny permission
@ -2014,7 +2014,7 @@ func (c *SQS) TagQueueRequest(input *TagQueueInput) (req *request.Request, outpu
// in the Amazon Simple Queue Service Developer Guide. // in the Amazon Simple Queue Service Developer Guide.
// //
// Cross-account permissions don't apply to this action. For more information, // Cross-account permissions don't apply to this action. For more information,
// see Grant Cross-Account Permissions to a Role and a User Name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name) // see Grant cross-account permissions to a role and a user name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name)
// in the Amazon Simple Queue Service Developer Guide. // in the Amazon Simple Queue Service Developer Guide.
// //
// Returns awserr.Error for service API and SDK errors. Use runtime type assertions // Returns awserr.Error for service API and SDK errors. Use runtime type assertions
@ -2095,7 +2095,7 @@ func (c *SQS) UntagQueueRequest(input *UntagQueueInput) (req *request.Request, o
// in the Amazon Simple Queue Service Developer Guide. // in the Amazon Simple Queue Service Developer Guide.
// //
// Cross-account permissions don't apply to this action. For more information, // Cross-account permissions don't apply to this action. For more information,
// see Grant Cross-Account Permissions to a Role and a User Name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name) // see Grant cross-account permissions to a role and a user name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name)
// in the Amazon Simple Queue Service Developer Guide. // in the Amazon Simple Queue Service Developer Guide.
// //
// Returns awserr.Error for service API and SDK errors. Use runtime type assertions // Returns awserr.Error for service API and SDK errors. Use runtime type assertions
@ -2655,22 +2655,22 @@ type CreateQueueInput struct {
// //
// The following attributes apply only to FIFO (first-in-first-out) queues (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html): // The following attributes apply only to FIFO (first-in-first-out) queues (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html):
// //
// * FifoQueue Designates a queue as FIFO. Valid values: true, false. // * FifoQueue Designates a queue as FIFO. Valid values are true and
// If you don't specify the FifoQueue attribute, Amazon SQS creates a standard // false. If you don't specify the FifoQueue attribute, Amazon SQS creates
// queue. You can provide this attribute only during queue creation. You // a standard queue. You can provide this attribute only during queue creation.
// can't change it for an existing queue. When you set this attribute, you // You can't change it for an existing queue. When you set this attribute,
// must also provide the MessageGroupId for your messages explicitly. For // you must also provide the MessageGroupId for your messages explicitly.
// more information, see FIFO Queue Logic (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html#FIFO-queues-understanding-logic) // For more information, see FIFO Queue Logic (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html#FIFO-queues-understanding-logic)
// in the Amazon Simple Queue Service Developer Guide. // in the Amazon Simple Queue Service Developer Guide.
// //
// * ContentBasedDeduplication Enables content-based deduplication. Valid // * ContentBasedDeduplication Enables content-based deduplication. Valid
// values: true, false. For more information, see Exactly-Once Processing // values are true and false. For more information, see Exactly-Once Processing
// (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html#FIFO-queues-exactly-once-processing) // (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html#FIFO-queues-exactly-once-processing)
// in the Amazon Simple Queue Service Developer Guide. Every message must // in the Amazon Simple Queue Service Developer Guide. Note the following:
// have a unique MessageDeduplicationId, You may provide a MessageDeduplicationId // Every message must have a unique MessageDeduplicationId. You may provide
// explicitly. If you aren't able to provide a MessageDeduplicationId and // a MessageDeduplicationId explicitly. If you aren't able to provide a MessageDeduplicationId
// you enable ContentBasedDeduplication for your queue, Amazon SQS uses a // and you enable ContentBasedDeduplication for your queue, Amazon SQS uses
// SHA-256 hash to generate the MessageDeduplicationId using the body of // a SHA-256 hash to generate the MessageDeduplicationId using the body of
// the message (but not the attributes of the message). If you don't provide // the message (but not the attributes of the message). If you don't provide
// a MessageDeduplicationId and the queue doesn't have ContentBasedDeduplication // a MessageDeduplicationId and the queue doesn't have ContentBasedDeduplication
// set, the action fails with an error. If the queue has ContentBasedDeduplication // set, the action fails with an error. If the queue has ContentBasedDeduplication
@ -2681,6 +2681,49 @@ type CreateQueueInput struct {
// and then another message with a MessageDeduplicationId that is the same // and then another message with a MessageDeduplicationId that is the same
// as the one generated for the first MessageDeduplicationId, the two messages // as the one generated for the first MessageDeduplicationId, the two messages
// are treated as duplicates and only one copy of the message is delivered. // are treated as duplicates and only one copy of the message is delivered.
//
// Preview: High throughput for FIFO queues
//
// High throughput for Amazon SQS FIFO queues is in preview release and is subject
// to change. This feature provides a high number of transactions per second
// (TPS) for messages in FIFO queues. For information on throughput quotas,
// see Quotas related to messages (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/quotas-messages.html)
// in the Amazon Simple Queue Service Developer Guide.
//
// This preview includes two new attributes:
//
// * DeduplicationScope Specifies whether message deduplication occurs
// at the message group or queue level. Valid values are messageGroup and
// queue.
//
// * FifoThroughputLimit Specifies whether the FIFO queue throughput
// quota applies to the entire queue or per message group. Valid values are
// perQueue and perMessageGroupId. The perMessageGroupId value is allowed
// only when the value for DeduplicationScope is messageGroup.
//
// To enable high throughput for FIFO queues, do the following:
//
// * Set DeduplicationScope to messageGroup.
//
// * Set FifoThroughputLimit to perMessageGroupId.
//
// If you set these attributes to anything other than the values shown for enabling
// high throughput, standard throughput is in effect and deduplication occurs
// as specified.
//
// This preview is available in the following AWS Regions:
//
// * US East (Ohio); us-east-2
//
// * US East (N. Virginia); us-east-1
//
// * US West (Oregon); us-west-2
//
// * Europe (Ireland); eu-west-1
//
// For more information about high throughput for FIFO queues, see Preview:
// High throughput for FIFO queues (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/high-throughput-fifo.html)
// in the Amazon Simple Queue Service Developer Guide.
Attributes map[string]*string `locationName:"Attribute" locationNameKey:"Name" locationNameValue:"Value" type:"map" flattened:"true"` Attributes map[string]*string `locationName:"Attribute" locationNameKey:"Name" locationNameValue:"Value" type:"map" flattened:"true"`
// The name of the new queue. The following limits apply to this name: // The name of the new queue. The following limits apply to this name:
@ -2720,7 +2763,7 @@ type CreateQueueInput struct {
// and sqs:TagQueue permissions. // and sqs:TagQueue permissions.
// //
// Cross-account permissions don't apply to this action. For more information, // Cross-account permissions don't apply to this action. For more information,
// see Grant Cross-Account Permissions to a Role and a User Name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name) // see Grant cross-account permissions to a role and a user name (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-customer-managed-policy-examples.html#grant-cross-account-permissions-to-role-and-user-name)
// in the Amazon Simple Queue Service Developer Guide. // in the Amazon Simple Queue Service Developer Guide.
Tags map[string]*string `locationName:"Tag" locationNameKey:"Key" locationNameValue:"Value" type:"map" flattened:"true"` Tags map[string]*string `locationName:"Tag" locationNameKey:"Key" locationNameValue:"Value" type:"map" flattened:"true"`
} }
@ -3182,8 +3225,8 @@ type GetQueueAttributesInput struct {
// //
// The following attributes apply only to FIFO (first-in-first-out) queues (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html): // The following attributes apply only to FIFO (first-in-first-out) queues (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html):
// //
// * FifoQueue Returns whether the queue is FIFO. For more information, // * FifoQueue Returns information about whether the queue is FIFO. For
// see FIFO Queue Logic (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html#FIFO-queues-understanding-logic) // more information, see FIFO Queue Logic (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html#FIFO-queues-understanding-logic)
// in the Amazon Simple Queue Service Developer Guide. To determine whether // in the Amazon Simple Queue Service Developer Guide. To determine whether
// a queue is FIFO (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html), // a queue is FIFO (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html),
// you can check whether QueueName ends with the .fifo suffix. // you can check whether QueueName ends with the .fifo suffix.
@ -3192,6 +3235,49 @@ type GetQueueAttributesInput struct {
// is enabled for the queue. For more information, see Exactly-Once Processing // is enabled for the queue. For more information, see Exactly-Once Processing
// (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html#FIFO-queues-exactly-once-processing) // (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html#FIFO-queues-exactly-once-processing)
// in the Amazon Simple Queue Service Developer Guide. // in the Amazon Simple Queue Service Developer Guide.
//
// Preview: High throughput for FIFO queues
//
// High throughput for Amazon SQS FIFO queues is in preview release and is subject
// to change. This feature provides a high number of transactions per second
// (TPS) for messages in FIFO queues. For information on throughput quotas,
// see Quotas related to messages (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/quotas-messages.html)
// in the Amazon Simple Queue Service Developer Guide.
//
// This preview includes two new attributes:
//
// * DeduplicationScope Specifies whether message deduplication occurs
// at the message group or queue level. Valid values are messageGroup and
// queue.
//
// * FifoThroughputLimit Specifies whether the FIFO queue throughput
// quota applies to the entire queue or per message group. Valid values are
// perQueue and perMessageGroupId. The perMessageGroupId value is allowed
// only when the value for DeduplicationScope is messageGroup.
//
// To enable high throughput for FIFO queues, do the following:
//
// * Set DeduplicationScope to messageGroup.
//
// * Set FifoThroughputLimit to perMessageGroupId.
//
// If you set these attributes to anything other than the values shown for enabling
// high throughput, standard throughput is in effect and deduplication occurs
// as specified.
//
// This preview is available in the following AWS Regions:
//
// * US East (Ohio); us-east-2
//
// * US East (N. Virginia); us-east-1
//
// * US West (Oregon); us-west-2
//
// * Europe (Ireland); eu-west-1
//
// For more information about high throughput for FIFO queues, see Preview:
// High throughput for FIFO queues (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/high-throughput-fifo.html)
// in the Amazon Simple Queue Service Developer Guide.
AttributeNames []*string `locationNameList:"AttributeName" type:"list" flattened:"true"` AttributeNames []*string `locationNameList:"AttributeName" type:"list" flattened:"true"`
// The URL of the Amazon SQS queue whose attribute information is retrieved. // The URL of the Amazon SQS queue whose attribute information is retrieved.
@ -4962,11 +5048,11 @@ type SetQueueAttributesInput struct {
// //
// * ContentBasedDeduplication Enables content-based deduplication. For // * ContentBasedDeduplication Enables content-based deduplication. For
// more information, see Exactly-Once Processing (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html#FIFO-queues-exactly-once-processing) // more information, see Exactly-Once Processing (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html#FIFO-queues-exactly-once-processing)
// in the Amazon Simple Queue Service Developer Guide. Every message must // in the Amazon Simple Queue Service Developer Guide. Note the following:
// have a unique MessageDeduplicationId, You may provide a MessageDeduplicationId // Every message must have a unique MessageDeduplicationId. You may provide
// explicitly. If you aren't able to provide a MessageDeduplicationId and // a MessageDeduplicationId explicitly. If you aren't able to provide a MessageDeduplicationId
// you enable ContentBasedDeduplication for your queue, Amazon SQS uses a // and you enable ContentBasedDeduplication for your queue, Amazon SQS uses
// SHA-256 hash to generate the MessageDeduplicationId using the body of // a SHA-256 hash to generate the MessageDeduplicationId using the body of
// the message (but not the attributes of the message). If you don't provide // the message (but not the attributes of the message). If you don't provide
// a MessageDeduplicationId and the queue doesn't have ContentBasedDeduplication // a MessageDeduplicationId and the queue doesn't have ContentBasedDeduplication
// set, the action fails with an error. If the queue has ContentBasedDeduplication // set, the action fails with an error. If the queue has ContentBasedDeduplication
@ -4978,6 +5064,49 @@ type SetQueueAttributesInput struct {
// as the one generated for the first MessageDeduplicationId, the two messages // as the one generated for the first MessageDeduplicationId, the two messages
// are treated as duplicates and only one copy of the message is delivered. // are treated as duplicates and only one copy of the message is delivered.
// //
// Preview: High throughput for FIFO queues
//
// High throughput for Amazon SQS FIFO queues is in preview release and is subject
// to change. This feature provides a high number of transactions per second
// (TPS) for messages in FIFO queues. For information on throughput quotas,
// see Quotas related to messages (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/quotas-messages.html)
// in the Amazon Simple Queue Service Developer Guide.
//
// This preview includes two new attributes:
//
// * DeduplicationScope Specifies whether message deduplication occurs
// at the message group or queue level. Valid values are messageGroup and
// queue.
//
// * FifoThroughputLimit Specifies whether the FIFO queue throughput
// quota applies to the entire queue or per message group. Valid values are
// perQueue and perMessageGroupId. The perMessageGroupId value is allowed
// only when the value for DeduplicationScope is messageGroup.
//
// To enable high throughput for FIFO queues, do the following:
//
// * Set DeduplicationScope to messageGroup.
//
// * Set FifoThroughputLimit to perMessageGroupId.
//
// If you set these attributes to anything other than the values shown for enabling
// high throughput, standard throughput is in effect and deduplication occurs
// as specified.
//
// This preview is available in the following AWS Regions:
//
// * US East (Ohio); us-east-2
//
// * US East (N. Virginia); us-east-1
//
// * US West (Oregon); us-west-2
//
// * Europe (Ireland); eu-west-1
//
// For more information about high throughput for FIFO queues, see Preview:
// High throughput for FIFO queues (https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/high-throughput-fifo.html)
// in the Amazon Simple Queue Service Developer Guide.
//
// Attributes is a required field // Attributes is a required field
Attributes map[string]*string `locationName:"Attribute" locationNameKey:"Name" locationNameValue:"Value" type:"map" flattened:"true" required:"true"` Attributes map[string]*string `locationName:"Attribute" locationNameKey:"Name" locationNameValue:"Value" type:"map" flattened:"true" required:"true"`
@ -5279,6 +5408,12 @@ const (
// QueueAttributeNameKmsDataKeyReusePeriodSeconds is a QueueAttributeName enum value // QueueAttributeNameKmsDataKeyReusePeriodSeconds is a QueueAttributeName enum value
QueueAttributeNameKmsDataKeyReusePeriodSeconds = "KmsDataKeyReusePeriodSeconds" QueueAttributeNameKmsDataKeyReusePeriodSeconds = "KmsDataKeyReusePeriodSeconds"
// QueueAttributeNameDeduplicationScope is a QueueAttributeName enum value
QueueAttributeNameDeduplicationScope = "DeduplicationScope"
// QueueAttributeNameFifoThroughputLimit is a QueueAttributeName enum value
QueueAttributeNameFifoThroughputLimit = "FifoThroughputLimit"
) )
// QueueAttributeName_Values returns all elements of the QueueAttributeName enum // QueueAttributeName_Values returns all elements of the QueueAttributeName enum
@ -5302,5 +5437,7 @@ func QueueAttributeName_Values() []string {
QueueAttributeNameContentBasedDeduplication, QueueAttributeNameContentBasedDeduplication,
QueueAttributeNameKmsMasterKeyId, QueueAttributeNameKmsMasterKeyId,
QueueAttributeNameKmsDataKeyReusePeriodSeconds, QueueAttributeNameKmsDataKeyReusePeriodSeconds,
QueueAttributeNameDeduplicationScope,
QueueAttributeNameFifoThroughputLimit,
} }
} }

1210
vendor/github.com/aws/aws-sdk-go/service/sso/api.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

44
vendor/github.com/aws/aws-sdk-go/service/sso/doc.go generated vendored Normal file
View File

@ -0,0 +1,44 @@
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
// Package sso provides the client and types for making API
// requests to AWS Single Sign-On.
//
// AWS Single Sign-On Portal is a web service that makes it easy for you to
// assign user access to AWS SSO resources such as the user portal. Users can
// get AWS account applications and roles assigned to them and get federated
// into the application.
//
// For general information about AWS SSO, see What is AWS Single Sign-On? (https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html)
// in the AWS SSO User Guide.
//
// This API reference guide describes the AWS SSO Portal operations that you
// can call programatically and includes detailed information on data types
// and errors.
//
// AWS provides SDKs that consist of libraries and sample code for various programming
// languages and platforms, such as Java, Ruby, .Net, iOS, or Android. The SDKs
// provide a convenient way to create programmatic access to AWS SSO and other
// AWS services. For more information about the AWS SDKs, including how to download
// and install them, see Tools for Amazon Web Services (http://aws.amazon.com/tools/).
//
// See https://docs.aws.amazon.com/goto/WebAPI/sso-2019-06-10 for more information on this service.
//
// See sso package documentation for more information.
// https://docs.aws.amazon.com/sdk-for-go/api/service/sso/
//
// Using the Client
//
// To contact AWS Single Sign-On with the SDK use the New function to create
// a new service client. With that client you can make API requests to the service.
// These clients are safe to use concurrently.
//
// See the SDK's documentation for more information on how to use the SDK.
// https://docs.aws.amazon.com/sdk-for-go/api/
//
// See aws.Config documentation for more information on configuring SDK clients.
// https://docs.aws.amazon.com/sdk-for-go/api/aws/#Config
//
// See the AWS Single Sign-On client SSO for more
// information on creating client for this service.
// https://docs.aws.amazon.com/sdk-for-go/api/service/sso/#New
package sso

44
vendor/github.com/aws/aws-sdk-go/service/sso/errors.go generated vendored Normal file
View File

@ -0,0 +1,44 @@
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
package sso
import (
"github.com/aws/aws-sdk-go/private/protocol"
)
const (
// ErrCodeInvalidRequestException for service response error code
// "InvalidRequestException".
//
// Indicates that a problem occurred with the input to the request. For example,
// a required parameter might be missing or out of range.
ErrCodeInvalidRequestException = "InvalidRequestException"
// ErrCodeResourceNotFoundException for service response error code
// "ResourceNotFoundException".
//
// The specified resource doesn't exist.
ErrCodeResourceNotFoundException = "ResourceNotFoundException"
// ErrCodeTooManyRequestsException for service response error code
// "TooManyRequestsException".
//
// Indicates that the request is being made too frequently and is more than
// what the server can handle.
ErrCodeTooManyRequestsException = "TooManyRequestsException"
// ErrCodeUnauthorizedException for service response error code
// "UnauthorizedException".
//
// Indicates that the request is not authorized. This can happen due to an invalid
// access token in the request.
ErrCodeUnauthorizedException = "UnauthorizedException"
)
var exceptionFromCode = map[string]func(protocol.ResponseMetadata) error{
"InvalidRequestException": newErrorInvalidRequestException,
"ResourceNotFoundException": newErrorResourceNotFoundException,
"TooManyRequestsException": newErrorTooManyRequestsException,
"UnauthorizedException": newErrorUnauthorizedException,
}

104
vendor/github.com/aws/aws-sdk-go/service/sso/service.go generated vendored Normal file
View File

@ -0,0 +1,104 @@
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
package sso
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/client/metadata"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/signer/v4"
"github.com/aws/aws-sdk-go/private/protocol"
"github.com/aws/aws-sdk-go/private/protocol/restjson"
)
// SSO provides the API operation methods for making requests to
// AWS Single Sign-On. See this package's package overview docs
// for details on the service.
//
// SSO methods are safe to use concurrently. It is not safe to
// modify mutate any of the struct's properties though.
type SSO struct {
*client.Client
}
// Used for custom client initialization logic
var initClient func(*client.Client)
// Used for custom request initialization logic
var initRequest func(*request.Request)
// Service information constants
const (
ServiceName = "SSO" // Name of service.
EndpointsID = "portal.sso" // ID to lookup a service endpoint with.
ServiceID = "SSO" // ServiceID is a unique identifier of a specific service.
)
// New creates a new instance of the SSO client with a session.
// If additional configuration is needed for the client instance use the optional
// aws.Config parameter to add your extra config.
//
// Example:
// mySession := session.Must(session.NewSession())
//
// // Create a SSO client from just a session.
// svc := sso.New(mySession)
//
// // Create a SSO client with additional configuration
// svc := sso.New(mySession, aws.NewConfig().WithRegion("us-west-2"))
func New(p client.ConfigProvider, cfgs ...*aws.Config) *SSO {
c := p.ClientConfig(EndpointsID, cfgs...)
if c.SigningNameDerived || len(c.SigningName) == 0 {
c.SigningName = "awsssoportal"
}
return newClient(*c.Config, c.Handlers, c.PartitionID, c.Endpoint, c.SigningRegion, c.SigningName)
}
// newClient creates, initializes and returns a new service client instance.
func newClient(cfg aws.Config, handlers request.Handlers, partitionID, endpoint, signingRegion, signingName string) *SSO {
svc := &SSO{
Client: client.New(
cfg,
metadata.ClientInfo{
ServiceName: ServiceName,
ServiceID: ServiceID,
SigningName: signingName,
SigningRegion: signingRegion,
PartitionID: partitionID,
Endpoint: endpoint,
APIVersion: "2019-06-10",
},
handlers,
),
}
// Handlers
svc.Handlers.Sign.PushBackNamed(v4.SignRequestHandler)
svc.Handlers.Build.PushBackNamed(restjson.BuildHandler)
svc.Handlers.Unmarshal.PushBackNamed(restjson.UnmarshalHandler)
svc.Handlers.UnmarshalMeta.PushBackNamed(restjson.UnmarshalMetaHandler)
svc.Handlers.UnmarshalError.PushBackNamed(
protocol.NewUnmarshalErrorHandler(restjson.NewUnmarshalTypedError(exceptionFromCode)).NamedHandler(),
)
// Run custom client initialization if present
if initClient != nil {
initClient(svc.Client)
}
return svc
}
// newRequest creates a new request for a SSO operation and runs any
// custom request initialization.
func (c *SSO) newRequest(op *request.Operation, params, data interface{}) *request.Request {
req := c.NewRequest(op, params, data)
// Run custom request initialization if present
if initRequest != nil {
initRequest(req)
}
return req
}

View File

@ -0,0 +1,86 @@
// Code generated by private/model/cli/gen-api/main.go. DO NOT EDIT.
// Package ssoiface provides an interface to enable mocking the AWS Single Sign-On service client
// for testing your code.
//
// It is important to note that this interface will have breaking changes
// when the service model is updated and adds new API operations, paginators,
// and waiters.
package ssoiface
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/sso"
)
// SSOAPI provides an interface to enable mocking the
// sso.SSO service client's API operation,
// paginators, and waiters. This make unit testing your code that calls out
// to the SDK's service client's calls easier.
//
// The best way to use this interface is so the SDK's service client's calls
// can be stubbed out for unit testing your code with the SDK without needing
// to inject custom request handlers into the SDK's request pipeline.
//
// // myFunc uses an SDK service client to make a request to
// // AWS Single Sign-On.
// func myFunc(svc ssoiface.SSOAPI) bool {
// // Make svc.GetRoleCredentials request
// }
//
// func main() {
// sess := session.New()
// svc := sso.New(sess)
//
// myFunc(svc)
// }
//
// In your _test.go file:
//
// // Define a mock struct to be used in your unit tests of myFunc.
// type mockSSOClient struct {
// ssoiface.SSOAPI
// }
// func (m *mockSSOClient) GetRoleCredentials(input *sso.GetRoleCredentialsInput) (*sso.GetRoleCredentialsOutput, error) {
// // mock response/functionality
// }
//
// func TestMyFunc(t *testing.T) {
// // Setup Test
// mockSvc := &mockSSOClient{}
//
// myfunc(mockSvc)
//
// // Verify myFunc's functionality
// }
//
// It is important to note that this interface will have breaking changes
// when the service model is updated and adds new API operations, paginators,
// and waiters. Its suggested to use the pattern above for testing, or using
// tooling to generate mocks to satisfy the interfaces.
type SSOAPI interface {
GetRoleCredentials(*sso.GetRoleCredentialsInput) (*sso.GetRoleCredentialsOutput, error)
GetRoleCredentialsWithContext(aws.Context, *sso.GetRoleCredentialsInput, ...request.Option) (*sso.GetRoleCredentialsOutput, error)
GetRoleCredentialsRequest(*sso.GetRoleCredentialsInput) (*request.Request, *sso.GetRoleCredentialsOutput)
ListAccountRoles(*sso.ListAccountRolesInput) (*sso.ListAccountRolesOutput, error)
ListAccountRolesWithContext(aws.Context, *sso.ListAccountRolesInput, ...request.Option) (*sso.ListAccountRolesOutput, error)
ListAccountRolesRequest(*sso.ListAccountRolesInput) (*request.Request, *sso.ListAccountRolesOutput)
ListAccountRolesPages(*sso.ListAccountRolesInput, func(*sso.ListAccountRolesOutput, bool) bool) error
ListAccountRolesPagesWithContext(aws.Context, *sso.ListAccountRolesInput, func(*sso.ListAccountRolesOutput, bool) bool, ...request.Option) error
ListAccounts(*sso.ListAccountsInput) (*sso.ListAccountsOutput, error)
ListAccountsWithContext(aws.Context, *sso.ListAccountsInput, ...request.Option) (*sso.ListAccountsOutput, error)
ListAccountsRequest(*sso.ListAccountsInput) (*request.Request, *sso.ListAccountsOutput)
ListAccountsPages(*sso.ListAccountsInput, func(*sso.ListAccountsOutput, bool) bool) error
ListAccountsPagesWithContext(aws.Context, *sso.ListAccountsInput, func(*sso.ListAccountsOutput, bool) bool, ...request.Option) error
Logout(*sso.LogoutInput) (*sso.LogoutOutput, error)
LogoutWithContext(aws.Context, *sso.LogoutInput, ...request.Option) (*sso.LogoutOutput, error)
LogoutRequest(*sso.LogoutInput) (*request.Request, *sso.LogoutOutput)
}
var _ SSOAPI = (*sso.SSO)(nil)

View File

@ -207,6 +207,10 @@ func (c *STS) AssumeRoleRequest(input *AssumeRoleInput) (req *request.Request, o
// and Deactivating AWS STS in an AWS Region (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html) // and Deactivating AWS STS in an AWS Region (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html)
// in the IAM User Guide. // in the IAM User Guide.
// //
// * ErrCodeExpiredTokenException "ExpiredTokenException"
// The web identity token that was passed is expired or is not valid. Get a
// new identity token from the identity provider and then retry the request.
//
// See also, https://docs.aws.amazon.com/goto/WebAPI/sts-2011-06-15/AssumeRole // See also, https://docs.aws.amazon.com/goto/WebAPI/sts-2011-06-15/AssumeRole
func (c *STS) AssumeRole(input *AssumeRoleInput) (*AssumeRoleOutput, error) { func (c *STS) AssumeRole(input *AssumeRoleInput) (*AssumeRoleOutput, error) {
req, out := c.AssumeRoleRequest(input) req, out := c.AssumeRoleRequest(input)
@ -626,7 +630,7 @@ func (c *STS) AssumeRoleWithWebIdentityRequest(input *AssumeRoleWithWebIdentityI
// * Using Web Identity Federation API Operations for Mobile Apps (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_oidc_manual.html) // * Using Web Identity Federation API Operations for Mobile Apps (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_oidc_manual.html)
// and Federation Through a Web-based Identity Provider (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#api_assumerolewithwebidentity). // and Federation Through a Web-based Identity Provider (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#api_assumerolewithwebidentity).
// //
// * Web Identity Federation Playground (https://web-identity-federation-playground.s3.amazonaws.com/index.html). // * Web Identity Federation Playground (https://aws.amazon.com/blogs/aws/the-aws-web-identity-federation-playground/).
// Walk through the process of authenticating through Login with Amazon, // Walk through the process of authenticating through Login with Amazon,
// Facebook, or Google, getting temporary security credentials, and then // Facebook, or Google, getting temporary security credentials, and then
// using those credentials to make a request to AWS. // using those credentials to make a request to AWS.
@ -1788,7 +1792,7 @@ type AssumeRoleWithSAMLInput struct {
// in the IAM User Guide. // in the IAM User Guide.
// //
// SAMLAssertion is a required field // SAMLAssertion is a required field
SAMLAssertion *string `min:"4" type:"string" required:"true" sensitive:"true"` SAMLAssertion *string `min:"4" type:"string" required:"true"`
} }
// String returns the string representation // String returns the string representation
@ -2100,7 +2104,7 @@ type AssumeRoleWithWebIdentityInput struct {
// the application makes an AssumeRoleWithWebIdentity call. // the application makes an AssumeRoleWithWebIdentity call.
// //
// WebIdentityToken is a required field // WebIdentityToken is a required field
WebIdentityToken *string `min:"4" type:"string" required:"true" sensitive:"true"` WebIdentityToken *string `min:"4" type:"string" required:"true"`
} }
// String returns the string representation // String returns the string representation

View File

@ -3,87 +3,11 @@
// Package sts provides the client and types for making API // Package sts provides the client and types for making API
// requests to AWS Security Token Service. // requests to AWS Security Token Service.
// //
// The AWS Security Token Service (STS) is a web service that enables you to // AWS Security Token Service (STS) enables you to request temporary, limited-privilege
// request temporary, limited-privilege credentials for AWS Identity and Access // credentials for AWS Identity and Access Management (IAM) users or for users
// Management (IAM) users or for users that you authenticate (federated users). // that you authenticate (federated users). This guide provides descriptions
// This guide provides descriptions of the STS API. For more detailed information // of the STS API. For more information about using this service, see Temporary
// about using this service, go to Temporary Security Credentials (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html). // Security Credentials (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html).
//
// For information about setting up signatures and authorization through the
// API, go to Signing AWS API Requests (https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html)
// in the AWS General Reference. For general information about the Query API,
// go to Making Query Requests (https://docs.aws.amazon.com/IAM/latest/UserGuide/IAM_UsingQueryAPI.html)
// in Using IAM. For information about using security tokens with other AWS
// products, go to AWS Services That Work with IAM (https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html)
// in the IAM User Guide.
//
// If you're new to AWS and need additional technical information about a specific
// AWS product, you can find the product's technical documentation at http://aws.amazon.com/documentation/
// (http://aws.amazon.com/documentation/).
//
// Endpoints
//
// By default, AWS Security Token Service (STS) is available as a global service,
// and all AWS STS requests go to a single endpoint at https://sts.amazonaws.com.
// Global requests map to the US East (N. Virginia) region. AWS recommends using
// Regional AWS STS endpoints instead of the global endpoint to reduce latency,
// build in redundancy, and increase session token validity. For more information,
// see Managing AWS STS in an AWS Region (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html)
// in the IAM User Guide.
//
// Most AWS Regions are enabled for operations in all AWS services by default.
// Those Regions are automatically activated for use with AWS STS. Some Regions,
// such as Asia Pacific (Hong Kong), must be manually enabled. To learn more
// about enabling and disabling AWS Regions, see Managing AWS Regions (https://docs.aws.amazon.com/general/latest/gr/rande-manage.html)
// in the AWS General Reference. When you enable these AWS Regions, they are
// automatically activated for use with AWS STS. You cannot activate the STS
// endpoint for a Region that is disabled. Tokens that are valid in all AWS
// Regions are longer than tokens that are valid in Regions that are enabled
// by default. Changing this setting might affect existing systems where you
// temporarily store tokens. For more information, see Managing Global Endpoint
// Session Tokens (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html#sts-regions-manage-tokens)
// in the IAM User Guide.
//
// After you activate a Region for use with AWS STS, you can direct AWS STS
// API calls to that Region. AWS STS recommends that you provide both the Region
// and endpoint when you make calls to a Regional endpoint. You can provide
// the Region alone for manually enabled Regions, such as Asia Pacific (Hong
// Kong). In this case, the calls are directed to the STS Regional endpoint.
// However, if you provide the Region alone for Regions enabled by default,
// the calls are directed to the global endpoint of https://sts.amazonaws.com.
//
// To view the list of AWS STS endpoints and whether they are active by default,
// see Writing Code to Use AWS STS Regions (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html#id_credentials_temp_enable-regions_writing_code)
// in the IAM User Guide.
//
// Recording API requests
//
// STS supports AWS CloudTrail, which is a service that records AWS calls for
// your AWS account and delivers log files to an Amazon S3 bucket. By using
// information collected by CloudTrail, you can determine what requests were
// successfully made to STS, who made the request, when it was made, and so
// on.
//
// If you activate AWS STS endpoints in Regions other than the default global
// endpoint, then you must also turn on CloudTrail logging in those Regions.
// This is necessary to record any AWS STS API calls that are made in those
// Regions. For more information, see Turning On CloudTrail in Additional Regions
// (https://docs.aws.amazon.com/awscloudtrail/latest/userguide/aggregating_logs_regions_turn_on_ct.html)
// in the AWS CloudTrail User Guide.
//
// AWS Security Token Service (STS) is a global service with a single endpoint
// at https://sts.amazonaws.com. Calls to this endpoint are logged as calls
// to a global service. However, because this endpoint is physically located
// in the US East (N. Virginia) Region, your logs list us-east-1 as the event
// Region. CloudTrail does not write these logs to the US East (Ohio) Region
// unless you choose to include global service logs in that Region. CloudTrail
// writes calls to all Regional endpoints to their respective Regions. For example,
// calls to sts.us-east-2.amazonaws.com are published to the US East (Ohio)
// Region and calls to sts.eu-central-1.amazonaws.com are published to the EU
// (Frankfurt) Region.
//
// To learn more about CloudTrail, including how to turn it on and find your
// log files, see the AWS CloudTrail User Guide (https://docs.aws.amazon.com/awscloudtrail/latest/userguide/what_is_cloud_trail_top_level.html).
// //
// See https://docs.aws.amazon.com/goto/WebAPI/sts-2011-06-15 for more information on this service. // See https://docs.aws.amazon.com/goto/WebAPI/sts-2011-06-15 for more information on this service.
// //

View File

@ -1,87 +1,20 @@
Eclipse Public License - v 1.0 This project is dual licensed under the Eclipse Public License 1.0 and the
Eclipse Distribution License 1.0 as described in the epl-v10 and edl-v10 files.
THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. The EDL is copied below in order to pass the pkg.go.dev license check (https://pkg.go.dev/license-policy).
1. DEFINITIONS ****
Eclipse Distribution License - v 1.0
"Contribution" means: Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors.
a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and All rights reserved.
b) in the case of each subsequent Contributor: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
i) changes to the Program, and Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of the Eclipse Foundation, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
ii) additions to the Program; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program.
"Contributor" means any person or entity that distributes the Program.
"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program.
"Program" means the Contributions distributed in accordance with this Agreement.
"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.
2. GRANT OF RIGHTS
a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.
b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder.
c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program.
d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement.
3. REQUIREMENTS
A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:
a) it complies with the terms and conditions of this Agreement; and
b) its license agreement:
i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose;
ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits;
iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and
iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.
When the Program is made available in source code form:
a) it must be made available under this Agreement; and
b) a copy of this Agreement must be included with each copy of the Program.
Contributors may not remove or alter any copyright notices contained within the Program.
Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution.
4. COMMERCIAL DISTRIBUTION
Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense.
For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages.
5. NO WARRANTY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED 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. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations.
6. DISCLAIMER OF LIABILITY
EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. GENERAL
If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed.
All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive.
Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved.
This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.

View File

@ -1,32 +1,47 @@
[![GoDoc](https://godoc.org/github.com/eclipse/paho.mqtt.golang?status.svg)](https://godoc.org/github.com/eclipse/paho.mqtt.golang) [![PkgGoDev](https://pkg.go.dev/badge/github.com/eclipse/paho.mqtt.golang)](https://pkg.go.dev/github.com/eclipse/paho.mqtt.golang)
[![Go Report Card](https://goreportcard.com/badge/github.com/eclipse/paho.mqtt.golang)](https://goreportcard.com/report/github.com/eclipse/paho.mqtt.golang) [![Go Report Card](https://goreportcard.com/badge/github.com/eclipse/paho.mqtt.golang)](https://goreportcard.com/report/github.com/eclipse/paho.mqtt.golang)
Eclipse Paho MQTT Go client Eclipse Paho MQTT Go client
=========================== ===========================
This repository contains the source code for the [Eclipse Paho](http://eclipse.org/paho) MQTT Go client library. This repository contains the source code for the [Eclipse Paho](https://eclipse.org/paho) MQTT 3.1/3.11 Go client library.
This code builds a library which enable applications to connect to an [MQTT](http://mqtt.org) broker to publish messages, and to subscribe to topics and receive published messages. This code builds a library which enable applications to connect to an [MQTT](https://mqtt.org) broker to publish
messages, and to subscribe to topics and receive published messages.
This library supports a fully asynchronous mode of operation. This library supports a fully asynchronous mode of operation.
A client supporting MQTT V5 is [also available](https://github.com/eclipse/paho.golang).
Installation and Build Installation and Build
---------------------- ----------------------
This client is designed to work with the standard Go tools, so installation is as easy as: The process depends upon whether you are using [modules](https://golang.org/ref/mod) (recommended) or `GOPATH`.
#### Modules
If you are using [modules](https://blog.golang.org/using-go-modules) then `import "github.com/eclipse/paho.mqtt.golang"`
and start using it. The necessary packages will be download automatically when you run `go build`.
Note that the latest release will be downloaded and changes may have been made since the release. If you have
encountered an issue, or wish to try the latest code for another reason, then run
`go get github.com/eclipse/paho.mqtt.golang@master` to get the latest commit.
#### GOPATH
Installation is as easy as:
``` ```
go get github.com/eclipse/paho.mqtt.golang go get github.com/eclipse/paho.mqtt.golang
``` ```
The client depends on Google's [websockets](https://godoc.org/golang.org/x/net/websocket) and [proxy](https://godoc.org/golang.org/x/net/proxy) package, The client depends on Google's [proxy](https://godoc.org/golang.org/x/net/proxy) package and the
also easily installed with the commands: [websockets](https://godoc.org/github.com/gorilla/websocket) package, also easily installed with the commands:
``` ```
go get golang.org/x/net/websocket go get github.com/gorilla/websocket
go get golang.org/x/net/proxy go get golang.org/x/net/proxy
``` ```
@ -35,27 +50,115 @@ Usage and API
------------- -------------
Detailed API documentation is available by using to godoc tool, or can be browsed online Detailed API documentation is available by using to godoc tool, or can be browsed online
using the [godoc.org](http://godoc.org/github.com/eclipse/paho.mqtt.golang) service. using the [pkg.go.dev](https://pkg.go.dev/github.com/eclipse/paho.mqtt.golang) service.
Make use of the library by importing it in your Go client source code. For example,
```
import "github.com/eclipse/paho.mqtt.golang"
```
Samples are available in the `cmd` directory for reference. Samples are available in the `cmd` directory for reference.
Note:
Runtime tracing The library also supports using MQTT over websockets by using the `ws://` (unsecure) or `wss://` (secure) prefix in the
URI. If the client is running behind a corporate http/https proxy then the following environment variables `HTTP_PROXY`,
`HTTPS_PROXY` and `NO_PROXY` are taken into account when establishing the connection.
Troubleshooting
--------------- ---------------
Tracing is enabled by assigning logs (from the Go log package) to the logging endpoints, ERROR, CRITICAL, WARN and DEBUG If you are new to MQTT and your application is not working as expected reviewing the
[MQTT specification](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html), which this library implements,
is a good first step. [MQTT.org](https://mqtt.org) has some [good resources](https://mqtt.org/getting-started/) that answer many
common questions.
### Error Handling
The asynchronous nature of this library makes it easy to forget to check for errors. Consider using a go routine to
log these:
```go
t := client.Publish("topic", qos, retained, msg)
go func() {
_ = t.Wait() // Can also use '<-t.Done()' in releases > 1.2.0
if t.Error() != nil {
log.Error(t.Error()) // Use your preferred logging technique (or just fmt.Printf)
}
}()
```
### Logging
If you are encountering issues then enabling logging, both within this library and on your broker, is a good way to
begin troubleshooting. This library can produce various levels of log by assigning the logging endpoints, ERROR,
CRITICAL, WARN and DEBUG. For example:
```go
func main() {
mqtt.ERROR = log.New(os.Stdout, "[ERROR] ", 0)
mqtt.CRITICAL = log.New(os.Stdout, "[CRIT] ", 0)
mqtt.WARN = log.New(os.Stdout, "[WARN] ", 0)
mqtt.DEBUG = log.New(os.Stdout, "[DEBUG] ", 0)
// Connect, Subscribe, Publish etc..
}
```
### Common Problems
* Seemingly random disconnections may be caused by another client connecting to the broker with the same client
identifier; this is as per the [spec](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc384800405).
* A `MessageHandler` (called when a new message is received) must not block. If you wish to perform a long-running task,
or publish a message, then please use a go routine (blocking in the handler is a common cause of unexpected `pingresp
not received, disconnecting` errors).
* When QOS1+ subscriptions have been created previously and you connect with `CleanSession` set to false it is possible that the broker will deliver retained
messages before `Subscribe` can be called. To process these messages either configure a handler with `AddRoute` or
set a `DefaultPublishHandler`.
* Loss of network connectivity may not be detected immediately. If this is an issue then consider setting
`ClientOptions.KeepAlive` (sends regular messages to check the link is active).
* Brokers offer many configuration options; some settings may lead to unexpected results. If using Mosquitto check
`max_inflight_messages`, `max_queued_messages`, `persistence` (the defaults may not be what you expect).
Reporting bugs Reporting bugs
-------------- --------------
Please report bugs by raising issues for this project in github https://github.com/eclipse/paho.mqtt.golang/issues Please report bugs by raising issues for this project in github https://github.com/eclipse/paho.mqtt.golang/issues
*A limited number of contributors monitor the issues section so if you have a general question please consider the
resources in the [more information](#more-information) section (your question will be seen by more people, and you are
likely to receive an answer more quickly).*
We welcome bug reports, but it is important they are actionable. A significant percentage of issues reported are not
resolved due to a lack of information. If we cannot replicate the problem then it is unlikely we will be able to fix it.
The information required will vary from issue to issue but consider including:
* Which version of the package you are using (tag or commit - this should be in your go.mod file)
* A [Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example). Providing an example
is the best way to demonstrate the issue you are facing; it is important this includes all relevant information
(including broker configuration). Docker (see `cmd/docker`) makes it relatively simple to provide a working end-to-end
example.
* A full, clear, description of the problem (detail what you are expecting vs what actually happens).
* Details of your attempts to resolve the issue (what have you tried, what worked, what did not).
* [Application Logs](#logging) covering the period the issue occurred. Unless you have isolated the root cause of the issue please include a link to a full log (including data from well before the problem arose).
* Broker Logs covering the period the issue occurred.
It is important to remember that this library does not stand alone; it communicates with a broker and any issues you are
seeing may be due to:
* Bugs in your code.
* Bugs in this library.
* The broker configuration.
* Bugs in the broker.
* Issues with whatever you are communicating with.
When submitting an issue, please ensure that you provide sufficient details to enable us to eliminate causes outside of
this library.
Contributing
------------
We welcome pull requests but before your contribution can be accepted by the project, you need to create and
electronically sign the Eclipse Contributor Agreement (ECA) and sign off on the Eclipse Foundation Certificate of Origin.
More information is available in the
[Eclipse Development Resources](http://wiki.eclipse.org/Development_Resources/Contributing_via_Git); please take special
note of the requirement that the commit record contain a "Signed-off-by" entry.
More information More information
---------------- ----------------
@ -65,3 +168,6 @@ Discussion of the Paho clients takes place on the [Eclipse paho-dev mailing list
General questions about the MQTT protocol are discussed in the [MQTT Google Group](https://groups.google.com/forum/?hl=en-US&fromgroups#!forum/mqtt). General questions about the MQTT protocol are discussed in the [MQTT Google Group](https://groups.google.com/forum/?hl=en-US&fromgroups#!forum/mqtt).
There is much more information available via the [MQTT community site](http://mqtt.org). There is much more information available via the [MQTT community site](http://mqtt.org).
[Stack Overflow](https://stackoverflow.com/questions/tagged/mqtt+go) has a range questions covering a range of common
issues (both relating to use of this library and MQTT in general).

File diff suppressed because it is too large Load Diff

View File

@ -28,4 +28,5 @@ const (
TST component = "[test] " TST component = "[test] "
STA component = "[state] " STA component = "[state] "
ERR component = "[error] " ERR component = "[error] "
ROU component = "[router] "
) )

View File

@ -101,7 +101,7 @@ func (store *FileStore) Get(key string) packets.ControlPacket {
store.RLock() store.RLock()
defer store.RUnlock() defer store.RUnlock()
if !store.opened { if !store.opened {
ERROR.Println(STR, "Trying to use file store, but not open") ERROR.Println(STR, "trying to use file store, but not open")
return nil return nil
} }
filepath := fullpath(store.directory, key) filepath := fullpath(store.directory, key)
@ -117,14 +117,16 @@ func (store *FileStore) Get(key string) packets.ControlPacket {
if rerr != nil { if rerr != nil {
newpath := corruptpath(store.directory, key) newpath := corruptpath(store.directory, key)
WARN.Println(STR, "corrupted file detected:", rerr.Error(), "archived at:", newpath) WARN.Println(STR, "corrupted file detected:", rerr.Error(), "archived at:", newpath)
os.Rename(filepath, newpath) if err := os.Rename(filepath, newpath); err != nil {
ERROR.Println(STR, err)
}
return nil return nil
} }
return msg return msg
} }
// All will provide a list of all of the keys associated with messages // All will provide a list of all of the keys associated with messages
// currenly residing in the FileStore. // currently residing in the FileStore.
func (store *FileStore) All() []string { func (store *FileStore) All() []string {
store.RLock() store.RLock()
defer store.RUnlock() defer store.RUnlock()
@ -156,7 +158,7 @@ func (store *FileStore) all() []string {
var files fileInfos var files fileInfos
if !store.opened { if !store.opened {
ERROR.Println(STR, "Trying to use file store, but not open") ERROR.Println(STR, "trying to use file store, but not open")
return nil return nil
} }
@ -166,7 +168,7 @@ func (store *FileStore) all() []string {
for _, f := range files { for _, f := range files {
DEBUG.Println(STR, "file in All():", f.Name()) DEBUG.Println(STR, "file in All():", f.Name())
name := f.Name() name := f.Name()
if name[len(name)-4:len(name)] != msgExt { if name[len(name)-4:] != msgExt {
DEBUG.Println(STR, "skipping file, doesn't have right extension: ", name) DEBUG.Println(STR, "skipping file, doesn't have right extension: ", name)
continue continue
} }
@ -179,7 +181,7 @@ func (store *FileStore) all() []string {
// lockless // lockless
func (store *FileStore) del(key string) { func (store *FileStore) del(key string) {
if !store.opened { if !store.opened {
ERROR.Println(STR, "Trying to use file store, but not open") ERROR.Println(STR, "trying to use file store, but not open")
return return
} }
DEBUG.Println(STR, "store del filepath:", store.directory) DEBUG.Println(STR, "store del filepath:", store.directory)

8
vendor/github.com/eclipse/paho.mqtt.golang/go.mod generated vendored Normal file
View File

@ -0,0 +1,8 @@
module github.com/eclipse/paho.mqtt.golang
go 1.14
require (
github.com/gorilla/websocket v1.4.2
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0
)

8
vendor/github.com/eclipse/paho.mqtt.golang/go.sum generated vendored Normal file
View File

@ -0,0 +1,8 @@
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U=
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -88,7 +88,7 @@ func (store *MemoryStore) All() []string {
ERROR.Println(STR, "Trying to use memory store, but not open") ERROR.Println(STR, "Trying to use memory store, but not open")
return nil return nil
} }
keys := []string{} var keys []string
for k := range store.messages { for k := range store.messages {
keys = append(keys, k) keys = append(keys, k)
} }

View File

@ -16,9 +16,9 @@ package mqtt
import ( import (
"net/url" "net/url"
"sync"
"github.com/eclipse/paho.mqtt.golang/packets" "github.com/eclipse/paho.mqtt.golang/packets"
"sync"
) )
// Message defines the externals that a message implementation must support // Message defines the externals that a message implementation must support
@ -114,7 +114,7 @@ func newConnectMsgFromOptions(options *ClientOptions, broker *url.URL) *packets.
if username != "" { if username != "" {
m.UsernameFlag = true m.UsernameFlag = true
m.Username = username m.Username = username
//mustn't have password without user as well // mustn't have password without user as well
if password != "" { if password != "" {
m.PasswordFlag = true m.PasswordFlag = true
m.Password = []byte(password) m.Password = []byte(password)

View File

@ -28,6 +28,8 @@ type MId uint16
type messageIds struct { type messageIds struct {
sync.RWMutex sync.RWMutex
index map[uint16]tokenCompletor index map[uint16]tokenCompletor
lastIssuedID uint16 // The most recently issued ID. Used so we cycle through ids rather than immediately reusing them (can make debugging easier)
} }
const ( const (
@ -40,11 +42,11 @@ func (mids *messageIds) cleanUp() {
for _, token := range mids.index { for _, token := range mids.index {
switch token.(type) { switch token.(type) {
case *PublishToken: case *PublishToken:
token.setError(fmt.Errorf("Connection lost before Publish completed")) token.setError(fmt.Errorf("connection lost before Publish completed"))
case *SubscribeToken: case *SubscribeToken:
token.setError(fmt.Errorf("Connection lost before Subscribe completed")) token.setError(fmt.Errorf("connection lost before Subscribe completed"))
case *UnsubscribeToken: case *UnsubscribeToken:
token.setError(fmt.Errorf("Connection lost before Unsubscribe completed")) token.setError(fmt.Errorf("connection lost before Unsubscribe completed"))
case nil: case nil:
continue continue
} }
@ -71,18 +73,33 @@ func (mids *messageIds) claimID(token tokenCompletor, id uint16) {
old.flowComplete() old.flowComplete()
mids.index[id] = token mids.index[id] = token
} }
if id > mids.lastIssuedID {
mids.lastIssuedID = id
}
} }
// getID will return an available id or 0 if none available
// The id will generally be the previous id + 1 (because this makes tracing messages a bit simpler)
func (mids *messageIds) getID(t tokenCompletor) uint16 { func (mids *messageIds) getID(t tokenCompletor) uint16 {
mids.Lock() mids.Lock()
defer mids.Unlock() defer mids.Unlock()
for i := midMin; i < midMax; i++ { i := mids.lastIssuedID // note: the only situation where lastIssuedID is 0 the map will be empty
looped := false // uint16 will loop from 65535->0
for {
i++
if i == 0 { // skip 0 because its not a valid id (Control Packets MUST contain a non-zero 16-bit Packet Identifier [MQTT-2.3.1-1])
i++
looped = true
}
if _, ok := mids.index[i]; !ok { if _, ok := mids.index[i]; !ok {
mids.index[i] = t mids.index[i] = t
mids.lastIssuedID = i
return i return i
} }
if (looped && i == mids.lastIssuedID) || (mids.lastIssuedID == 0 && i == midMax) { // lastIssuedID will be 0 at startup
return 0 // no free ids
}
} }
return 0
} }
func (mids *messageIds) getToken(id uint16) tokenCompletor { func (mids *messageIds) getToken(id uint16) tokenCompletor {
@ -98,14 +115,23 @@ type DummyToken struct {
id uint16 id uint16
} }
// Wait implements the Token Wait method.
func (d *DummyToken) Wait() bool { func (d *DummyToken) Wait() bool {
return true return true
} }
// WaitTimeout implements the Token WaitTimeout method.
func (d *DummyToken) WaitTimeout(t time.Duration) bool { func (d *DummyToken) WaitTimeout(t time.Duration) bool {
return true return true
} }
// Done implements the Token Done method.
func (d *DummyToken) Done() <-chan struct{} {
ch := make(chan struct{})
close(ch)
return ch
}
func (d *DummyToken) flowComplete() { func (d *DummyToken) flowComplete() {
ERROR.Printf("A lookup for token %d returned nil\n", d.id) ERROR.Printf("A lookup for token %d returned nil\n", d.id)
} }
@ -115,3 +141,36 @@ func (d *DummyToken) Error() error {
} }
func (d *DummyToken) setError(e error) {} func (d *DummyToken) setError(e error) {}
// PlaceHolderToken does nothing and was implemented to allow a messageid to be reserved
// it differs from DummyToken in that calling flowComplete does not generate an error (it
// is expected that flowComplete will be called when the token is overwritten with a real token)
type PlaceHolderToken struct {
id uint16
}
// Wait implements the Token Wait method.
func (p *PlaceHolderToken) Wait() bool {
return true
}
// WaitTimeout implements the Token WaitTimeout method.
func (p *PlaceHolderToken) WaitTimeout(t time.Duration) bool {
return true
}
// Done implements the Token Done method.
func (p *PlaceHolderToken) Done() <-chan struct{} {
ch := make(chan struct{})
close(ch)
return ch
}
func (p *PlaceHolderToken) flowComplete() {
}
func (p *PlaceHolderToken) Error() error {
return nil
}
func (p *PlaceHolderToken) setError(e error) {}

View File

@ -15,341 +15,450 @@
package mqtt package mqtt
import ( import (
"crypto/tls"
"errors" "errors"
"fmt" "io"
"net" "net"
"net/http"
"net/url"
"os"
"reflect" "reflect"
"sync/atomic" "strings"
"sync"
"time" "time"
"github.com/eclipse/paho.mqtt.golang/packets" "github.com/eclipse/paho.mqtt.golang/packets"
"golang.org/x/net/proxy"
"golang.org/x/net/websocket"
) )
func signalError(c chan<- error, err error) { const closedNetConnErrorText = "use of closed network connection" // error string for closed conn (https://golang.org/src/net/error_test.go)
select {
case c <- err: // ConnectMQTT takes a connected net.Conn and performs the initial MQTT handshake. Parameters are:
// conn - Connected net.Conn
// cm - Connect Packet with everything other than the protocol name/version populated (historical reasons)
// protocolVersion - The protocol version to attempt to connect with
//
// Note that, for backward compatibility, ConnectMQTT() suppresses the actual connection error (compare to connectMQTT()).
func ConnectMQTT(conn net.Conn, cm *packets.ConnectPacket, protocolVersion uint) (byte, bool) {
rc, sessionPresent, _ := connectMQTT(conn, cm, protocolVersion)
return rc, sessionPresent
}
func connectMQTT(conn io.ReadWriter, cm *packets.ConnectPacket, protocolVersion uint) (byte, bool, error) {
switch protocolVersion {
case 3:
DEBUG.Println(CLI, "Using MQTT 3.1 protocol")
cm.ProtocolName = "MQIsdp"
cm.ProtocolVersion = 3
case 0x83:
DEBUG.Println(CLI, "Using MQTT 3.1b protocol")
cm.ProtocolName = "MQIsdp"
cm.ProtocolVersion = 0x83
case 0x84:
DEBUG.Println(CLI, "Using MQTT 3.1.1b protocol")
cm.ProtocolName = "MQTT"
cm.ProtocolVersion = 0x84
default: default:
DEBUG.Println(CLI, "Using MQTT 3.1.1 protocol")
cm.ProtocolName = "MQTT"
cm.ProtocolVersion = 4
} }
if err := cm.Write(conn); err != nil {
ERROR.Println(CLI, err)
return packets.ErrNetworkError, false, err
}
rc, sessionPresent, err := verifyCONNACK(conn)
return rc, sessionPresent, err
} }
func openConnection(uri *url.URL, tlsc *tls.Config, timeout time.Duration, headers http.Header) (net.Conn, error) { // This function is only used for receiving a connack
switch uri.Scheme { // when the connection is first started.
case "ws": // This prevents receiving incoming data while resume
config, _ := websocket.NewConfig(uri.String(), fmt.Sprintf("http://%s", uri.Host)) // is in progress if clean session is false.
config.Protocol = []string{"mqtt"} func verifyCONNACK(conn io.Reader) (byte, bool, error) {
config.Header = headers DEBUG.Println(NET, "connect started")
config.Dialer = &net.Dialer{Timeout: timeout}
conn, err := websocket.DialConfig(config)
if err != nil {
return nil, err
}
conn.PayloadType = websocket.BinaryFrame
return conn, err
case "wss":
config, _ := websocket.NewConfig(uri.String(), fmt.Sprintf("https://%s", uri.Host))
config.Protocol = []string{"mqtt"}
config.TlsConfig = tlsc
config.Header = headers
config.Dialer = &net.Dialer{Timeout: timeout}
conn, err := websocket.DialConfig(config)
if err != nil {
return nil, err
}
conn.PayloadType = websocket.BinaryFrame
return conn, err
case "tcp":
allProxy := os.Getenv("all_proxy")
if len(allProxy) == 0 {
conn, err := net.DialTimeout("tcp", uri.Host, timeout)
if err != nil {
return nil, err
}
return conn, nil
}
proxyDialer := proxy.FromEnvironment()
conn, err := proxyDialer.Dial("tcp", uri.Host) ca, err := packets.ReadPacket(conn)
if err != nil { if err != nil {
return nil, err ERROR.Println(NET, "connect got error", err)
} return packets.ErrNetworkError, false, err
return conn, nil
case "unix":
conn, err := net.DialTimeout("unix", uri.Host, timeout)
if err != nil {
return nil, err
}
return conn, nil
case "ssl":
fallthrough
case "tls":
fallthrough
case "tcps":
allProxy := os.Getenv("all_proxy")
if len(allProxy) == 0 {
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: timeout}, "tcp", uri.Host, tlsc)
if err != nil {
return nil, err
}
return conn, nil
}
proxyDialer := proxy.FromEnvironment()
conn, err := proxyDialer.Dial("tcp", uri.Host)
if err != nil {
return nil, err
}
tlsConn := tls.Client(conn, tlsc)
err = tlsConn.Handshake()
if err != nil {
conn.Close()
return nil, err
}
return tlsConn, nil
} }
return nil, errors.New("Unknown protocol")
if ca == nil {
ERROR.Println(NET, "received nil packet")
return packets.ErrNetworkError, false, errors.New("nil CONNACK packet")
}
msg, ok := ca.(*packets.ConnackPacket)
if !ok {
ERROR.Println(NET, "received msg that was not CONNACK")
return packets.ErrNetworkError, false, errors.New("non-CONNACK first packet received")
}
DEBUG.Println(NET, "received connack")
return msg.ReturnCode, msg.SessionPresent, nil
} }
// actually read incoming messages off the wire // inbound encapsulates the output from startIncoming.
// send Message object into ibound channel // err - If != nil then an error has occurred
func incoming(c *client) { // cp - A control packet received over the network link
type inbound struct {
err error
cp packets.ControlPacket
}
// startIncoming initiates a goroutine that reads incoming messages off the wire and sends them to the channel (returned).
// If there are any issues with the network connection then the returned channel will be closed and the goroutine will exit
// (so closing the connection will terminate the goroutine)
func startIncoming(conn io.Reader) <-chan inbound {
var err error var err error
var cp packets.ControlPacket var cp packets.ControlPacket
ibound := make(chan inbound)
defer c.workers.Done()
DEBUG.Println(NET, "incoming started") DEBUG.Println(NET, "incoming started")
for { go func() {
if cp, err = packets.ReadPacket(c.conn); err != nil { for {
break if cp, err = packets.ReadPacket(conn); err != nil {
} // We do not want to log the error if it is due to the network connection having been closed
DEBUG.Println(NET, "Received Message") // elsewhere (i.e. after sending DisconnectPacket). Detecting this situation is the subject of
select { // https://github.com/golang/go/issues/4373
case c.ibound <- cp: if !strings.Contains(err.Error(), closedNetConnErrorText) {
// Notify keepalive logic that we recently received a packet ibound <- inbound{err: err}
if c.options.KeepAlive != 0 {
c.lastReceived.Store(time.Now())
}
case <-c.stop:
// This avoids a deadlock should a message arrive while shutting down.
// In that case the "reader" of c.ibound might already be gone
WARN.Println(NET, "incoming dropped a received message during shutdown")
break
}
}
// We received an error on read.
// If disconnect is in progress, swallow error and return
select {
case <-c.stop:
DEBUG.Println(NET, "incoming stopped")
return
// Not trying to disconnect, send the error to the errors channel
default:
ERROR.Println(NET, "incoming stopped with error", err)
signalError(c.errors, err)
return
}
}
// receive a Message object on obound, and then
// actually send outgoing message to the wire
func outgoing(c *client) {
defer c.workers.Done()
DEBUG.Println(NET, "outgoing started")
for {
DEBUG.Println(NET, "outgoing waiting for an outbound message")
select {
case <-c.stop:
DEBUG.Println(NET, "outgoing stopped")
return
case pub := <-c.obound:
msg := pub.p.(*packets.PublishPacket)
if c.options.WriteTimeout > 0 {
c.conn.SetWriteDeadline(time.Now().Add(c.options.WriteTimeout))
}
if err := msg.Write(c.conn); err != nil {
ERROR.Println(NET, "outgoing stopped with error", err)
pub.t.setError(err)
signalError(c.errors, err)
return
}
if c.options.WriteTimeout > 0 {
// If we successfully wrote, we don't want the timeout to happen during an idle period
// so we reset it to infinite.
c.conn.SetWriteDeadline(time.Time{})
}
if msg.Qos == 0 {
pub.t.flowComplete()
}
DEBUG.Println(NET, "obound wrote msg, id:", msg.MessageID)
case msg := <-c.oboundP:
switch msg.p.(type) {
case *packets.SubscribePacket:
msg.p.(*packets.SubscribePacket).MessageID = c.getID(msg.t)
case *packets.UnsubscribePacket:
msg.p.(*packets.UnsubscribePacket).MessageID = c.getID(msg.t)
}
DEBUG.Println(NET, "obound priority msg to write, type", reflect.TypeOf(msg.p))
if err := msg.p.Write(c.conn); err != nil {
ERROR.Println(NET, "outgoing stopped with error", err)
if msg.t != nil {
msg.t.setError(err)
} }
signalError(c.errors, err) close(ibound)
return DEBUG.Println(NET, "incoming complete")
}
switch msg.p.(type) {
case *packets.DisconnectPacket:
msg.t.(*DisconnectToken).flowComplete()
DEBUG.Println(NET, "outbound wrote disconnect, stopping")
return return
} }
DEBUG.Println(NET, "startIncoming Received Message")
ibound <- inbound{cp: cp}
} }
// Reset ping timer after sending control packet. }()
if c.options.KeepAlive != 0 {
c.lastSent.Store(time.Now()) return ibound
}
}
} }
// receive Message objects on ibound // incomingComms encapsulates the possible output of the incomingComms routine. If err != nil then an error has occurred and
// store messages if necessary // the routine will have terminated; otherwise one of the other members should be non-nil
// send replies on obound type incomingComms struct {
// delete messages from store if necessary err error // If non-nil then there has been an error (ignore everything else)
func alllogic(c *client) { outbound *PacketAndToken // Packet (with token) than needs to be sent out (e.g. an acknowledgement)
defer c.workers.Done() incomingPub *packets.PublishPacket // A new publish has been received; this will need to be passed on to our user
DEBUG.Println(NET, "logic started") }
for { // startIncomingComms initiates incoming communications; this includes starting a goroutine to process incoming
DEBUG.Println(NET, "logic waiting for msg on ibound") // messages.
// Accepts a channel of inbound messages from the store (persisted messages); note this must be closed as soon as the
// everything in the store has been sent.
// Returns a channel that will be passed any received packets; this will be closed on a network error (and inboundFromStore closed)
func startIncomingComms(conn io.Reader,
c commsFns,
inboundFromStore <-chan packets.ControlPacket,
) <-chan incomingComms {
ibound := startIncoming(conn) // Start goroutine that reads from network connection
output := make(chan incomingComms)
DEBUG.Println(NET, "startIncomingComms started")
go func() {
for {
if inboundFromStore == nil && ibound == nil {
close(output)
DEBUG.Println(NET, "startIncomingComms goroutine complete")
return // As soon as ibound is closed we can exit (should have already processed an error)
}
DEBUG.Println(NET, "logic waiting for msg on ibound")
var msg packets.ControlPacket
var ok bool
select {
case msg, ok = <-inboundFromStore:
if !ok {
DEBUG.Println(NET, "startIncomingComms: inboundFromStore complete")
inboundFromStore = nil // should happen quickly as this is only for persisted messages
continue
}
DEBUG.Println(NET, "startIncomingComms: got msg from store")
case ibMsg, ok := <-ibound:
if !ok {
DEBUG.Println(NET, "startIncomingComms: ibound complete")
ibound = nil
continue
}
DEBUG.Println(NET, "startIncomingComms: got msg on ibound")
// If the inbound comms routine encounters any issues it will send us an error.
if ibMsg.err != nil {
output <- incomingComms{err: ibMsg.err}
continue // Usually the channel will be closed immediately after sending an error but safer that we do not assume this
}
msg = ibMsg.cp
c.persistInbound(msg)
c.UpdateLastReceived() // Notify keepalive logic that we recently received a packet
}
select {
case msg := <-c.ibound:
DEBUG.Println(NET, "logic got msg on ibound")
persistInbound(c.persist, msg)
switch m := msg.(type) { switch m := msg.(type) {
case *packets.PingrespPacket: case *packets.PingrespPacket:
DEBUG.Println(NET, "received pingresp") DEBUG.Println(NET, "startIncomingComms: received pingresp")
atomic.StoreInt32(&c.pingOutstanding, 0) c.pingRespReceived()
case *packets.SubackPacket: case *packets.SubackPacket:
DEBUG.Println(NET, "received suback, id:", m.MessageID) DEBUG.Println(NET, "startIncomingComms: received suback, id:", m.MessageID)
token := c.getToken(m.MessageID) token := c.getToken(m.MessageID)
switch t := token.(type) {
case *SubscribeToken: if t, ok := token.(*SubscribeToken); ok {
DEBUG.Println(NET, "granted qoss", m.ReturnCodes) DEBUG.Println(NET, "startIncomingComms: granted qoss", m.ReturnCodes)
for i, qos := range m.ReturnCodes { for i, qos := range m.ReturnCodes {
t.subResult[t.subs[i]] = qos t.subResult[t.subs[i]] = qos
} }
} }
token.flowComplete() token.flowComplete()
c.freeID(m.MessageID) c.freeID(m.MessageID)
case *packets.UnsubackPacket: case *packets.UnsubackPacket:
DEBUG.Println(NET, "received unsuback, id:", m.MessageID) DEBUG.Println(NET, "startIncomingComms: received unsuback, id:", m.MessageID)
c.getToken(m.MessageID).flowComplete() c.getToken(m.MessageID).flowComplete()
c.freeID(m.MessageID) c.freeID(m.MessageID)
case *packets.PublishPacket: case *packets.PublishPacket:
DEBUG.Println(NET, "received publish, msgId:", m.MessageID) DEBUG.Println(NET, "startIncomingComms: received publish, msgId:", m.MessageID)
DEBUG.Println(NET, "putting msg on onPubChan") output <- incomingComms{incomingPub: m}
switch m.Qos {
case 2:
c.incomingPubChan <- m
DEBUG.Println(NET, "done putting msg on incomingPubChan")
case 1:
c.incomingPubChan <- m
DEBUG.Println(NET, "done putting msg on incomingPubChan")
case 0:
select {
case c.incomingPubChan <- m:
case <-c.stop:
}
DEBUG.Println(NET, "done putting msg on incomingPubChan")
}
case *packets.PubackPacket: case *packets.PubackPacket:
DEBUG.Println(NET, "received puback, id:", m.MessageID) DEBUG.Println(NET, "startIncomingComms: received puback, id:", m.MessageID)
// c.receipts.get(msg.MsgId()) <- Receipt{}
// c.receipts.end(msg.MsgId())
c.getToken(m.MessageID).flowComplete() c.getToken(m.MessageID).flowComplete()
c.freeID(m.MessageID) c.freeID(m.MessageID)
case *packets.PubrecPacket: case *packets.PubrecPacket:
DEBUG.Println(NET, "received pubrec, id:", m.MessageID) DEBUG.Println(NET, "startIncomingComms: received pubrec, id:", m.MessageID)
prel := packets.NewControlPacket(packets.Pubrel).(*packets.PubrelPacket) prel := packets.NewControlPacket(packets.Pubrel).(*packets.PubrelPacket)
prel.MessageID = m.MessageID prel.MessageID = m.MessageID
select { output <- incomingComms{outbound: &PacketAndToken{p: prel, t: nil}}
case c.oboundP <- &PacketAndToken{p: prel, t: nil}:
case <-c.stop:
}
case *packets.PubrelPacket: case *packets.PubrelPacket:
DEBUG.Println(NET, "received pubrel, id:", m.MessageID) DEBUG.Println(NET, "startIncomingComms: received pubrel, id:", m.MessageID)
pc := packets.NewControlPacket(packets.Pubcomp).(*packets.PubcompPacket) pc := packets.NewControlPacket(packets.Pubcomp).(*packets.PubcompPacket)
pc.MessageID = m.MessageID pc.MessageID = m.MessageID
persistOutbound(c.persist, pc) c.persistOutbound(pc)
select { output <- incomingComms{outbound: &PacketAndToken{p: pc, t: nil}}
case c.oboundP <- &PacketAndToken{p: pc, t: nil}:
case <-c.stop:
}
case *packets.PubcompPacket: case *packets.PubcompPacket:
DEBUG.Println(NET, "received pubcomp, id:", m.MessageID) DEBUG.Println(NET, "startIncomingComms: received pubcomp, id:", m.MessageID)
c.getToken(m.MessageID).flowComplete() c.getToken(m.MessageID).flowComplete()
c.freeID(m.MessageID) c.freeID(m.MessageID)
} }
case <-c.stop:
WARN.Println(NET, "logic stopped")
return
} }
} }()
return output
} }
func (c *client) ackFunc(packet *packets.PublishPacket) func() { // startOutgoingComms initiates a go routine to transmit outgoing packets.
// Pass in an open network connection and channels for outbound messages (including those triggered
// directly from incoming comms).
// Returns a channel that will receive details of any errors (closed when the goroutine exits)
// This function wil only terminate when all input channels are closed
func startOutgoingComms(conn net.Conn,
c commsFns,
oboundp <-chan *PacketAndToken,
obound <-chan *PacketAndToken,
oboundFromIncoming <-chan *PacketAndToken,
) <-chan error {
errChan := make(chan error)
DEBUG.Println(NET, "outgoing started")
go func() {
for {
DEBUG.Println(NET, "outgoing waiting for an outbound message")
// This goroutine will only exits when all of the input channels we receive on have been closed. This approach is taken to avoid any
// deadlocks (if the connection goes down there are limited options as to what we can do with anything waiting on us and
// throwing away the packets seems the best option)
if oboundp == nil && obound == nil && oboundFromIncoming == nil {
DEBUG.Println(NET, "outgoing comms stopping")
close(errChan)
return
}
select {
case pub, ok := <-obound:
if !ok {
obound = nil
continue
}
msg := pub.p.(*packets.PublishPacket)
DEBUG.Println(NET, "obound msg to write", msg.MessageID)
writeTimeout := c.getWriteTimeOut()
if writeTimeout > 0 {
if err := conn.SetWriteDeadline(time.Now().Add(writeTimeout)); err != nil {
ERROR.Println(NET, "SetWriteDeadline ", err)
}
}
if err := msg.Write(conn); err != nil {
ERROR.Println(NET, "outgoing obound reporting error ", err)
pub.t.setError(err)
// report error if it's not due to the connection being closed elsewhere
if !strings.Contains(err.Error(), closedNetConnErrorText) {
errChan <- err
}
continue
}
if writeTimeout > 0 {
// If we successfully wrote, we don't want the timeout to happen during an idle period
// so we reset it to infinite.
if err := conn.SetWriteDeadline(time.Time{}); err != nil {
ERROR.Println(NET, "SetWriteDeadline to 0 ", err)
}
}
if msg.Qos == 0 {
pub.t.flowComplete()
}
DEBUG.Println(NET, "obound wrote msg, id:", msg.MessageID)
case msg, ok := <-oboundp:
if !ok {
oboundp = nil
continue
}
DEBUG.Println(NET, "obound priority msg to write, type", reflect.TypeOf(msg.p))
if err := msg.p.Write(conn); err != nil {
ERROR.Println(NET, "outgoing oboundp reporting error ", err)
if msg.t != nil {
msg.t.setError(err)
}
errChan <- err
continue
}
if _, ok := msg.p.(*packets.DisconnectPacket); ok {
msg.t.(*DisconnectToken).flowComplete()
DEBUG.Println(NET, "outbound wrote disconnect, closing connection")
// As per the MQTT spec "After sending a DISCONNECT Packet the Client MUST close the Network Connection"
// Closing the connection will cause the goroutines to end in sequence (starting with incoming comms)
conn.Close()
}
case msg, ok := <-oboundFromIncoming: // message triggered by an inbound message (PubrecPacket or PubrelPacket)
if !ok {
oboundFromIncoming = nil
continue
}
DEBUG.Println(NET, "obound from incoming msg to write, type", reflect.TypeOf(msg.p), " ID ", msg.p.Details().MessageID)
if err := msg.p.Write(conn); err != nil {
ERROR.Println(NET, "outgoing oboundFromIncoming reporting error", err)
if msg.t != nil {
msg.t.setError(err)
}
errChan <- err
continue
}
}
c.UpdateLastSent() // Record that a packet has been received (for keepalive routine)
}
}()
return errChan
}
// commsFns provide access to the client state (messageids, requesting disconnection and updating timing)
type commsFns interface {
getToken(id uint16) tokenCompletor // Retrieve the token for the specified messageid (if none then a dummy token must be returned)
freeID(id uint16) // Release the specified messageid (clearing out of any persistent store)
UpdateLastReceived() // Must be called whenever a packet is received
UpdateLastSent() // Must be called whenever a packet is successfully sent
getWriteTimeOut() time.Duration // Return the writetimeout (or 0 if none)
persistOutbound(m packets.ControlPacket) // add the packet to the outbound store
persistInbound(m packets.ControlPacket) // add the packet to the inbound store
pingRespReceived() // Called when a ping response is received
}
// startComms initiates goroutines that handles communications over the network connection
// Messages will be stored (via commsFns) and deleted from the store as necessary
// It returns two channels:
// packets.PublishPacket - Will receive publish packets received over the network.
// Closed when incoming comms routines exit (on shutdown or if network link closed)
// error - Any errors will be sent on this channel. The channel is closed when all comms routines have shut down
//
// Note: The comms routines monitoring oboundp and obound will not shutdown until those channels are both closed. Any messages received between the
// connection being closed and those channels being closed will generate errors (and nothing will be sent). That way the chance of a deadlock is
// minimised.
func startComms(conn net.Conn, // Network connection (must be active)
c commsFns, // getters and setters to enable us to cleanly interact with client
inboundFromStore <-chan packets.ControlPacket, // Inbound packets from the persistence store (should be closed relatively soon after startup)
oboundp <-chan *PacketAndToken,
obound <-chan *PacketAndToken) (
<-chan *packets.PublishPacket, // Publishpackages received over the network
<-chan error, // Any errors (should generally trigger a disconnect)
) {
// Start inbound comms handler; this needs to be able to transmit messages so we start a go routine to add these to the priority outbound channel
ibound := startIncomingComms(conn, c, inboundFromStore)
outboundFromIncoming := make(chan *PacketAndToken) // Will accept outgoing messages triggered by startIncomingComms (e.g. acknowledgements)
// Start the outgoing handler. It is important to note that output from startIncomingComms is fed into startOutgoingComms (for ACK's)
oboundErr := startOutgoingComms(conn, c, oboundp, obound, outboundFromIncoming)
DEBUG.Println(NET, "startComms started")
// Run up go routines to handle the output from the above comms functions - these are handled in separate
// go routines because they can interact (e.g. ibound triggers an ACK to obound which triggers an error)
var wg sync.WaitGroup
wg.Add(2)
outPublish := make(chan *packets.PublishPacket)
outError := make(chan error)
// Any messages received get passed to the appropriate channel
go func() {
for ic := range ibound {
if ic.err != nil {
outError <- ic.err
continue
}
if ic.outbound != nil {
outboundFromIncoming <- ic.outbound
continue
}
if ic.incomingPub != nil {
outPublish <- ic.incomingPub
continue
}
ERROR.Println(STR, "startComms received empty incomingComms msg")
}
// Close channels that will not be written to again (allowing other routines to exit)
close(outboundFromIncoming)
close(outPublish)
wg.Done()
}()
// Any errors will be passed out to our caller
go func() {
for err := range oboundErr {
outError <- err
}
wg.Done()
}()
// outError is used by both routines so can only be closed when they are both complete
go func() {
wg.Wait()
close(outError)
DEBUG.Println(NET, "startComms closing outError")
}()
return outPublish, outError
}
// ackFunc acknowledges a packet
// WARNING the function returned must not be called if the comms routine is shutting down or not running
// (it needs outgoing comms in order to send the acknowledgement). Currently this is only called from
// matchAndDispatch which will be shutdown before the comms are
func ackFunc(oboundP chan *PacketAndToken, persist Store, packet *packets.PublishPacket) func() {
return func() { return func() {
switch packet.Qos { switch packet.Qos {
case 2: case 2:
pr := packets.NewControlPacket(packets.Pubrec).(*packets.PubrecPacket) pr := packets.NewControlPacket(packets.Pubrec).(*packets.PubrecPacket)
pr.MessageID = packet.MessageID pr.MessageID = packet.MessageID
DEBUG.Println(NET, "putting pubrec msg on obound") DEBUG.Println(NET, "putting pubrec msg on obound")
select { oboundP <- &PacketAndToken{p: pr, t: nil}
case c.oboundP <- &PacketAndToken{p: pr, t: nil}:
case <-c.stop:
}
DEBUG.Println(NET, "done putting pubrec msg on obound") DEBUG.Println(NET, "done putting pubrec msg on obound")
case 1: case 1:
pa := packets.NewControlPacket(packets.Puback).(*packets.PubackPacket) pa := packets.NewControlPacket(packets.Puback).(*packets.PubackPacket)
pa.MessageID = packet.MessageID pa.MessageID = packet.MessageID
DEBUG.Println(NET, "putting puback msg on obound") DEBUG.Println(NET, "putting puback msg on obound")
persistOutbound(c.persist, pa) persistOutbound(persist, pa)
select { oboundP <- &PacketAndToken{p: pa, t: nil}
case c.oboundP <- &PacketAndToken{p: pa, t: nil}:
case <-c.stop:
}
DEBUG.Println(NET, "done putting puback msg on obound") DEBUG.Println(NET, "done putting puback msg on obound")
case 0: case 0:
// do nothing, since there is no need to send an ack packet back // do nothing, since there is no need to send an ack packet back
} }
} }
} }
func errorWatch(c *client) {
defer c.workers.Done()
select {
case <-c.stop:
WARN.Println(NET, "errorWatch stopped")
return
case err := <-c.errors:
ERROR.Println(NET, "error triggered, stopping")
go c.internalConnLost(err)
return
}
}

91
vendor/github.com/eclipse/paho.mqtt.golang/netconn.go generated vendored Normal file
View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2013 IBM Corp.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Seth Hoenig
* Allan Stockdill-Mander
* Mike Robertson
*/
package mqtt
import (
"crypto/tls"
"errors"
"net"
"net/http"
"net/url"
"os"
"time"
"golang.org/x/net/proxy"
)
//
// This just establishes the network connection; once established the type of connection should be irrelevant
//
// openConnection opens a network connection using the protocol indicated in the URL. Does not carry out any MQTT specific handshakes
func openConnection(uri *url.URL, tlsc *tls.Config, timeout time.Duration, headers http.Header, websocketOptions *WebsocketOptions) (net.Conn, error) {
switch uri.Scheme {
case "ws":
conn, err := NewWebsocket(uri.String(), nil, timeout, headers, websocketOptions)
return conn, err
case "wss":
conn, err := NewWebsocket(uri.String(), tlsc, timeout, headers, websocketOptions)
return conn, err
case "mqtt", "tcp":
allProxy := os.Getenv("all_proxy")
if len(allProxy) == 0 {
conn, err := net.DialTimeout("tcp", uri.Host, timeout)
if err != nil {
return nil, err
}
return conn, nil
}
proxyDialer := proxy.FromEnvironment()
conn, err := proxyDialer.Dial("tcp", uri.Host)
if err != nil {
return nil, err
}
return conn, nil
case "unix":
conn, err := net.DialTimeout("unix", uri.Host, timeout)
if err != nil {
return nil, err
}
return conn, nil
case "ssl", "tls", "mqtts", "mqtt+ssl", "tcps":
allProxy := os.Getenv("all_proxy")
if len(allProxy) == 0 {
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: timeout}, "tcp", uri.Host, tlsc)
if err != nil {
return nil, err
}
return conn, nil
}
proxyDialer := proxy.FromEnvironment()
conn, err := proxyDialer.Dial("tcp", uri.Host)
if err != nil {
return nil, err
}
tlsConn := tls.Client(conn, tlsc)
err = tlsConn.Handshake()
if err != nil {
conn.Close()
return nil, err
}
return tlsConn, nil
}
return nil, errors.New("unknown protocol")
}

View File

@ -10,6 +10,7 @@
* Seth Hoenig * Seth Hoenig
* Allan Stockdill-Mander * Allan Stockdill-Mander
* Mike Robertson * Mike Robertson
* Måns Ansgariusson
*/ */
// Portions copyright © 2018 TIBCO Software Inc. // Portions copyright © 2018 TIBCO Software Inc.
@ -20,6 +21,7 @@ import (
"crypto/tls" "crypto/tls"
"net/http" "net/http"
"net/url" "net/url"
"regexp"
"strings" "strings"
"time" "time"
) )
@ -44,6 +46,10 @@ type ConnectionLostHandler func(Client, error)
// at initial connection and on reconnection // at initial connection and on reconnection
type OnConnectHandler func(Client) type OnConnectHandler func(Client)
// ReconnectHandler is invoked prior to reconnecting after
// the initial connection is lost
type ReconnectHandler func(Client, *ClientOptions)
// ClientOptions contains configurable options for an Client. // ClientOptions contains configurable options for an Client.
type ClientOptions struct { type ClientOptions struct {
Servers []*url.URL Servers []*url.URL
@ -66,14 +72,18 @@ type ClientOptions struct {
ConnectTimeout time.Duration ConnectTimeout time.Duration
MaxReconnectInterval time.Duration MaxReconnectInterval time.Duration
AutoReconnect bool AutoReconnect bool
ConnectRetryInterval time.Duration
ConnectRetry bool
Store Store Store Store
DefaultPublishHandler MessageHandler DefaultPublishHandler MessageHandler
OnConnect OnConnectHandler OnConnect OnConnectHandler
OnConnectionLost ConnectionLostHandler OnConnectionLost ConnectionLostHandler
OnReconnecting ReconnectHandler
WriteTimeout time.Duration WriteTimeout time.Duration
MessageChannelDepth uint MessageChannelDepth uint
ResumeSubs bool ResumeSubs bool
HTTPHeaders http.Header HTTPHeaders http.Header
WebsocketOptions *WebsocketOptions
} }
// NewClientOptions will create a new ClientClientOptions type with some // NewClientOptions will create a new ClientClientOptions type with some
@ -105,13 +115,15 @@ func NewClientOptions() *ClientOptions {
ConnectTimeout: 30 * time.Second, ConnectTimeout: 30 * time.Second,
MaxReconnectInterval: 10 * time.Minute, MaxReconnectInterval: 10 * time.Minute,
AutoReconnect: true, AutoReconnect: true,
ConnectRetryInterval: 30 * time.Second,
ConnectRetry: false,
Store: nil, Store: nil,
OnConnect: nil, OnConnect: nil,
OnConnectionLost: DefaultConnectionLostHandler, OnConnectionLost: DefaultConnectionLostHandler,
WriteTimeout: 0, // 0 represents timeout disabled WriteTimeout: 0, // 0 represents timeout disabled
MessageChannelDepth: 100,
ResumeSubs: false, ResumeSubs: false,
HTTPHeaders: make(map[string][]string), HTTPHeaders: make(map[string][]string),
WebsocketOptions: &WebsocketOptions{},
} }
return o return o
} }
@ -125,12 +137,14 @@ func NewClientOptions() *ClientOptions {
// //
// An example broker URI would look like: tcp://foobar.com:1883 // An example broker URI would look like: tcp://foobar.com:1883
func (o *ClientOptions) AddBroker(server string) *ClientOptions { func (o *ClientOptions) AddBroker(server string) *ClientOptions {
re := regexp.MustCompile(`%(25)?`)
if len(server) > 0 && server[0] == ':' { if len(server) > 0 && server[0] == ':' {
server = "127.0.0.1" + server server = "127.0.0.1" + server
} }
if !strings.Contains(server, "://") { if !strings.Contains(server, "://") {
server = "tcp://" + server server = "tcp://" + server
} }
server = re.ReplaceAllLiteralString(server, "%25")
brokerURI, err := url.Parse(server) brokerURI, err := url.Parse(server)
if err != nil { if err != nil {
ERROR.Println(CLI, "Failed to parse %q broker address: %s", server, err) ERROR.Println(CLI, "Failed to parse %q broker address: %s", server, err)
@ -149,7 +163,7 @@ func (o *ClientOptions) SetResumeSubs(resume bool) *ClientOptions {
// SetClientID will set the client id to be used by this client when // SetClientID will set the client id to be used by this client when
// connecting to the MQTT broker. According to the MQTT v3.1 specification, // connecting to the MQTT broker. According to the MQTT v3.1 specification,
// a client id mus be no longer than 23 characters. // a client id must be no longer than 23 characters.
func (o *ClientOptions) SetClientID(id string) *ClientOptions { func (o *ClientOptions) SetClientID(id string) *ClientOptions {
o.ClientID = id o.ClientID = id
return o return o
@ -157,7 +171,7 @@ func (o *ClientOptions) SetClientID(id string) *ClientOptions {
// SetUsername will set the username to be used by this client when connecting // SetUsername will set the username to be used by this client when connecting
// to the MQTT broker. Note: without the use of SSL/TLS, this information will // to the MQTT broker. Note: without the use of SSL/TLS, this information will
// be sent in plaintext accross the wire. // be sent in plaintext across the wire.
func (o *ClientOptions) SetUsername(u string) *ClientOptions { func (o *ClientOptions) SetUsername(u string) *ClientOptions {
o.Username = u o.Username = u
return o return o
@ -165,7 +179,7 @@ func (o *ClientOptions) SetUsername(u string) *ClientOptions {
// SetPassword will set the password to be used by this client when connecting // SetPassword will set the password to be used by this client when connecting
// to the MQTT broker. Note: without the use of SSL/TLS, this information will // to the MQTT broker. Note: without the use of SSL/TLS, this information will
// be sent in plaintext accross the wire. // be sent in plaintext across the wire.
func (o *ClientOptions) SetPassword(p string) *ClientOptions { func (o *ClientOptions) SetPassword(p string) *ClientOptions {
o.Password = p o.Password = p
return o return o
@ -174,7 +188,7 @@ func (o *ClientOptions) SetPassword(p string) *ClientOptions {
// SetCredentialsProvider will set a method to be called by this client when // SetCredentialsProvider will set a method to be called by this client when
// connecting to the MQTT broker that provide the current username and password. // connecting to the MQTT broker that provide the current username and password.
// Note: without the use of SSL/TLS, this information will be sent // Note: without the use of SSL/TLS, this information will be sent
// in plaintext accross the wire. // in plaintext across the wire.
func (o *ClientOptions) SetCredentialsProvider(p CredentialsProvider) *ClientOptions { func (o *ClientOptions) SetCredentialsProvider(p CredentialsProvider) *ClientOptions {
o.CredentialsProvider = p o.CredentialsProvider = p
return o return o
@ -184,7 +198,7 @@ func (o *ClientOptions) SetCredentialsProvider(p CredentialsProvider) *ClientOpt
// when this client connects to an MQTT broker. By setting this flag, you are // when this client connects to an MQTT broker. By setting this flag, you are
// indicating that no messages saved by the broker for this client should be // indicating that no messages saved by the broker for this client should be
// delivered. Any messages that were going to be sent by this client before // delivered. Any messages that were going to be sent by this client before
// diconnecting previously but didn't will not be sent upon connecting to the // disconnecting previously but didn't will not be sent upon connecting to the
// broker. // broker.
func (o *ClientOptions) SetCleanSession(clean bool) *ClientOptions { func (o *ClientOptions) SetCleanSession(clean bool) *ClientOptions {
o.CleanSession = clean o.CleanSession = clean
@ -195,6 +209,7 @@ func (o *ClientOptions) SetCleanSession(clean bool) *ClientOptions {
// each QoS level. By default, this value is true. If set to false, // each QoS level. By default, this value is true. If set to false,
// this flag indicates that messages can be delivered asynchronously // this flag indicates that messages can be delivered asynchronously
// from the client to the application and possibly arrive out of order. // from the client to the application and possibly arrive out of order.
// Specifically, the message handler is called in its own go routine.
func (o *ClientOptions) SetOrderMatters(order bool) *ClientOptions { func (o *ClientOptions) SetOrderMatters(order bool) *ClientOptions {
o.Order = order o.Order = order
return o return o
@ -293,15 +308,22 @@ func (o *ClientOptions) SetConnectionLostHandler(onLost ConnectionLostHandler) *
return o return o
} }
// SetReconnectingHandler sets the OnReconnecting callback to be executed prior
// to the client attempting a reconnect to the MQTT broker.
func (o *ClientOptions) SetReconnectingHandler(cb ReconnectHandler) *ClientOptions {
o.OnReconnecting = cb
return o
}
// SetWriteTimeout puts a limit on how long a mqtt publish should block until it unblocks with a // SetWriteTimeout puts a limit on how long a mqtt publish should block until it unblocks with a
// timeout error. A duration of 0 never times out. Default 30 seconds // timeout error. A duration of 0 never times out. Default never times out
func (o *ClientOptions) SetWriteTimeout(t time.Duration) *ClientOptions { func (o *ClientOptions) SetWriteTimeout(t time.Duration) *ClientOptions {
o.WriteTimeout = t o.WriteTimeout = t
return o return o
} }
// SetConnectTimeout limits how long the client will wait when trying to open a connection // SetConnectTimeout limits how long the client will wait when trying to open a connection
// to an MQTT server before timeing out and erroring the attempt. A duration of 0 never times out. // to an MQTT server before timing out. A duration of 0 never times out.
// Default 30 seconds. Currently only operational on TCP/TLS connections. // Default 30 seconds. Currently only operational on TCP/TLS connections.
func (o *ClientOptions) SetConnectTimeout(t time.Duration) *ClientOptions { func (o *ClientOptions) SetConnectTimeout(t time.Duration) *ClientOptions {
o.ConnectTimeout = t o.ConnectTimeout = t
@ -323,10 +345,25 @@ func (o *ClientOptions) SetAutoReconnect(a bool) *ClientOptions {
return o return o
} }
// SetMessageChannelDepth sets the size of the internal queue that holds messages while the // SetConnectRetryInterval sets the time that will be waited between connection attempts
// client is temporairily offline, allowing the application to publish when the client is // when initially connecting if ConnectRetry is TRUE
// reconnecting. This setting is only valid if AutoReconnect is set to true, it is otherwise func (o *ClientOptions) SetConnectRetryInterval(t time.Duration) *ClientOptions {
// ignored. o.ConnectRetryInterval = t
return o
}
// SetConnectRetry sets whether the connect function will automatically retry the connection
// in the event of a failure (when true the token returned by the Connect function will
// not complete until the connection is up or it is cancelled)
// If ConnectRetry is true then subscriptions should be requested in OnConnect handler
// Setting this to TRUE permits messages to be published before the connection is established
func (o *ClientOptions) SetConnectRetry(a bool) *ClientOptions {
o.ConnectRetry = a
return o
}
// SetMessageChannelDepth DEPRECATED The value set here no longer has any effect, this function
// remains so the API is not altered.
func (o *ClientOptions) SetMessageChannelDepth(s uint) *ClientOptions { func (o *ClientOptions) SetMessageChannelDepth(s uint) *ClientOptions {
o.MessageChannelDepth = s o.MessageChannelDepth = s
return o return o
@ -338,3 +375,9 @@ func (o *ClientOptions) SetHTTPHeaders(h http.Header) *ClientOptions {
o.HTTPHeaders = h o.HTTPHeaders = h
return o return o
} }
// SetWebsocketOptions sets the additional websocket options used in a WebSocket connection
func (o *ClientOptions) SetWebsocketOptions(w *WebsocketOptions) *ClientOptions {
o.WebsocketOptions = w
return o
}

View File

@ -26,7 +26,7 @@ type ClientOptionsReader struct {
options *ClientOptions options *ClientOptions
} }
//Servers returns a slice of the servers defined in the clientoptions // Servers returns a slice of the servers defined in the clientoptions
func (r *ClientOptionsReader) Servers() []*url.URL { func (r *ClientOptionsReader) Servers() []*url.URL {
s := make([]*url.URL, len(r.options.Servers)) s := make([]*url.URL, len(r.options.Servers))
@ -38,31 +38,31 @@ func (r *ClientOptionsReader) Servers() []*url.URL {
return s return s
} }
//ResumeSubs returns true if resuming stored (un)sub is enabled // ResumeSubs returns true if resuming stored (un)sub is enabled
func (r *ClientOptionsReader) ResumeSubs() bool { func (r *ClientOptionsReader) ResumeSubs() bool {
s := r.options.ResumeSubs s := r.options.ResumeSubs
return s return s
} }
//ClientID returns the set client id // ClientID returns the set client id
func (r *ClientOptionsReader) ClientID() string { func (r *ClientOptionsReader) ClientID() string {
s := r.options.ClientID s := r.options.ClientID
return s return s
} }
//Username returns the set username // Username returns the set username
func (r *ClientOptionsReader) Username() string { func (r *ClientOptionsReader) Username() string {
s := r.options.Username s := r.options.Username
return s return s
} }
//Password returns the set password // Password returns the set password
func (r *ClientOptionsReader) Password() string { func (r *ClientOptionsReader) Password() string {
s := r.options.Password s := r.options.Password
return s return s
} }
//CleanSession returns whether Cleansession is set // CleanSession returns whether Cleansession is set
func (r *ClientOptionsReader) CleanSession() bool { func (r *ClientOptionsReader) CleanSession() bool {
s := r.options.CleanSession s := r.options.CleanSession
return s return s
@ -133,6 +133,18 @@ func (r *ClientOptionsReader) AutoReconnect() bool {
return s return s
} }
// ConnectRetryInterval returns the delay between retries on the initial connection (if ConnectRetry true)
func (r *ClientOptionsReader) ConnectRetryInterval() time.Duration {
s := r.options.ConnectRetryInterval
return s
}
// ConnectRetry returns whether the initial connection request will be retried until connection established
func (r *ClientOptionsReader) ConnectRetry() bool {
s := r.options.ConnectRetry
return s
}
func (r *ClientOptionsReader) WriteTimeout() time.Duration { func (r *ClientOptionsReader) WriteTimeout() time.Duration {
s := r.options.WriteTimeout s := r.options.WriteTimeout
return s return s
@ -147,3 +159,9 @@ func (r *ClientOptionsReader) HTTPHeaders() http.Header {
h := r.options.HTTPHeaders h := r.options.HTTPHeaders
return h return h
} }
// WebsocketOptions returns the currently configured WebSocket options
func (r *ClientOptionsReader) WebsocketOptions() *WebsocketOptions {
s := r.options.WebsocketOptions
return s
}

View File

@ -6,8 +6,8 @@ import (
"io" "io"
) )
//ConnackPacket is an internal representation of the fields of the // ConnackPacket is an internal representation of the fields of the
//Connack MQTT packet // Connack MQTT packet
type ConnackPacket struct { type ConnackPacket struct {
FixedHeader FixedHeader
SessionPresent bool SessionPresent bool
@ -15,10 +15,7 @@ type ConnackPacket struct {
} }
func (ca *ConnackPacket) String() string { func (ca *ConnackPacket) String() string {
str := fmt.Sprintf("%s", ca.FixedHeader) return fmt.Sprintf("%s sessionpresent: %t returncode: %d", ca.FixedHeader, ca.SessionPresent, ca.ReturnCode)
str += " "
str += fmt.Sprintf("sessionpresent: %t returncode: %d", ca.SessionPresent, ca.ReturnCode)
return str
} }
func (ca *ConnackPacket) Write(w io.Writer) error { func (ca *ConnackPacket) Write(w io.Writer) error {
@ -35,8 +32,8 @@ func (ca *ConnackPacket) Write(w io.Writer) error {
return err return err
} }
//Unpack decodes the details of a ControlPacket after the fixed // Unpack decodes the details of a ControlPacket after the fixed
//header has been read // header has been read
func (ca *ConnackPacket) Unpack(b io.Reader) error { func (ca *ConnackPacket) Unpack(b io.Reader) error {
flags, err := decodeByte(b) flags, err := decodeByte(b)
if err != nil { if err != nil {
@ -48,8 +45,8 @@ func (ca *ConnackPacket) Unpack(b io.Reader) error {
return err return err
} }
//Details returns a Details struct containing the Qos and // Details returns a Details struct containing the Qos and
//MessageID of this ControlPacket // MessageID of this ControlPacket
func (ca *ConnackPacket) Details() Details { func (ca *ConnackPacket) Details() Details {
return Details{Qos: 0, MessageID: 0} return Details{Qos: 0, MessageID: 0}
} }

View File

@ -6,8 +6,8 @@ import (
"io" "io"
) )
//ConnectPacket is an internal representation of the fields of the // ConnectPacket is an internal representation of the fields of the
//Connect MQTT packet // Connect MQTT packet
type ConnectPacket struct { type ConnectPacket struct {
FixedHeader FixedHeader
ProtocolName string ProtocolName string
@ -29,10 +29,7 @@ type ConnectPacket struct {
} }
func (c *ConnectPacket) String() string { func (c *ConnectPacket) String() string {
str := fmt.Sprintf("%s", c.FixedHeader) return fmt.Sprintf("%s protocolversion: %d protocolname: %s cleansession: %t willflag: %t WillQos: %d WillRetain: %t Usernameflag: %t Passwordflag: %t keepalive: %d clientId: %s willtopic: %s willmessage: %s Username: %s Password: %s", c.FixedHeader, c.ProtocolVersion, c.ProtocolName, c.CleanSession, c.WillFlag, c.WillQos, c.WillRetain, c.UsernameFlag, c.PasswordFlag, c.Keepalive, c.ClientIdentifier, c.WillTopic, c.WillMessage, c.Username, c.Password)
str += " "
str += fmt.Sprintf("protocolversion: %d protocolname: %s cleansession: %t willflag: %t WillQos: %d WillRetain: %t Usernameflag: %t Passwordflag: %t keepalive: %d clientId: %s willtopic: %s willmessage: %s Username: %s Password: %s", c.ProtocolVersion, c.ProtocolName, c.CleanSession, c.WillFlag, c.WillQos, c.WillRetain, c.UsernameFlag, c.PasswordFlag, c.Keepalive, c.ClientIdentifier, c.WillTopic, c.WillMessage, c.Username, c.Password)
return str
} }
func (c *ConnectPacket) Write(w io.Writer) error { func (c *ConnectPacket) Write(w io.Writer) error {
@ -62,8 +59,8 @@ func (c *ConnectPacket) Write(w io.Writer) error {
return err return err
} }
//Unpack decodes the details of a ControlPacket after the fixed // Unpack decodes the details of a ControlPacket after the fixed
//header has been read // header has been read
func (c *ConnectPacket) Unpack(b io.Reader) error { func (c *ConnectPacket) Unpack(b io.Reader) error {
var err error var err error
c.ProtocolName, err = decodeString(b) c.ProtocolName, err = decodeString(b)
@ -119,36 +116,36 @@ func (c *ConnectPacket) Unpack(b io.Reader) error {
return nil return nil
} }
//Validate performs validation of the fields of a Connect packet // Validate performs validation of the fields of a Connect packet
func (c *ConnectPacket) Validate() byte { func (c *ConnectPacket) Validate() byte {
if c.PasswordFlag && !c.UsernameFlag { if c.PasswordFlag && !c.UsernameFlag {
return ErrRefusedBadUsernameOrPassword return ErrRefusedBadUsernameOrPassword
} }
if c.ReservedBit != 0 { if c.ReservedBit != 0 {
//Bad reserved bit // Bad reserved bit
return ErrProtocolViolation return ErrProtocolViolation
} }
if (c.ProtocolName == "MQIsdp" && c.ProtocolVersion != 3) || (c.ProtocolName == "MQTT" && c.ProtocolVersion != 4) { if (c.ProtocolName == "MQIsdp" && c.ProtocolVersion != 3) || (c.ProtocolName == "MQTT" && c.ProtocolVersion != 4) {
//Mismatched or unsupported protocol version // Mismatched or unsupported protocol version
return ErrRefusedBadProtocolVersion return ErrRefusedBadProtocolVersion
} }
if c.ProtocolName != "MQIsdp" && c.ProtocolName != "MQTT" { if c.ProtocolName != "MQIsdp" && c.ProtocolName != "MQTT" {
//Bad protocol name // Bad protocol name
return ErrProtocolViolation return ErrProtocolViolation
} }
if len(c.ClientIdentifier) > 65535 || len(c.Username) > 65535 || len(c.Password) > 65535 { if len(c.ClientIdentifier) > 65535 || len(c.Username) > 65535 || len(c.Password) > 65535 {
//Bad size field // Bad size field
return ErrProtocolViolation return ErrProtocolViolation
} }
if len(c.ClientIdentifier) == 0 && !c.CleanSession { if len(c.ClientIdentifier) == 0 && !c.CleanSession {
//Bad client identifier // Bad client identifier
return ErrRefusedIDRejected return ErrRefusedIDRejected
} }
return Accepted return Accepted
} }
//Details returns a Details struct containing the Qos and // Details returns a Details struct containing the Qos and
//MessageID of this ControlPacket // MessageID of this ControlPacket
func (c *ConnectPacket) Details() Details { func (c *ConnectPacket) Details() Details {
return Details{Qos: 0, MessageID: 0} return Details{Qos: 0, MessageID: 0}
} }

View File

@ -1,19 +1,17 @@
package packets package packets
import ( import (
"fmt"
"io" "io"
) )
//DisconnectPacket is an internal representation of the fields of the // DisconnectPacket is an internal representation of the fields of the
//Disconnect MQTT packet // Disconnect MQTT packet
type DisconnectPacket struct { type DisconnectPacket struct {
FixedHeader FixedHeader
} }
func (d *DisconnectPacket) String() string { func (d *DisconnectPacket) String() string {
str := fmt.Sprintf("%s", d.FixedHeader) return d.FixedHeader.String()
return str
} }
func (d *DisconnectPacket) Write(w io.Writer) error { func (d *DisconnectPacket) Write(w io.Writer) error {
@ -23,14 +21,14 @@ func (d *DisconnectPacket) Write(w io.Writer) error {
return err return err
} }
//Unpack decodes the details of a ControlPacket after the fixed // Unpack decodes the details of a ControlPacket after the fixed
//header has been read // header has been read
func (d *DisconnectPacket) Unpack(b io.Reader) error { func (d *DisconnectPacket) Unpack(b io.Reader) error {
return nil return nil
} }
//Details returns a Details struct containing the Qos and // Details returns a Details struct containing the Qos and
//MessageID of this ControlPacket // MessageID of this ControlPacket
func (d *DisconnectPacket) Details() Details { func (d *DisconnectPacket) Details() Details {
return Details{Qos: 0, MessageID: 0} return Details{Qos: 0, MessageID: 0}
} }

View File

@ -8,9 +8,9 @@ import (
"io" "io"
) )
//ControlPacket defines the interface for structs intended to hold // ControlPacket defines the interface for structs intended to hold
//decoded MQTT packets, either from being read or before being // decoded MQTT packets, either from being read or before being
//written // written
type ControlPacket interface { type ControlPacket interface {
Write(io.Writer) error Write(io.Writer) error
Unpack(io.Reader) error Unpack(io.Reader) error
@ -18,8 +18,8 @@ type ControlPacket interface {
Details() Details Details() Details
} }
//PacketNames maps the constants for each of the MQTT packet types // PacketNames maps the constants for each of the MQTT packet types
//to a string representation of their name. // to a string representation of their name.
var PacketNames = map[uint8]string{ var PacketNames = map[uint8]string{
1: "CONNECT", 1: "CONNECT",
2: "CONNACK", 2: "CONNACK",
@ -37,7 +37,7 @@ var PacketNames = map[uint8]string{
14: "DISCONNECT", 14: "DISCONNECT",
} }
//Below are the constants assigned to each of the MQTT packet types // Below are the constants assigned to each of the MQTT packet types
const ( const (
Connect = 1 Connect = 1
Connack = 2 Connack = 2
@ -55,8 +55,8 @@ const (
Disconnect = 14 Disconnect = 14
) )
//Below are the const definitions for error codes returned by // Below are the const definitions for error codes returned by
//Connect() // Connect()
const ( const (
Accepted = 0x00 Accepted = 0x00
ErrRefusedBadProtocolVersion = 0x01 ErrRefusedBadProtocolVersion = 0x01
@ -68,8 +68,8 @@ const (
ErrProtocolViolation = 0xFF ErrProtocolViolation = 0xFF
) )
//ConnackReturnCodes is a map of the error codes constants for Connect() // ConnackReturnCodes is a map of the error codes constants for Connect()
//to a string representation of the error // to a string representation of the error
var ConnackReturnCodes = map[uint8]string{ var ConnackReturnCodes = map[uint8]string{
0: "Connection Accepted", 0: "Connection Accepted",
1: "Connection Refused: Bad Protocol Version", 1: "Connection Refused: Bad Protocol Version",
@ -81,23 +81,23 @@ var ConnackReturnCodes = map[uint8]string{
255: "Connection Refused: Protocol Violation", 255: "Connection Refused: Protocol Violation",
} }
//ConnErrors is a map of the errors codes constants for Connect() // ConnErrors is a map of the errors codes constants for Connect()
//to a Go error // to a Go error
var ConnErrors = map[byte]error{ var ConnErrors = map[byte]error{
Accepted: nil, Accepted: nil,
ErrRefusedBadProtocolVersion: errors.New("Unnacceptable protocol version"), ErrRefusedBadProtocolVersion: errors.New("unacceptable protocol version"),
ErrRefusedIDRejected: errors.New("Identifier rejected"), ErrRefusedIDRejected: errors.New("identifier rejected"),
ErrRefusedServerUnavailable: errors.New("Server Unavailable"), ErrRefusedServerUnavailable: errors.New("server Unavailable"),
ErrRefusedBadUsernameOrPassword: errors.New("Bad user name or password"), ErrRefusedBadUsernameOrPassword: errors.New("bad user name or password"),
ErrRefusedNotAuthorised: errors.New("Not Authorized"), ErrRefusedNotAuthorised: errors.New("not Authorized"),
ErrNetworkError: errors.New("Network Error"), ErrNetworkError: errors.New("network Error"),
ErrProtocolViolation: errors.New("Protocol Violation"), ErrProtocolViolation: errors.New("protocol Violation"),
} }
//ReadPacket takes an instance of an io.Reader (such as net.Conn) and attempts // ReadPacket takes an instance of an io.Reader (such as net.Conn) and attempts
//to read an MQTT packet from the stream. It returns a ControlPacket // to read an MQTT packet from the stream. It returns a ControlPacket
//representing the decoded MQTT packet and an error. One of these returns will // representing the decoded MQTT packet and an error. One of these returns will
//always be nil, a nil ControlPacket indicating an error occurred. // always be nil, a nil ControlPacket indicating an error occurred.
func ReadPacket(r io.Reader) (ControlPacket, error) { func ReadPacket(r io.Reader) (ControlPacket, error) {
var fh FixedHeader var fh FixedHeader
b := make([]byte, 1) b := make([]byte, 1)
@ -123,17 +123,17 @@ func ReadPacket(r io.Reader) (ControlPacket, error) {
return nil, err return nil, err
} }
if n != fh.RemainingLength { if n != fh.RemainingLength {
return nil, errors.New("Failed to read expected data") return nil, errors.New("failed to read expected data")
} }
err = cp.Unpack(bytes.NewBuffer(packetBytes)) err = cp.Unpack(bytes.NewBuffer(packetBytes))
return cp, err return cp, err
} }
//NewControlPacket is used to create a new ControlPacket of the type specified // NewControlPacket is used to create a new ControlPacket of the type specified
//by packetType, this is usually done by reference to the packet type constants // by packetType, this is usually done by reference to the packet type constants
//defined in packets.go. The newly created ControlPacket is empty and a pointer // defined in packets.go. The newly created ControlPacket is empty and a pointer
//is returned. // is returned.
func NewControlPacket(packetType byte) ControlPacket { func NewControlPacket(packetType byte) ControlPacket {
switch packetType { switch packetType {
case Connect: case Connect:
@ -168,9 +168,9 @@ func NewControlPacket(packetType byte) ControlPacket {
return nil return nil
} }
//NewControlPacketWithHeader is used to create a new ControlPacket of the type // NewControlPacketWithHeader is used to create a new ControlPacket of the type
//specified within the FixedHeader that is passed to the function. // specified within the FixedHeader that is passed to the function.
//The newly created ControlPacket is empty and a pointer is returned. // The newly created ControlPacket is empty and a pointer is returned.
func NewControlPacketWithHeader(fh FixedHeader) (ControlPacket, error) { func NewControlPacketWithHeader(fh FixedHeader) (ControlPacket, error) {
switch fh.MessageType { switch fh.MessageType {
case Connect: case Connect:
@ -205,16 +205,16 @@ func NewControlPacketWithHeader(fh FixedHeader) (ControlPacket, error) {
return nil, fmt.Errorf("unsupported packet type 0x%x", fh.MessageType) return nil, fmt.Errorf("unsupported packet type 0x%x", fh.MessageType)
} }
//Details struct returned by the Details() function called on // Details struct returned by the Details() function called on
//ControlPackets to present details of the Qos and MessageID // ControlPackets to present details of the Qos and MessageID
//of the ControlPacket // of the ControlPacket
type Details struct { type Details struct {
Qos byte Qos byte
MessageID uint16 MessageID uint16
} }
//FixedHeader is a struct to hold the decoded information from // FixedHeader is a struct to hold the decoded information from
//the fixed header of an MQTT ControlPacket // the fixed header of an MQTT ControlPacket
type FixedHeader struct { type FixedHeader struct {
MessageType byte MessageType byte
Dup bool Dup bool
@ -274,9 +274,9 @@ func decodeUint16(b io.Reader) (uint16, error) {
} }
func encodeUint16(num uint16) []byte { func encodeUint16(num uint16) []byte {
bytes := make([]byte, 2) bytesResult := make([]byte, 2)
binary.BigEndian.PutUint16(bytes, num) binary.BigEndian.PutUint16(bytesResult, num)
return bytes return bytesResult
} }
func encodeString(field string) []byte { func encodeString(field string) []byte {
@ -329,7 +329,7 @@ func decodeLength(r io.Reader) (int, error) {
var rLength uint32 var rLength uint32
var multiplier uint32 var multiplier uint32
b := make([]byte, 1) b := make([]byte, 1)
for multiplier < 27 { //fix: Infinite '(digit & 128) == 1' will cause the dead loop for multiplier < 27 { // fix: Infinite '(digit & 128) == 1' will cause the dead loop
_, err := io.ReadFull(r, b) _, err := io.ReadFull(r, b)
if err != nil { if err != nil {
return 0, err return 0, err

View File

@ -1,19 +1,17 @@
package packets package packets
import ( import (
"fmt"
"io" "io"
) )
//PingreqPacket is an internal representation of the fields of the // PingreqPacket is an internal representation of the fields of the
//Pingreq MQTT packet // Pingreq MQTT packet
type PingreqPacket struct { type PingreqPacket struct {
FixedHeader FixedHeader
} }
func (pr *PingreqPacket) String() string { func (pr *PingreqPacket) String() string {
str := fmt.Sprintf("%s", pr.FixedHeader) return pr.FixedHeader.String()
return str
} }
func (pr *PingreqPacket) Write(w io.Writer) error { func (pr *PingreqPacket) Write(w io.Writer) error {
@ -23,14 +21,14 @@ func (pr *PingreqPacket) Write(w io.Writer) error {
return err return err
} }
//Unpack decodes the details of a ControlPacket after the fixed // Unpack decodes the details of a ControlPacket after the fixed
//header has been read // header has been read
func (pr *PingreqPacket) Unpack(b io.Reader) error { func (pr *PingreqPacket) Unpack(b io.Reader) error {
return nil return nil
} }
//Details returns a Details struct containing the Qos and // Details returns a Details struct containing the Qos and
//MessageID of this ControlPacket // MessageID of this ControlPacket
func (pr *PingreqPacket) Details() Details { func (pr *PingreqPacket) Details() Details {
return Details{Qos: 0, MessageID: 0} return Details{Qos: 0, MessageID: 0}
} }

View File

@ -1,19 +1,17 @@
package packets package packets
import ( import (
"fmt"
"io" "io"
) )
//PingrespPacket is an internal representation of the fields of the // PingrespPacket is an internal representation of the fields of the
//Pingresp MQTT packet // Pingresp MQTT packet
type PingrespPacket struct { type PingrespPacket struct {
FixedHeader FixedHeader
} }
func (pr *PingrespPacket) String() string { func (pr *PingrespPacket) String() string {
str := fmt.Sprintf("%s", pr.FixedHeader) return pr.FixedHeader.String()
return str
} }
func (pr *PingrespPacket) Write(w io.Writer) error { func (pr *PingrespPacket) Write(w io.Writer) error {
@ -23,14 +21,14 @@ func (pr *PingrespPacket) Write(w io.Writer) error {
return err return err
} }
//Unpack decodes the details of a ControlPacket after the fixed // Unpack decodes the details of a ControlPacket after the fixed
//header has been read // header has been read
func (pr *PingrespPacket) Unpack(b io.Reader) error { func (pr *PingrespPacket) Unpack(b io.Reader) error {
return nil return nil
} }
//Details returns a Details struct containing the Qos and // Details returns a Details struct containing the Qos and
//MessageID of this ControlPacket // MessageID of this ControlPacket
func (pr *PingrespPacket) Details() Details { func (pr *PingrespPacket) Details() Details {
return Details{Qos: 0, MessageID: 0} return Details{Qos: 0, MessageID: 0}
} }

View File

@ -5,18 +5,15 @@ import (
"io" "io"
) )
//PubackPacket is an internal representation of the fields of the // PubackPacket is an internal representation of the fields of the
//Puback MQTT packet // Puback MQTT packet
type PubackPacket struct { type PubackPacket struct {
FixedHeader FixedHeader
MessageID uint16 MessageID uint16
} }
func (pa *PubackPacket) String() string { func (pa *PubackPacket) String() string {
str := fmt.Sprintf("%s", pa.FixedHeader) return fmt.Sprintf("%s MessageID: %d", pa.FixedHeader, pa.MessageID)
str += " "
str += fmt.Sprintf("MessageID: %d", pa.MessageID)
return str
} }
func (pa *PubackPacket) Write(w io.Writer) error { func (pa *PubackPacket) Write(w io.Writer) error {
@ -29,8 +26,8 @@ func (pa *PubackPacket) Write(w io.Writer) error {
return err return err
} }
//Unpack decodes the details of a ControlPacket after the fixed // Unpack decodes the details of a ControlPacket after the fixed
//header has been read // header has been read
func (pa *PubackPacket) Unpack(b io.Reader) error { func (pa *PubackPacket) Unpack(b io.Reader) error {
var err error var err error
pa.MessageID, err = decodeUint16(b) pa.MessageID, err = decodeUint16(b)
@ -38,8 +35,8 @@ func (pa *PubackPacket) Unpack(b io.Reader) error {
return err return err
} }
//Details returns a Details struct containing the Qos and // Details returns a Details struct containing the Qos and
//MessageID of this ControlPacket // MessageID of this ControlPacket
func (pa *PubackPacket) Details() Details { func (pa *PubackPacket) Details() Details {
return Details{Qos: pa.Qos, MessageID: pa.MessageID} return Details{Qos: pa.Qos, MessageID: pa.MessageID}
} }

View File

@ -5,18 +5,15 @@ import (
"io" "io"
) )
//PubcompPacket is an internal representation of the fields of the // PubcompPacket is an internal representation of the fields of the
//Pubcomp MQTT packet // Pubcomp MQTT packet
type PubcompPacket struct { type PubcompPacket struct {
FixedHeader FixedHeader
MessageID uint16 MessageID uint16
} }
func (pc *PubcompPacket) String() string { func (pc *PubcompPacket) String() string {
str := fmt.Sprintf("%s", pc.FixedHeader) return fmt.Sprintf("%s MessageID: %d", pc.FixedHeader, pc.MessageID)
str += " "
str += fmt.Sprintf("MessageID: %d", pc.MessageID)
return str
} }
func (pc *PubcompPacket) Write(w io.Writer) error { func (pc *PubcompPacket) Write(w io.Writer) error {
@ -29,8 +26,8 @@ func (pc *PubcompPacket) Write(w io.Writer) error {
return err return err
} }
//Unpack decodes the details of a ControlPacket after the fixed // Unpack decodes the details of a ControlPacket after the fixed
//header has been read // header has been read
func (pc *PubcompPacket) Unpack(b io.Reader) error { func (pc *PubcompPacket) Unpack(b io.Reader) error {
var err error var err error
pc.MessageID, err = decodeUint16(b) pc.MessageID, err = decodeUint16(b)
@ -38,8 +35,8 @@ func (pc *PubcompPacket) Unpack(b io.Reader) error {
return err return err
} }
//Details returns a Details struct containing the Qos and // Details returns a Details struct containing the Qos and
//MessageID of this ControlPacket // MessageID of this ControlPacket
func (pc *PubcompPacket) Details() Details { func (pc *PubcompPacket) Details() Details {
return Details{Qos: pc.Qos, MessageID: pc.MessageID} return Details{Qos: pc.Qos, MessageID: pc.MessageID}
} }

View File

@ -6,8 +6,8 @@ import (
"io" "io"
) )
//PublishPacket is an internal representation of the fields of the // PublishPacket is an internal representation of the fields of the
//Publish MQTT packet // Publish MQTT packet
type PublishPacket struct { type PublishPacket struct {
FixedHeader FixedHeader
TopicName string TopicName string
@ -16,12 +16,7 @@ type PublishPacket struct {
} }
func (p *PublishPacket) String() string { func (p *PublishPacket) String() string {
str := fmt.Sprintf("%s", p.FixedHeader) return fmt.Sprintf("%s topicName: %s MessageID: %d payload: %s", p.FixedHeader, p.TopicName, p.MessageID, string(p.Payload))
str += " "
str += fmt.Sprintf("topicName: %s MessageID: %d", p.TopicName, p.MessageID)
str += " "
str += fmt.Sprintf("payload: %s", string(p.Payload))
return str
} }
func (p *PublishPacket) Write(w io.Writer) error { func (p *PublishPacket) Write(w io.Writer) error {
@ -41,8 +36,8 @@ func (p *PublishPacket) Write(w io.Writer) error {
return err return err
} }
//Unpack decodes the details of a ControlPacket after the fixed // Unpack decodes the details of a ControlPacket after the fixed
//header has been read // header has been read
func (p *PublishPacket) Unpack(b io.Reader) error { func (p *PublishPacket) Unpack(b io.Reader) error {
var payloadLength = p.FixedHeader.RemainingLength var payloadLength = p.FixedHeader.RemainingLength
var err error var err error
@ -61,7 +56,7 @@ func (p *PublishPacket) Unpack(b io.Reader) error {
payloadLength -= len(p.TopicName) + 2 payloadLength -= len(p.TopicName) + 2
} }
if payloadLength < 0 { if payloadLength < 0 {
return fmt.Errorf("Error unpacking publish, payload length < 0") return fmt.Errorf("error unpacking publish, payload length < 0")
} }
p.Payload = make([]byte, payloadLength) p.Payload = make([]byte, payloadLength)
_, err = b.Read(p.Payload) _, err = b.Read(p.Payload)
@ -69,10 +64,10 @@ func (p *PublishPacket) Unpack(b io.Reader) error {
return err return err
} }
//Copy creates a new PublishPacket with the same topic and payload // Copy creates a new PublishPacket with the same topic and payload
//but an empty fixed header, useful for when you want to deliver // but an empty fixed header, useful for when you want to deliver
//a message with different properties such as Qos but the same // a message with different properties such as Qos but the same
//content // content
func (p *PublishPacket) Copy() *PublishPacket { func (p *PublishPacket) Copy() *PublishPacket {
newP := NewControlPacket(Publish).(*PublishPacket) newP := NewControlPacket(Publish).(*PublishPacket)
newP.TopicName = p.TopicName newP.TopicName = p.TopicName
@ -81,8 +76,8 @@ func (p *PublishPacket) Copy() *PublishPacket {
return newP return newP
} }
//Details returns a Details struct containing the Qos and // Details returns a Details struct containing the Qos and
//MessageID of this ControlPacket // MessageID of this ControlPacket
func (p *PublishPacket) Details() Details { func (p *PublishPacket) Details() Details {
return Details{Qos: p.Qos, MessageID: p.MessageID} return Details{Qos: p.Qos, MessageID: p.MessageID}
} }

View File

@ -5,18 +5,15 @@ import (
"io" "io"
) )
//PubrecPacket is an internal representation of the fields of the // PubrecPacket is an internal representation of the fields of the
//Pubrec MQTT packet // Pubrec MQTT packet
type PubrecPacket struct { type PubrecPacket struct {
FixedHeader FixedHeader
MessageID uint16 MessageID uint16
} }
func (pr *PubrecPacket) String() string { func (pr *PubrecPacket) String() string {
str := fmt.Sprintf("%s", pr.FixedHeader) return fmt.Sprintf("%s MessageID: %d", pr.FixedHeader, pr.MessageID)
str += " "
str += fmt.Sprintf("MessageID: %d", pr.MessageID)
return str
} }
func (pr *PubrecPacket) Write(w io.Writer) error { func (pr *PubrecPacket) Write(w io.Writer) error {
@ -29,8 +26,8 @@ func (pr *PubrecPacket) Write(w io.Writer) error {
return err return err
} }
//Unpack decodes the details of a ControlPacket after the fixed // Unpack decodes the details of a ControlPacket after the fixed
//header has been read // header has been read
func (pr *PubrecPacket) Unpack(b io.Reader) error { func (pr *PubrecPacket) Unpack(b io.Reader) error {
var err error var err error
pr.MessageID, err = decodeUint16(b) pr.MessageID, err = decodeUint16(b)
@ -38,8 +35,8 @@ func (pr *PubrecPacket) Unpack(b io.Reader) error {
return err return err
} }
//Details returns a Details struct containing the Qos and // Details returns a Details struct containing the Qos and
//MessageID of this ControlPacket // MessageID of this ControlPacket
func (pr *PubrecPacket) Details() Details { func (pr *PubrecPacket) Details() Details {
return Details{Qos: pr.Qos, MessageID: pr.MessageID} return Details{Qos: pr.Qos, MessageID: pr.MessageID}
} }

View File

@ -5,18 +5,15 @@ import (
"io" "io"
) )
//PubrelPacket is an internal representation of the fields of the // PubrelPacket is an internal representation of the fields of the
//Pubrel MQTT packet // Pubrel MQTT packet
type PubrelPacket struct { type PubrelPacket struct {
FixedHeader FixedHeader
MessageID uint16 MessageID uint16
} }
func (pr *PubrelPacket) String() string { func (pr *PubrelPacket) String() string {
str := fmt.Sprintf("%s", pr.FixedHeader) return fmt.Sprintf("%s MessageID: %d", pr.FixedHeader, pr.MessageID)
str += " "
str += fmt.Sprintf("MessageID: %d", pr.MessageID)
return str
} }
func (pr *PubrelPacket) Write(w io.Writer) error { func (pr *PubrelPacket) Write(w io.Writer) error {
@ -29,8 +26,8 @@ func (pr *PubrelPacket) Write(w io.Writer) error {
return err return err
} }
//Unpack decodes the details of a ControlPacket after the fixed // Unpack decodes the details of a ControlPacket after the fixed
//header has been read // header has been read
func (pr *PubrelPacket) Unpack(b io.Reader) error { func (pr *PubrelPacket) Unpack(b io.Reader) error {
var err error var err error
pr.MessageID, err = decodeUint16(b) pr.MessageID, err = decodeUint16(b)
@ -38,8 +35,8 @@ func (pr *PubrelPacket) Unpack(b io.Reader) error {
return err return err
} }
//Details returns a Details struct containing the Qos and // Details returns a Details struct containing the Qos and
//MessageID of this ControlPacket // MessageID of this ControlPacket
func (pr *PubrelPacket) Details() Details { func (pr *PubrelPacket) Details() Details {
return Details{Qos: pr.Qos, MessageID: pr.MessageID} return Details{Qos: pr.Qos, MessageID: pr.MessageID}
} }

View File

@ -6,8 +6,8 @@ import (
"io" "io"
) )
//SubackPacket is an internal representation of the fields of the // SubackPacket is an internal representation of the fields of the
//Suback MQTT packet // Suback MQTT packet
type SubackPacket struct { type SubackPacket struct {
FixedHeader FixedHeader
MessageID uint16 MessageID uint16
@ -15,10 +15,7 @@ type SubackPacket struct {
} }
func (sa *SubackPacket) String() string { func (sa *SubackPacket) String() string {
str := fmt.Sprintf("%s", sa.FixedHeader) return fmt.Sprintf("%s MessageID: %d", sa.FixedHeader, sa.MessageID)
str += " "
str += fmt.Sprintf("MessageID: %d", sa.MessageID)
return str
} }
func (sa *SubackPacket) Write(w io.Writer) error { func (sa *SubackPacket) Write(w io.Writer) error {
@ -34,8 +31,8 @@ func (sa *SubackPacket) Write(w io.Writer) error {
return err return err
} }
//Unpack decodes the details of a ControlPacket after the fixed // Unpack decodes the details of a ControlPacket after the fixed
//header has been read // header has been read
func (sa *SubackPacket) Unpack(b io.Reader) error { func (sa *SubackPacket) Unpack(b io.Reader) error {
var qosBuffer bytes.Buffer var qosBuffer bytes.Buffer
var err error var err error
@ -53,8 +50,8 @@ func (sa *SubackPacket) Unpack(b io.Reader) error {
return nil return nil
} }
//Details returns a Details struct containing the Qos and // Details returns a Details struct containing the Qos and
//MessageID of this ControlPacket // MessageID of this ControlPacket
func (sa *SubackPacket) Details() Details { func (sa *SubackPacket) Details() Details {
return Details{Qos: 0, MessageID: sa.MessageID} return Details{Qos: 0, MessageID: sa.MessageID}
} }

View File

@ -6,8 +6,8 @@ import (
"io" "io"
) )
//SubscribePacket is an internal representation of the fields of the // SubscribePacket is an internal representation of the fields of the
//Subscribe MQTT packet // Subscribe MQTT packet
type SubscribePacket struct { type SubscribePacket struct {
FixedHeader FixedHeader
MessageID uint16 MessageID uint16
@ -16,10 +16,7 @@ type SubscribePacket struct {
} }
func (s *SubscribePacket) String() string { func (s *SubscribePacket) String() string {
str := fmt.Sprintf("%s", s.FixedHeader) return fmt.Sprintf("%s MessageID: %d topics: %s", s.FixedHeader, s.MessageID, s.Topics)
str += " "
str += fmt.Sprintf("MessageID: %d topics: %s", s.MessageID, s.Topics)
return str
} }
func (s *SubscribePacket) Write(w io.Writer) error { func (s *SubscribePacket) Write(w io.Writer) error {
@ -39,8 +36,8 @@ func (s *SubscribePacket) Write(w io.Writer) error {
return err return err
} }
//Unpack decodes the details of a ControlPacket after the fixed // Unpack decodes the details of a ControlPacket after the fixed
//header has been read // header has been read
func (s *SubscribePacket) Unpack(b io.Reader) error { func (s *SubscribePacket) Unpack(b io.Reader) error {
var err error var err error
s.MessageID, err = decodeUint16(b) s.MessageID, err = decodeUint16(b)
@ -59,14 +56,14 @@ func (s *SubscribePacket) Unpack(b io.Reader) error {
return err return err
} }
s.Qoss = append(s.Qoss, qos) s.Qoss = append(s.Qoss, qos)
payloadLength -= 2 + len(topic) + 1 //2 bytes of string length, plus string, plus 1 byte for Qos payloadLength -= 2 + len(topic) + 1 // 2 bytes of string length, plus string, plus 1 byte for Qos
} }
return nil return nil
} }
//Details returns a Details struct containing the Qos and // Details returns a Details struct containing the Qos and
//MessageID of this ControlPacket // MessageID of this ControlPacket
func (s *SubscribePacket) Details() Details { func (s *SubscribePacket) Details() Details {
return Details{Qos: 1, MessageID: s.MessageID} return Details{Qos: 1, MessageID: s.MessageID}
} }

View File

@ -5,18 +5,15 @@ import (
"io" "io"
) )
//UnsubackPacket is an internal representation of the fields of the // UnsubackPacket is an internal representation of the fields of the
//Unsuback MQTT packet // Unsuback MQTT packet
type UnsubackPacket struct { type UnsubackPacket struct {
FixedHeader FixedHeader
MessageID uint16 MessageID uint16
} }
func (ua *UnsubackPacket) String() string { func (ua *UnsubackPacket) String() string {
str := fmt.Sprintf("%s", ua.FixedHeader) return fmt.Sprintf("%s MessageID: %d", ua.FixedHeader, ua.MessageID)
str += " "
str += fmt.Sprintf("MessageID: %d", ua.MessageID)
return str
} }
func (ua *UnsubackPacket) Write(w io.Writer) error { func (ua *UnsubackPacket) Write(w io.Writer) error {
@ -29,8 +26,8 @@ func (ua *UnsubackPacket) Write(w io.Writer) error {
return err return err
} }
//Unpack decodes the details of a ControlPacket after the fixed // Unpack decodes the details of a ControlPacket after the fixed
//header has been read // header has been read
func (ua *UnsubackPacket) Unpack(b io.Reader) error { func (ua *UnsubackPacket) Unpack(b io.Reader) error {
var err error var err error
ua.MessageID, err = decodeUint16(b) ua.MessageID, err = decodeUint16(b)
@ -38,8 +35,8 @@ func (ua *UnsubackPacket) Unpack(b io.Reader) error {
return err return err
} }
//Details returns a Details struct containing the Qos and // Details returns a Details struct containing the Qos and
//MessageID of this ControlPacket // MessageID of this ControlPacket
func (ua *UnsubackPacket) Details() Details { func (ua *UnsubackPacket) Details() Details {
return Details{Qos: 0, MessageID: ua.MessageID} return Details{Qos: 0, MessageID: ua.MessageID}
} }

View File

@ -6,8 +6,8 @@ import (
"io" "io"
) )
//UnsubscribePacket is an internal representation of the fields of the // UnsubscribePacket is an internal representation of the fields of the
//Unsubscribe MQTT packet // Unsubscribe MQTT packet
type UnsubscribePacket struct { type UnsubscribePacket struct {
FixedHeader FixedHeader
MessageID uint16 MessageID uint16
@ -15,10 +15,7 @@ type UnsubscribePacket struct {
} }
func (u *UnsubscribePacket) String() string { func (u *UnsubscribePacket) String() string {
str := fmt.Sprintf("%s", u.FixedHeader) return fmt.Sprintf("%s MessageID: %d", u.FixedHeader, u.MessageID)
str += " "
str += fmt.Sprintf("MessageID: %d", u.MessageID)
return str
} }
func (u *UnsubscribePacket) Write(w io.Writer) error { func (u *UnsubscribePacket) Write(w io.Writer) error {
@ -36,8 +33,8 @@ func (u *UnsubscribePacket) Write(w io.Writer) error {
return err return err
} }
//Unpack decodes the details of a ControlPacket after the fixed // Unpack decodes the details of a ControlPacket after the fixed
//header has been read // header has been read
func (u *UnsubscribePacket) Unpack(b io.Reader) error { func (u *UnsubscribePacket) Unpack(b io.Reader) error {
var err error var err error
u.MessageID, err = decodeUint16(b) u.MessageID, err = decodeUint16(b)
@ -52,8 +49,8 @@ func (u *UnsubscribePacket) Unpack(b io.Reader) error {
return err return err
} }
//Details returns a Details struct containing the Qos and // Details returns a Details struct containing the Qos and
//MessageID of this ControlPacket // MessageID of this ControlPacket
func (u *UnsubscribePacket) Details() Details { func (u *UnsubscribePacket) Details() Details {
return Details{Qos: 1, MessageID: u.MessageID} return Details{Qos: 1, MessageID: u.MessageID}
} }

View File

@ -16,13 +16,16 @@ package mqtt
import ( import (
"errors" "errors"
"io"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/eclipse/paho.mqtt.golang/packets" "github.com/eclipse/paho.mqtt.golang/packets"
) )
func keepalive(c *client) { // keepalive - Send ping when connection unused for set period
// connection passed in to avoid race condition on shutdown
func keepalive(c *client, conn io.Writer) {
defer c.workers.Done() defer c.workers.Done()
DEBUG.Println(PNG, "keepalive starting") DEBUG.Println(PNG, "keepalive starting")
var checkInterval int64 var checkInterval int64
@ -51,17 +54,19 @@ func keepalive(c *client) {
if atomic.LoadInt32(&c.pingOutstanding) == 0 { if atomic.LoadInt32(&c.pingOutstanding) == 0 {
DEBUG.Println(PNG, "keepalive sending ping") DEBUG.Println(PNG, "keepalive sending ping")
ping := packets.NewControlPacket(packets.Pingreq).(*packets.PingreqPacket) ping := packets.NewControlPacket(packets.Pingreq).(*packets.PingreqPacket)
//We don't want to wait behind large messages being sent, the Write call // We don't want to wait behind large messages being sent, the Write call
//will block until it it able to send the packet. // will block until it it able to send the packet.
atomic.StoreInt32(&c.pingOutstanding, 1) atomic.StoreInt32(&c.pingOutstanding, 1)
ping.Write(c.conn) if err := ping.Write(conn); err != nil {
ERROR.Println(PNG, err)
}
c.lastSent.Store(time.Now()) c.lastSent.Store(time.Now())
pingSent = time.Now() pingSent = time.Now()
} }
} }
if atomic.LoadInt32(&c.pingOutstanding) > 0 && time.Now().Sub(pingSent) >= c.options.PingTimeout { if atomic.LoadInt32(&c.pingOutstanding) > 0 && time.Since(pingSent) >= c.options.PingTimeout {
CRITICAL.Println(PNG, "pingresp not received, disconnecting") CRITICAL.Println(PNG, "pingresp not received, disconnecting")
c.errors <- errors.New("pingresp not received, disconnecting") c.internalConnLost(errors.New("pingresp not received, disconnecting")) // no harm in calling this if the connection is already down (or shutdown is in progress)
return return
} }
} }

View File

@ -37,17 +37,11 @@ type route struct {
// and returns a boolean of the outcome // and returns a boolean of the outcome
func match(route []string, topic []string) bool { func match(route []string, topic []string) bool {
if len(route) == 0 { if len(route) == 0 {
if len(topic) == 0 { return len(topic) == 0
return true
}
return false
} }
if len(topic) == 0 { if len(topic) == 0 {
if route[0] == "#" { return route[0] == "#"
return true
}
return false
} }
if route[0] == "#" { if route[0] == "#" {
@ -87,15 +81,13 @@ type router struct {
routes *list.List routes *list.List
defaultHandler MessageHandler defaultHandler MessageHandler
messages chan *packets.PublishPacket messages chan *packets.PublishPacket
stop chan bool
} }
// newRouter returns a new instance of a Router and channel which can be used to tell the Router // newRouter returns a new instance of a Router and channel which can be used to tell the Router
// to stop // to stop
func newRouter() (*router, chan bool) { func newRouter() *router {
router := &router{routes: list.New(), messages: make(chan *packets.PublishPacket), stop: make(chan bool)} router := &router{routes: list.New(), messages: make(chan *packets.PublishPacket)}
stop := router.stop return router
return router, stop
} }
// addRoute takes a topic string and MessageHandler callback. It looks in the current list of // addRoute takes a topic string and MessageHandler callback. It looks in the current list of
@ -105,7 +97,7 @@ func (r *router) addRoute(topic string, callback MessageHandler) {
r.Lock() r.Lock()
defer r.Unlock() defer r.Unlock()
for e := r.routes.Front(); e != nil; e = e.Next() { for e := r.routes.Front(); e != nil; e = e.Next() {
if e.Value.(*route).match(topic) { if e.Value.(*route).topic == topic {
r := e.Value.(*route) r := e.Value.(*route)
r.callback = callback r.callback = callback
return return
@ -120,7 +112,7 @@ func (r *router) deleteRoute(topic string) {
r.Lock() r.Lock()
defer r.Unlock() defer r.Unlock()
for e := r.routes.Front(); e != nil; e = e.Next() { for e := r.routes.Front(); e != nil; e = e.Next() {
if e.Value.(*route).match(topic) { if e.Value.(*route).topic == topic {
r.routes.Remove(e) r.routes.Remove(e)
return return
} }
@ -139,30 +131,31 @@ func (r *router) setDefaultHandler(handler MessageHandler) {
// takes messages off the channel, matches them against the internal route list and calls the // takes messages off the channel, matches them against the internal route list and calls the
// associated callback (or the defaultHandler, if one exists and no other route matched). If // associated callback (or the defaultHandler, if one exists and no other route matched). If
// anything is sent down the stop channel the function will end. // anything is sent down the stop channel the function will end.
func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order bool, client *client) { func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order bool, client *client) <-chan *PacketAndToken {
ackChan := make(chan *PacketAndToken)
go func() { go func() {
for { for message := range messages {
select { // DEBUG.Println(ROU, "matchAndDispatch received message")
case message := <-messages: sent := false
sent := false r.RLock()
r.RLock() m := messageFromPublish(message, ackFunc(ackChan, client.persist, message))
m := messageFromPublish(message, client.ackFunc(message)) var handlers []MessageHandler
handlers := []MessageHandler{} for e := r.routes.Front(); e != nil; e = e.Next() {
for e := r.routes.Front(); e != nil; e = e.Next() { if e.Value.(*route).match(message.TopicName) {
if e.Value.(*route).match(message.TopicName) { if order {
if order { handlers = append(handlers, e.Value.(*route).callback)
handlers = append(handlers, e.Value.(*route).callback) } else {
} else { hd := e.Value.(*route).callback
hd := e.Value.(*route).callback go func() {
go func() { hd(client, m)
hd(client, m) m.Ack()
m.Ack() }()
}()
}
sent = true
} }
sent = true
} }
if !sent && r.defaultHandler != nil { }
if !sent {
if r.defaultHandler != nil {
if order { if order {
handlers = append(handlers, r.defaultHandler) handlers = append(handlers, r.defaultHandler)
} else { } else {
@ -171,17 +164,19 @@ func (r *router) matchAndDispatch(messages <-chan *packets.PublishPacket, order
m.Ack() m.Ack()
}() }()
} }
} else {
DEBUG.Println(ROU, "matchAndDispatch received message and no handler was available. Message will NOT be acknowledged.")
} }
r.RUnlock()
for _, handler := range handlers {
func() {
handler(client, m)
m.Ack()
}()
}
case <-r.stop:
return
} }
r.RUnlock()
for _, handler := range handlers {
handler(client, m)
m.Ack()
}
// DEBUG.Println(ROU, "matchAndDispatch handled message")
} }
close(ackChan)
DEBUG.Println(ROU, "matchAndDispatch exiting")
}() }()
return ackChan
} }

View File

@ -31,8 +31,24 @@ type PacketAndToken struct {
// Token defines the interface for the tokens used to indicate when // Token defines the interface for the tokens used to indicate when
// actions have completed. // actions have completed.
type Token interface { type Token interface {
// Wait will wait indefinitely for the Token to complete, ie the Publish
// to be sent and confirmed receipt from the broker.
Wait() bool Wait() bool
// WaitTimeout takes a time.Duration to wait for the flow associated with the
// Token to complete, returns true if it returned before the timeout or
// returns false if the timeout occurred. In the case of a timeout the Token
// does not have an error set in case the caller wishes to wait again.
WaitTimeout(time.Duration) bool WaitTimeout(time.Duration) bool
// Done returns a channel that is closed when the flow associated
// with the Token completes. Clients should call Error after the
// channel is closed to check if the flow completed successfully.
//
// Done is provided for use in select statements. Simple use cases may
// use Wait or WaitTimeout.
Done() <-chan struct{}
Error() error Error() error
} }
@ -52,21 +68,14 @@ type baseToken struct {
err error err error
} }
// Wait will wait indefinitely for the Token to complete, ie the Publish // Wait implements the Token Wait method.
// to be sent and confirmed receipt from the broker
func (b *baseToken) Wait() bool { func (b *baseToken) Wait() bool {
<-b.complete <-b.complete
return true return true
} }
// WaitTimeout takes a time.Duration to wait for the flow associated with the // WaitTimeout implements the Token WaitTimeout method.
// Token to complete, returns true if it returned before the timeout or
// returns false if the timeout occurred. In the case of a timeout the Token
// does not have an error set in case the caller wishes to wait again
func (b *baseToken) WaitTimeout(d time.Duration) bool { func (b *baseToken) WaitTimeout(d time.Duration) bool {
b.m.Lock()
defer b.m.Unlock()
timer := time.NewTimer(d) timer := time.NewTimer(d)
select { select {
case <-b.complete: case <-b.complete:
@ -80,6 +89,11 @@ func (b *baseToken) WaitTimeout(d time.Duration) bool {
return false return false
} }
// Done implements the Token Done method.
func (b *baseToken) Done() <-chan struct{} {
return b.complete
}
func (b *baseToken) flowComplete() { func (b *baseToken) flowComplete() {
select { select {
case <-b.complete: case <-b.complete:
@ -125,7 +139,7 @@ type ConnectToken struct {
sessionPresent bool sessionPresent bool
} }
// ReturnCode returns the acknowlegement code in the connack sent // ReturnCode returns the acknowledgement code in the connack sent
// in response to a Connect() // in response to a Connect()
func (c *ConnectToken) ReturnCode() byte { func (c *ConnectToken) ReturnCode() byte {
c.m.RLock() c.m.RLock()
@ -160,6 +174,7 @@ type SubscribeToken struct {
baseToken baseToken
subs []string subs []string
subResult map[string]byte subResult map[string]byte
messageID uint16
} }
// Result returns a map of topics that were subscribed to along with // Result returns a map of topics that were subscribed to along with
@ -175,6 +190,7 @@ func (s *SubscribeToken) Result() map[string]byte {
// required to provide information about calls to Unsubscribe() // required to provide information about calls to Unsubscribe()
type UnsubscribeToken struct { type UnsubscribeToken struct {
baseToken baseToken
messageID uint16
} }
// DisconnectToken is an extension of Token containing the extra fields // DisconnectToken is an extension of Token containing the extra fields

View File

@ -19,18 +19,18 @@ import (
"strings" "strings"
) )
//ErrInvalidQos is the error returned when an packet is to be sent // ErrInvalidQos is the error returned when an packet is to be sent
//with an invalid Qos value // with an invalid Qos value
var ErrInvalidQos = errors.New("Invalid QoS") var ErrInvalidQos = errors.New("invalid QoS")
//ErrInvalidTopicEmptyString is the error returned when a topic string // ErrInvalidTopicEmptyString is the error returned when a topic string
//is passed in that is 0 length // is passed in that is 0 length
var ErrInvalidTopicEmptyString = errors.New("Invalid Topic; empty string") var ErrInvalidTopicEmptyString = errors.New("invalid Topic; empty string")
//ErrInvalidTopicMultilevel is the error returned when a topic string // ErrInvalidTopicMultilevel is the error returned when a topic string
//is passed in that has the multi level wildcard in any position but // is passed in that has the multi level wildcard in any position but
//the last // the last
var ErrInvalidTopicMultilevel = errors.New("Invalid Topic; multi-level wildcard must be last level") var ErrInvalidTopicMultilevel = errors.New("invalid Topic; multi-level wildcard must be last level")
// Topic Names and Topic Filters // Topic Names and Topic Filters
// The MQTT v3.1.1 spec clarifies a number of ambiguities with regard // The MQTT v3.1.1 spec clarifies a number of ambiguities with regard
@ -46,10 +46,14 @@ var ErrInvalidTopicMultilevel = errors.New("Invalid Topic; multi-level wildcard
// - A TopicName may not contain a wildcard. // - A TopicName may not contain a wildcard.
// - A TopicFilter may only have a # (multi-level) wildcard as the last level. // - A TopicFilter may only have a # (multi-level) wildcard as the last level.
// - A TopicFilter may contain any number of + (single-level) wildcards. // - A TopicFilter may contain any number of + (single-level) wildcards.
// - A TopicFilter with a # will match the absense of a level // - A TopicFilter with a # will match the absence of a level
// Example: a subscription to "foo/#" will match messages published to "foo". // Example: a subscription to "foo/#" will match messages published to "foo".
func validateSubscribeMap(subs map[string]byte) ([]string, []byte, error) { func validateSubscribeMap(subs map[string]byte) ([]string, []byte, error) {
if len(subs) == 0 {
return nil, nil, errors.New("invalid subscription; subscribe map must not be empty")
}
var topics []string var topics []string
var qoss []byte var qoss []byte
for topic, qos := range subs { for topic, qos := range subs {
@ -75,7 +79,7 @@ func validateTopicAndQos(topic string, qos byte) error {
} }
} }
if qos < 0 || qos > 2 { if qos > 2 {
return ErrInvalidQos return ErrInvalidQos
} }
return nil return nil

View File

@ -27,7 +27,7 @@ type (
NOOPLogger struct{} NOOPLogger struct{}
) )
func (NOOPLogger) Println(v ...interface{}) {} func (NOOPLogger) Println(v ...interface{}) {}
func (NOOPLogger) Printf(format string, v ...interface{}) {} func (NOOPLogger) Printf(format string, v ...interface{}) {}
// Internal levels of library output that are initialised to not print // Internal levels of library output that are initialised to not print

109
vendor/github.com/eclipse/paho.mqtt.golang/websocket.go generated vendored Normal file
View File

@ -0,0 +1,109 @@
package mqtt
import (
"crypto/tls"
"io"
"net"
"net/http"
"sync"
"time"
"github.com/gorilla/websocket"
)
// WebsocketOptions are config options for a websocket dialer
type WebsocketOptions struct {
ReadBufferSize int
WriteBufferSize int
}
// NewWebsocket returns a new websocket and returns a net.Conn compatible interface using the gorilla/websocket package
func NewWebsocket(host string, tlsc *tls.Config, timeout time.Duration, requestHeader http.Header, options *WebsocketOptions) (net.Conn, error) {
if timeout == 0 {
timeout = 10 * time.Second
}
if options == nil {
// Apply default options
options = &WebsocketOptions{}
}
dialer := &websocket.Dialer{
Proxy: http.ProxyFromEnvironment,
HandshakeTimeout: timeout,
EnableCompression: false,
TLSClientConfig: tlsc,
Subprotocols: []string{"mqtt"},
ReadBufferSize: options.ReadBufferSize,
WriteBufferSize: options.WriteBufferSize,
}
ws, _, err := dialer.Dial(host, requestHeader)
if err != nil {
return nil, err
}
wrapper := &websocketConnector{
Conn: ws,
}
return wrapper, err
}
// websocketConnector is a websocket wrapper so it satisfies the net.Conn interface so it is a
// drop in replacement of the golang.org/x/net/websocket package.
// Implementation guide taken from https://github.com/gorilla/websocket/issues/282
type websocketConnector struct {
*websocket.Conn
r io.Reader
rio sync.Mutex
wio sync.Mutex
}
// SetDeadline sets both the read and write deadlines
func (c *websocketConnector) SetDeadline(t time.Time) error {
if err := c.SetReadDeadline(t); err != nil {
return err
}
err := c.SetWriteDeadline(t)
return err
}
// Write writes data to the websocket
func (c *websocketConnector) Write(p []byte) (int, error) {
c.wio.Lock()
defer c.wio.Unlock()
err := c.WriteMessage(websocket.BinaryMessage, p)
if err != nil {
return 0, err
}
return len(p), nil
}
// Read reads the current websocket frame
func (c *websocketConnector) Read(p []byte) (int, error) {
c.rio.Lock()
defer c.rio.Unlock()
for {
if c.r == nil {
// Advance to next message.
var err error
_, c.r, err = c.NextReader()
if err != nil {
return 0, err
}
}
n, err := c.r.Read(p)
if err == io.EOF {
// At end of message.
c.r = nil
if n > 0 {
return n, nil
}
// No data read, continue to next message.
continue
}
return n, err
}
}

View File

@ -765,7 +765,7 @@ func unescape(s string) (ch string, tail string, err error) {
if i > utf8.MaxRune { if i > utf8.MaxRune {
return "", "", fmt.Errorf(`\%c%s is not a valid Unicode code point`, r, ss) return "", "", fmt.Errorf(`\%c%s is not a valid Unicode code point`, r, ss)
} }
return string(i), s, nil return string(rune(i)), s, nil
} }
return "", "", fmt.Errorf(`unknown escape \%c`, r) return "", "", fmt.Errorf(`unknown escape \%c`, r)
} }

View File

@ -173,3 +173,5 @@
defend, and hold each Contributor harmless for any liability defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability. of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -75,17 +75,27 @@ type DialOption struct {
} }
type dialOptions struct { type dialOptions struct {
readTimeout time.Duration readTimeout time.Duration
writeTimeout time.Duration writeTimeout time.Duration
dialer *net.Dialer tlsHandshakeTimeout time.Duration
dialContext func(ctx context.Context, network, addr string) (net.Conn, error) dialer *net.Dialer
db int dialContext func(ctx context.Context, network, addr string) (net.Conn, error)
username string db int
password string username string
clientName string password string
useTLS bool clientName string
skipVerify bool useTLS bool
tlsConfig *tls.Config skipVerify bool
tlsConfig *tls.Config
}
// DialTLSHandshakeTimeout specifies the maximum amount of time waiting to
// wait for a TLS handshake. Zero means no timeout.
// If no DialTLSHandshakeTimeout option is specified then the default is 30 seconds.
func DialTLSHandshakeTimeout(d time.Duration) DialOption {
return DialOption{func(do *dialOptions) {
do.tlsHandshakeTimeout = d
}}
} }
// DialReadTimeout specifies the timeout for reading a single command reply. // DialReadTimeout specifies the timeout for reading a single command reply.
@ -104,6 +114,7 @@ func DialWriteTimeout(d time.Duration) DialOption {
// DialConnectTimeout specifies the timeout for connecting to the Redis server when // DialConnectTimeout specifies the timeout for connecting to the Redis server when
// no DialNetDial option is specified. // no DialNetDial option is specified.
// If no DialConnectTimeout option is specified then the default is 30 seconds.
func DialConnectTimeout(d time.Duration) DialOption { func DialConnectTimeout(d time.Duration) DialOption {
return DialOption{func(do *dialOptions) { return DialOption{func(do *dialOptions) {
do.dialer.Timeout = d do.dialer.Timeout = d
@ -201,13 +212,21 @@ func Dial(network, address string, options ...DialOption) (Conn, error) {
return DialContext(context.Background(), network, address, options...) return DialContext(context.Background(), network, address, options...)
} }
type tlsHandshakeTimeoutError struct{}
func (tlsHandshakeTimeoutError) Timeout() bool { return true }
func (tlsHandshakeTimeoutError) Temporary() bool { return true }
func (tlsHandshakeTimeoutError) Error() string { return "TLS handshake timeout" }
// DialContext connects to the Redis server at the given network and // DialContext connects to the Redis server at the given network and
// address using the specified options and context. // address using the specified options and context.
func DialContext(ctx context.Context, network, address string, options ...DialOption) (Conn, error) { func DialContext(ctx context.Context, network, address string, options ...DialOption) (Conn, error) {
do := dialOptions{ do := dialOptions{
dialer: &net.Dialer{ dialer: &net.Dialer{
Timeout: time.Second * 30,
KeepAlive: time.Minute * 5, KeepAlive: time.Minute * 5,
}, },
tlsHandshakeTimeout: time.Second * 10,
} }
for _, option := range options { for _, option := range options {
option.f(&do) option.f(&do)
@ -238,10 +257,22 @@ func DialContext(ctx context.Context, network, address string, options ...DialOp
} }
tlsConn := tls.Client(netConn, tlsConfig) tlsConn := tls.Client(netConn, tlsConfig)
if err := tlsConn.Handshake(); err != nil { errc := make(chan error, 2) // buffered so we don't block timeout or Handshake
netConn.Close() if d := do.tlsHandshakeTimeout; d != 0 {
timer := time.AfterFunc(d, func() {
errc <- tlsHandshakeTimeoutError{}
})
defer timer.Stop()
}
go func() {
errc <- tlsConn.Handshake()
}()
if err := <-errc; err != nil {
// Timeout or Handshake error.
netConn.Close() // nolint: errcheck
return nil, err return nil, err
} }
netConn = tlsConn netConn = tlsConn
} }

View File

@ -23,7 +23,6 @@ import (
"io" "io"
"strconv" "strconv"
"sync" "sync"
"sync/atomic"
"time" "time"
) )
@ -161,11 +160,10 @@ type Pool struct {
// the pool does not close connections based on age. // the pool does not close connections based on age.
MaxConnLifetime time.Duration MaxConnLifetime time.Duration
chInitialized uint32 // set to 1 when field ch is initialized
mu sync.Mutex // mu protects the following fields mu sync.Mutex // mu protects the following fields
closed bool // set to true when the pool is closed. closed bool // set to true when the pool is closed.
active int // the number of open connections in the pool active int // the number of open connections in the pool
initOnce sync.Once // the init ch once func
ch chan struct{} // limits open connections when p.Wait is true ch chan struct{} // limits open connections when p.Wait is true
idle idleList // idle connections idle idleList // idle connections
waitCount int64 // total number of connections waited for. waitCount int64 // total number of connections waited for.
@ -202,7 +200,7 @@ func (p *Pool) GetContext(ctx context.Context) (Conn, error) {
// Wait until there is a vacant connection in the pool. // Wait until there is a vacant connection in the pool.
waited, err := p.waitVacantConn(ctx) waited, err := p.waitVacantConn(ctx)
if err != nil { if err != nil {
return nil, err return errorConn{err}, err
} }
p.mu.Lock() p.mu.Lock()
@ -339,13 +337,7 @@ func (p *Pool) Close() error {
} }
func (p *Pool) lazyInit() { func (p *Pool) lazyInit() {
// Fast path. p.initOnce.Do(func() {
if atomic.LoadUint32(&p.chInitialized) == 1 {
return
}
// Slow path.
p.mu.Lock()
if p.chInitialized == 0 {
p.ch = make(chan struct{}, p.MaxActive) p.ch = make(chan struct{}, p.MaxActive)
if p.closed { if p.closed {
close(p.ch) close(p.ch)
@ -354,9 +346,7 @@ func (p *Pool) lazyInit() {
p.ch <- struct{}{} p.ch <- struct{}{}
} }
} }
atomic.StoreUint32(&p.chInitialized, 1) })
}
p.mu.Unlock()
} }
// waitVacantConn waits for a vacant connection in pool if waiting // waitVacantConn waits for a vacant connection in pool if waiting
@ -381,21 +371,18 @@ func (p *Pool) waitVacantConn(ctx context.Context) (waited time.Duration, err er
start = time.Now() start = time.Now()
} }
if ctx == nil { select {
<-p.ch case <-p.ch:
} else { // Additionally check that context hasn't expired while we were waiting,
// because `select` picks a random `case` if several of them are "ready".
select { select {
case <-p.ch:
// Additionally check that context hasn't expired while we were waiting,
// because `select` picks a random `case` if several of them are "ready".
select {
case <-ctx.Done():
return 0, ctx.Err()
default:
}
case <-ctx.Done(): case <-ctx.Done():
p.ch <- struct{}{}
return 0, ctx.Err() return 0, ctx.Err()
default:
} }
case <-ctx.Done():
return 0, ctx.Err()
} }
if wait { if wait {

View File

@ -118,7 +118,7 @@ func Uint64(reply interface{}, err error) (uint64, error) {
// Float64 is a helper that converts a command reply to 64 bit float. If err is // Float64 is a helper that converts a command reply to 64 bit float. If err is
// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts // not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts
// the reply to an int as follows: // the reply to a float64 as follows:
// //
// Reply type Result // Reply type Result
// bulk string parsed reply, nil // bulk string parsed reply, nil
@ -348,7 +348,7 @@ func Int64s(reply interface{}, err error) ([]int64, error) {
return result, err return result, err
} }
// Ints is a helper that converts an array command reply to a []in. // Ints is a helper that converts an array command reply to a []int.
// If err is not equal to nil, then Ints returns nil, err. Nil array // If err is not equal to nil, then Ints returns nil, err. Nil array
// items are stay nil. Ints returns an error if an array item is not a // items are stay nil. Ints returns an error if an array item is not a
// bulk string or nil. // bulk string or nil.

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