Update dependencies

This commit is contained in:
Ingo Oppermann 2022-10-28 17:24:57 +02:00
parent 4334105f95
commit 4cc82dd333
No known key found for this signature in database
GPG key ID: 2AB32426E9DD229E
273 changed files with 19686 additions and 3612 deletions

View file

@ -669,20 +669,6 @@ func (a *api) start() error {
} }
certmagic.Default.DefaultServerName = cfg.Host.Name[0] certmagic.Default.DefaultServerName = cfg.Host.Name[0]
certmagic.Default.Logger = nil certmagic.Default.Logger = nil
certmagic.Default.OnEvent = func(event string, data interface{}) {
message := ""
switch data := data.(type) {
case string:
message = data
case fmt.Stringer:
message = data.String()
}
if len(message) != 0 {
a.log.logger.core.WithComponent("certmagic").Info().WithField("event", event).Log(message)
}
}
magic := certmagic.NewDefault() magic := certmagic.NewDefault()
acme := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME) acme := certmagic.NewACMEIssuer(magic, certmagic.DefaultACME)

View file

@ -3837,7 +3837,7 @@ const docTemplate = `{
"description": "The total number of received KM (Key Material) control packets", "description": "The total number of received KM (Key Material) control packets",
"type": "integer" "type": "integer"
}, },
"recv_loss__bytes": { "recv_loss_bytes": {
"description": "Same as pktRcvLoss, but expressed in bytes, including payload and all the headers (IP, TCP, SRT), bytes for the presently missing (either reordered or lost) packets' payloads are estimated based on the average packet size", "description": "Same as pktRcvLoss, but expressed in bytes, including payload and all the headers (IP, TCP, SRT), bytes for the presently missing (either reordered or lost) packets' payloads are estimated based on the average packet size",
"type": "integer" "type": "integer"
}, },
@ -3945,7 +3945,7 @@ const docTemplate = `{
"description": "The total number of retransmitted packets sent by the SRT sender", "description": "The total number of retransmitted packets sent by the SRT sender",
"type": "integer" "type": "integer"
}, },
"sent_unique__bytes": { "sent_unique_bytes": {
"description": "Same as pktSentUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)", "description": "Same as pktSentUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)",
"type": "integer" "type": "integer"
}, },

View file

@ -3829,7 +3829,7 @@
"description": "The total number of received KM (Key Material) control packets", "description": "The total number of received KM (Key Material) control packets",
"type": "integer" "type": "integer"
}, },
"recv_loss__bytes": { "recv_loss_bytes": {
"description": "Same as pktRcvLoss, but expressed in bytes, including payload and all the headers (IP, TCP, SRT), bytes for the presently missing (either reordered or lost) packets' payloads are estimated based on the average packet size", "description": "Same as pktRcvLoss, but expressed in bytes, including payload and all the headers (IP, TCP, SRT), bytes for the presently missing (either reordered or lost) packets' payloads are estimated based on the average packet size",
"type": "integer" "type": "integer"
}, },
@ -3937,7 +3937,7 @@
"description": "The total number of retransmitted packets sent by the SRT sender", "description": "The total number of retransmitted packets sent by the SRT sender",
"type": "integer" "type": "integer"
}, },
"sent_unique__bytes": { "sent_unique_bytes": {
"description": "Same as pktSentUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)", "description": "Same as pktSentUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)",
"type": "integer" "type": "integer"
}, },

View file

@ -979,7 +979,7 @@ definitions:
recv_km_pkt: recv_km_pkt:
description: The total number of received KM (Key Material) control packets description: The total number of received KM (Key Material) control packets
type: integer type: integer
recv_loss__bytes: recv_loss_bytes:
description: Same as pktRcvLoss, but expressed in bytes, including payload description: Same as pktRcvLoss, but expressed in bytes, including payload
and all the headers (IP, TCP, SRT), bytes for the presently missing (either and all the headers (IP, TCP, SRT), bytes for the presently missing (either
reordered or lost) packets' payloads are estimated based on the average reordered or lost) packets' payloads are estimated based on the average
@ -1088,7 +1088,7 @@ definitions:
sent_retrans_pkt: sent_retrans_pkt:
description: The total number of retransmitted packets sent by the SRT sender description: The total number of retransmitted packets sent by the SRT sender
type: integer type: integer
sent_unique__bytes: sent_unique_bytes:
description: Same as pktSentUnique, but expressed in bytes, including payload description: Same as pktSentUnique, but expressed in bytes, including payload
and all the headers (IP, TCP, SRT) and all the headers (IP, TCP, SRT)
type: integer type: integer

54
go.mod
View file

@ -3,29 +3,29 @@ module github.com/datarhei/core/v16
go 1.18 go 1.18
require ( require (
github.com/99designs/gqlgen v0.17.16 github.com/99designs/gqlgen v0.17.20
github.com/atrox/haikunatorgo/v2 v2.0.1 github.com/atrox/haikunatorgo/v2 v2.0.1
github.com/caddyserver/certmagic v0.16.2 github.com/caddyserver/certmagic v0.17.2
github.com/datarhei/gosrt v0.2.1-0.20220817080252-d44df04a3845 github.com/datarhei/gosrt v0.3.1
github.com/datarhei/joy4 v0.0.0-20220914170649-23c70d207759 github.com/datarhei/joy4 v0.0.0-20220914170649-23c70d207759
github.com/go-playground/validator/v10 v10.11.0 github.com/go-playground/validator/v10 v10.11.1
github.com/gobwas/glob v0.2.3 github.com/gobwas/glob v0.2.3
github.com/golang-jwt/jwt/v4 v4.4.2 github.com/golang-jwt/jwt/v4 v4.4.2
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/invopop/jsonschema v0.4.0 github.com/invopop/jsonschema v0.4.0
github.com/joho/godotenv v1.4.0 github.com/joho/godotenv v1.4.0
github.com/labstack/echo/v4 v4.9.0 github.com/labstack/echo/v4 v4.9.1
github.com/lithammer/shortuuid/v4 v4.0.0 github.com/lithammer/shortuuid/v4 v4.0.0
github.com/mattn/go-isatty v0.0.16 github.com/mattn/go-isatty v0.0.16
github.com/prep/average v0.0.0-20200506183628-d26c465f48c3 github.com/prep/average v0.0.0-20200506183628-d26c465f48c3
github.com/prometheus/client_golang v1.13.0 github.com/prometheus/client_golang v1.13.0
github.com/shirou/gopsutil/v3 v3.22.8 github.com/shirou/gopsutil/v3 v3.22.9
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.1
github.com/swaggo/echo-swagger v1.3.4 github.com/swaggo/echo-swagger v1.3.5
github.com/swaggo/swag v1.8.5 github.com/swaggo/swag v1.8.7
github.com/vektah/gqlparser/v2 v2.5.0 github.com/vektah/gqlparser/v2 v2.5.1
github.com/xeipuuv/gojsonschema v1.2.0 github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 golang.org/x/mod v0.6.0
) )
require ( require (
@ -49,20 +49,20 @@ require (
github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/iancoleman/orderedmap v0.2.0 // indirect github.com/iancoleman/orderedmap v0.2.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/cpuid/v2 v2.0.11 // indirect github.com/klauspost/cpuid/v2 v2.1.2 // indirect
github.com/labstack/gommon v0.3.1 // indirect github.com/labstack/gommon v0.4.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/libdns/libdns v0.2.1 // indirect github.com/libdns/libdns v0.2.1 // indirect
github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281 // indirect github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mholt/acmez v1.0.4 // indirect github.com/mholt/acmez v1.0.4 // indirect
github.com/miekg/dns v1.1.46 // indirect github.com/miekg/dns v1.1.50 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
@ -71,20 +71,20 @@ require (
github.com/tklauser/numcpus v0.5.0 // indirect github.com/tklauser/numcpus v0.5.0 // indirect
github.com/urfave/cli/v2 v2.8.1 // indirect github.com/urfave/cli/v2 v2.8.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.uber.org/atomic v1.7.0 // indirect go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.6.0 // indirect go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.21.0 // indirect go.uber.org/zap v1.23.0 // indirect
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect golang.org/x/crypto v0.1.0 // indirect
golang.org/x/net v0.0.0-20220907135653-1e95f45603a7 // indirect golang.org/x/net v0.1.0 // indirect
golang.org/x/sys v0.0.0-20220907062415-87db552b00fd // indirect golang.org/x/sys v0.1.0 // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.4.0 // indirect
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect golang.org/x/time v0.1.0 // indirect
golang.org/x/tools v0.1.12 // indirect golang.org/x/tools v0.2.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

111
go.sum
View file

@ -31,8 +31,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/99designs/gqlgen v0.17.16 h1:tTIw/cQ/uvf3iXIb2I6YSkdaDkmHmH2W2eZkVe0IVLA= github.com/99designs/gqlgen v0.17.20 h1:O7WzccIhKB1dm+7g6dhQcULINftfiLSBg2l/mwbpJMw=
github.com/99designs/gqlgen v0.17.16/go.mod h1:dnJdUkgfh8iw8CEx2hhTdgTQO/GvVWKLcm/kult5gwI= github.com/99designs/gqlgen v0.17.20/go.mod h1:Mja2HI23kWT1VRH09hvWshFgOzKswpO20o4ScpJIES4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@ -63,8 +63,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/caddyserver/certmagic v0.16.2 h1:k2n3LkkUG3aMUK/kckMuF9/0VFo+0FtMX3drPYESbmQ= github.com/caddyserver/certmagic v0.17.2 h1:o30seC1T/dBqBCNNGNHWwj2i5/I/FMjBbTAhjADP3nE=
github.com/caddyserver/certmagic v0.16.2/go.mod h1:PgLIr/dSJa+WA7t7z6Je5xuS/e5A/GFCPHRuZ1QP+MQ= github.com/caddyserver/certmagic v0.17.2/go.mod h1:ouWUuC490GOLJzkyN35eXfV8bSbwMwSf4bdhkIxtdQE=
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/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
@ -78,8 +78,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/datarhei/gosrt v0.2.1-0.20220817080252-d44df04a3845 h1:nlVb4EVMwdVUwH6e10WZrx4lW0n2utnlE+4ILMPyD5o= github.com/datarhei/gosrt v0.3.1 h1:9A75hIvnY74IUFyeguqYXh1lsGF8Qt8fjxJS2Ewr12Q=
github.com/datarhei/gosrt v0.2.1-0.20220817080252-d44df04a3845/go.mod h1:wyoTu+DG45XRuCgEq/y+R8nhZCrJbOyQKn+SwNrNVZ8= github.com/datarhei/gosrt v0.3.1/go.mod h1:M2nl2WPrawncUc1FtUBK6gZX4tpZRC7FqL8NjOdBZV0=
github.com/datarhei/joy4 v0.0.0-20220914170649-23c70d207759 h1:h8NyekuQSDvLIsZVTV172m5/RVArXkEM/cnHaUzszQU= github.com/datarhei/joy4 v0.0.0-20220914170649-23c70d207759 h1:h8NyekuQSDvLIsZVTV172m5/RVArXkEM/cnHaUzszQU=
github.com/datarhei/joy4 v0.0.0-20220914170649-23c70d207759/go.mod h1:Jcw/6jZDQQmPx8A7INEkXmuEF7E9jjBbSTfVSLwmiQw= github.com/datarhei/joy4 v0.0.0-20220914170649-23c70d207759/go.mod h1:Jcw/6jZDQQmPx8A7INEkXmuEF7E9jjBbSTfVSLwmiQw=
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=
@ -124,8 +124,8 @@ github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
@ -174,8 +174,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -220,8 +220,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM= github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.11 h1:i2lw1Pm7Yi/4O6XCSyJWqEHI2MDw2FzUK6o/D21xn2A= github.com/klauspost/cpuid/v2 v2.1.2 h1:XhdX4fqAJUA0yj+kUwMavO0hHrSPAecYdYf1ZmxHvak=
github.com/klauspost/cpuid/v2 v2.0.11/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.1.2/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@ -233,11 +233,12 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
github.com/labstack/echo/v4 v4.9.0 h1:wPOF1CE6gvt/kmbMR4dGzWvHMPT+sAEUJOwOTtvITVY=
github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= github.com/labstack/echo/v4 v4.9.0/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y=
github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo=
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis= github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis=
@ -246,8 +247,8 @@ github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y= github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc= github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281 h1:aczX6NMOtt6L4YT0fQvKkDK6LZEtdOso9sUH89V1+P0= github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c h1:VtwQ41oftZwlMnOEbMWQtSEUgU64U4s+GHk7hZK+jtY=
github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281/go.mod h1:lc+czkgO/8F7puNki5jk8QyujbfK1LOT7Wl0ON2hxyk= github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
@ -255,18 +256,18 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk= github.com/matryer/moq v0.2.7/go.mod h1:kITsx543GOENm48TUAQyJ9+SAvFSr7iGQXPoth/VUBk=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mholt/acmez v1.0.4 h1:N3cE4Pek+dSolbsofIkAYz6H1d3pE+2G0os7QHslf80= github.com/mholt/acmez v1.0.4 h1:N3cE4Pek+dSolbsofIkAYz6H1d3pE+2G0os7QHslf80=
github.com/mholt/acmez v1.0.4/go.mod h1:qFGLZ4u+ehWINeJZjzPlsnjJBCPAADWTcIqE/7DAYQY= github.com/mholt/acmez v1.0.4/go.mod h1:qFGLZ4u+ehWINeJZjzPlsnjJBCPAADWTcIqE/7DAYQY=
github.com/miekg/dns v1.1.46 h1:uzwpxRtSVxtcIZmz/4Uz6/Rn7G11DvsaslXoy5LxQio= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.46/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@ -306,8 +307,9 @@ github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
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/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
@ -330,8 +332,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shirou/gopsutil/v3 v3.22.8 h1:a4s3hXogo5mE2PfdfJIonDbstO/P+9JszdfhAHSzD9Y= github.com/shirou/gopsutil/v3 v3.22.9 h1:yibtJhIVEMcdw+tCTbOPiF1VcsuDeTE4utJ8Dm4c5eA=
github.com/shirou/gopsutil/v3 v3.22.8/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI= github.com/shirou/gopsutil/v3 v3.22.9/go.mod h1:bBYl1kjgEJpWpxeHmLI+dVHWtyAwfcmSBLDsp2TNT8A=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@ -341,6 +343,7 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
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/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@ -348,16 +351,16 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/swaggo/echo-swagger v1.3.4 h1:8B+yVqjVm7cMy4QBLRUuRaOzrTVAqZahcrgrOSdpC5I= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/swaggo/echo-swagger v1.3.4/go.mod h1:vh8QAdbHtTXwTSaWzc1Nby7zMYJd/g0FwQyArmrFHA8= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/swaggo/echo-swagger v1.3.5 h1:kCx1wvX5AKhjI6Ykt48l3PTsfL9UD40ZROOx/tYzWyY=
github.com/swaggo/echo-swagger v1.3.5/go.mod h1:3IMHd2Z8KftdWFEEjGmv6QpWj370LwMCOfovuh7vF34=
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY= github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
github.com/swaggo/swag v1.8.5 h1:7NgtfXsXE+jrcOwRyiftGKW7Ppydj7tZiVenuRf1fE4= github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU=
github.com/swaggo/swag v1.8.5/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg= github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
@ -368,10 +371,11 @@ github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4=
github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY= github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vektah/gqlparser/v2 v2.5.0 h1:GwEwy7AJsqPWrey0bHnn+3JLaHLZVT66wY/+O+Tf9SU= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/vektah/gqlparser/v2 v2.5.0/go.mod h1:mPgqFBu/woKTVYWyNk8cO3kh4S/f4aRFZrvOnp3hmCs= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vektah/gqlparser/v2 v2.5.1 h1:ZGu+bquAY23jsxDRcYpWjttRZrUz07LbiY77gUOHcr4=
github.com/vektah/gqlparser/v2 v2.5.1/go.mod h1:mPgqFBu/woKTVYWyNk8cO3kh4S/f4aRFZrvOnp3hmCs=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@ -387,6 +391,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@ -394,14 +399,17 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -412,9 +420,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -447,8 +454,9 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -489,8 +497,9 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220907135653-1e95f45603a7 h1:1WGATo9HAhkWMbfyuVU0tEFP88OIkUvwaHFveQPvzCQ= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220907135653-1e95f45603a7/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
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/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -509,6 +518,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -562,25 +572,29 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220907062415-87db552b00fd h1:AZeIEzg+8RCELJYq8w+ODLVxFgLMMigSwO/ffKPEd9U= golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.0.0-20220907062415-87db552b00fd/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 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/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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=
@ -626,8 +640,9 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View file

@ -33,9 +33,9 @@ type SRTStatistics struct {
ByteSent uint64 `json:"sent_bytes"` // Same as pktSent, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) ByteSent uint64 `json:"sent_bytes"` // Same as pktSent, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecv uint64 `json:"recv_bytes"` // Same as pktRecv, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) ByteRecv uint64 `json:"recv_bytes"` // Same as pktRecv, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteSentUnique uint64 `json:"sent_unique__bytes"` // Same as pktSentUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) ByteSentUnique uint64 `json:"sent_unique_bytes"` // Same as pktSentUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecvUnique uint64 `json:"recv_unique_bytes"` // Same as pktRecvUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) ByteRecvUnique uint64 `json:"recv_unique_bytes"` // Same as pktRecvUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRcvLoss uint64 `json:"recv_loss__bytes"` // Same as pktRcvLoss, but expressed in bytes, including payload and all the headers (IP, TCP, SRT), bytes for the presently missing (either reordered or lost) packets' payloads are estimated based on the average packet size ByteRcvLoss uint64 `json:"recv_loss_bytes"` // Same as pktRcvLoss, but expressed in bytes, including payload and all the headers (IP, TCP, SRT), bytes for the presently missing (either reordered or lost) packets' payloads are estimated based on the average packet size
ByteRetrans uint64 `json:"sent_retrans_bytes"` // Same as pktRetrans, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) ByteRetrans uint64 `json:"sent_retrans_bytes"` // Same as pktRetrans, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteSndDrop uint64 `json:"send_drop_bytes"` // Same as pktSndDrop, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) ByteSndDrop uint64 `json:"send_drop_bytes"` // Same as pktSndDrop, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRcvDrop uint64 `json:"recv_drop_bytes"` // Same as pktRcvDrop, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) ByteRcvDrop uint64 `json:"recv_drop_bytes"` // Same as pktRcvDrop, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
@ -68,54 +68,54 @@ type SRTStatistics struct {
func (s *SRTStatistics) Unmarshal(ss *gosrt.Statistics) { func (s *SRTStatistics) Unmarshal(ss *gosrt.Statistics) {
s.MsTimeStamp = ss.MsTimeStamp s.MsTimeStamp = ss.MsTimeStamp
s.PktSent = ss.PktSent s.PktSent = ss.Accumulated.PktSent
s.PktRecv = ss.PktRecv s.PktRecv = ss.Accumulated.PktRecv
s.PktSentUnique = ss.PktSentUnique s.PktSentUnique = ss.Accumulated.PktSentUnique
s.PktRecvUnique = ss.PktRecvUnique s.PktRecvUnique = ss.Accumulated.PktRecvUnique
s.PktSndLoss = ss.PktSndLoss s.PktSndLoss = ss.Accumulated.PktSendLoss
s.PktRcvLoss = ss.PktRcvLoss s.PktRcvLoss = ss.Accumulated.PktRecvLoss
s.PktRetrans = ss.PktRetrans s.PktRetrans = ss.Accumulated.PktRetrans
s.PktRcvRetrans = ss.PktRcvRetrans s.PktRcvRetrans = ss.Accumulated.PktRecvRetrans
s.PktSentACK = ss.PktSentACK s.PktSentACK = ss.Accumulated.PktSentACK
s.PktRecvACK = ss.PktRecvACK s.PktRecvACK = ss.Accumulated.PktRecvACK
s.PktSentNAK = ss.PktSentNAK s.PktSentNAK = ss.Accumulated.PktSentNAK
s.PktRecvNAK = ss.PktRecvNAK s.PktRecvNAK = ss.Accumulated.PktRecvNAK
s.PktSentKM = ss.PktSentKM s.PktSentKM = ss.Accumulated.PktSentKM
s.PktRecvKM = ss.PktRecvKM s.PktRecvKM = ss.Accumulated.PktRecvKM
s.UsSndDuration = ss.UsSndDuration s.UsSndDuration = ss.Accumulated.UsSndDuration
s.PktSndDrop = ss.PktSndDrop s.PktSndDrop = ss.Accumulated.PktSendDrop
s.PktRcvDrop = ss.PktRcvDrop s.PktRcvDrop = ss.Accumulated.PktRecvDrop
s.PktRcvUndecrypt = ss.PktRcvUndecrypt s.PktRcvUndecrypt = ss.Accumulated.PktRecvUndecrypt
s.ByteSent = ss.ByteSent s.ByteSent = ss.Accumulated.ByteSent
s.ByteRecv = ss.ByteRecv s.ByteRecv = ss.Accumulated.ByteRecv
s.ByteSentUnique = ss.ByteSentUnique s.ByteSentUnique = ss.Accumulated.ByteSentUnique
s.ByteRecvUnique = ss.ByteRecvUnique s.ByteRecvUnique = ss.Accumulated.ByteRecvUnique
s.ByteRcvLoss = ss.ByteRcvLoss s.ByteRcvLoss = ss.Accumulated.ByteRecvLoss
s.ByteRetrans = ss.ByteRetrans s.ByteRetrans = ss.Accumulated.ByteRetrans
s.ByteSndDrop = ss.ByteSndDrop s.ByteSndDrop = ss.Accumulated.ByteSendDrop
s.ByteRcvDrop = ss.ByteRcvDrop s.ByteRcvDrop = ss.Accumulated.ByteRecvDrop
s.ByteRcvUndecrypt = ss.ByteRcvUndecrypt s.ByteRcvUndecrypt = ss.Accumulated.ByteRecvUndecrypt
s.UsPktSndPeriod = ss.UsPktSndPeriod s.UsPktSndPeriod = ss.Instantaneous.UsPktSendPeriod
s.PktFlowWindow = ss.PktFlowWindow s.PktFlowWindow = ss.Instantaneous.PktFlowWindow
s.PktFlightSize = ss.PktFlightSize s.PktFlightSize = ss.Instantaneous.PktFlightSize
s.MsRTT = ss.MsRTT s.MsRTT = ss.Instantaneous.MsRTT
s.MbpsBandwidth = ss.MbpsBandwidth s.MbpsBandwidth = ss.Instantaneous.MbpsLinkCapacity
s.ByteAvailSndBuf = ss.ByteAvailSndBuf s.ByteAvailSndBuf = ss.Instantaneous.ByteAvailSendBuf
s.ByteAvailRcvBuf = ss.ByteAvailRcvBuf s.ByteAvailRcvBuf = ss.Instantaneous.ByteAvailRecvBuf
s.MbpsMaxBW = ss.MbpsMaxBW s.MbpsMaxBW = ss.Instantaneous.MbpsMaxBW
s.ByteMSS = ss.ByteMSS s.ByteMSS = ss.Instantaneous.ByteMSS
s.PktSndBuf = ss.PktSndBuf s.PktSndBuf = ss.Instantaneous.PktSendBuf
s.ByteSndBuf = ss.ByteSndBuf s.ByteSndBuf = ss.Instantaneous.ByteSendBuf
s.MsSndBuf = ss.MsSndBuf s.MsSndBuf = ss.Instantaneous.MsSendBuf
s.MsSndTsbPdDelay = ss.MsSndTsbPdDelay s.MsSndTsbPdDelay = ss.Instantaneous.MsSendTsbPdDelay
s.PktRcvBuf = ss.PktRcvBuf s.PktRcvBuf = ss.Instantaneous.PktRecvBuf
s.ByteRcvBuf = ss.ByteRcvBuf s.ByteRcvBuf = ss.Instantaneous.ByteRecvBuf
s.MsRcvBuf = ss.MsRcvBuf s.MsRcvBuf = ss.Instantaneous.MsRecvBuf
s.MsRcvTsbPdDelay = ss.MsRcvTsbPdDelay s.MsRcvTsbPdDelay = ss.Instantaneous.MsRecvTsbPdDelay
s.PktReorderTolerance = ss.PktReorderTolerance s.PktReorderTolerance = ss.Instantaneous.PktReorderTolerance
s.PktRcvAvgBelatedTime = ss.PktRcvAvgBelatedTime s.PktRcvAvgBelatedTime = ss.Instantaneous.PktRecvAvgBelatedTime
} }
type SRTLog struct { type SRTLog struct {

View file

@ -53,15 +53,17 @@ func (c *client) ticker(ctx context.Context) {
ticker := time.NewTicker(1 * time.Second) ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop() defer ticker.Stop()
stats := &srt.Statistics{}
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
case <-ticker.C: case <-ticker.C:
stats := c.conn.Stats() c.conn.Stats(stats)
rxbytes := stats.ByteRecv rxbytes := stats.Accumulated.ByteRecv
txbytes := stats.ByteSent txbytes := stats.Accumulated.ByteSent
c.collector.Ingress(c.id, int64(rxbytes-c.rxbytes)) c.collector.Ingress(c.id, int64(rxbytes-c.rxbytes))
c.collector.Egress(c.id, int64(txbytes-c.txbytes)) c.collector.Egress(c.id, int64(txbytes-c.txbytes))
@ -285,8 +287,11 @@ func (s *server) Channels() Channels {
socketId := ch.publisher.conn.SocketId() socketId := ch.publisher.conn.SocketId()
st.Publisher[id] = socketId st.Publisher[id] = socketId
stats := &srt.Statistics{}
ch.publisher.conn.Stats(stats)
st.Connections[socketId] = Connection{ st.Connections[socketId] = Connection{
Stats: ch.publisher.conn.Stats(), Stats: *stats,
Log: map[string][]Log{}, Log: map[string][]Log{},
} }
@ -294,8 +299,11 @@ func (s *server) Channels() Channels {
socketId := c.conn.SocketId() socketId := c.conn.SocketId()
st.Subscriber[id] = append(st.Subscriber[id], socketId) st.Subscriber[id] = append(st.Subscriber[id], socketId)
stats := &srt.Statistics{}
c.conn.Stats(stats)
st.Connections[socketId] = Connection{ st.Connections[socketId] = Connection{
Stats: c.conn.Stats(), Stats: *stats,
Log: map[string][]Log{}, Log: map[string][]Log{},
} }
} }

View file

@ -5,10 +5,138 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
<a name="unreleased"></a> <a name="unreleased"></a>
## [Unreleased](https://github.com/99designs/gqlgen/compare/v0.17.14...HEAD) ## [Unreleased](https://github.com/99designs/gqlgen/compare/v0.17.19...HEAD)
<!-- end of if --> <!-- end of if -->
<!-- end of CommitGroups --> <!-- end of CommitGroups -->
<a name="v0.17.19"></a>
## [v0.17.19](https://github.com/99designs/gqlgen/compare/v0.17.18...v0.17.19) - 2022-09-15
- <a href="https://github.com/99designs/gqlgen/commit/588c6ac137b8ed7aea1bc7c009ea23cb9dec5caa"><tt>588c6ac1</tt></a> release v0.17.19
- <a href="https://github.com/99designs/gqlgen/commit/c671317056298db8073498c8db02120b6f737032"><tt>c6713170</tt></a> v0.17.18 postrelease bump
<!-- end of Commits -->
<!-- end of Else -->
<!-- end of If NoteGroups -->
<a name="v0.17.18"></a>
## [v0.17.18](https://github.com/99designs/gqlgen/compare/v0.17.17...v0.17.18) - 2022-09-15
- <a href="https://github.com/99designs/gqlgen/commit/1d41c808a93446fca8ff867e957ef552e56f6ae3"><tt>1d41c808</tt></a> release v0.17.18
- <a href="https://github.com/99designs/gqlgen/commit/4dbe2e475f15ce77a498c841ea6c9149ef5ceaba"><tt>4dbe2e47</tt></a> update graphiql to 2.0.7 (<a href="https://github.com/99designs/gqlgen/pull/2375">#2375</a>)
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/b7cc094a49e3d348cfc457aa76f1640c86cdcae9"><tt>b7cc094a</tt></a> testfix: make apollo federated tracer test more consistent (<a href="https://github.com/99designs/gqlgen/pull/2374">#2374</a>)</summary>
* Update tracing_test.go
* add missing imports
</details></dd></dl>
- <a href="https://github.com/99designs/gqlgen/commit/d096fb9b08531b0dc389a786b6f44add045ea75e"><tt>d096fb9b</tt></a> Update directives (<a href="https://github.com/99designs/gqlgen/pull/2371">#2371</a>)
- <a href="https://github.com/99designs/gqlgen/commit/1acfea2fbdf3564df16f8023f4e736e90a05b909"><tt>1acfea2f</tt></a> Add v0.17.17 changelog
- <a href="https://github.com/99designs/gqlgen/commit/c273adc8ad45e15940bbb6fe211603670d9f3220"><tt>c273adc8</tt></a> v0.17.17 postrelease bump
<!-- end of Commits -->
<!-- end of Else -->
<!-- end of If NoteGroups -->
<a name="v0.17.17"></a>
## [v0.17.17](https://github.com/99designs/gqlgen/compare/v0.17.16...v0.17.17) - 2022-09-13
- <a href="https://github.com/99designs/gqlgen/commit/d50bc5aca10c5a5dd6a1680b2288c35a61327ade"><tt>d50bc5ac</tt></a> release v0.17.17
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/462025b400e9b792a5afbe320cde4cc952f6b547"><tt>462025b4</tt></a> nil check error before type assertion follow-up from <a href="https://github.com/99designs/gqlgen/pull/2341">#2341</a> (<a href="https://github.com/99designs/gqlgen/pull/2368">#2368</a>)</summary>
* Improve errcode.Set safety
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/59493aff86020d170e58900654d334f5ebc2ceee"><tt>59493aff</tt></a> fix: apollo federation tracer was race prone (<a href="https://github.com/99designs/gqlgen/pull/2366">#2366</a>)</summary>
The tracer was using a global state across different goroutines
Added req headers to operation context to allow it to be fetched in InterceptOperation
</details></dd></dl>
- <a href="https://github.com/99designs/gqlgen/commit/fc0185567f2dfc37b38f11283efb9cc1db69e96d"><tt>fc018556</tt></a> Update gqlparser to v2.5.1 (<a href="https://github.com/99designs/gqlgen/pull/2363">#2363</a>)
- <a href="https://github.com/99designs/gqlgen/commit/56574a146bd16a13c9055128ec3c80e96a7c4b29"><tt>56574a14</tt></a> feat: make Playground HTML content compatible with UTF-8 charset (<a href="https://github.com/99designs/gqlgen/pull/2355">#2355</a>)
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/182b039d34cb730f432c486ebe763f246937dea4"><tt>182b039d</tt></a> Add `subscriptions.md` recipe to docs (<a href="https://github.com/99designs/gqlgen/pull/2346">#2346</a>)</summary>
* Add `subscriptions.md` recipe to docs
* Fix wrong request type
</details></dd></dl>
- <a href="https://github.com/99designs/gqlgen/commit/b66fff16de0b16edc317398a5574fcff2cb39e66"><tt>b66fff16</tt></a> Add omit_getters config option (<a href="https://github.com/99designs/gqlgen/pull/2348">#2348</a>)
- <a href="https://github.com/99designs/gqlgen/commit/2ba8040f20e32d06dc6d5bfacaadc5619a6e66ee"><tt>2ba8040f</tt></a> Update changelog for v0.17.16
- <a href="https://github.com/99designs/gqlgen/commit/8bef8c8061222071e6c814e45bbc33fcabcb3980"><tt>8bef8c80</tt></a> v0.17.16 postrelease bump
<!-- end of Commits -->
<!-- end of Else -->
<!-- end of If NoteGroups -->
<a name="v0.17.16"></a>
## [v0.17.16](https://github.com/99designs/gqlgen/compare/v0.17.15...v0.17.16) - 2022-08-26
- <a href="https://github.com/99designs/gqlgen/commit/9593ceadd6e07c6fd0f0b0e0c55b9f1bf8ade762"><tt>9593cead</tt></a> release v0.17.16
- <a href="https://github.com/99designs/gqlgen/commit/2390af2db920dc632fe47bc778a24c30495b9efd"><tt>2390af2d</tt></a> Update gqlparser to v2.5.0 (<a href="https://github.com/99designs/gqlgen/pull/2341">#2341</a>)
- <a href="https://github.com/99designs/gqlgen/commit/2a87fe0645fd271e4e71d2b7bde34ecf31bf844c"><tt>2a87fe06</tt></a> feat: update Graphiql to version 2 (<a href="https://github.com/99designs/gqlgen/pull/2340">#2340</a>)
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/32e2ccd30e82fc566ca022a65dcc4a67c4b6125a"><tt>32e2ccd3</tt></a> Update yaml to v3 (<a href="https://github.com/99designs/gqlgen/pull/2339">#2339</a>)</summary>
* update yaml to v3
* add missing go entry for yaml on _example
* add missing sum file
</details></dd></dl>
- <a href="https://github.com/99designs/gqlgen/commit/7949117a524be7f8882a61e2d4ade1bedf105107"><tt>7949117a</tt></a> v0.17.15 postrelease bump
<!-- end of Commits -->
<!-- end of Else -->
<!-- end of If NoteGroups -->
<a name="v0.17.15"></a>
## [v0.17.15](https://github.com/99designs/gqlgen/compare/v0.17.14...v0.17.15) - 2022-08-23
- <a href="https://github.com/99designs/gqlgen/commit/23cc749256b4e2edc4b11ce9e84c643a7bb3194f"><tt>23cc7492</tt></a> release v0.17.15
- <a href="https://github.com/99designs/gqlgen/commit/577a570cdb6b1b9185f24940690a14cdced37a36"><tt>577a570c</tt></a> Markdown formatting fixes (<a href="https://github.com/99designs/gqlgen/pull/2335">#2335</a>)
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/2b584011fc64a55cbda67f46637a280bf94d9cc1"><tt>2b584011</tt></a> Fix Interface Slice Getter Generation (<a href="https://github.com/99designs/gqlgen/pull/2332">#2332</a>)</summary>
* Make modelgen test fail if generated doesn't build
Added returning list of interface to modelgen test schema
* Implement slice copying when returning interface slices
* Re-generate to satisfy the linter
</details></dd></dl>
<dl><dd><details><summary><a href="https://github.com/99designs/gqlgen/commit/aee57b4c521e527ebc0538b8edfbe610973abf21"><tt>aee57b4c</tt></a> Correct boolean logic (<a href="https://github.com/99designs/gqlgen/pull/2330">#2330</a>)</summary>
Correcting boolean logic issue
</details></dd></dl>
- <a href="https://github.com/99designs/gqlgen/commit/da0610e11accf3afd34903f03bfc0abd045d07ed"><tt>da0610e1</tt></a> Update changelog for v0.17.14
- <a href="https://github.com/99designs/gqlgen/commit/ddcb524e3321d849505f6937307ef3dcbd3acace"><tt>ddcb524e</tt></a> v0.17.14 postrelease bump
<!-- end of Commits -->
<!-- end of Else -->
<!-- end of If NoteGroups -->
<a name="v0.17.14"></a> <a name="v0.17.14"></a>
## [v0.17.14](https://github.com/99designs/gqlgen/compare/v0.17.13...v0.17.14) - 2022-08-18 ## [v0.17.14](https://github.com/99designs/gqlgen/compare/v0.17.13...v0.17.14) - 2022-08-18
- <a href="https://github.com/99designs/gqlgen/commit/581bf6eb063a0d6a3cec3b6bc7a16ca10e310a97"><tt>581bf6eb</tt></a> release v0.17.14 - <a href="https://github.com/99designs/gqlgen/commit/581bf6eb063a0d6a3cec3b6bc7a16ca10e310a97"><tt>581bf6eb</tt></a> release v0.17.14

View file

@ -142,6 +142,16 @@ first model in this list is used as the default type and it will always be used
There isn't any way around this, gqlgen has no way to know what you want in a given context. There isn't any way around this, gqlgen has no way to know what you want in a given context.
### Why do my interfaces have getters? Can I disable these?
These were added in v0.17.14 to allow accessing common interface fields without casting to a concrete type.
However, certain fields, like Relay-style Connections, cannot be implemented with simple getters.
If you'd prefer to not have getters generated in your interfaces, you can add the following in your `gqlgen.yml`:
```yaml
# gqlgen.yml
omit_getters: true
```
## Other Resources ## Other Resources
- [Christopher Biscardi @ Gophercon UK 2018](https://youtu.be/FdURVezcdcw) - [Christopher Biscardi @ Gophercon UK 2018](https://youtu.be/FdURVezcdcw)

View file

@ -26,6 +26,7 @@ type Config struct {
StructTag string `yaml:"struct_tag,omitempty"` StructTag string `yaml:"struct_tag,omitempty"`
Directives map[string]DirectiveConfig `yaml:"directives,omitempty"` Directives map[string]DirectiveConfig `yaml:"directives,omitempty"`
OmitSliceElementPointers bool `yaml:"omit_slice_element_pointers,omitempty"` OmitSliceElementPointers bool `yaml:"omit_slice_element_pointers,omitempty"`
OmitGetters bool `yaml:"omit_getters,omitempty"`
StructFieldsAlwaysPointers bool `yaml:"struct_fields_always_pointers,omitempty"` StructFieldsAlwaysPointers bool `yaml:"struct_fields_always_pointers,omitempty"`
ResolversAlwaysReturnPointers bool `yaml:"resolvers_always_return_pointers,omitempty"` ResolversAlwaysReturnPointers bool `yaml:"resolvers_always_return_pointers,omitempty"`
SkipValidation bool `yaml:"skip_validation,omitempty"` SkipValidation bool `yaml:"skip_validation,omitempty"`

View file

@ -3,6 +3,7 @@ package graphql
import ( import (
"context" "context"
"errors" "errors"
"net/http"
"github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/ast"
) )
@ -15,6 +16,7 @@ type OperationContext struct {
Variables map[string]interface{} Variables map[string]interface{}
OperationName string OperationName string
Doc *ast.QueryDocument Doc *ast.QueryDocument
Headers http.Header
Operation *ast.OperationDefinition Operation *ast.OperationDefinition
DisableIntrospection bool DisableIntrospection bool

View file

@ -23,14 +23,22 @@ var codeType = map[string]ErrorKind{
ParseFailed: KindProtocol, ParseFailed: KindProtocol,
} }
// RegisterErrorType should be called by extensions that want to customize the http status codes for errors they return // RegisterErrorType should be called by extensions that want to customize the http status codes for
// errors they return
func RegisterErrorType(code string, kind ErrorKind) { func RegisterErrorType(code string, kind ErrorKind) {
codeType[code] = kind codeType[code] = kind
} }
// Set the error code on a given graphql error extension // Set the error code on a given graphql error extension
func Set(err error, value string) { func Set(err error, value string) {
gqlErr, _ := err.(*gqlerror.Error) if err == nil {
return
}
gqlErr, ok := err.(*gqlerror.Error)
if !ok {
return
}
if gqlErr.Extensions == nil { if gqlErr.Extensions == nil {
gqlErr.Extensions = map[string]interface{}{} gqlErr.Extensions = map[string]interface{}{}
} }

View file

@ -118,6 +118,11 @@ func getOrCreateAndAppendField(c *[]CollectedField, name string, alias string, o
return &(*c)[i] return &(*c)[i]
} }
} }
for _, ifc := range cf.ObjectDefinition.Interfaces {
if ifc == objectDefinition.Name {
return &(*c)[i]
}
}
} }
} }

View file

@ -37,7 +37,10 @@ func New(es graphql.ExecutableSchema) *Executor {
return e return e
} }
func (e *Executor) CreateOperationContext(ctx context.Context, params *graphql.RawParams) (*graphql.OperationContext, gqlerror.List) { func (e *Executor) CreateOperationContext(
ctx context.Context,
params *graphql.RawParams,
) (*graphql.OperationContext, gqlerror.List) {
rc := &graphql.OperationContext{ rc := &graphql.OperationContext{
DisableIntrospection: true, DisableIntrospection: true,
RecoverFunc: e.recoverFunc, RecoverFunc: e.recoverFunc,
@ -58,6 +61,7 @@ func (e *Executor) CreateOperationContext(ctx context.Context, params *graphql.R
rc.RawQuery = params.Query rc.RawQuery = params.Query
rc.OperationName = params.OperationName rc.OperationName = params.OperationName
rc.Headers = params.Headers
var listErr gqlerror.List var listErr gqlerror.List
rc.Doc, listErr = e.parseQuery(ctx, &rc.Stats, params.Query) rc.Doc, listErr = e.parseQuery(ctx, &rc.Stats, params.Query)
@ -74,10 +78,13 @@ func (e *Executor) CreateOperationContext(ctx context.Context, params *graphql.R
var err error var err error
rc.Variables, err = validator.VariableValues(e.es.Schema(), rc.Operation, params.Variables) rc.Variables, err = validator.VariableValues(e.es.Schema(), rc.Operation, params.Variables)
gqlErr, _ := err.(*gqlerror.Error)
if gqlErr != nil { if err != nil {
errcode.Set(gqlErr, errcode.ValidationFailed) gqlErr, ok := err.(*gqlerror.Error)
return rc, gqlerror.List{gqlErr} if ok {
errcode.Set(gqlErr, errcode.ValidationFailed)
return rc, gqlerror.List{gqlErr}
}
} }
rc.Stats.Validation.End = graphql.Now() rc.Stats.Validation.End = graphql.Now()
@ -90,7 +97,10 @@ func (e *Executor) CreateOperationContext(ctx context.Context, params *graphql.R
return rc, nil return rc, nil
} }
func (e *Executor) DispatchOperation(ctx context.Context, rc *graphql.OperationContext) (graphql.ResponseHandler, context.Context) { func (e *Executor) DispatchOperation(
ctx context.Context,
rc *graphql.OperationContext,
) (graphql.ResponseHandler, context.Context) {
ctx = graphql.WithOperationContext(ctx, rc) ctx = graphql.WithOperationContext(ctx, rc)
var innerCtx context.Context var innerCtx context.Context
@ -160,9 +170,14 @@ func (e *Executor) SetRecoverFunc(f graphql.RecoverFunc) {
// parseQuery decodes the incoming query and validates it, pulling from cache if present. // parseQuery decodes the incoming query and validates it, pulling from cache if present.
// //
// NOTE: This should NOT look at variables, they will change per request. It should only parse and validate // NOTE: This should NOT look at variables, they will change per request. It should only parse and
// validate
// the raw query string. // the raw query string.
func (e *Executor) parseQuery(ctx context.Context, stats *graphql.Stats, query string) (*ast.QueryDocument, gqlerror.List) { func (e *Executor) parseQuery(
ctx context.Context,
stats *graphql.Stats,
query string,
) (*ast.QueryDocument, gqlerror.List) {
stats.Parsing.Start = graphql.Now() stats.Parsing.Start = graphql.Now()
if doc, ok := e.queryCache.Get(ctx, query); ok { if doc, ok := e.queryCache.Get(ctx, query); ok {
@ -174,10 +189,12 @@ func (e *Executor) parseQuery(ctx context.Context, stats *graphql.Stats, query s
} }
doc, err := parser.ParseQuery(&ast.Source{Input: query}) doc, err := parser.ParseQuery(&ast.Source{Input: query})
gqlErr, _ := err.(*gqlerror.Error) if err != nil {
if gqlErr != nil { gqlErr, ok := err.(*gqlerror.Error)
errcode.Set(gqlErr, errcode.ParseFailed) if ok {
return nil, gqlerror.List{gqlErr} errcode.Set(gqlErr, errcode.ParseFailed)
return nil, gqlerror.List{gqlErr}
}
} }
stats.Parsing.End = graphql.Now() stats.Parsing.End = graphql.Now()

View file

@ -42,14 +42,14 @@ type (
// Its important to understand the lifecycle of a graphql request and the terminology we use in gqlgen // Its important to understand the lifecycle of a graphql request and the terminology we use in gqlgen
// before working with these // before working with these
// //
// +--- REQUEST POST /graphql --------------------------------------------+ // +--- REQUEST POST /graphql --------------------------------------------+
// | +- OPERATION query OpName { viewer { name } } -----------------------+ | // | +- OPERATION query OpName { viewer { name } } -----------------------+ |
// | | RESPONSE { "data": { "viewer": { "name": "bob" } } } | | // | | RESPONSE { "data": { "viewer": { "name": "bob" } } } | |
// | +- OPERATION subscription OpName2 { chat { message } } --------------+ | // | +- OPERATION subscription OpName2 { chat { message } } --------------+ |
// | | RESPONSE { "data": { "chat": { "message": "hello" } } } | | // | | RESPONSE { "data": { "chat": { "message": "hello" } } } | |
// | | RESPONSE { "data": { "chat": { "message": "byee" } } } | | // | | RESPONSE { "data": { "chat": { "message": "byee" } } } | |
// | +--------------------------------------------------------------------+ | // | +--------------------------------------------------------------------+ |
// +------------------------------------------------------------------------+ // +------------------------------------------------------------------------+
HandlerExtension interface { HandlerExtension interface {
// ExtensionName should be a CamelCase string version of the extension which may be shown in stats and logging. // ExtensionName should be a CamelCase string version of the extension which may be shown in stats and logging.
ExtensionName() string ExtensionName() string

View file

@ -9,6 +9,7 @@ import (
var page = template.Must(template.New("graphiql").Parse(`<!DOCTYPE html> var page = template.Must(template.New("graphiql").Parse(`<!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8">
<title>{{.title}}</title> <title>{{.title}}</title>
<style> <style>
body { body {
@ -75,15 +76,15 @@ var page = template.Must(template.New("graphiql").Parse(`<!DOCTYPE html>
// Handler responsible for setting up the playground // Handler responsible for setting up the playground
func Handler(title string, endpoint string) http.HandlerFunc { func Handler(title string, endpoint string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/html") w.Header().Add("Content-Type", "text/html; charset=UTF-8")
err := page.Execute(w, map[string]interface{}{ err := page.Execute(w, map[string]interface{}{
"title": title, "title": title,
"endpoint": endpoint, "endpoint": endpoint,
"endpointIsAbsolute": endpointHasScheme(endpoint), "endpointIsAbsolute": endpointHasScheme(endpoint),
"subscriptionEndpoint": getSubscriptionEndpoint(endpoint), "subscriptionEndpoint": getSubscriptionEndpoint(endpoint),
"version": "2.0.1", "version": "2.0.7",
"cssSRI": "sha256-hYUgpHapGug0ucdB5kG0zSipubcQOJcGjclIZke2rl8=", "cssSRI": "sha256-gQryfbGYeYFxnJYnfPStPYFt0+uv8RP8Dm++eh00G9c=",
"jsSRI": "sha256-jMXGO5+Y4OhcHPSR34jpzpzlz4OZTlxcvaDXSWmUMRo=", "jsSRI": "sha256-qQ6pw7LwTLC+GfzN+cJsYXfVWRKH9O5o7+5H96gTJhQ=",
"reactSRI": "sha256-Ipu/TQ50iCCVZBUsZyNJfxrDk0E2yhaEIz0vqI+kFG8=", "reactSRI": "sha256-Ipu/TQ50iCCVZBUsZyNJfxrDk0E2yhaEIz0vqI+kFG8=",
"reactDOMSRI": "sha256-nbMykgB6tsOFJ7OdVmPpdqMFVk4ZsqWocT6issAPUF0=", "reactDOMSRI": "sha256-nbMykgB6tsOFJ7OdVmPpdqMFVk4ZsqWocT6issAPUF0=",
}) })

View file

@ -1,3 +1,3 @@
package graphql package graphql
const Version = "v0.17.16" const Version = "v0.17.20"

View file

@ -102,10 +102,10 @@ func (f *federation) InjectSourceEarly() *ast.Source {
` `
} else if f.Version == 2 { } else if f.Version == 2 {
input += ` input += `
directive @key(fields: _FieldSet!, resolvable: Boolean) repeatable on OBJECT | INTERFACE directive @key(fields: _FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE
directive @link(import: [String!], url: String!) repeatable on SCHEMA directive @link(import: [String!], url: String!) repeatable on SCHEMA
directive @shareable on OBJECT | FIELD_DEFINITION directive @shareable on OBJECT | FIELD_DEFINITION
directive @tag repeatable on OBJECT | FIELD_DEFINITION | INTERFACE | UNION directive @tag(name: String!) repeatable on FIELD_DEFINITION | INTERFACE | OBJECT | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
directive @override(from: String!) on FIELD_DEFINITION directive @override(from: String!) on FIELD_DEFINITION
directive @inaccessible on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION directive @inaccessible on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION
` `

View file

@ -103,9 +103,13 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error {
} }
switch schemaType.Kind { switch schemaType.Kind {
case ast.Interface, ast.Union: case ast.Interface, ast.Union:
fields, err := m.generateFields(cfg, schemaType) var fields []*Field
if err != nil { var err error
return err if !cfg.OmitGetters {
fields, err = m.generateFields(cfg, schemaType)
if err != nil {
return err
}
} }
it := &Interface{ it := &Interface{

View file

@ -458,6 +458,42 @@ Most applications will not need to interact with certificate caches directly. Us
Again, if you're needing to do this, you've probably over-complicated your application design. Again, if you're needing to do this, you've probably over-complicated your application design.
## Events
(Events are new and still experimental, so they may change.)
CertMagic emits events when possible things of interest happen. Set the [`OnEvent` field of your `Config`](https://pkg.go.dev/github.com/caddyserver/certmagic#Config.OnEvent) to subscribe to events; ignore the ones you aren't interested in. Here are the events currently emitted along with their metadata you can use:
- **`cached_unmanaged_cert`** An unmanaged certificate was cached
- `sans`: The subject names on the certificate
- **`cert_obtaining`** A certificate is about to be obtained
- `renewal`: Whether this is a renewal
- `identifier`: The name on the certificate
- `forced`: Whether renewal is being forced (if renewal)
- `remaining`: Time left on the certificate (if renewal)
- `issuer`: The previous or current issuer
- **`cert_obtained`** A certificate was successfully obtained
- `renewal`: Whether this is a renewal
- `identifier`: The name on the certificate
- `remaining`: Time left on the certificate (if renewal)
- `issuer`: The previous or current issuer
- `storage_key`: The path to the cert resources within storage
- **`cert_failed`** An attempt to obtain a certificate failed
- `renewal`: Whether this is a renewal
- `identifier`: The name on the certificate
- `remaining`: Time left on the certificate (if renewal)
- `issuer`: The previous or current issuer
- `storage_key`: The path to the cert resources within storage
- `error`: The (final) error message
- **`tls_get_certificate`** The GetCertificate phase of a TLS handshake is under way
- `client_hello`: The tls.ClientHelloInfo struct
- **`cert_ocsp_revoked`** A certificate's OCSP indicates it has been revoked
- `subjects`: The subject names on the certificate
- `certificate`: The Certificate struct
- `reason`: The OCSP revocation reason
- `revoked_at`: When the certificate was revoked
`OnEvent` can return an error. Some events may be aborted by returning an error. For example, returning an error from `cert_obtained` can cancel obtaining the certificate. Only return an error from `OnEvent` if you want to abort program flow.
## FAQ ## FAQ

View file

@ -175,9 +175,7 @@ func (iss *ACMEIssuer) newACMEClient(useTestCA bool) (*acmez.Client, error) {
}, },
ChallengeSolvers: make(map[string]acmez.Solver), ChallengeSolvers: make(map[string]acmez.Solver),
} }
if iss.Logger != nil { client.Logger = iss.Logger.Named("acme_client")
client.Logger = iss.Logger.Named("acme_client")
}
// configure challenges (most of the time, DNS challenge is // configure challenges (most of the time, DNS challenge is
// exclusive of other ones because it is usually only used // exclusive of other ones because it is usually only used
@ -260,24 +258,20 @@ func (c *acmeClient) throttle(ctx context.Context, names []string) error {
// TODO: stop rate limiter when it is garbage-collected... // TODO: stop rate limiter when it is garbage-collected...
} }
rateLimitersMu.Unlock() rateLimitersMu.Unlock()
if c.iss.Logger != nil { c.iss.Logger.Info("waiting on internal rate limiter",
c.iss.Logger.Info("waiting on internal rate limiter", zap.Strings("identifiers", names),
zap.Strings("identifiers", names), zap.String("ca", c.acmeClient.Directory),
zap.String("ca", c.acmeClient.Directory), zap.String("account", email),
zap.String("account", email), )
)
}
err := rl.Wait(ctx) err := rl.Wait(ctx)
if err != nil { if err != nil {
return err return err
} }
if c.iss.Logger != nil { c.iss.Logger.Info("done waiting on internal rate limiter",
c.iss.Logger.Info("done waiting on internal rate limiter", zap.Strings("identifiers", names),
zap.Strings("identifiers", names), zap.String("ca", c.acmeClient.Directory),
zap.String("ca", c.acmeClient.Directory), zap.String("account", email),
zap.String("account", email), )
)
}
return nil return nil
} }

View file

@ -110,7 +110,9 @@ type ACMEIssuer struct {
// certificate chains // certificate chains
PreferredChains ChainPreference PreferredChains ChainPreference
// Set a logger to enable logging // Set a logger to configure logging; a default
// logger must always be set; if no logging is
// desired, set this to zap.NewNop().
Logger *zap.Logger Logger *zap.Logger
config *Config config *Config
@ -197,6 +199,11 @@ func NewACMEIssuer(cfg *Config, template ACMEIssuer) *ACMEIssuer {
template.Logger = DefaultACME.Logger template.Logger = DefaultACME.Logger
} }
// absolutely do not allow a nil logger; that would panic
if template.Logger == nil {
template.Logger = defaultLogger
}
template.config = cfg template.config = cfg
template.mu = new(sync.Mutex) template.mu = new(sync.Mutex)
@ -398,7 +405,7 @@ func (am *ACMEIssuer) doIssue(ctx context.Context, csr *x509.CertificateRequest,
// processing. If there are no matches, the first chain is returned. // processing. If there are no matches, the first chain is returned.
func (am *ACMEIssuer) selectPreferredChain(certChains []acme.Certificate) acme.Certificate { func (am *ACMEIssuer) selectPreferredChain(certChains []acme.Certificate) acme.Certificate {
if len(certChains) == 1 { if len(certChains) == 1 {
if am.Logger != nil && (len(am.PreferredChains.AnyCommonName) > 0 || len(am.PreferredChains.RootCommonName) > 0) { if len(am.PreferredChains.AnyCommonName) > 0 || len(am.PreferredChains.RootCommonName) > 0 {
am.Logger.Debug("there is only one chain offered; selecting it regardless of preferences", am.Logger.Debug("there is only one chain offered; selecting it regardless of preferences",
zap.String("chain_url", certChains[0].URL)) zap.String("chain_url", certChains[0].URL))
} }
@ -423,11 +430,9 @@ func (am *ACMEIssuer) selectPreferredChain(certChains []acme.Certificate) acme.C
for i, chain := range certChains { for i, chain := range certChains {
certs, err := parseCertsFromPEMBundle(chain.ChainPEM) certs, err := parseCertsFromPEMBundle(chain.ChainPEM)
if err != nil { if err != nil {
if am.Logger != nil { am.Logger.Error("unable to parse PEM certificate chain",
am.Logger.Error("unable to parse PEM certificate chain", zap.Int("chain", i),
zap.Int("chain", i), zap.Error(err))
zap.Error(err))
}
continue continue
} }
decodedChains[i] = certs decodedChains[i] = certs
@ -438,11 +443,9 @@ func (am *ACMEIssuer) selectPreferredChain(certChains []acme.Certificate) acme.C
for i, chain := range decodedChains { for i, chain := range decodedChains {
for _, cert := range chain { for _, cert := range chain {
if cert.Issuer.CommonName == prefAnyCN { if cert.Issuer.CommonName == prefAnyCN {
if am.Logger != nil { am.Logger.Debug("found preferred certificate chain by issuer common name",
am.Logger.Debug("found preferred certificate chain by issuer common name", zap.String("preference", prefAnyCN),
zap.String("preference", prefAnyCN), zap.Int("chain", i))
zap.Int("chain", i))
}
return certChains[i] return certChains[i]
} }
} }
@ -454,20 +457,16 @@ func (am *ACMEIssuer) selectPreferredChain(certChains []acme.Certificate) acme.C
for _, prefRootCN := range am.PreferredChains.RootCommonName { for _, prefRootCN := range am.PreferredChains.RootCommonName {
for i, chain := range decodedChains { for i, chain := range decodedChains {
if chain[len(chain)-1].Issuer.CommonName == prefRootCN { if chain[len(chain)-1].Issuer.CommonName == prefRootCN {
if am.Logger != nil { am.Logger.Debug("found preferred certificate chain by root common name",
am.Logger.Debug("found preferred certificate chain by root common name", zap.String("preference", prefRootCN),
zap.String("preference", prefRootCN), zap.Int("chain", i))
zap.Int("chain", i))
}
return certChains[i] return certChains[i]
} }
} }
} }
} }
if am.Logger != nil { am.Logger.Warn("did not find chain matching preferences; using first")
am.Logger.Warn("did not find chain matching preferences; using first")
}
} }
return certChains[0] return certChains[0]
@ -509,6 +508,7 @@ type ChainPreference struct {
var DefaultACME = ACMEIssuer{ var DefaultACME = ACMEIssuer{
CA: LetsEncryptProductionCA, CA: LetsEncryptProductionCA,
TestCA: LetsEncryptStagingCA, TestCA: LetsEncryptStagingCA,
Logger: defaultLogger,
} }
// Some well-known CA endpoints available to use. // Some well-known CA endpoints available to use.

View file

@ -71,9 +71,7 @@ func (jm *jobManager) worker() {
jm.queue = jm.queue[1:] jm.queue = jm.queue[1:]
jm.mu.Unlock() jm.mu.Unlock()
if err := next.job(); err != nil { if err := next.job(); err != nil {
if next.logger != nil { next.logger.Error("job failed", zap.Error(err))
next.logger.Error("job failed", zap.Error(err))
}
} }
if next.name != "" { if next.name != "" {
jm.mu.Lock() jm.mu.Lock()
@ -116,22 +114,19 @@ func doWithRetry(ctx context.Context, log *zap.Logger, f func(context.Context) e
intervalIndex++ intervalIndex++
} }
if time.Since(start) < maxRetryDuration { if time.Since(start) < maxRetryDuration {
if log != nil { log.Error("will retry",
log.Error("will retry", zap.Error(err),
zap.Error(err), zap.Int("attempt", attempts),
zap.Int("attempt", attempts), zap.Duration("retrying_in", retryIntervals[intervalIndex]),
zap.Duration("retrying_in", retryIntervals[intervalIndex]), zap.Duration("elapsed", time.Since(start)),
zap.Duration("elapsed", time.Since(start)), zap.Duration("max_duration", maxRetryDuration))
zap.Duration("max_duration", maxRetryDuration))
}
} else { } else {
if log != nil { log.Error("final attempt; giving up",
log.Error("final attempt; giving up", zap.Error(err),
zap.Error(err), zap.Int("attempt", attempts),
zap.Int("attempt", attempts), zap.Duration("elapsed", time.Since(start)),
zap.Duration("elapsed", time.Since(start)), zap.Duration("max_duration", maxRetryDuration))
zap.Duration("max_duration", maxRetryDuration))
}
return nil return nil
} }
} }

View file

@ -118,6 +118,11 @@ func NewCache(opts CacheOptions) *Cache {
logger: opts.Logger, logger: opts.Logger,
} }
// absolutely do not allow a nil logger; panics galore
if c.logger == nil {
c.logger = defaultLogger
}
go c.maintainAssets(0) go c.maintainAssets(0)
return c return c
@ -194,14 +199,12 @@ func (certCache *Cache) cacheCertificate(cert Certificate) {
func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) { func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) {
// no-op if this certificate already exists in the cache // no-op if this certificate already exists in the cache
if _, ok := certCache.cache[cert.hash]; ok { if _, ok := certCache.cache[cert.hash]; ok {
if certCache.logger != nil { certCache.logger.Debug("certificate already cached",
certCache.logger.Debug("certificate already cached", zap.Strings("subjects", cert.Names),
zap.Strings("subjects", cert.Names), zap.Time("expiration", expiresAt(cert.Leaf)),
zap.Time("expiration", cert.Leaf.NotAfter), zap.Bool("managed", cert.managed),
zap.Bool("managed", cert.managed), zap.String("issuer_key", cert.issuerKey),
zap.String("issuer_key", cert.issuerKey), zap.String("hash", cert.hash))
zap.String("hash", cert.hash))
}
return return
} }
@ -217,13 +220,11 @@ func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) {
i := 0 i := 0
for _, randomCert := range certCache.cache { for _, randomCert := range certCache.cache {
if i == rnd { if i == rnd {
if certCache.logger != nil { certCache.logger.Debug("cache full; evicting random certificate",
certCache.logger.Debug("cache full; evicting random certificate", zap.Strings("removing_subjects", randomCert.Names),
zap.Strings("removing_subjects", randomCert.Names), zap.String("removing_hash", randomCert.hash),
zap.String("removing_hash", randomCert.hash), zap.Strings("inserting_subjects", cert.Names),
zap.Strings("inserting_subjects", cert.Names), zap.String("inserting_hash", cert.hash))
zap.String("inserting_hash", cert.hash))
}
certCache.removeCertificate(randomCert) certCache.removeCertificate(randomCert)
break break
} }
@ -239,16 +240,14 @@ func (certCache *Cache) unsyncedCacheCertificate(cert Certificate) {
certCache.cacheIndex[name] = append(certCache.cacheIndex[name], cert.hash) certCache.cacheIndex[name] = append(certCache.cacheIndex[name], cert.hash)
} }
if certCache.logger != nil { certCache.logger.Debug("added certificate to cache",
certCache.logger.Debug("added certificate to cache", zap.Strings("subjects", cert.Names),
zap.Strings("subjects", cert.Names), zap.Time("expiration", expiresAt(cert.Leaf)),
zap.Time("expiration", cert.Leaf.NotAfter), zap.Bool("managed", cert.managed),
zap.Bool("managed", cert.managed), zap.String("issuer_key", cert.issuerKey),
zap.String("issuer_key", cert.issuerKey), zap.String("hash", cert.hash),
zap.String("hash", cert.hash), zap.Int("cache_size", len(certCache.cache)),
zap.Int("cache_size", len(certCache.cache)), zap.Int("cache_capacity", certCache.options.Capacity))
zap.Int("cache_capacity", certCache.options.Capacity))
}
} }
// removeCertificate removes cert from the cache. // removeCertificate removes cert from the cache.
@ -275,16 +274,14 @@ func (certCache *Cache) removeCertificate(cert Certificate) {
// delete the actual cert from the cache // delete the actual cert from the cache
delete(certCache.cache, cert.hash) delete(certCache.cache, cert.hash)
if certCache.logger != nil { certCache.logger.Debug("removed certificate from cache",
certCache.logger.Debug("removed certificate from cache", zap.Strings("subjects", cert.Names),
zap.Strings("subjects", cert.Names), zap.Time("expiration", expiresAt(cert.Leaf)),
zap.Time("expiration", cert.Leaf.NotAfter), zap.Bool("managed", cert.managed),
zap.Bool("managed", cert.managed), zap.String("issuer_key", cert.issuerKey),
zap.String("issuer_key", cert.issuerKey), zap.String("hash", cert.hash),
zap.String("hash", cert.hash), zap.Int("cache_size", len(certCache.cache)),
zap.Int("cache_size", len(certCache.cache)), zap.Int("cache_capacity", certCache.options.Capacity))
zap.Int("cache_capacity", certCache.options.Capacity))
}
} }
// replaceCertificate atomically replaces oldCert with newCert in // replaceCertificate atomically replaces oldCert with newCert in
@ -296,11 +293,9 @@ func (certCache *Cache) replaceCertificate(oldCert, newCert Certificate) {
certCache.removeCertificate(oldCert) certCache.removeCertificate(oldCert)
certCache.unsyncedCacheCertificate(newCert) certCache.unsyncedCacheCertificate(newCert)
certCache.mu.Unlock() certCache.mu.Unlock()
if certCache.logger != nil { certCache.logger.Info("replaced certificate in cache",
certCache.logger.Info("replaced certificate in cache", zap.Strings("subjects", newCert.Names),
zap.Strings("subjects", newCert.Names), zap.Time("new_expiration", expiresAt(newCert.Leaf)))
zap.Time("new_expiration", newCert.Leaf.NotAfter))
}
} }
func (certCache *Cache) getAllMatchingCerts(name string) []Certificate { func (certCache *Cache) getAllMatchingCerts(name string) []Certificate {

View file

@ -67,7 +67,7 @@ func (cert Certificate) Empty() bool {
// NeedsRenewal returns true if the certificate is // NeedsRenewal returns true if the certificate is
// expiring soon (according to cfg) or has expired. // expiring soon (according to cfg) or has expired.
func (cert Certificate) NeedsRenewal(cfg *Config) bool { func (cert Certificate) NeedsRenewal(cfg *Config) bool {
return currentlyInRenewalWindow(cert.Leaf.NotBefore, cert.Leaf.NotAfter, cfg.RenewalWindowRatio) return currentlyInRenewalWindow(cert.Leaf.NotBefore, expiresAt(cert.Leaf), cfg.RenewalWindowRatio)
} }
// Expired returns true if the certificate has expired. // Expired returns true if the certificate has expired.
@ -79,7 +79,7 @@ func (cert Certificate) Expired() bool {
// tls.X509KeyPair() discards the leaf; oh well // tls.X509KeyPair() discards the leaf; oh well
return false return false
} }
return time.Now().After(cert.Leaf.NotAfter) return time.Now().After(expiresAt(cert.Leaf))
} }
// currentlyInRenewalWindow returns true if the current time is // currentlyInRenewalWindow returns true if the current time is
@ -109,6 +109,13 @@ func (cert Certificate) HasTag(tag string) bool {
return false return false
} }
// expiresAt return the time that a certificate expires. Account for the 1s
// resolution of ASN.1 UTCTime/GeneralizedTime by including the extra fraction
// of a second of certificate validity beyond the NotAfter value.
func expiresAt(cert *x509.Certificate) time.Time {
return cert.NotAfter.Truncate(time.Second).Add(1 * time.Second)
}
// CacheManagedCertificate loads the certificate for domain into the // CacheManagedCertificate loads the certificate for domain into the
// cache, from the TLS storage for managed certificates. It returns a // cache, from the TLS storage for managed certificates. It returns a
// copy of the Certificate that was put into the cache. // copy of the Certificate that was put into the cache.
@ -122,7 +129,7 @@ func (cfg *Config) CacheManagedCertificate(ctx context.Context, domain string) (
return cert, err return cert, err
} }
cfg.certCache.cacheCertificate(cert) cfg.certCache.cacheCertificate(cert)
cfg.emit("cached_managed_cert", cert.Names) cfg.emit(ctx, "cached_managed_cert", map[string]any{"sans": cert.Names})
return cert, nil return cert, nil
} }
@ -155,7 +162,7 @@ func (cfg *Config) CacheUnmanagedCertificatePEMFile(ctx context.Context, certFil
} }
cert.Tags = tags cert.Tags = tags
cfg.certCache.cacheCertificate(cert) cfg.certCache.cacheCertificate(cert)
cfg.emit("cached_unmanaged_cert", cert.Names) cfg.emit(ctx, "cached_unmanaged_cert", map[string]any{"sans": cert.Names})
return nil return nil
} }
@ -170,10 +177,10 @@ func (cfg *Config) CacheUnmanagedTLSCertificate(ctx context.Context, tlsCert tls
return err return err
} }
err = stapleOCSP(ctx, cfg.OCSP, cfg.Storage, &cert, nil) err = stapleOCSP(ctx, cfg.OCSP, cfg.Storage, &cert, nil)
if err != nil && cfg.Logger != nil { if err != nil {
cfg.Logger.Warn("stapling OCSP", zap.Error(err)) cfg.Logger.Warn("stapling OCSP", zap.Error(err))
} }
cfg.emit("cached_unmanaged_cert", cert.Names) cfg.emit(ctx, "cached_unmanaged_cert", map[string]any{"sans": cert.Names})
cert.Tags = tags cert.Tags = tags
cfg.certCache.cacheCertificate(cert) cfg.certCache.cacheCertificate(cert)
return nil return nil
@ -190,7 +197,7 @@ func (cfg *Config) CacheUnmanagedCertificatePEMBytes(ctx context.Context, certBy
} }
cert.Tags = tags cert.Tags = tags
cfg.certCache.cacheCertificate(cert) cfg.certCache.cacheCertificate(cert)
cfg.emit("cached_unmanaged_cert", cert.Names) cfg.emit(ctx, "cached_unmanaged_cert", map[string]any{"sans": cert.Names})
return nil return nil
} }
@ -218,7 +225,7 @@ func (cfg Config) makeCertificateWithOCSP(ctx context.Context, certPEMBlock, key
return cert, err return cert, err
} }
err = stapleOCSP(ctx, cfg.OCSP, cfg.Storage, &cert, certPEMBlock) err = stapleOCSP(ctx, cfg.OCSP, cfg.Storage, &cert, certPEMBlock)
if err != nil && cfg.Logger != nil { if err != nil {
cfg.Logger.Warn("stapling OCSP", zap.Error(err), zap.Strings("identifiers", cert.Names)) cfg.Logger.Warn("stapling OCSP", zap.Error(err), zap.Strings("identifiers", cert.Names))
} }
return cert, nil return cert, nil
@ -326,9 +333,7 @@ func (cfg *Config) managedCertInStorageExpiresSoon(ctx context.Context, cert Cer
// to the new cert. It assumes that the new certificate for oldCert.Names[0] is // to the new cert. It assumes that the new certificate for oldCert.Names[0] is
// already in storage. It returns the newly-loaded certificate if successful. // already in storage. It returns the newly-loaded certificate if successful.
func (cfg *Config) reloadManagedCertificate(ctx context.Context, oldCert Certificate) (Certificate, error) { func (cfg *Config) reloadManagedCertificate(ctx context.Context, oldCert Certificate) (Certificate, error) {
if cfg.Logger != nil { cfg.Logger.Info("reloading managed certificate", zap.Strings("identifiers", oldCert.Names))
cfg.Logger.Info("reloading managed certificate", zap.Strings("identifiers", oldCert.Names))
}
newCert, err := cfg.loadManagedCertificate(ctx, oldCert.Names[0]) newCert, err := cfg.loadManagedCertificate(ctx, oldCert.Names[0])
if err != nil { if err != nil {
return Certificate{}, fmt.Errorf("loading managed certificate for %v from storage: %v", oldCert.Names, err) return Certificate{}, fmt.Errorf("loading managed certificate for %v from storage: %v", oldCert.Names, err)

View file

@ -43,10 +43,14 @@ import (
"log" "log"
"net" "net"
"net/http" "net/http"
"os"
"sort" "sort"
"strings" "strings"
"sync" "sync"
"time" "time"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
) )
// HTTPS serves mux for all domainNames using the HTTP // HTTPS serves mux for all domainNames using the HTTP
@ -405,7 +409,7 @@ type IssuedCertificate struct {
// Any extra information to serialize alongside the // Any extra information to serialize alongside the
// certificate in storage. // certificate in storage.
Metadata interface{} Metadata any
} }
// CertificateResource associates a certificate with its private // CertificateResource associates a certificate with its private
@ -425,7 +429,7 @@ type CertificateResource struct {
// Any extra information associated with the certificate, // Any extra information associated with the certificate,
// usually provided by the issuer implementation. // usually provided by the issuer implementation.
IssuerData interface{} `json:"issuer_data,omitempty"` IssuerData any `json:"issuer_data,omitempty"`
// The unique string identifying the issuer of the // The unique string identifying the issuer of the
// certificate; internally useful for storage access. // certificate; internally useful for storage access.
@ -468,8 +472,16 @@ var Default = Config{
RenewalWindowRatio: DefaultRenewalWindowRatio, RenewalWindowRatio: DefaultRenewalWindowRatio,
Storage: defaultFileStorage, Storage: defaultFileStorage,
KeySource: DefaultKeyGenerator, KeySource: DefaultKeyGenerator,
Logger: defaultLogger,
} }
// defaultLogger is guaranteed to be a non-nil fallback logger.
var defaultLogger = zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
os.Stderr,
zap.InfoLevel,
))
const ( const (
// HTTPChallengePort is the officially-designated port for // HTTPChallengePort is the officially-designated port for
// the HTTP challenge according to the ACME spec. // the HTTP challenge according to the ACME spec.

View file

@ -56,7 +56,16 @@ type Config struct {
// to subscribe to certain things happening // to subscribe to certain things happening
// internally by this config; invocations are // internally by this config; invocations are
// synchronous, so make them return quickly! // synchronous, so make them return quickly!
OnEvent func(event string, data interface{}) // Functions should honor context cancellation.
//
// An error should only be returned to advise
// the emitter to abort or cancel an upcoming
// event. Some events, especially those that have
// already happened, cannot be aborted. For example,
// cert_obtaining can be canceled, but
// cert_obtained cannot. Emitters may choose to
// ignore returned errors.
OnEvent func(ctx context.Context, event string, data map[string]any) error
// DefaultServerName specifies a server name // DefaultServerName specifies a server name
// to use when choosing a certificate if the // to use when choosing a certificate if the
@ -110,7 +119,8 @@ type Config struct {
// TLS assets. Default is the local file system. // TLS assets. Default is the local file system.
Storage Storage Storage Storage
// Set a logger to enable logging. // Set a logger to enable logging. If not set,
// a default logger will be created.
Logger *zap.Logger Logger *zap.Logger
// required pointer to the in-memory cert cache // required pointer to the in-memory cert cache
@ -196,6 +206,16 @@ func newWithCache(certCache *Cache, cfg Config) *Config {
if cfg.OnDemand == nil { if cfg.OnDemand == nil {
cfg.OnDemand = Default.OnDemand cfg.OnDemand = Default.OnDemand
} }
if !cfg.MustStaple {
cfg.MustStaple = Default.MustStaple
}
if len(cfg.Issuers) == 0 {
cfg.Issuers = Default.Issuers
if len(cfg.Issuers) == 0 {
// at least one issuer is absolutely required
cfg.Issuers = []Issuer{NewACMEIssuer(&cfg, DefaultACME)}
}
}
if cfg.RenewalWindowRatio == 0 { if cfg.RenewalWindowRatio == 0 {
cfg.RenewalWindowRatio = Default.RenewalWindowRatio cfg.RenewalWindowRatio = Default.RenewalWindowRatio
} }
@ -208,18 +228,11 @@ func newWithCache(certCache *Cache, cfg Config) *Config {
if cfg.DefaultServerName == "" { if cfg.DefaultServerName == "" {
cfg.DefaultServerName = Default.DefaultServerName cfg.DefaultServerName = Default.DefaultServerName
} }
if !cfg.MustStaple {
cfg.MustStaple = Default.MustStaple
}
if cfg.Storage == nil { if cfg.Storage == nil {
cfg.Storage = Default.Storage cfg.Storage = Default.Storage
} }
if len(cfg.Issuers) == 0 { if cfg.Logger == nil {
cfg.Issuers = Default.Issuers cfg.Logger = Default.Logger
if len(cfg.Issuers) == 0 {
// at least one issuer is absolutely required
cfg.Issuers = []Issuer{NewACMEIssuer(&cfg, DefaultACME)}
}
} }
// absolutely don't allow a nil storage, // absolutely don't allow a nil storage,
@ -229,6 +242,12 @@ func newWithCache(certCache *Cache, cfg Config) *Config {
cfg.Storage = defaultFileStorage cfg.Storage = defaultFileStorage
} }
// absolutely don't allow a nil logger either,
// because that would result in panics
if cfg.Logger == nil {
cfg.Logger = defaultLogger
}
cfg.certCache = certCache cfg.certCache = certCache
return &cfg return &cfg
@ -460,11 +479,9 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
return fmt.Errorf("failed storage check: %v - storage is probably misconfigured", err) return fmt.Errorf("failed storage check: %v - storage is probably misconfigured", err)
} }
log := loggerNamed(cfg.Logger, "obtain") log := cfg.Logger.Named("obtain")
if log != nil { log.Info("acquiring lock", zap.String("identifier", name))
log.Info("acquiring lock", zap.String("identifier", name))
}
// ensure idempotency of the obtain operation for this name // ensure idempotency of the obtain operation for this name
lockKey := cfg.lockKey(certIssueLockOp, name) lockKey := cfg.lockKey(certIssueLockOp, name)
@ -473,33 +490,30 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
return fmt.Errorf("unable to acquire lock '%s': %v", lockKey, err) return fmt.Errorf("unable to acquire lock '%s': %v", lockKey, err)
} }
defer func() { defer func() {
if log != nil { log.Info("releasing lock", zap.String("identifier", name))
log.Info("releasing lock", zap.String("identifier", name))
}
if err := releaseLock(ctx, cfg.Storage, lockKey); err != nil { if err := releaseLock(ctx, cfg.Storage, lockKey); err != nil {
if log != nil { log.Error("unable to unlock",
log.Error("unable to unlock", zap.String("identifier", name),
zap.String("identifier", name), zap.String("lock_key", lockKey),
zap.String("lock_key", lockKey), zap.Error(err))
zap.Error(err))
}
} }
}() }()
if log != nil { log.Info("lock acquired", zap.String("identifier", name))
log.Info("lock acquired", zap.String("identifier", name))
}
f := func(ctx context.Context) error { f := func(ctx context.Context) error {
// check if obtain is still needed -- might have been obtained during lock // check if obtain is still needed -- might have been obtained during lock
if cfg.storageHasCertResourcesAnyIssuer(ctx, name) { if cfg.storageHasCertResourcesAnyIssuer(ctx, name) {
if log != nil { log.Info("certificate already exists in storage", zap.String("identifier", name))
log.Info("certificate already exists in storage", zap.String("identifier", name))
}
return nil return nil
} }
// if storage has a private key already, use it; otherwise, log.Info("obtaining certificate", zap.String("identifier", name))
// we'll generate our own
if err := cfg.emit(ctx, "cert_obtaining", map[string]any{"identifier": name}); err != nil {
return fmt.Errorf("obtaining certificate aborted by event handler: %w", err)
}
// if storage has a private key already, use it; otherwise we'll generate our own
privKey, privKeyPEM, issuers, err := cfg.reusePrivateKey(ctx, name) privKey, privKeyPEM, issuers, err := cfg.reusePrivateKey(ctx, name)
if err != nil { if err != nil {
return err return err
@ -523,11 +537,12 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
// try to obtain from each issuer until we succeed // try to obtain from each issuer until we succeed
var issuedCert *IssuedCertificate var issuedCert *IssuedCertificate
var issuerUsed Issuer var issuerUsed Issuer
var issuerKeys []string
for i, issuer := range issuers { for i, issuer := range issuers {
if log != nil { issuerKeys = append(issuerKeys, issuer.IssuerKey())
log.Debug(fmt.Sprintf("trying issuer %d/%d", i+1, len(cfg.Issuers)),
zap.String("issuer", issuer.IssuerKey())) log.Debug(fmt.Sprintf("trying issuer %d/%d", i+1, len(cfg.Issuers)),
} zap.String("issuer", issuer.IssuerKey()))
if prechecker, ok := issuer.(PreChecker); ok { if prechecker, ok := issuer.(PreChecker); ok {
err = prechecker.PreCheck(ctx, []string{name}, interactive) err = prechecker.PreCheck(ctx, []string{name}, interactive)
@ -549,14 +564,19 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
if errors.As(err, &problem) { if errors.As(err, &problem) {
errToLog = problem errToLog = problem
} }
if log != nil { log.Error("could not get certificate from issuer",
log.Error("could not get certificate from issuer", zap.String("identifier", name),
zap.String("identifier", name), zap.String("issuer", issuer.IssuerKey()),
zap.String("issuer", issuer.IssuerKey()), zap.Error(errToLog))
zap.Error(errToLog))
}
} }
if err != nil { if err != nil {
cfg.emit(ctx, "cert_failed", map[string]any{
"renewal": false,
"identifier": name,
"issuers": issuerKeys,
"error": err,
})
// only the error from the last issuer will be returned, but we logged the others // only the error from the last issuer will be returned, but we logged the others
return fmt.Errorf("[%s] Obtain: %w", name, err) return fmt.Errorf("[%s] Obtain: %w", name, err)
} }
@ -573,15 +593,14 @@ func (cfg *Config) obtainCert(ctx context.Context, name string, interactive bool
return fmt.Errorf("[%s] Obtain: saving assets: %v", name, err) return fmt.Errorf("[%s] Obtain: saving assets: %v", name, err)
} }
cfg.emit("cert_obtained", CertificateEventData{ log.Info("certificate obtained successfully", zap.String("identifier", name))
Name: name,
IssuerKey: issuerUsed.IssuerKey(),
StorageKey: certRes.NamesKey(),
})
if log != nil { cfg.emit(ctx, "cert_obtained", map[string]any{
log.Info("certificate obtained successfully", zap.String("identifier", name)) "renewal": false,
} "identifier": name,
"issuers": issuerUsed.IssuerKey(),
"storage_key": certRes.NamesKey(),
})
return nil return nil
} }
@ -675,11 +694,9 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
return fmt.Errorf("failed storage check: %v - storage is probably misconfigured", err) return fmt.Errorf("failed storage check: %v - storage is probably misconfigured", err)
} }
log := loggerNamed(cfg.Logger, "renew") log := cfg.Logger.Named("renew")
if log != nil { log.Info("acquiring lock", zap.String("identifier", name))
log.Info("acquiring lock", zap.String("identifier", name))
}
// ensure idempotency of the renew operation for this name // ensure idempotency of the renew operation for this name
lockKey := cfg.lockKey(certIssueLockOp, name) lockKey := cfg.lockKey(certIssueLockOp, name)
@ -688,21 +705,16 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
return fmt.Errorf("unable to acquire lock '%s': %v", lockKey, err) return fmt.Errorf("unable to acquire lock '%s': %v", lockKey, err)
} }
defer func() { defer func() {
if log != nil { log.Info("releasing lock", zap.String("identifier", name))
log.Info("releasing lock", zap.String("identifier", name))
}
if err := releaseLock(ctx, cfg.Storage, lockKey); err != nil { if err := releaseLock(ctx, cfg.Storage, lockKey); err != nil {
if log != nil { log.Error("unable to unlock",
log.Error("unable to unlock", zap.String("identifier", name),
zap.String("identifier", name), zap.String("lock_key", lockKey),
zap.String("lock_key", lockKey), zap.Error(err))
zap.Error(err))
}
} }
}() }()
if log != nil { log.Info("lock acquired", zap.String("identifier", name))
log.Info("lock acquired", zap.String("identifier", name))
}
f := func(ctx context.Context) error { f := func(ctx context.Context) error {
// prepare for renewal (load PEM cert, key, and meta) // prepare for renewal (load PEM cert, key, and meta)
@ -715,25 +727,29 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
timeLeft, needsRenew := cfg.managedCertNeedsRenewal(certRes) timeLeft, needsRenew := cfg.managedCertNeedsRenewal(certRes)
if !needsRenew { if !needsRenew {
if force { if force {
if log != nil { log.Info("certificate does not need to be renewed, but renewal is being forced",
log.Info("certificate does not need to be renewed, but renewal is being forced", zap.String("identifier", name),
zap.String("identifier", name), zap.Duration("remaining", timeLeft))
zap.Duration("remaining", timeLeft))
}
} else { } else {
if log != nil { log.Info("certificate appears to have been renewed already",
log.Info("certificate appears to have been renewed already", zap.String("identifier", name),
zap.String("identifier", name), zap.Duration("remaining", timeLeft))
zap.Duration("remaining", timeLeft))
}
return nil return nil
} }
} }
if log != nil { log.Info("renewing certificate",
log.Info("renewing certificate", zap.String("identifier", name),
zap.String("identifier", name), zap.Duration("remaining", timeLeft))
zap.Duration("remaining", timeLeft))
if err := cfg.emit(ctx, "cert_obtaining", map[string]any{
"renewal": true,
"identifier": name,
"forced": force,
"remaining": timeLeft,
"issuer": certRes.issuerKey, // previous/current issuer
}); err != nil {
return fmt.Errorf("renewing certificate aborted by event handler: %w", err)
} }
privateKey, err := PEMDecodePrivateKey(certRes.PrivateKeyPEM) privateKey, err := PEMDecodePrivateKey(certRes.PrivateKeyPEM)
@ -748,7 +764,9 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
// try to obtain from each issuer until we succeed // try to obtain from each issuer until we succeed
var issuedCert *IssuedCertificate var issuedCert *IssuedCertificate
var issuerUsed Issuer var issuerUsed Issuer
var issuerKeys []string
for _, issuer := range cfg.Issuers { for _, issuer := range cfg.Issuers {
issuerKeys = append(issuerKeys, issuer.IssuerKey())
if prechecker, ok := issuer.(PreChecker); ok { if prechecker, ok := issuer.(PreChecker); ok {
err = prechecker.PreCheck(ctx, []string{name}, interactive) err = prechecker.PreCheck(ctx, []string{name}, interactive)
if err != nil { if err != nil {
@ -769,14 +787,21 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
if errors.As(err, &problem) { if errors.As(err, &problem) {
errToLog = problem errToLog = problem
} }
if log != nil { log.Error("could not get certificate from issuer",
log.Error("could not get certificate from issuer", zap.String("identifier", name),
zap.String("identifier", name), zap.String("issuer", issuer.IssuerKey()),
zap.String("issuer", issuer.IssuerKey()), zap.Error(errToLog))
zap.Error(errToLog))
}
} }
if err != nil { if err != nil {
cfg.emit(ctx, "cert_failed", map[string]any{
"renewal": true,
"identifier": name,
"remaining": timeLeft,
"issuers": issuerKeys,
"storage_key": certRes.NamesKey(),
"error": err,
})
// only the error from the last issuer will be returned, but we logged the others // only the error from the last issuer will be returned, but we logged the others
return fmt.Errorf("[%s] Renew: %w", name, err) return fmt.Errorf("[%s] Renew: %w", name, err)
} }
@ -793,15 +818,15 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
return fmt.Errorf("[%s] Renew: saving assets: %v", name, err) return fmt.Errorf("[%s] Renew: saving assets: %v", name, err)
} }
cfg.emit("cert_renewed", CertificateEventData{ log.Info("certificate renewed successfully", zap.String("identifier", name))
Name: name,
IssuerKey: issuerUsed.IssuerKey(),
StorageKey: certRes.NamesKey(),
})
if log != nil { cfg.emit(ctx, "cert_obtained", map[string]any{
log.Info("certificate renewed successfully", zap.String("identifier", name)) "renewal": true,
} "remaining": timeLeft,
"identifier": name,
"issuer": issuerUsed.IssuerKey(),
"storage_key": certRes.NamesKey(),
})
return nil return nil
} }
@ -876,12 +901,6 @@ func (cfg *Config) RevokeCert(ctx context.Context, domain string, reason int, in
return fmt.Errorf("issuer %d (%s): %v", i, issuerKey, err) return fmt.Errorf("issuer %d (%s): %v", i, issuerKey, err)
} }
cfg.emit("cert_revoked", CertificateEventData{
Name: domain,
IssuerKey: issuerKey,
StorageKey: certRes.NamesKey(),
})
err = cfg.deleteSiteAssets(ctx, issuerKey, domain) err = cfg.deleteSiteAssets(ctx, issuerKey, domain)
if err != nil { if err != nil {
return fmt.Errorf("certificate revoked, but unable to fully clean up assets from issuer %s: %v", issuerKey, err) return fmt.Errorf("certificate revoked, but unable to fully clean up assets from issuer %s: %v", issuerKey, err)
@ -994,10 +1013,8 @@ func (cfg *Config) checkStorage(ctx context.Context) error {
defer func() { defer func() {
deleteErr := cfg.Storage.Delete(ctx, key) deleteErr := cfg.Storage.Delete(ctx, key)
if deleteErr != nil { if deleteErr != nil {
if cfg.Logger != nil { cfg.Logger.Error("deleting test key from storage",
cfg.Logger.Error("deleting test key from storage", zap.String("key", key), zap.Error(err))
zap.String("key", key), zap.Error(err))
}
} }
// if there was no other error, make sure // if there was no other error, make sure
// to return any error returned from Delete // to return any error returned from Delete
@ -1065,23 +1082,16 @@ func (cfg *Config) managedCertNeedsRenewal(certRes CertificateResource) (time.Du
if err != nil { if err != nil {
return 0, true return 0, true
} }
remaining := time.Until(certChain[0].NotAfter) remaining := time.Until(expiresAt(certChain[0]))
needsRenew := currentlyInRenewalWindow(certChain[0].NotBefore, certChain[0].NotAfter, cfg.RenewalWindowRatio) needsRenew := currentlyInRenewalWindow(certChain[0].NotBefore, expiresAt(certChain[0]), cfg.RenewalWindowRatio)
return remaining, needsRenew return remaining, needsRenew
} }
func (cfg *Config) emit(eventName string, data interface{}) { func (cfg *Config) emit(ctx context.Context, eventName string, data map[string]any) error {
if cfg.OnEvent == nil { if cfg.OnEvent == nil {
return
}
cfg.OnEvent(eventName, data)
}
func loggerNamed(l *zap.Logger, name string) *zap.Logger {
if l == nil {
return nil return nil
} }
return l.Named(name) return cfg.OnEvent(ctx, eventName, data)
} }
// CertificateSelector is a type which can select a certificate to use given multiple choices. // CertificateSelector is a type which can select a certificate to use given multiple choices.
@ -1105,20 +1115,6 @@ type OCSPConfig struct {
ResponderOverrides map[string]string ResponderOverrides map[string]string
} }
// CertificateEventData contains contextual information for
// an obtained, renewed or revoked certificate.
// EXPERIMENTAL: subject to change.
type CertificateEventData struct {
// Domain or subject name of the certificate.
Name string
// Storage key for the issuer used for this certificate.
IssuerKey string
// Location in storage at which the certificate could be found.
StorageKey string
}
// certIssueLockOp is the name of the operation used // certIssueLockOp is the name of the operation used
// when naming a lock to make it mutually exclusive // when naming a lock to make it mutually exclusive
// with other certificate issuance operations for a // with other certificate issuance operations for a

View file

@ -226,14 +226,12 @@ func (cfg *Config) loadCertResourceAnyIssuer(ctx context.Context, certNamesKey s
return certResources[j].decoded.NotBefore.Before(certResources[i].decoded.NotBefore) return certResources[j].decoded.NotBefore.Before(certResources[i].decoded.NotBefore)
}) })
if cfg.Logger != nil { cfg.Logger.Debug("loading managed certificate",
cfg.Logger.Debug("loading managed certificate", zap.String("domain", certNamesKey),
zap.String("domain", certNamesKey), zap.Time("expiration", expiresAt(certResources[0].decoded)),
zap.Time("expiration", certResources[0].decoded.NotAfter), zap.String("issuer_key", certResources[0].issuer.IssuerKey()),
zap.String("issuer_key", certResources[0].issuer.IssuerKey()), zap.Any("storage", cfg.Storage),
zap.Any("storage", cfg.Storage), )
)
}
return certResources[0].CertificateResource, nil return certResources[0].CertificateResource, nil
} }

View file

@ -243,7 +243,7 @@ func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, erro
} }
if r.Rcode != dns.RcodeSuccess { if r.Rcode != dns.RcodeSuccess {
if r.Rcode == dns.RcodeNameError { if r.Rcode == dns.RcodeNameError || r.Rcode == dns.RcodeServerFailure {
// if Present() succeeded, then it must show up eventually, or else // if Present() succeeded, then it must show up eventually, or else
// something is really broken in the DNS provider or their API; // something is really broken in the DNS provider or their API;
// no need for error here, simply have the caller try again // no need for error here, simply have the caller try again

View file

@ -149,10 +149,10 @@ func (s *FileStorage) Filename(key string) string {
return filepath.Join(s.Path, filepath.FromSlash(key)) return filepath.Join(s.Path, filepath.FromSlash(key))
} }
// Lock obtains a lock named by the given key. It blocks // Lock obtains a lock named by the given name. It blocks
// until the lock can be obtained or an error is returned. // until the lock can be obtained or an error is returned.
func (s *FileStorage) Lock(ctx context.Context, key string) error { func (s *FileStorage) Lock(ctx context.Context, name string) error {
filename := s.lockFilename(key) filename := s.lockFilename(name)
for { for {
err := createLockfile(filename) err := createLockfile(filename)
@ -172,7 +172,13 @@ func (s *FileStorage) Lock(ctx context.Context, key string) error {
if err == nil { if err == nil {
err2 := json.NewDecoder(f).Decode(&meta) err2 := json.NewDecoder(f).Decode(&meta)
f.Close() f.Close()
if err2 != nil { if errors.Is(err2, io.EOF) {
// lockfile is empty or truncated; I *think* we can assume the previous
// acquirer either crashed or had some sort of failure that caused them
// to be unable to fully acquire or retain the lock, therefore we should
// treat it as if the lockfile did not exist
log.Printf("[INFO][%s] %s: Empty lockfile (%v) - likely previous process crashed or storage medium failure; treating as stale", s, filename, err2)
} else if err2 != nil {
return fmt.Errorf("decoding lockfile contents: %w", err2) return fmt.Errorf("decoding lockfile contents: %w", err2)
} }
} }
@ -193,10 +199,10 @@ func (s *FileStorage) Lock(ctx context.Context, key string) error {
// or must give up on perfect mutual exclusivity; however, these cases are rare, // or must give up on perfect mutual exclusivity; however, these cases are rare,
// so we prefer the simpler solution that avoids infinite loops) // so we prefer the simpler solution that avoids infinite loops)
log.Printf("[INFO][%s] Lock for '%s' is stale (created: %s, last update: %s); removing then retrying: %s", log.Printf("[INFO][%s] Lock for '%s' is stale (created: %s, last update: %s); removing then retrying: %s",
s, key, meta.Created, meta.Updated, filename) s, name, meta.Created, meta.Updated, filename)
if err = os.Remove(filename); err != nil { // hopefully we can replace the lock file quickly! if err = os.Remove(filename); err != nil { // hopefully we can replace the lock file quickly!
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("unable to delete stale lock; deadlocked: %w", err) return fmt.Errorf("unable to delete stale lockfile; deadlocked: %w", err)
} }
} }
continue continue
@ -215,16 +221,16 @@ func (s *FileStorage) Lock(ctx context.Context, key string) error {
} }
// Unlock releases the lock for name. // Unlock releases the lock for name.
func (s *FileStorage) Unlock(_ context.Context, key string) error { func (s *FileStorage) Unlock(_ context.Context, name string) error {
return os.Remove(s.lockFilename(key)) return os.Remove(s.lockFilename(name))
} }
func (s *FileStorage) String() string { func (s *FileStorage) String() string {
return "FileStorage:" + s.Path return "FileStorage:" + s.Path
} }
func (s *FileStorage) lockFilename(key string) string { func (s *FileStorage) lockFilename(name string) string {
return filepath.Join(s.lockDir(), StorageKeys.Safe(key)+".lock") return filepath.Join(s.lockDir(), StorageKeys.Safe(name)+".lock")
} }
func (s *FileStorage) lockDir() string { func (s *FileStorage) lockDir() string {

View file

@ -43,7 +43,15 @@ import (
// //
// This method is safe for use as a tls.Config.GetCertificate callback. // This method is safe for use as a tls.Config.GetCertificate callback.
func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
cfg.emit("tls_handshake_started", clientHello) ctx := context.TODO() // TODO: get a proper context? from somewhere...
if err := cfg.emit(ctx, "tls_get_certificate", map[string]any{"client_hello": clientHello}); err != nil {
cfg.Logger.Error("TLS handshake aborted by event handler",
zap.String("server_name", clientHello.ServerName),
zap.String("remote", clientHello.Conn.RemoteAddr().String()),
zap.Error(err))
return nil, fmt.Errorf("handshake aborted by event handler: %w", err)
}
// special case: serve up the certificate for a TLS-ALPN ACME challenge // special case: serve up the certificate for a TLS-ALPN ACME challenge
// (https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05) // (https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05)
@ -51,29 +59,23 @@ func (cfg *Config) GetCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certif
if proto == acmez.ACMETLS1Protocol { if proto == acmez.ACMETLS1Protocol {
challengeCert, distributed, err := cfg.getTLSALPNChallengeCert(clientHello) challengeCert, distributed, err := cfg.getTLSALPNChallengeCert(clientHello)
if err != nil { if err != nil {
if cfg.Logger != nil { cfg.Logger.Error("tls-alpn challenge",
cfg.Logger.Error("tls-alpn challenge", zap.String("server_name", clientHello.ServerName),
zap.String("server_name", clientHello.ServerName), zap.Error(err))
zap.Error(err))
}
return nil, err return nil, err
} }
if cfg.Logger != nil { cfg.Logger.Info("served key authentication certificate",
cfg.Logger.Info("served key authentication certificate", zap.String("server_name", clientHello.ServerName),
zap.String("server_name", clientHello.ServerName), zap.String("challenge", "tls-alpn-01"),
zap.String("challenge", "tls-alpn-01"), zap.String("remote", clientHello.Conn.RemoteAddr().String()),
zap.String("remote", clientHello.Conn.RemoteAddr().String()), zap.Bool("distributed", distributed))
zap.Bool("distributed", distributed))
}
return challengeCert, nil return challengeCert, nil
} }
} }
// get the certificate and serve it up // get the certificate and serve it up
cert, err := cfg.getCertDuringHandshake(clientHello, true, true) cert, err := cfg.getCertDuringHandshake(ctx, clientHello, true, true)
if err == nil {
cfg.emit("tls_handshake_completed", clientHello)
}
return &cert.Certificate, err return &cert.Certificate, err
} }
@ -152,48 +154,44 @@ func (cfg *Config) getCertificateFromCache(hello *tls.ClientHelloInfo) (cert Cer
// then all certificates in the cache will be passed in // then all certificates in the cache will be passed in
// for the cfg.CertSelection to make the final decision. // for the cfg.CertSelection to make the final decision.
func (cfg *Config) selectCert(hello *tls.ClientHelloInfo, name string) (Certificate, bool) { func (cfg *Config) selectCert(hello *tls.ClientHelloInfo, name string) (Certificate, bool) {
logger := loggerNamed(cfg.Logger, "handshake") logger := cfg.Logger.Named("handshake")
choices := cfg.certCache.getAllMatchingCerts(name) choices := cfg.certCache.getAllMatchingCerts(name)
if len(choices) == 0 { if len(choices) == 0 {
if cfg.CertSelection == nil { if cfg.CertSelection == nil {
if logger != nil { logger.Debug("no matching certificates and no custom selection logic", zap.String("identifier", name))
logger.Debug("no matching certificates and no custom selection logic", zap.String("identifier", name))
}
return Certificate{}, false return Certificate{}, false
} }
if logger != nil { logger.Debug("no matching certificate; will choose from all certificates", zap.String("identifier", name))
logger.Debug("no matching certificate; will choose from all certificates", zap.String("identifier", name))
}
choices = cfg.certCache.getAllCerts() choices = cfg.certCache.getAllCerts()
} }
if logger != nil {
logger.Debug("choosing certificate", logger.Debug("choosing certificate",
zap.String("identifier", name), zap.String("identifier", name),
zap.Int("num_choices", len(choices))) zap.Int("num_choices", len(choices)))
}
if cfg.CertSelection == nil { if cfg.CertSelection == nil {
cert, err := DefaultCertificateSelector(hello, choices) cert, err := DefaultCertificateSelector(hello, choices)
if logger != nil { logger.Debug("default certificate selection results",
logger.Debug("default certificate selection results",
zap.Error(err),
zap.String("identifier", name),
zap.Strings("subjects", cert.Names),
zap.Bool("managed", cert.managed),
zap.String("issuer_key", cert.issuerKey),
zap.String("hash", cert.hash))
}
return cert, err == nil
}
cert, err := cfg.CertSelection.SelectCertificate(hello, choices)
if logger != nil {
logger.Debug("custom certificate selection results",
zap.Error(err), zap.Error(err),
zap.String("identifier", name), zap.String("identifier", name),
zap.Strings("subjects", cert.Names), zap.Strings("subjects", cert.Names),
zap.Bool("managed", cert.managed), zap.Bool("managed", cert.managed),
zap.String("issuer_key", cert.issuerKey), zap.String("issuer_key", cert.issuerKey),
zap.String("hash", cert.hash)) zap.String("hash", cert.hash))
return cert, err == nil
} }
cert, err := cfg.CertSelection.SelectCertificate(hello, choices)
logger.Debug("custom certificate selection results",
zap.Error(err),
zap.String("identifier", name),
zap.Strings("subjects", cert.Names),
zap.Bool("managed", cert.managed),
zap.String("issuer_key", cert.issuerKey),
zap.String("hash", cert.hash))
return cert, err == nil return cert, err == nil
} }
@ -214,7 +212,7 @@ func DefaultCertificateSelector(hello *tls.ClientHelloInfo, choices []Certificat
continue continue
} }
best = choice // at least the client supports it... best = choice // at least the client supports it...
if now.After(choice.Leaf.NotBefore) && now.Before(choice.Leaf.NotAfter) { if now.After(choice.Leaf.NotBefore) && now.Before(expiresAt(choice.Leaf)) {
return choice, nil // ...and unexpired, great! "Certificate, I choose you!" return choice, nil // ...and unexpired, great! "Certificate, I choose you!"
} }
} }
@ -234,26 +232,22 @@ func DefaultCertificateSelector(hello *tls.ClientHelloInfo, choices []Certificat
// An error will be returned if and only if no certificate is available. // An error will be returned if and only if no certificate is available.
// //
// This function is safe for concurrent use. // This function is safe for concurrent use.
func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNecessary, obtainIfNecessary bool) (Certificate, error) { func (cfg *Config) getCertDuringHandshake(ctx context.Context, hello *tls.ClientHelloInfo, loadIfNecessary, obtainIfNecessary bool) (Certificate, error) {
log := loggerNamed(cfg.Logger, "handshake") log := logWithRemote(cfg.Logger.Named("handshake"), hello)
ctx := context.TODO() // TODO: get a proper context? from somewhere...
// First check our in-memory cache to see if we've already loaded it // First check our in-memory cache to see if we've already loaded it
cert, matched, defaulted := cfg.getCertificateFromCache(hello) cert, matched, defaulted := cfg.getCertificateFromCache(hello)
if matched { if matched {
if log != nil { log.Debug("matched certificate in cache",
log.Debug("matched certificate in cache", zap.Strings("subjects", cert.Names),
zap.Strings("subjects", cert.Names), zap.Bool("managed", cert.managed),
zap.Bool("managed", cert.managed), zap.Time("expiration", expiresAt(cert.Leaf)),
zap.Time("expiration", cert.Leaf.NotAfter), zap.String("hash", cert.hash))
zap.String("hash", cert.hash))
}
if cert.managed && cfg.OnDemand != nil && obtainIfNecessary { if cert.managed && cfg.OnDemand != nil && obtainIfNecessary {
// On-demand certificates are maintained in the background, but // On-demand certificates are maintained in the background, but
// maintenance is triggered by handshakes instead of by a timer // maintenance is triggered by handshakes instead of by a timer
// as in maintain.go. // as in maintain.go.
return cfg.optionalMaintenance(ctx, loggerNamed(cfg.Logger, "on_demand"), cert, hello) return cfg.optionalMaintenance(ctx, cfg.Logger.Named("on_demand"), cert, hello)
} }
return cert, nil return cert, nil
} }
@ -302,20 +296,16 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece
loadedCert, err = cfg.CacheManagedCertificate(ctx, strings.Join(labels, ".")) loadedCert, err = cfg.CacheManagedCertificate(ctx, strings.Join(labels, "."))
} }
if err == nil { if err == nil {
if log != nil { log.Debug("loaded certificate from storage",
log.Debug("loaded certificate from storage", zap.Strings("subjects", loadedCert.Names),
zap.Strings("subjects", loadedCert.Names), zap.Bool("managed", loadedCert.managed),
zap.Bool("managed", loadedCert.managed), zap.Time("expiration", expiresAt(loadedCert.Leaf)),
zap.Time("expiration", loadedCert.Leaf.NotAfter), zap.String("hash", loadedCert.hash))
zap.String("hash", loadedCert.hash))
}
loadedCert, err = cfg.handshakeMaintenance(ctx, hello, loadedCert) loadedCert, err = cfg.handshakeMaintenance(ctx, hello, loadedCert)
if err != nil { if err != nil {
if log != nil { log.Error("maintaining newly-loaded certificate",
log.Error("maintaining newly-loaded certificate", zap.String("server_name", name),
zap.String("server_name", name), zap.Error(err))
zap.Error(err))
}
} }
return loadedCert, nil return loadedCert, nil
} }
@ -327,27 +317,23 @@ func (cfg *Config) getCertDuringHandshake(hello *tls.ClientHelloInfo, loadIfNece
// Fall back to the default certificate if there is one // Fall back to the default certificate if there is one
if defaulted { if defaulted {
if log != nil { log.Debug("fell back to default certificate",
log.Debug("fell back to default certificate", zap.Strings("subjects", cert.Names),
zap.Strings("subjects", cert.Names), zap.Bool("managed", cert.managed),
zap.Bool("managed", cert.managed), zap.Time("expiration", expiresAt(cert.Leaf)),
zap.Time("expiration", cert.Leaf.NotAfter), zap.String("hash", cert.hash))
zap.String("hash", cert.hash))
}
return cert, nil return cert, nil
} }
if log != nil { log.Debug("no certificate matching TLS ClientHello",
log.Debug("no certificate matching TLS ClientHello", zap.String("server_name", hello.ServerName),
zap.String("server_name", hello.ServerName), zap.String("remote", hello.Conn.RemoteAddr().String()),
zap.String("remote", hello.Conn.RemoteAddr().String()), zap.String("identifier", name),
zap.String("identifier", name), zap.Uint16s("cipher_suites", hello.CipherSuites),
zap.Uint16s("cipher_suites", hello.CipherSuites), zap.Float64("cert_cache_fill", float64(cacheSize)/cacheCapacity), // may be approximate! because we are not within the lock
zap.Float64("cert_cache_fill", float64(cacheSize)/cacheCapacity), // may be approximate! because we are not within the lock zap.Bool("load_if_necessary", loadIfNecessary),
zap.Bool("load_if_necessary", loadIfNecessary), zap.Bool("obtain_if_necessary", obtainIfNecessary),
zap.Bool("obtain_if_necessary", obtainIfNecessary), zap.Bool("on_demand", cfg.OnDemand != nil))
zap.Bool("on_demand", cfg.OnDemand != nil))
}
return Certificate{}, fmt.Errorf("no certificate available for '%s'", name) return Certificate{}, fmt.Errorf("no certificate available for '%s'", name)
} }
@ -361,12 +347,10 @@ func (cfg *Config) optionalMaintenance(ctx context.Context, log *zap.Logger, cer
return newCert, nil return newCert, nil
} }
if log != nil { log.Error("renewing certificate on-demand failed",
log.Error("renewing certificate on-demand failed", zap.Strings("subjects", cert.Names),
zap.Strings("subjects", cert.Names), zap.Time("not_after", expiresAt(cert.Leaf)),
zap.Time("not_after", cert.Leaf.NotAfter), zap.Error(err))
zap.Error(err))
}
if cert.Expired() { if cert.Expired() {
return cert, err return cert, err
@ -402,13 +386,13 @@ func (cfg *Config) checkIfCertShouldBeObtained(name string) error {
// //
// This function is safe for use by multiple concurrent goroutines. // This function is safe for use by multiple concurrent goroutines.
func (cfg *Config) obtainOnDemandCertificate(ctx context.Context, hello *tls.ClientHelloInfo) (Certificate, error) { func (cfg *Config) obtainOnDemandCertificate(ctx context.Context, hello *tls.ClientHelloInfo) (Certificate, error) {
log := loggerNamed(cfg.Logger, "on_demand") log := logWithRemote(cfg.Logger.Named("on_demand"), hello)
name := cfg.getNameFromClientHello(hello) name := cfg.getNameFromClientHello(hello)
getCertWithoutReobtaining := func() (Certificate, error) { getCertWithoutReobtaining := func() (Certificate, error) {
// very important to set the obtainIfNecessary argument to false, so we don't repeat this infinitely // very important to set the obtainIfNecessary argument to false, so we don't repeat this infinitely
return cfg.getCertDuringHandshake(hello, true, false) return cfg.getCertDuringHandshake(ctx, hello, true, false)
} }
// We must protect this process from happening concurrently, so synchronize. // We must protect this process from happening concurrently, so synchronize.
@ -451,9 +435,7 @@ func (cfg *Config) obtainOnDemandCertificate(ctx context.Context, hello *tls.Cli
return Certificate{}, err return Certificate{}, err
} }
if log != nil { log.Info("obtaining new certificate", zap.String("server_name", name))
log.Info("obtaining new certificate", zap.String("server_name", name))
}
// TODO: we are only adding a timeout because we don't know if the context passed in is actually cancelable... // TODO: we are only adding a timeout because we don't know if the context passed in is actually cancelable...
// (timeout duration is based on https://caddy.community/t/zerossl-dns-challenge-failing-often-route53-plugin/13822/24?u=matt) // (timeout duration is based on https://caddy.community/t/zerossl-dns-challenge-failing-often-route53-plugin/13822/24?u=matt)
@ -485,35 +467,29 @@ func (cfg *Config) obtainOnDemandCertificate(ctx context.Context, hello *tls.Cli
// //
// This function is safe for use by multiple concurrent goroutines. // This function is safe for use by multiple concurrent goroutines.
func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHelloInfo, cert Certificate) (Certificate, error) { func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHelloInfo, cert Certificate) (Certificate, error) {
log := loggerNamed(cfg.Logger, "on_demand") log := cfg.Logger.Named("on_demand")
// Check OCSP staple validity // Check OCSP staple validity
if cert.ocsp != nil && !freshOCSP(cert.ocsp) { if cert.ocsp != nil && !freshOCSP(cert.ocsp) {
if log != nil { log.Debug("OCSP response needs refreshing",
log.Debug("OCSP response needs refreshing", zap.Strings("identifiers", cert.Names),
zap.Strings("identifiers", cert.Names), zap.Int("ocsp_status", cert.ocsp.Status),
zap.Int("ocsp_status", cert.ocsp.Status), zap.Time("this_update", cert.ocsp.ThisUpdate),
zap.Time("this_update", cert.ocsp.ThisUpdate), zap.Time("next_update", cert.ocsp.NextUpdate))
zap.Time("next_update", cert.ocsp.NextUpdate))
}
err := stapleOCSP(ctx, cfg.OCSP, cfg.Storage, &cert, nil) err := stapleOCSP(ctx, cfg.OCSP, cfg.Storage, &cert, nil)
if err != nil { if err != nil {
// An error with OCSP stapling is not the end of the world, and in fact, is // An error with OCSP stapling is not the end of the world, and in fact, is
// quite common considering not all certs have issuer URLs that support it. // quite common considering not all certs have issuer URLs that support it.
if log != nil { log.Warn("stapling OCSP",
log.Warn("stapling OCSP", zap.String("server_name", hello.ServerName),
zap.String("server_name", hello.ServerName), zap.Error(err))
zap.Error(err)) } else {
} log.Debug("successfully stapled new OCSP response",
} else if log != nil { zap.Strings("identifiers", cert.Names),
if log != nil { zap.Int("ocsp_status", cert.ocsp.Status),
log.Debug("successfully stapled new OCSP response", zap.Time("this_update", cert.ocsp.ThisUpdate),
zap.Strings("identifiers", cert.Names), zap.Time("next_update", cert.ocsp.NextUpdate))
zap.Int("ocsp_status", cert.ocsp.Status),
zap.Time("this_update", cert.ocsp.ThisUpdate),
zap.Time("next_update", cert.ocsp.NextUpdate))
}
} }
// our copy of cert has the new OCSP staple, so replace it in the cache // our copy of cert has the new OCSP staple, so replace it in the cache
@ -525,19 +501,17 @@ func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHe
// We attempt to replace any certificates that were revoked. // We attempt to replace any certificates that were revoked.
// Crucially, this happens OUTSIDE a lock on the certCache. // Crucially, this happens OUTSIDE a lock on the certCache.
if certShouldBeForceRenewed(cert) { if certShouldBeForceRenewed(cert) {
if log != nil { log.Warn("on-demand certificate's OCSP status is REVOKED; will try to forcefully renew",
log.Warn("on-demand certificate's OCSP status is REVOKED; will try to forcefully renew", zap.Strings("identifiers", cert.Names),
zap.Strings("identifiers", cert.Names), zap.Int("ocsp_status", cert.ocsp.Status),
zap.Int("ocsp_status", cert.ocsp.Status), zap.Time("revoked_at", cert.ocsp.RevokedAt),
zap.Time("revoked_at", cert.ocsp.RevokedAt), zap.Time("this_update", cert.ocsp.ThisUpdate),
zap.Time("this_update", cert.ocsp.ThisUpdate), zap.Time("next_update", cert.ocsp.NextUpdate))
zap.Time("next_update", cert.ocsp.NextUpdate))
}
return cfg.renewDynamicCertificate(ctx, hello, cert) return cfg.renewDynamicCertificate(ctx, hello, cert)
} }
// Check cert expiration // Check cert expiration
if currentlyInRenewalWindow(cert.Leaf.NotBefore, cert.Leaf.NotAfter, cfg.RenewalWindowRatio) { if currentlyInRenewalWindow(cert.Leaf.NotBefore, expiresAt(cert.Leaf), cfg.RenewalWindowRatio) {
return cfg.renewDynamicCertificate(ctx, hello, cert) return cfg.renewDynamicCertificate(ctx, hello, cert)
} }
@ -556,15 +530,15 @@ func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHe
// //
// This function is safe for use by multiple concurrent goroutines. // This function is safe for use by multiple concurrent goroutines.
func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.ClientHelloInfo, currentCert Certificate) (Certificate, error) { func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.ClientHelloInfo, currentCert Certificate) (Certificate, error) {
log := loggerNamed(cfg.Logger, "on_demand") log := cfg.Logger.Named("on_demand")
name := cfg.getNameFromClientHello(hello) name := cfg.getNameFromClientHello(hello)
timeLeft := time.Until(currentCert.Leaf.NotAfter) timeLeft := time.Until(expiresAt(currentCert.Leaf))
revoked := currentCert.ocsp != nil && currentCert.ocsp.Status == ocsp.Revoked revoked := currentCert.ocsp != nil && currentCert.ocsp.Status == ocsp.Revoked
getCertWithoutReobtaining := func() (Certificate, error) { getCertWithoutReobtaining := func() (Certificate, error) {
// very important to set the obtainIfNecessary argument to false, so we don't repeat this infinitely // very important to set the obtainIfNecessary argument to false, so we don't repeat this infinitely
return cfg.getCertDuringHandshake(hello, true, false) return cfg.getCertDuringHandshake(ctx, hello, true, false)
} }
// see if another goroutine is already working on this certificate // see if another goroutine is already working on this certificate
@ -578,23 +552,19 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
// renewing it, so we might as well serve what we have without blocking, UNLESS // renewing it, so we might as well serve what we have without blocking, UNLESS
// we're forcing renewal, in which case the current certificate is not usable // we're forcing renewal, in which case the current certificate is not usable
if timeLeft > 0 && !revoked { if timeLeft > 0 && !revoked {
if log != nil { log.Debug("certificate expires soon but is already being renewed; serving current certificate",
log.Debug("certificate expires soon but is already being renewed; serving current certificate", zap.Strings("subjects", currentCert.Names),
zap.Strings("subjects", currentCert.Names), zap.Duration("remaining", timeLeft))
zap.Duration("remaining", timeLeft))
}
return currentCert, nil return currentCert, nil
} }
// otherwise, we'll have to wait for the renewal to finish so we don't serve // otherwise, we'll have to wait for the renewal to finish so we don't serve
// a revoked or expired certificate // a revoked or expired certificate
if log != nil { log.Debug("certificate has expired, but is already being renewed; waiting for renewal to complete",
log.Debug("certificate has expired, but is already being renewed; waiting for renewal to complete", zap.Strings("subjects", currentCert.Names),
zap.Strings("subjects", currentCert.Names), zap.Time("expired", expiresAt(currentCert.Leaf)),
zap.Time("expired", currentCert.Leaf.NotAfter), zap.Bool("revoked", revoked))
zap.Bool("revoked", revoked))
}
// TODO: see if we can get a proper context in here, for true cancellation // TODO: see if we can get a proper context in here, for true cancellation
timeout := time.NewTimer(2 * time.Minute) timeout := time.NewTimer(2 * time.Minute)
@ -620,30 +590,36 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
obtainCertWaitChansMu.Unlock() obtainCertWaitChansMu.Unlock()
} }
if log != nil { log = log.With(
log.Info("attempting certificate renewal", zap.String("server_name", name),
zap.String("server_name", name), zap.Strings("subjects", currentCert.Names),
zap.Strings("subjects", currentCert.Names), zap.Time("expiration", expiresAt(currentCert.Leaf)),
zap.Time("expiration", currentCert.Leaf.NotAfter), zap.Duration("remaining", timeLeft),
zap.Duration("remaining", timeLeft), zap.Bool("revoked", revoked),
zap.Bool("revoked", revoked)) )
}
// Make sure a certificate for this name should be obtained on-demand
err := cfg.checkIfCertShouldBeObtained(name)
if err != nil {
// if not, remove from cache (it will be deleted from storage later)
cfg.certCache.mu.Lock()
cfg.certCache.removeCertificate(currentCert)
cfg.certCache.mu.Unlock()
unblockWaiters()
return Certificate{}, err
}
// Renew and reload the certificate // Renew and reload the certificate
renewAndReload := func(ctx context.Context, cancel context.CancelFunc) (Certificate, error) { renewAndReload := func(ctx context.Context, cancel context.CancelFunc) (Certificate, error) {
defer cancel() defer cancel()
log.Info("attempting certificate renewal")
// Make sure a certificate for this name should be obtained on-demand
err := cfg.checkIfCertShouldBeObtained(name)
if err != nil {
// if not, remove from cache (it will be deleted from storage later)
cfg.certCache.mu.Lock()
cfg.certCache.removeCertificate(currentCert)
cfg.certCache.mu.Unlock()
unblockWaiters()
if log != nil {
log.Error("certificate should not be obtained", zap.Error(err))
}
return Certificate{}, err
}
// otherwise, renew with issuer, etc. // otherwise, renew with issuer, etc.
var newCert Certificate var newCert Certificate
if revoked { if revoked {
@ -656,9 +632,7 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
// make the replacement as atomic as possible. // make the replacement as atomic as possible.
newCert, err = cfg.CacheManagedCertificate(ctx, name) newCert, err = cfg.CacheManagedCertificate(ctx, name)
if err != nil { if err != nil {
if log != nil { log.Error("loading renewed certificate", zap.String("server_name", name), zap.Error(err))
log.Error("loading renewed certificate", zap.String("server_name", name), zap.Error(err))
}
} else { } else {
// replace the old certificate with the new one // replace the old certificate with the new one
cfg.certCache.replaceCertificate(currentCert, newCert) cfg.certCache.replaceCertificate(currentCert, newCert)
@ -672,12 +646,7 @@ func (cfg *Config) renewDynamicCertificate(ctx context.Context, hello *tls.Clien
unblockWaiters() unblockWaiters()
if err != nil { if err != nil {
if log != nil { log.Error("renewing and reloading certificate", zap.Error(err))
log.Error("renewing and reloading certificate",
zap.String("server_name", name),
zap.Error(err),
zap.Bool("forced", revoked))
}
return newCert, err return newCert, err
} }
@ -723,9 +692,7 @@ func (cfg *Config) getCertFromAnyCertManager(ctx context.Context, hello *tls.Cli
} }
} }
if upstreamCert == nil { if upstreamCert == nil {
if log != nil { log.Debug("all external certificate managers yielded no certificates and no errors", zap.String("sni", hello.ServerName))
log.Debug("all external certificate managers yielded no certificates and no errors", zap.String("sni", hello.ServerName))
}
return Certificate{}, nil return Certificate{}, nil
} }
@ -735,12 +702,10 @@ func (cfg *Config) getCertFromAnyCertManager(ctx context.Context, hello *tls.Cli
return Certificate{}, fmt.Errorf("external certificate manager: %s: filling cert from leaf: %v", hello.ServerName, err) return Certificate{}, fmt.Errorf("external certificate manager: %s: filling cert from leaf: %v", hello.ServerName, err)
} }
if log != nil { log.Debug("using externally-managed certificate",
log.Debug("using externally-managed certificate", zap.String("sni", hello.ServerName),
zap.String("sni", hello.ServerName), zap.Strings("names", cert.Names),
zap.Strings("names", cert.Names), zap.Time("expiration", expiresAt(cert.Leaf)))
zap.Time("expiration", cert.Leaf.NotAfter))
}
return cert, nil return cert, nil
} }
@ -785,6 +750,20 @@ func (*Config) getNameFromClientHello(hello *tls.ClientHelloInfo) string {
return localIPFromConn(hello.Conn) return localIPFromConn(hello.Conn)
} }
// logWithRemote adds the remote host and port to the logger.
func logWithRemote(l *zap.Logger, hello *tls.ClientHelloInfo) *zap.Logger {
if hello.Conn == nil || l == nil {
return l
}
addr := hello.Conn.RemoteAddr().String()
ip, port, err := net.SplitHostPort(addr)
if err != nil {
ip = addr
port = ""
}
return l.With(zap.String("remote_ip", ip), zap.String("remote_port", port))
}
// localIPFromConn returns the host portion of c's local address // localIPFromConn returns the host portion of c's local address
// and strips the scope ID if one exists (see RFC 4007). // and strips the scope ID if one exists (see RFC 4007).
func localIPFromConn(c net.Conn) string { func localIPFromConn(c net.Conn) string {

View file

@ -73,11 +73,9 @@ func (am *ACMEIssuer) distributedHTTPChallengeSolver(w http.ResponseWriter, r *h
host := hostOnly(r.Host) host := hostOnly(r.Host)
chalInfo, distributed, err := am.config.getChallengeInfo(r.Context(), host) chalInfo, distributed, err := am.config.getChallengeInfo(r.Context(), host)
if err != nil { if err != nil {
if am.Logger != nil { am.Logger.Error("looking up info for HTTP challenge",
am.Logger.Error("looking up info for HTTP challenge", zap.String("host", host),
zap.String("host", host), zap.Error(err))
zap.Error(err))
}
return false return false
} }
return solveHTTPChallenge(am.Logger, w, r, chalInfo.Challenge, distributed) return solveHTTPChallenge(am.Logger, w, r, chalInfo.Challenge, distributed)
@ -95,13 +93,11 @@ func solveHTTPChallenge(logger *zap.Logger, w http.ResponseWriter, r *http.Reque
w.Header().Add("Content-Type", "text/plain") w.Header().Add("Content-Type", "text/plain")
w.Write([]byte(challenge.KeyAuthorization)) w.Write([]byte(challenge.KeyAuthorization))
r.Close = true r.Close = true
if logger != nil { logger.Info("served key authentication",
logger.Info("served key authentication", zap.String("identifier", challenge.Identifier.Value),
zap.String("identifier", challenge.Identifier.Value), zap.String("challenge", "http-01"),
zap.String("challenge", "http-01"), zap.String("remote", r.RemoteAddr),
zap.String("remote", r.RemoteAddr), zap.Bool("distributed", distributed))
zap.Bool("distributed", distributed))
}
return true return true
} }
return false return false

View file

@ -39,18 +39,14 @@ import (
// incrementing panicCount each time. Initial invocation should // incrementing panicCount each time. Initial invocation should
// start panicCount at 0. // start panicCount at 0.
func (certCache *Cache) maintainAssets(panicCount int) { func (certCache *Cache) maintainAssets(panicCount int) {
log := loggerNamed(certCache.logger, "maintenance") log := certCache.logger.Named("maintenance")
if log != nil { log = log.With(zap.String("cache", fmt.Sprintf("%p", certCache)))
log = log.With(zap.String("cache", fmt.Sprintf("%p", certCache)))
}
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
buf := make([]byte, stackTraceBufferSize) buf := make([]byte, stackTraceBufferSize)
buf = buf[:runtime.Stack(buf, false)] buf = buf[:runtime.Stack(buf, false)]
if log != nil { log.Error("panic", zap.Any("error", err), zap.ByteString("stack", buf))
log.Error("panic", zap.Any("error", err), zap.ByteString("stack", buf))
}
if panicCount < 10 { if panicCount < 10 {
certCache.maintainAssets(panicCount + 1) certCache.maintainAssets(panicCount + 1)
} }
@ -60,9 +56,7 @@ func (certCache *Cache) maintainAssets(panicCount int) {
renewalTicker := time.NewTicker(certCache.options.RenewCheckInterval) renewalTicker := time.NewTicker(certCache.options.RenewCheckInterval)
ocspTicker := time.NewTicker(certCache.options.OCSPCheckInterval) ocspTicker := time.NewTicker(certCache.options.OCSPCheckInterval)
if log != nil { log.Info("started background certificate maintenance")
log.Info("started background certificate maintenance")
}
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@ -71,7 +65,7 @@ func (certCache *Cache) maintainAssets(panicCount int) {
select { select {
case <-renewalTicker.C: case <-renewalTicker.C:
err := certCache.RenewManagedCertificates(ctx) err := certCache.RenewManagedCertificates(ctx)
if err != nil && log != nil { if err != nil {
log.Error("renewing managed certificates", zap.Error(err)) log.Error("renewing managed certificates", zap.Error(err))
} }
case <-ocspTicker.C: case <-ocspTicker.C:
@ -79,9 +73,7 @@ func (certCache *Cache) maintainAssets(panicCount int) {
case <-certCache.stopChan: case <-certCache.stopChan:
renewalTicker.Stop() renewalTicker.Stop()
ocspTicker.Stop() ocspTicker.Stop()
if log != nil { log.Info("stopped background certificate maintenance")
log.Info("stopped background certificate maintenance")
}
close(certCache.doneChan) close(certCache.doneChan)
return return
} }
@ -94,7 +86,7 @@ func (certCache *Cache) maintainAssets(panicCount int) {
// need to call this. This method assumes non-interactive // need to call this. This method assumes non-interactive
// mode (i.e. operating in the background). // mode (i.e. operating in the background).
func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error { func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
log := loggerNamed(certCache.logger, "maintenance") log := certCache.logger.Named("maintenance")
// configs will hold a map of certificate name to the config // configs will hold a map of certificate name to the config
// to use when managing that certificate // to use when managing that certificate
@ -116,9 +108,7 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
// the list of names on this cert should never be empty... programmer error? // the list of names on this cert should never be empty... programmer error?
if cert.Names == nil || len(cert.Names) == 0 { if cert.Names == nil || len(cert.Names) == 0 {
if log != nil { log.Warn("certificate has no names; removing from cache", zap.String("cert_key", certKey))
log.Warn("certificate has no names; removing from cache", zap.String("cert_key", certKey))
}
deleteQueue = append(deleteQueue, cert) deleteQueue = append(deleteQueue, cert)
continue continue
} }
@ -126,19 +116,15 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
// get the config associated with this certificate // get the config associated with this certificate
cfg, err := certCache.getConfig(cert) cfg, err := certCache.getConfig(cert)
if err != nil { if err != nil {
if log != nil { log.Error("unable to get configuration to manage certificate; unable to renew",
log.Error("unable to get configuration to manage certificate; unable to renew", zap.Strings("identifiers", cert.Names),
zap.Strings("identifiers", cert.Names), zap.Error(err))
zap.Error(err))
}
continue continue
} }
if cfg == nil { if cfg == nil {
// this is bad if this happens, probably a programmer error (oops) // this is bad if this happens, probably a programmer error (oops)
if log != nil { log.Error("no configuration associated with certificate; unable to manage",
log.Error("no configuration associated with certificate; unable to manage", zap.Strings("identifiers", cert.Names))
zap.Strings("identifiers", cert.Names))
}
continue continue
} }
if cfg.OnDemand != nil { if cfg.OnDemand != nil {
@ -156,11 +142,9 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
storedCertExpiring, err := cfg.managedCertInStorageExpiresSoon(ctx, cert) storedCertExpiring, err := cfg.managedCertInStorageExpiresSoon(ctx, cert)
if err != nil { if err != nil {
// hmm, weird, but not a big deal, maybe it was deleted or something // hmm, weird, but not a big deal, maybe it was deleted or something
if log != nil { log.Warn("error while checking if stored certificate is also expiring soon",
log.Warn("error while checking if stored certificate is also expiring soon", zap.Strings("identifiers", cert.Names),
zap.Strings("identifiers", cert.Names), zap.Error(err))
zap.Error(err))
}
} else if !storedCertExpiring { } else if !storedCertExpiring {
// if the certificate is NOT expiring soon and there was no error, then we // if the certificate is NOT expiring soon and there was no error, then we
// are good to just reload the certificate from storage instead of repeating // are good to just reload the certificate from storage instead of repeating
@ -180,23 +164,19 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
// Reload certificates that merely need to be updated in memory // Reload certificates that merely need to be updated in memory
for _, oldCert := range reloadQueue { for _, oldCert := range reloadQueue {
timeLeft := oldCert.Leaf.NotAfter.Sub(time.Now().UTC()) timeLeft := expiresAt(oldCert.Leaf).Sub(time.Now().UTC())
if log != nil { log.Info("certificate expires soon, but is already renewed in storage; reloading stored certificate",
log.Info("certificate expires soon, but is already renewed in storage; reloading stored certificate", zap.Strings("identifiers", oldCert.Names),
zap.Strings("identifiers", oldCert.Names), zap.Duration("remaining", timeLeft))
zap.Duration("remaining", timeLeft))
}
cfg := configs[oldCert.Names[0]] cfg := configs[oldCert.Names[0]]
// crucially, this happens OUTSIDE a lock on the certCache // crucially, this happens OUTSIDE a lock on the certCache
_, err := cfg.reloadManagedCertificate(ctx, oldCert) _, err := cfg.reloadManagedCertificate(ctx, oldCert)
if err != nil { if err != nil {
if log != nil { log.Error("loading renewed certificate",
log.Error("loading renewed certificate", zap.Strings("identifiers", oldCert.Names),
zap.Strings("identifiers", oldCert.Names), zap.Error(err))
zap.Error(err))
}
continue continue
} }
} }
@ -206,11 +186,9 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
cfg := configs[oldCert.Names[0]] cfg := configs[oldCert.Names[0]]
err := certCache.queueRenewalTask(ctx, oldCert, cfg) err := certCache.queueRenewalTask(ctx, oldCert, cfg)
if err != nil { if err != nil {
if log != nil { log.Error("queueing renewal task",
log.Error("queueing renewal task", zap.Strings("identifiers", oldCert.Names),
zap.Strings("identifiers", oldCert.Names), zap.Error(err))
zap.Error(err))
}
continue continue
} }
} }
@ -226,14 +204,12 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
} }
func (certCache *Cache) queueRenewalTask(ctx context.Context, oldCert Certificate, cfg *Config) error { func (certCache *Cache) queueRenewalTask(ctx context.Context, oldCert Certificate, cfg *Config) error {
log := loggerNamed(certCache.logger, "maintenance") log := certCache.logger.Named("maintenance")
timeLeft := oldCert.Leaf.NotAfter.Sub(time.Now().UTC()) timeLeft := expiresAt(oldCert.Leaf).Sub(time.Now().UTC())
if log != nil { log.Info("certificate expires soon; queuing for renewal",
log.Info("certificate expires soon; queuing for renewal", zap.Strings("identifiers", oldCert.Names),
zap.Strings("identifiers", oldCert.Names), zap.Duration("remaining", timeLeft))
zap.Duration("remaining", timeLeft))
}
// Get the name which we should use to renew this certificate; // Get the name which we should use to renew this certificate;
// we only support managing certificates with one name per cert, // we only support managing certificates with one name per cert,
@ -242,12 +218,10 @@ func (certCache *Cache) queueRenewalTask(ctx context.Context, oldCert Certificat
// queue up this renewal job (is a no-op if already active or queued) // queue up this renewal job (is a no-op if already active or queued)
jm.Submit(cfg.Logger, "renew_"+renewName, func() error { jm.Submit(cfg.Logger, "renew_"+renewName, func() error {
timeLeft := oldCert.Leaf.NotAfter.Sub(time.Now().UTC()) timeLeft := expiresAt(oldCert.Leaf).Sub(time.Now().UTC())
if log != nil { log.Info("attempting certificate renewal",
log.Info("attempting certificate renewal", zap.Strings("identifiers", oldCert.Names),
zap.Strings("identifiers", oldCert.Names), zap.Duration("remaining", timeLeft))
zap.Duration("remaining", timeLeft))
}
// perform renewal - crucially, this happens OUTSIDE a lock on certCache // perform renewal - crucially, this happens OUTSIDE a lock on certCache
err := cfg.RenewCertAsync(ctx, renewName, false) err := cfg.RenewCertAsync(ctx, renewName, false)
@ -280,7 +254,7 @@ func (certCache *Cache) queueRenewalTask(ctx context.Context, oldCert Certificat
// Ryan Sleevi's recommendations for good OCSP support: // Ryan Sleevi's recommendations for good OCSP support:
// https://gist.github.com/sleevi/5efe9ef98961ecfb4da8 // https://gist.github.com/sleevi/5efe9ef98961ecfb4da8
func (certCache *Cache) updateOCSPStaples(ctx context.Context) { func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
logger := loggerNamed(certCache.logger, "maintenance") logger := certCache.logger.Named("maintenance")
// temporary structures to store updates or tasks // temporary structures to store updates or tasks
// so that we can keep our locks short-lived // so that we can keep our locks short-lived
@ -311,11 +285,9 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
} }
cfg, err := certCache.getConfig(cert) cfg, err := certCache.getConfig(cert)
if err != nil { if err != nil {
if logger != nil { logger.Error("unable to get automation config for certificate; maintenance for this certificate will likely fail",
logger.Error("unable to get automation config for certificate; maintenance for this certificate will likely fail", zap.Strings("identifiers", cert.Names),
zap.Strings("identifiers", cert.Names), zap.Error(err))
zap.Error(err))
}
continue continue
} }
// always try to replace revoked certificates, even if OCSP response is still fresh // always try to replace revoked certificates, even if OCSP response is still fresh
@ -347,10 +319,8 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
if qe.cfg == nil { if qe.cfg == nil {
// this is bad if this happens, probably a programmer error (oops) // this is bad if this happens, probably a programmer error (oops)
if logger != nil { logger.Error("no configuration associated with certificate; unable to manage OCSP staples",
logger.Error("no configuration associated with certificate; unable to manage OCSP staples", zap.Strings("identifiers", cert.Names))
zap.Strings("identifiers", cert.Names))
}
continue continue
} }
@ -358,11 +328,9 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
if err != nil { if err != nil {
if cert.ocsp != nil { if cert.ocsp != nil {
// if there was no staple before, that's fine; otherwise we should log the error // if there was no staple before, that's fine; otherwise we should log the error
if logger != nil { logger.Error("stapling OCSP",
logger.Error("stapling OCSP", zap.Strings("identifiers", cert.Names),
zap.Strings("identifiers", cert.Names), zap.Error(err))
zap.Error(err))
}
} }
continue continue
} }
@ -372,17 +340,22 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
// sure we apply the update to all names on the certificate if // sure we apply the update to all names on the certificate if
// the status is still Good. // the status is still Good.
if cert.ocsp != nil && cert.ocsp.Status == ocsp.Good && (lastNextUpdate.IsZero() || lastNextUpdate != cert.ocsp.NextUpdate) { if cert.ocsp != nil && cert.ocsp.Status == ocsp.Good && (lastNextUpdate.IsZero() || lastNextUpdate != cert.ocsp.NextUpdate) {
if logger != nil { logger.Info("advancing OCSP staple",
logger.Info("advancing OCSP staple", zap.Strings("identifiers", cert.Names),
zap.Strings("identifiers", cert.Names), zap.Time("from", lastNextUpdate),
zap.Time("from", lastNextUpdate), zap.Time("to", cert.ocsp.NextUpdate))
zap.Time("to", cert.ocsp.NextUpdate))
}
updated[certHash] = ocspUpdate{rawBytes: cert.Certificate.OCSPStaple, parsed: cert.ocsp} updated[certHash] = ocspUpdate{rawBytes: cert.Certificate.OCSPStaple, parsed: cert.ocsp}
} }
// If the updated staple shows that the certificate was revoked, we should immediately renew it // If the updated staple shows that the certificate was revoked, we should immediately renew it
if certShouldBeForceRenewed(cert) { if certShouldBeForceRenewed(cert) {
qe.cfg.emit(ctx, "cert_ocsp_revoked", map[string]any{
"subjects": cert.Names,
"certificate": cert,
"reason": cert.ocsp.RevocationReason,
"revoked_at": cert.ocsp.RevokedAt,
})
renewQueue = append(renewQueue, renewQueueEntry{ renewQueue = append(renewQueue, renewQueueEntry{
oldCert: cert, oldCert: cert,
cfg: qe.cfg, cfg: qe.cfg,
@ -405,7 +378,7 @@ func (certCache *Cache) updateOCSPStaples(ctx context.Context) {
// Crucially, this happens OUTSIDE a lock on the certCache. // Crucially, this happens OUTSIDE a lock on the certCache.
for _, renew := range renewQueue { for _, renew := range renewQueue {
_, err := renew.cfg.forceRenew(ctx, logger, renew.oldCert) _, err := renew.cfg.forceRenew(ctx, logger, renew.oldCert)
if err != nil && logger != nil { if err != nil {
logger.Info("forcefully renewing certificate due to REVOKED status", logger.Info("forcefully renewing certificate due to REVOKED status",
zap.Strings("identifiers", renew.oldCert.Names), zap.Strings("identifiers", renew.oldCert.Names),
zap.Error(err)) zap.Error(err))
@ -522,7 +495,7 @@ func deleteExpiredCerts(ctx context.Context, storage Storage, gracePeriod time.D
return fmt.Errorf("certificate file %s is malformed; error parsing PEM: %v", assetKey, err) return fmt.Errorf("certificate file %s is malformed; error parsing PEM: %v", assetKey, err)
} }
if expiredTime := time.Since(cert.NotAfter); expiredTime >= gracePeriod { if expiredTime := time.Since(expiresAt(cert)); expiredTime >= gracePeriod {
log.Printf("[INFO] Certificate %s expired %s ago; cleaning up", assetKey, expiredTime) log.Printf("[INFO] Certificate %s expired %s ago; cleaning up", assetKey, expiredTime)
baseName := strings.TrimSuffix(assetKey, ".crt") baseName := strings.TrimSuffix(assetKey, ".crt")
for _, relatedAsset := range []string{ for _, relatedAsset := range []string{
@ -560,16 +533,14 @@ func deleteExpiredCerts(ctx context.Context, storage Storage, gracePeriod time.D
// forceRenew forcefully renews cert and replaces it in the cache, and returns the new certificate. It is intended // forceRenew forcefully renews cert and replaces it in the cache, and returns the new certificate. It is intended
// for use primarily in the case of cert revocation. This MUST NOT be called within a lock on cfg.certCacheMu. // for use primarily in the case of cert revocation. This MUST NOT be called within a lock on cfg.certCacheMu.
func (cfg *Config) forceRenew(ctx context.Context, logger *zap.Logger, cert Certificate) (Certificate, error) { func (cfg *Config) forceRenew(ctx context.Context, logger *zap.Logger, cert Certificate) (Certificate, error) {
if logger != nil { if cert.ocsp != nil && cert.ocsp.Status == ocsp.Revoked {
if cert.ocsp != nil && cert.ocsp.Status == ocsp.Revoked { logger.Warn("OCSP status for managed certificate is REVOKED; attempting to replace with new certificate",
logger.Warn("OCSP status for managed certificate is REVOKED; attempting to replace with new certificate", zap.Strings("identifiers", cert.Names),
zap.Strings("identifiers", cert.Names), zap.Time("expiration", expiresAt(cert.Leaf)))
zap.Time("expiration", cert.Leaf.NotAfter)) } else {
} else { logger.Warn("forcefully renewing certificate",
logger.Warn("forcefully renewing certificate", zap.Strings("identifiers", cert.Names),
zap.Strings("identifiers", cert.Names), zap.Time("expiration", expiresAt(cert.Leaf)))
zap.Time("expiration", cert.Leaf.NotAfter))
}
} }
renewName := cert.Names[0] renewName := cert.Names[0]
@ -584,7 +555,7 @@ func (cfg *Config) forceRenew(ctx context.Context, logger *zap.Logger, cert Cert
var obtainInsteadOfRenew bool var obtainInsteadOfRenew bool
if cert.ocsp != nil && cert.ocsp.RevocationReason == acme.ReasonKeyCompromise { if cert.ocsp != nil && cert.ocsp.RevocationReason == acme.ReasonKeyCompromise {
err := cfg.moveCompromisedPrivateKey(ctx, cert, logger) err := cfg.moveCompromisedPrivateKey(ctx, cert, logger)
if err != nil && logger != nil { if err != nil {
logger.Error("could not remove compromised private key from use", logger.Error("could not remove compromised private key from use",
zap.Strings("identifiers", cert.Names), zap.Strings("identifiers", cert.Names),
zap.String("issuer", cert.issuerKey), zap.String("issuer", cert.issuerKey),
@ -605,11 +576,9 @@ func (cfg *Config) forceRenew(ctx context.Context, logger *zap.Logger, cert Cert
if err != nil { if err != nil {
if cert.ocsp != nil && cert.ocsp.Status == ocsp.Revoked { if cert.ocsp != nil && cert.ocsp.Status == ocsp.Revoked {
// probably better to not serve a revoked certificate at all // probably better to not serve a revoked certificate at all
if logger != nil { logger.Error("unable to obtain new to certificate after OCSP status of REVOKED; removing from cache",
logger.Error("unable to obtain new to certificate after OCSP status of REVOKED; removing from cache", zap.Strings("identifiers", cert.Names),
zap.Strings("identifiers", cert.Names), zap.Error(err))
zap.Error(err))
}
cfg.certCache.mu.Lock() cfg.certCache.mu.Lock()
cfg.certCache.removeCertificate(cert) cfg.certCache.removeCertificate(cert)
cfg.certCache.mu.Unlock() cfg.certCache.mu.Unlock()

View file

@ -98,12 +98,12 @@ func stapleOCSP(ctx context.Context, ocspConfig OCSPConfig, storage Storage, cer
gotNewOCSP = true gotNewOCSP = true
} }
if ocspResp.NextUpdate.After(cert.Leaf.NotAfter) { if ocspResp.NextUpdate.After(expiresAt(cert.Leaf)) {
// uh oh, this OCSP response expires AFTER the certificate does, that's kinda bogus. // uh oh, this OCSP response expires AFTER the certificate does, that's kinda bogus.
// it was the reason a lot of Symantec-validated sites (not Caddy) went down // it was the reason a lot of Symantec-validated sites (not Caddy) went down
// in October 2017. https://twitter.com/mattiasgeniar/status/919432824708648961 // in October 2017. https://twitter.com/mattiasgeniar/status/919432824708648961
return fmt.Errorf("invalid: OCSP response for %v valid after certificate expiration (%s)", return fmt.Errorf("invalid: OCSP response for %v valid after certificate expiration (%s)",
cert.Names, cert.Leaf.NotAfter.Sub(ocspResp.NextUpdate)) cert.Names, expiresAt(cert.Leaf).Sub(ocspResp.NextUpdate))
} }
// Attach the latest OCSP response to the certificate; this is NOT the same // Attach the latest OCSP response to the certificate; this is NOT the same

View file

@ -99,7 +99,7 @@ func (s *httpSolver) serve(ctx context.Context, si *solverInfo) {
} }
// CleanUp cleans up the HTTP server if it is the last one to finish. // CleanUp cleans up the HTTP server if it is the last one to finish.
func (s *httpSolver) CleanUp(ctx context.Context, _ acme.Challenge) error { func (s *httpSolver) CleanUp(_ context.Context, _ acme.Challenge) error {
solversMu.Lock() solversMu.Lock()
defer solversMu.Unlock() defer solversMu.Unlock()
si := getSolverInfo(s.address) si := getSolverInfo(s.address)
@ -220,7 +220,7 @@ func (*tlsALPNSolver) handleConn(conn net.Conn) {
// CleanUp removes the challenge certificate from the cache, and if // CleanUp removes the challenge certificate from the cache, and if
// it is the last one to finish, stops the TLS server. // it is the last one to finish, stops the TLS server.
func (s *tlsALPNSolver) CleanUp(ctx context.Context, chal acme.Challenge) error { func (s *tlsALPNSolver) CleanUp(_ context.Context, chal acme.Challenge) error {
solversMu.Lock() solversMu.Lock()
defer solversMu.Unlock() defer solversMu.Unlock()
si := getSolverInfo(s.address) si := getSolverInfo(s.address)
@ -234,7 +234,6 @@ func (s *tlsALPNSolver) CleanUp(ctx context.Context, chal acme.Challenge) error
} }
delete(solvers, s.address) delete(solvers, s.address)
} }
return nil return nil
} }
@ -357,7 +356,7 @@ func (s *DNS01Solver) Wait(ctx context.Context, challenge acme.Challenge) error
// timings // timings
timeout := s.PropagationTimeout timeout := s.PropagationTimeout
if timeout == 0 { if timeout == 0 {
timeout = 2 * time.Minute timeout = defaultDNSPropagationTimeout
} }
const interval = 2 * time.Second const interval = 2 * time.Second
@ -386,7 +385,12 @@ func (s *DNS01Solver) Wait(ctx context.Context, challenge acme.Challenge) error
} }
// CleanUp deletes the DNS TXT record created in Present(). // CleanUp deletes the DNS TXT record created in Present().
func (s *DNS01Solver) CleanUp(ctx context.Context, challenge acme.Challenge) error { //
// We ignore the context because cleanup is often/likely performed after
// a context cancellation, and properly-implemented DNS providers should
// honor cancellation, which would result in cleanup being aborted.
// Cleanup must always occur.
func (s *DNS01Solver) CleanUp(_ context.Context, challenge acme.Challenge) error {
dnsName := challenge.DNS01TXTRecordName() dnsName := challenge.DNS01TXTRecordName()
if s.OverrideDomain != "" { if s.OverrideDomain != "" {
dnsName = s.OverrideDomain dnsName = s.OverrideDomain
@ -402,7 +406,17 @@ func (s *DNS01Solver) CleanUp(ctx context.Context, challenge acme.Challenge) err
return err return err
} }
// clean up the record // clean up the record - use a different context though, since
// one common reason cleanup is performed is because a context
// was canceled, and if so, any HTTP requests by this provider
// should fail if the provider is properly implemented
// (see issue #200)
timeout := s.PropagationTimeout
if timeout <= 0 {
timeout = defaultDNSPropagationTimeout
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
_, err = s.DNSProvider.DeleteRecords(ctx, memory.dnsZone, []libdns.Record{memory.rec}) _, err = s.DNSProvider.DeleteRecords(ctx, memory.dnsZone, []libdns.Record{memory.rec})
if err != nil { if err != nil {
return fmt.Errorf("deleting temporary record for name %q in zone %q: %w", memory.dnsName, memory.dnsZone, err) return fmt.Errorf("deleting temporary record for name %q in zone %q: %w", memory.dnsName, memory.dnsZone, err)
@ -411,6 +425,8 @@ func (s *DNS01Solver) CleanUp(ctx context.Context, challenge acme.Challenge) err
return nil return nil
} }
const defaultDNSPropagationTimeout = 2 * time.Minute
type dnsPresentMemory struct { type dnsPresentMemory struct {
dnsZone string dnsZone string
dnsName string dnsName string
@ -680,7 +696,7 @@ var (
// data that can make it easier or more efficient to solve. // data that can make it easier or more efficient to solve.
type Challenge struct { type Challenge struct {
acme.Challenge acme.Challenge
data interface{} data any
} }
// challengeKey returns the map key for a given challenge; it is the identifier // challengeKey returns the map key for a given challenge; it is the identifier

View file

@ -29,19 +29,30 @@ import (
// Keys are prefix-based, with forward slash '/' as separators // Keys are prefix-based, with forward slash '/' as separators
// and without a leading slash. // and without a leading slash.
// //
// Processes running in a cluster will wish to use the // Processes running in a cluster should use the same Storage
// same Storage value (its implementation and configuration) // value (with the same configuration) in order to share
// in order to share certificates and other TLS resources // certificates and other TLS resources with the cluster.
// with the cluster.
// //
// The Load, Delete, List, and Stat methods should return // The Load, Delete, List, and Stat methods should return
// fs.ErrNotExist if the key does not exist. // fs.ErrNotExist if the key does not exist.
// //
// Implementations of Storage must be safe for concurrent use // Implementations of Storage must be safe for concurrent use
// and honor context cancellations. // and honor context cancellations. Methods should block until
// their operation is complete; that is, Load() should always
// return the value from the last call to Store() for a given
// key, and concurrent calls to Store() should not corrupt a
// file.
//
// For simplicity, this is not a streaming API and is not
// suitable for very large files.
type Storage interface { type Storage interface {
// Locker provides atomic synchronization // Locker provides atomic synchronization
// operations, making Storage safe to share. // operations, making Storage safe to share.
// The use of Locker is not expected around
// every other method (Store, Load, etc.)
// as those should already be thread-safe;
// Locker is intended for custom jobs or
// transactions that need synchronization.
Locker Locker
// Store puts value at key. // Store puts value at key.
@ -70,10 +81,11 @@ type Storage interface {
Stat(ctx context.Context, key string) (KeyInfo, error) Stat(ctx context.Context, key string) (KeyInfo, error)
} }
// Locker facilitates synchronization of certificate tasks across // Locker facilitates synchronization across machines and networks.
// machines and networks. // It essentially provides a distributed named-mutex service so
// that multiple consumers can coordinate tasks and share resources.
type Locker interface { type Locker interface {
// Lock acquires the lock for key, blocking until the lock // Lock acquires the lock for name, blocking until the lock
// can be obtained or an error is returned. Note that, even // can be obtained or an error is returned. Note that, even
// after acquiring a lock, an idempotent operation may have // after acquiring a lock, an idempotent operation may have
// already been performed by another process that acquired // already been performed by another process that acquired
@ -86,20 +98,25 @@ type Locker interface {
// same time always results in only one caller receiving the // same time always results in only one caller receiving the
// lock at any given time. // lock at any given time.
// //
// To prevent deadlocks, all implementations (where this concern // To prevent deadlocks, all implementations should put a
// is relevant) should put a reasonable expiration on the lock in // reasonable expiration on the lock in case Unlock is unable
// case Unlock is unable to be called due to some sort of network // to be called due to some sort of network failure or system
// failure or system crash. Additionally, implementations should // crash. Additionally, implementations should honor context
// honor context cancellation as much as possible (in case the // cancellation as much as possible (in case the caller wishes
// caller wishes to give up and free resources before the lock // to give up and free resources before the lock can be obtained).
// can be obtained). //
Lock(ctx context.Context, key string) error // Additionally, implementations may wish to support fencing
// tokens (https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html)
// in order to be robust against long process pauses, extremely
// high network latency (or other factors that get in the way of
// renewing lock leases).
Lock(ctx context.Context, name string) error
// Unlock releases the lock for key. This method must ONLY be // Unlock releases the lock for name. This method must ONLY be
// called after a successful call to Lock, and only after the // called after a successful call to Lock, and only after the
// critical section is finished, even if it errored or timed // critical section is finished, even if it errored or timed
// out. Unlock cleans up any resources allocated during Lock. // out. Unlock cleans up any resources allocated during Lock.
Unlock(ctx context.Context, key string) error Unlock(ctx context.Context, name string) error
} }
// KeyInfo holds information about a key in storage. // KeyInfo holds information about a key in storage.
@ -220,16 +237,14 @@ func CleanUpOwnLocks(ctx context.Context, logger *zap.Logger) {
locksMu.Lock() locksMu.Lock()
defer locksMu.Unlock() defer locksMu.Unlock()
for lockKey, storage := range locks { for lockKey, storage := range locks {
err := storage.Unlock(ctx, lockKey) if err := storage.Unlock(ctx, lockKey); err != nil {
if err == nil {
delete(locks, lockKey)
} else if logger != nil {
logger.Error("unable to clean up lock in storage backend", logger.Error("unable to clean up lock in storage backend",
zap.Any("storage", storage), zap.Any("storage", storage),
zap.String("lock_key", lockKey), zap.String("lock_key", lockKey),
zap.Error(err), zap.Error(err))
) continue
} }
delete(locks, lockKey)
} }
} }

View file

@ -5,7 +5,7 @@ all: build
## test: Run all tests ## test: Run all tests
test: test:
go test -race -coverprofile=/dev/null -timeout 60s -v ./... go test -race -coverprofile=/dev/null -v ./...
## vet: Analyze code for potential errors ## vet: Analyze code for potential errors
vet: vet:

View file

@ -53,7 +53,7 @@ type Conn interface {
StreamId() string StreamId() string
// Stats returns accumulated and instantaneous statistics of the connection. // Stats returns accumulated and instantaneous statistics of the connection.
Stats() Statistics Stats(s *Statistics)
} }
type connStats struct { type connStats struct {
@ -73,6 +73,7 @@ type connStats struct {
pktRecvKeepalive uint64 pktRecvKeepalive uint64
pktSentShutdown uint64 pktSentShutdown uint64
pktRecvShutdown uint64 pktRecvShutdown uint64
mbpsLinkCapacity float64
} }
// Check if we implement the net.Conn interface // Check if we implement the net.Conn interface
@ -118,6 +119,8 @@ type srtConn struct {
tsbpdTimeBaseOffset uint64 // microseconds tsbpdTimeBaseOffset uint64 // microseconds
tsbpdDelay uint64 // microseconds tsbpdDelay uint64 // microseconds
tsbpdDrift uint64 // microseconds tsbpdDrift uint64 // microseconds
peerTsbpdDelay uint64 // microseconds
dropThreshold uint64 // microseconds
// Queue for packets that are coming from the network // Queue for packets that are coming from the network
networkQueue chan packet.Packet networkQueue chan packet.Packet
@ -162,7 +165,8 @@ type srtConnConfig struct {
socketId uint32 socketId uint32
peerSocketId uint32 peerSocketId uint32
tsbpdTimeBase uint64 // microseconds tsbpdTimeBase uint64 // microseconds
tsbpdDelay uint64 tsbpdDelay uint64 // microseconds
peerTsbpdDelay uint64 // microseconds
initialPacketSequenceNumber circular.Number initialPacketSequenceNumber circular.Number
crypto crypto.Crypto crypto crypto.Crypto
keyBaseEncryption packet.PacketEncryption keyBaseEncryption packet.PacketEncryption
@ -181,6 +185,7 @@ func newSRTConn(config srtConnConfig) *srtConn {
peerSocketId: config.peerSocketId, peerSocketId: config.peerSocketId,
tsbpdTimeBase: config.tsbpdTimeBase, tsbpdTimeBase: config.tsbpdTimeBase,
tsbpdDelay: config.tsbpdDelay, tsbpdDelay: config.tsbpdDelay,
peerTsbpdDelay: config.peerTsbpdDelay,
initialPacketSequenceNumber: config.initialPacketSequenceNumber, initialPacketSequenceNumber: config.initialPacketSequenceNumber,
crypto: config.crypto, crypto: config.crypto,
keyBaseEncryption: config.keyBaseEncryption, keyBaseEncryption: config.keyBaseEncryption,
@ -237,9 +242,16 @@ func newSRTConn(config srtConnConfig) *srtConn {
}) })
// 4.6. Too-Late Packet Drop -> 125% of SRT latency, at least 1 second // 4.6. Too-Late Packet Drop -> 125% of SRT latency, at least 1 second
// https://github.com/Haivision/srt/blob/master/docs/API/API-socket-options.md#SRTO_SNDDROPDELAY
c.dropThreshold = uint64(float64(c.peerTsbpdDelay)*1.25) + uint64(c.config.SendDropDelay.Microseconds())
if c.dropThreshold < uint64(time.Second.Microseconds()) {
c.dropThreshold = uint64(time.Second.Microseconds())
}
c.dropThreshold += 20_000
c.snd = congestion.NewLiveSend(congestion.SendConfig{ c.snd = congestion.NewLiveSend(congestion.SendConfig{
InitialSequenceNumber: c.initialPacketSequenceNumber, InitialSequenceNumber: c.initialPacketSequenceNumber,
DropInterval: uint64(c.config.SendDropDelay.Microseconds()), DropThreshold: c.dropThreshold,
MaxBW: c.config.MaxBW, MaxBW: c.config.MaxBW,
InputBW: c.config.InputBW, InputBW: c.config.InputBW,
MinInputBW: c.config.MinInputBW, MinInputBW: c.config.MinInputBW,
@ -582,6 +594,8 @@ func (c *srtConn) handlePacket(p packet.Packet) {
c.debug.expectedRcvPacketSequenceNumber = header.PacketSequenceNumber.Inc() c.debug.expectedRcvPacketSequenceNumber = header.PacketSequenceNumber.Inc()
//fmt.Printf("%s\n", p.String())
// Ignore FEC filter control packets // Ignore FEC filter control packets
// https://github.com/Haivision/srt/blob/master/docs/features/packet-filtering-and-fec.md // https://github.com/Haivision/srt/blob/master/docs/features/packet-filtering-and-fec.md
// "An FEC control packet is distinguished from a regular data packet by having // "An FEC control packet is distinguished from a regular data packet by having
@ -682,6 +696,9 @@ func (c *srtConn) handleACK(p packet.Packet) {
// 4.10. Round-Trip Time Estimation // 4.10. Round-Trip Time Estimation
c.recalculateRTT(time.Duration(int64(cif.RTT)) * time.Microsecond) c.recalculateRTT(time.Duration(int64(cif.RTT)) * time.Microsecond)
// Estimated Link Capacity (from packets/s to Mbps)
c.statistics.mbpsLinkCapacity = float64(cif.EstimatedLinkCapacity) * MAX_PAYLOAD_SIZE * 8 / 1024 / 1024
c.sendACKACK(p.Header().TypeSpecific) c.sendACKACK(p.Header().TypeSpecific)
} }
} }
@ -901,14 +918,14 @@ func (c *srtConn) sendACK(seq circular.Number, lite bool) {
p.Header().TypeSpecific = 0 p.Header().TypeSpecific = 0
} else { } else {
pps, _ := c.recv.PacketRate() pps, bps, capacity := c.recv.PacketRate()
cif.RTT = uint32(c.rtt) cif.RTT = uint32(c.rtt)
cif.RTTVar = uint32(c.rttVar) cif.RTTVar = uint32(c.rttVar)
cif.AvailableBufferSize = c.config.FC // TODO: available buffer size (packets) cif.AvailableBufferSize = c.config.FC // TODO: available buffer size (packets)
cif.PacketsReceivingRate = pps // packets receiving rate (packets/s) cif.PacketsReceivingRate = uint32(pps) // packets receiving rate (packets/s)
cif.EstimatedLinkCapacity = 0 // estimated link capacity (packets/s), not relevant for live mode cif.EstimatedLinkCapacity = uint32(capacity) // estimated link capacity (packets/s), not relevant for live mode
cif.ReceivingRate = 0 // receiving rate (bytes/s), not relevant for live mode cif.ReceivingRate = uint32(bps) // receiving rate (bytes/s), not relevant for live mode
p.Header().TypeSpecific = c.nextACKNumber.Val() p.Header().TypeSpecific = c.nextACKNumber.Val()
@ -1049,63 +1066,119 @@ func (c *srtConn) SetDeadline(t time.Time) error { return nil }
func (c *srtConn) SetReadDeadline(t time.Time) error { return nil } func (c *srtConn) SetReadDeadline(t time.Time) error { return nil }
func (c *srtConn) SetWriteDeadline(t time.Time) error { return nil } func (c *srtConn) SetWriteDeadline(t time.Time) error { return nil }
func (c *srtConn) Stats() Statistics { func (c *srtConn) Stats(s *Statistics) {
now := uint64(time.Since(c.start).Milliseconds())
send := c.snd.Stats() send := c.snd.Stats()
recv := c.recv.Stats() recv := c.recv.Stats()
s := Statistics{ previous := s.Accumulated
MsTimeStamp: uint64(time.Since(c.start).Milliseconds()), interval := now - s.MsTimeStamp
// Accumulated // Accumulated
PktSent: send.PktSent, s.Accumulated = StatisticsAccumulated{
PktRecv: recv.PktRecv, PktSent: send.Pkt,
PktSentUnique: send.PktSentUnique, PktRecv: recv.Pkt,
PktRecvUnique: recv.PktRecvUnique, PktSentUnique: send.PktUnique,
PktSndLoss: send.PktSndLoss, PktRecvUnique: recv.PktUnique,
PktRcvLoss: recv.PktRcvLoss, PktSendLoss: send.PktLoss,
PktRetrans: send.PktRetrans, PktRecvLoss: recv.PktLoss,
PktRcvRetrans: recv.PktRcvRetrans, PktRetrans: send.PktRetrans,
PktSentACK: c.statistics.pktSentACK, PktRecvRetrans: recv.PktRetrans,
PktRecvACK: c.statistics.pktRecvACK, PktSentACK: c.statistics.pktSentACK,
PktSentNAK: c.statistics.pktSentNAK, PktRecvACK: c.statistics.pktRecvACK,
PktRecvNAK: c.statistics.pktRecvNAK, PktSentNAK: c.statistics.pktSentNAK,
PktSentKM: c.statistics.pktSentKM, PktRecvNAK: c.statistics.pktRecvNAK,
PktRecvKM: c.statistics.pktRecvKM, PktSentKM: c.statistics.pktSentKM,
UsSndDuration: send.UsSndDuration, PktRecvKM: c.statistics.pktRecvKM,
PktSndDrop: send.PktSndDrop, UsSndDuration: send.UsSndDuration,
PktRcvDrop: recv.PktRcvDrop, PktSendDrop: send.PktDrop,
PktRcvUndecrypt: c.statistics.pktRecvUndecrypt, PktRecvDrop: recv.PktDrop,
ByteSent: send.ByteSent + (send.PktSent * c.statistics.headerSize), PktRecvUndecrypt: c.statistics.pktRecvUndecrypt,
ByteRecv: recv.ByteRecv + (recv.PktRecv * c.statistics.headerSize), ByteSent: send.Byte + (send.Pkt * c.statistics.headerSize),
ByteSentUnique: send.ByteSentUnique + (send.PktSentUnique * c.statistics.headerSize), ByteRecv: recv.Byte + (recv.Pkt * c.statistics.headerSize),
ByteRecvUnique: recv.ByteRecvUnique + (recv.PktRecvUnique * c.statistics.headerSize), ByteSentUnique: send.ByteUnique + (send.PktUnique * c.statistics.headerSize),
ByteRcvLoss: recv.ByteRcvLoss + (recv.PktRcvLoss * c.statistics.headerSize), ByteRecvUnique: recv.ByteUnique + (recv.PktUnique * c.statistics.headerSize),
ByteRetrans: send.ByteRetrans + (send.PktRetrans * c.statistics.headerSize), ByteRecvLoss: recv.ByteLoss + (recv.PktLoss * c.statistics.headerSize),
ByteSndDrop: send.ByteSndDrop + (send.PktSndDrop * c.statistics.headerSize), ByteRetrans: send.ByteRetrans + (send.PktRetrans * c.statistics.headerSize),
ByteRcvDrop: recv.ByteRcvDrop + (recv.PktRcvDrop * c.statistics.headerSize), ByteRecvRetrans: recv.ByteRetrans + (recv.PktRetrans * c.statistics.headerSize),
ByteRcvUndecrypt: c.statistics.byteRecvUndecrypt + (c.statistics.pktRecvUndecrypt * c.statistics.headerSize), ByteSendDrop: send.ByteDrop + (send.PktDrop * c.statistics.headerSize),
ByteRecvDrop: recv.ByteDrop + (recv.PktDrop * c.statistics.headerSize),
// Instantaneous ByteRecvUndecrypt: c.statistics.byteRecvUndecrypt + (c.statistics.pktRecvUndecrypt * c.statistics.headerSize),
UsPktSndPeriod: send.UsPktSndPeriod,
PktFlowWindow: uint64(c.config.FC),
PktFlightSize: send.PktFlightSize,
MsRTT: c.rtt / 1_000,
MbpsBandwidth: 0,
ByteAvailSndBuf: 0,
ByteAvailRcvBuf: 0,
MbpsMaxBW: float64(c.config.MaxBW / 1024 / 1024),
ByteMSS: uint64(c.config.MSS),
PktSndBuf: send.PktSndBuf,
ByteSndBuf: send.ByteSndBuf,
MsSndBuf: send.MsSndBuf,
MsSndTsbPdDelay: uint64(c.config.PeerLatency),
PktRcvBuf: recv.PktRcvBuf,
ByteRcvBuf: recv.ByteRcvBuf,
MsRcvBuf: recv.MsRcvBuf,
MsRcvTsbPdDelay: uint64(c.config.ReceiverLatency),
PktReorderTolerance: 0,
PktRcvAvgBelatedTime: 0,
} }
return s // Interval
s.Interval = StatisticsInterval{
MsInterval: interval,
PktSent: s.Accumulated.PktSent - previous.PktSent,
PktRecv: s.Accumulated.PktRecv - previous.PktRecv,
PktSentUnique: s.Accumulated.PktSentUnique - previous.PktSentUnique,
PktRecvUnique: s.Accumulated.PktRecvUnique - previous.PktRecvUnique,
PktSendLoss: s.Accumulated.PktSendLoss - previous.PktSendLoss,
PktRecvLoss: s.Accumulated.PktRecvLoss - previous.PktRecvLoss,
PktRetrans: s.Accumulated.PktRetrans - previous.PktRetrans,
PktRecvRetrans: s.Accumulated.PktRecvRetrans - previous.PktRecvRetrans,
PktSentACK: s.Accumulated.PktSentACK - previous.PktSentACK,
PktRecvACK: s.Accumulated.PktRecvACK - previous.PktRecvACK,
PktSentNAK: s.Accumulated.PktSentNAK - previous.PktSentNAK,
PktRecvNAK: s.Accumulated.PktRecvNAK - previous.PktRecvNAK,
MbpsSendRate: float64(s.Accumulated.ByteSent-previous.ByteSent) * 8 / 1024 / 1024 / (float64(interval) / 1000),
MbpsRecvRate: float64(s.Accumulated.ByteRecv-previous.ByteRecv) * 8 / 1024 / 1024 / (float64(interval) / 1000),
UsSndDuration: s.Accumulated.UsSndDuration - previous.UsSndDuration,
PktReorderDistance: 0,
PktRecvBelated: s.Accumulated.PktRecvBelated - previous.PktRecvBelated,
PktSndDrop: s.Accumulated.PktSendDrop - previous.PktSendDrop,
PktRecvDrop: s.Accumulated.PktRecvDrop - previous.PktRecvDrop,
PktRecvUndecrypt: s.Accumulated.PktRecvUndecrypt - previous.PktRecvUndecrypt,
ByteSent: s.Accumulated.ByteSent - previous.ByteSent,
ByteRecv: s.Accumulated.ByteRecv - previous.ByteRecv,
ByteSentUnique: s.Accumulated.ByteSentUnique - previous.ByteSentUnique,
ByteRecvUnique: s.Accumulated.ByteRecvUnique - previous.ByteRecvUnique,
ByteRecvLoss: s.Accumulated.ByteRecvLoss - previous.ByteRecvLoss,
ByteRetrans: s.Accumulated.ByteRetrans - previous.ByteRetrans,
ByteRecvRetrans: s.Accumulated.ByteRecvRetrans - previous.ByteRecvRetrans,
ByteRecvBelated: s.Accumulated.ByteRecvBelated - previous.ByteRecvBelated,
ByteSendDrop: s.Accumulated.ByteSendDrop - previous.ByteSendDrop,
ByteRecvDrop: s.Accumulated.ByteRecvDrop - previous.ByteRecvDrop,
ByteRecvUndecrypt: s.Accumulated.ByteRecvUndecrypt - previous.ByteRecvUndecrypt,
}
// Instantaneous
s.Instantaneous = StatisticsInstantaneous{
UsPktSendPeriod: send.UsPktSndPeriod,
PktFlowWindow: uint64(c.config.FC),
PktFlightSize: send.PktFlightSize,
MsRTT: c.rtt / 1000,
MbpsSentRate: send.MbpsEstimatedSentBandwidth,
MbpsRecvRate: recv.MbpsEstimatedRecvBandwidth,
MbpsLinkCapacity: recv.MbpsEstimatedLinkCapacity,
ByteAvailSendBuf: 0, // unlimited
ByteAvailRecvBuf: 0, // unlimited
MbpsMaxBW: float64(c.config.MaxBW) / 1024 / 1024,
ByteMSS: uint64(c.config.MSS),
PktSendBuf: send.PktBuf,
ByteSendBuf: send.ByteBuf,
MsSendBuf: send.MsBuf,
MsSendTsbPdDelay: c.peerTsbpdDelay / 1000,
PktRecvBuf: recv.PktBuf,
ByteRecvBuf: recv.ByteBuf,
MsRecvBuf: recv.MsBuf,
MsRecvTsbPdDelay: c.tsbpdDelay / 1000,
PktReorderTolerance: uint64(c.config.LossMaxTTL),
PktRecvAvgBelatedTime: 0,
PktSendLossRate: send.PktLossRate,
PktRecvLossRate: recv.PktLossRate,
}
// If we're only sending, the receiver congestion control value for the link capacity is zero,
// use the value that we got from the receiver via the ACK packets.
if s.Instantaneous.MbpsLinkCapacity == 0 {
s.Instantaneous.MbpsLinkCapacity = c.statistics.mbpsLinkCapacity
}
if c.config.MaxBW < 0 {
s.Instantaneous.MbpsMaxBW = -1
}
s.MsTimeStamp = now
} }

View file

@ -475,15 +475,16 @@ func (dl *dialer) handleHandshake(p packet.Packet) {
return return
} }
// Use the largest TSBPD delay as advertised by the listener, but // Select the largest TSBPD delay advertised by the listener, but at least 120ms
// at least 120ms recvTsbpdDelay := uint16(dl.config.ReceiverLatency.Milliseconds())
tsbpdDelay := uint16(120) sendTsbpdDelay := uint16(dl.config.PeerLatency.Milliseconds())
if cif.RecvTSBPDDelay > tsbpdDelay {
tsbpdDelay = cif.RecvTSBPDDelay if cif.SendTSBPDDelay > recvTsbpdDelay {
recvTsbpdDelay = cif.SendTSBPDDelay
} }
if cif.SendTSBPDDelay > tsbpdDelay { if cif.RecvTSBPDDelay > sendTsbpdDelay {
tsbpdDelay = cif.SendTSBPDDelay sendTsbpdDelay = cif.RecvTSBPDDelay
} }
// If the peer has a smaller MTU size, adjust to it // If the peer has a smaller MTU size, adjust to it
@ -512,7 +513,8 @@ func (dl *dialer) handleHandshake(p packet.Packet) {
socketId: dl.socketId, socketId: dl.socketId,
peerSocketId: cif.SRTSocketId, peerSocketId: cif.SRTSocketId,
tsbpdTimeBase: uint64(time.Since(dl.start).Microseconds()), tsbpdTimeBase: uint64(time.Since(dl.start).Microseconds()),
tsbpdDelay: uint64(tsbpdDelay) * 1000, tsbpdDelay: uint64(recvTsbpdDelay) * 1000,
peerTsbpdDelay: uint64(sendTsbpdDelay) * 1000,
initialPacketSequenceNumber: cif.InitialPacketSequenceNumber, initialPacketSequenceNumber: cif.InitialPacketSequenceNumber,
crypto: dl.crypto, crypto: dl.crypto,
keyBaseEncryption: packet.EvenKeyEncrypted, keyBaseEncryption: packet.EvenKeyEncrypted,
@ -700,7 +702,7 @@ func (dl *dialer) writePacket(p packet.Packet) error {
func (dl *dialer) SetDeadline(t time.Time) error { return dl.conn.SetDeadline(t) } func (dl *dialer) SetDeadline(t time.Time) error { return dl.conn.SetDeadline(t) }
func (dl *dialer) SetReadDeadline(t time.Time) error { return dl.conn.SetReadDeadline(t) } func (dl *dialer) SetReadDeadline(t time.Time) error { return dl.conn.SetReadDeadline(t) }
func (dl *dialer) SetWriteDeadline(t time.Time) error { return dl.conn.SetWriteDeadline(t) } func (dl *dialer) SetWriteDeadline(t time.Time) error { return dl.conn.SetWriteDeadline(t) }
func (dl *dialer) Stats() Statistics { return dl.conn.Stats() } func (dl *dialer) Stats(s *Statistics) { dl.conn.Stats(s) }
func (dl *dialer) log(topic string, message func() string) { func (dl *dialer) log(topic string, message func() string) {
dl.config.Logger.Print(topic, dl.socketId, 2, message) dl.config.Logger.Print(topic, dl.socketId, 2, message)

View file

@ -9,7 +9,7 @@ import (
// SendConfig is the configuration for the liveSend congestion control // SendConfig is the configuration for the liveSend congestion control
type SendConfig struct { type SendConfig struct {
InitialSequenceNumber circular.Number InitialSequenceNumber circular.Number
DropInterval uint64 DropThreshold uint64
MaxBW int64 MaxBW int64
InputBW int64 InputBW int64
MinInputBW int64 MinInputBW int64
@ -25,6 +25,7 @@ type Sender interface {
Tick(now uint64) Tick(now uint64)
ACK(sequenceNumber circular.Number) ACK(sequenceNumber circular.Number)
NAK(sequenceNumbers []circular.Number) NAK(sequenceNumbers []circular.Number)
SetDropThreshold(threshold uint64)
} }
// ReceiveConfig is the configuration for the liveResv congestion control // ReceiveConfig is the configuration for the liveResv congestion control
@ -40,7 +41,7 @@ type ReceiveConfig struct {
// Receiver is the receiving part of the congestion control // Receiver is the receiving part of the congestion control
type Receiver interface { type Receiver interface {
Stats() ReceiveStats Stats() ReceiveStats
PacketRate() (pps, bps uint32) PacketRate() (pps, bps, capacity float64)
Flush() Flush()
Push(pkt packet.Packet) Push(pkt packet.Packet)
Tick(now uint64) Tick(now uint64)
@ -49,55 +50,68 @@ type Receiver interface {
// SendStats are collected statistics from liveSend // SendStats are collected statistics from liveSend
type SendStats struct { type SendStats struct {
PktSent uint64 Pkt uint64 // Sent packets in total
ByteSent uint64 Byte uint64 // Sent bytes in total
PktSentUnique uint64 PktUnique uint64
ByteSentUnique uint64 ByteUnique uint64
PktSndLoss uint64 PktLoss uint64
ByteSndLoss uint64 ByteLoss uint64
PktRetrans uint64 PktRetrans uint64
ByteRetrans uint64 ByteRetrans uint64
UsSndDuration uint64 // microseconds UsSndDuration uint64 // microseconds
PktSndDrop uint64 PktDrop uint64
ByteSndDrop uint64 ByteDrop uint64
// instantaneous // instantaneous
PktSndBuf uint64 PktBuf uint64
ByteSndBuf uint64 ByteBuf uint64
MsSndBuf uint64 MsBuf uint64
PktFlightSize uint64 PktFlightSize uint64
UsPktSndPeriod float64 // microseconds UsPktSndPeriod float64 // microseconds
BytePayload uint64 BytePayload uint64
MbpsEstimatedInputBandwidth float64
MbpsEstimatedSentBandwidth float64
PktLossRate float64
} }
// ReceiveStats are collected statistics from liveRecv // ReceiveStats are collected statistics from liveRecv
type ReceiveStats struct { type ReceiveStats struct {
PktRecv uint64 Pkt uint64
ByteRecv uint64 Byte uint64
PktRecvUnique uint64 PktUnique uint64
ByteRecvUnique uint64 ByteUnique uint64
PktRcvLoss uint64 PktLoss uint64
ByteRcvLoss uint64 ByteLoss uint64
PktRcvRetrans uint64 PktRetrans uint64
ByteRcvRetrans uint64 ByteRetrans uint64
PktRcvDrop uint64 PktBelated uint64
ByteRcvDrop uint64 ByteBelated uint64
PktDrop uint64
ByteDrop uint64
// instantaneous // instantaneous
PktRcvBuf uint64 PktBuf uint64
ByteRcvBuf uint64 ByteBuf uint64
MsRcvBuf uint64 MsBuf uint64
BytePayload uint64 BytePayload uint64
MbpsEstimatedRecvBandwidth float64
MbpsEstimatedLinkCapacity float64
PktLossRate float64
} }

View file

@ -14,13 +14,12 @@ import (
// liveSend implements the Sender interface // liveSend implements the Sender interface
type liveSend struct { type liveSend struct {
nextSequenceNumber circular.Number nextSequenceNumber circular.Number
dropThreshold uint64
packetList *list.List packetList *list.List
lossList *list.List lossList *list.List
lock sync.RWMutex lock sync.RWMutex
dropInterval uint64 // microseconds
avgPayloadSize float64 // bytes avgPayloadSize float64 // bytes
pktSndPeriod float64 // microseconds pktSndPeriod float64 // microseconds
maxBW float64 // bytes/s maxBW float64 // bytes/s
@ -29,14 +28,20 @@ type liveSend struct {
statistics SendStats statistics SendStats
rate struct { probeTime uint64
period time.Duration
last time.Time
bytes uint64 rate struct {
prevBytes uint64 period uint64 // microseconds
last uint64
bytes uint64
bytesSent uint64
bytesRetrans uint64
estimatedInputBW float64 // bytes/s estimatedInputBW float64 // bytes/s
estimatedSentBW float64 // bytes/s
pktLossRate float64
} }
deliver func(p packet.Packet) deliver func(p packet.Packet)
@ -46,12 +51,11 @@ type liveSend struct {
func NewLiveSend(config SendConfig) Sender { func NewLiveSend(config SendConfig) Sender {
s := &liveSend{ s := &liveSend{
nextSequenceNumber: config.InitialSequenceNumber, nextSequenceNumber: config.InitialSequenceNumber,
dropThreshold: config.DropThreshold,
packetList: list.New(), packetList: list.New(),
lossList: list.New(), lossList: list.New(),
dropInterval: config.DropInterval, // microseconds avgPayloadSize: packet.MAX_PAYLOAD_SIZE, // 5.1.2. SRT's Default LiveCC Algorithm
avgPayloadSize: 1456, // 5.1.2. SRT's Default LiveCC Algorithm
maxBW: float64(config.MaxBW), maxBW: float64(config.MaxBW),
inputBW: float64(config.InputBW), inputBW: float64(config.InputBW),
overheadBW: float64(config.OverheadBW), overheadBW: float64(config.OverheadBW),
@ -66,8 +70,8 @@ func NewLiveSend(config SendConfig) Sender {
s.maxBW = 128 * 1024 * 1024 // 1 Gbit/s s.maxBW = 128 * 1024 * 1024 // 1 Gbit/s
s.pktSndPeriod = (s.avgPayloadSize + 16) * 1_000_000 / s.maxBW s.pktSndPeriod = (s.avgPayloadSize + 16) * 1_000_000 / s.maxBW
s.rate.period = time.Second s.rate.period = uint64(time.Second.Microseconds())
s.rate.last = time.Now() s.rate.last = 0
return s return s
} }
@ -78,15 +82,20 @@ func (s *liveSend) Stats() SendStats {
s.statistics.UsPktSndPeriod = s.pktSndPeriod s.statistics.UsPktSndPeriod = s.pktSndPeriod
s.statistics.BytePayload = uint64(s.avgPayloadSize) s.statistics.BytePayload = uint64(s.avgPayloadSize)
s.statistics.MsSndBuf = 0 s.statistics.MsBuf = 0
max := s.lossList.Back() max := s.lossList.Back()
min := s.lossList.Front() min := s.lossList.Front()
if max != nil && min != nil { if max != nil && min != nil {
s.statistics.MsSndBuf = (max.Value.(packet.Packet).Header().PktTsbpdTime - min.Value.(packet.Packet).Header().PktTsbpdTime) / 1_000 s.statistics.MsBuf = (max.Value.(packet.Packet).Header().PktTsbpdTime - min.Value.(packet.Packet).Header().PktTsbpdTime) / 1_000
} }
s.statistics.MbpsEstimatedInputBandwidth = s.rate.estimatedInputBW * 8 / 1024 / 1024
s.statistics.MbpsEstimatedSentBandwidth = s.rate.estimatedSentBW * 8 / 1024 / 1024
s.statistics.PktLossRate = s.rate.pktLossRate
return s.statistics return s.statistics
} }
@ -112,24 +121,27 @@ func (s *liveSend) Push(p packet.Packet) {
pktLen := p.Len() pktLen := p.Len()
s.statistics.PktSndBuf++ s.statistics.PktBuf++
s.statistics.ByteSndBuf += pktLen s.statistics.ByteBuf += pktLen
// bandwidth calculation // input bandwidth calculation
s.rate.bytes += pktLen s.rate.bytes += pktLen
now := time.Now()
tdiff := now.Sub(s.rate.last)
if tdiff > s.rate.period {
s.rate.estimatedInputBW = float64(s.rate.bytes-s.rate.prevBytes) / tdiff.Seconds()
s.rate.prevBytes = s.rate.bytes
s.rate.last = now
}
p.Header().Timestamp = uint32(p.Header().PktTsbpdTime & uint64(packet.MAX_TIMESTAMP)) p.Header().Timestamp = uint32(p.Header().PktTsbpdTime & uint64(packet.MAX_TIMESTAMP))
// Every 16th and 17th packet should be sent at the same time in order
// for the receiver to determine the link capacity. Not really well
// documented in the specs.
// PktTsbpdTime is used for the timing of sending the packets. Here we
// can modify it because it has already been used to set the packet's
// timestamp.
probe := p.Header().PacketSequenceNumber.Val() & 0xF
if probe == 0 {
s.probeTime = p.Header().PktTsbpdTime
} else if probe == 1 {
p.Header().PktTsbpdTime = s.probeTime
}
s.packetList.PushBack(p) s.packetList.PushBack(p)
s.statistics.PktFlightSize = uint64(s.packetList.Len()) s.statistics.PktFlightSize = uint64(s.packetList.Len())
@ -142,16 +154,20 @@ func (s *liveSend) Tick(now uint64) {
for e := s.packetList.Front(); e != nil; e = e.Next() { for e := s.packetList.Front(); e != nil; e = e.Next() {
p := e.Value.(packet.Packet) p := e.Value.(packet.Packet)
if p.Header().PktTsbpdTime <= now { if p.Header().PktTsbpdTime <= now {
s.statistics.PktSent++ s.statistics.Pkt++
s.statistics.PktSentUnique++ s.statistics.PktUnique++
s.statistics.ByteSent += p.Len() pktLen := p.Len()
s.statistics.ByteSentUnique += p.Len()
s.statistics.Byte += pktLen
s.statistics.ByteUnique += pktLen
s.statistics.UsSndDuration += uint64(s.pktSndPeriod) s.statistics.UsSndDuration += uint64(s.pktSndPeriod)
// 5.1.2. SRT's Default LiveCC Algorithm // 5.1.2. SRT's Default LiveCC Algorithm
s.avgPayloadSize = 0.875*s.avgPayloadSize + 0.125*float64(p.Len()) s.avgPayloadSize = 0.875*s.avgPayloadSize + 0.125*float64(pktLen)
s.rate.bytesSent += pktLen
s.deliver(p) s.deliver(p)
removeList = append(removeList, e) removeList = append(removeList, e)
@ -171,12 +187,12 @@ func (s *liveSend) Tick(now uint64) {
for e := s.lossList.Front(); e != nil; e = e.Next() { for e := s.lossList.Front(); e != nil; e = e.Next() {
p := e.Value.(packet.Packet) p := e.Value.(packet.Packet)
if p.Header().PktTsbpdTime+s.dropInterval <= now { if p.Header().PktTsbpdTime+s.dropThreshold <= now {
// dropped packet because too old // dropped packet because too old
s.statistics.PktSndDrop++ s.statistics.PktDrop++
s.statistics.PktSndLoss++ s.statistics.PktLoss++
s.statistics.ByteSndDrop += p.Len() s.statistics.ByteDrop += p.Len()
s.statistics.ByteSndLoss += p.Len() s.statistics.ByteLoss += p.Len()
removeList = append(removeList, e) removeList = append(removeList, e)
} }
@ -186,8 +202,8 @@ func (s *liveSend) Tick(now uint64) {
for _, e := range removeList { for _, e := range removeList {
p := e.Value.(packet.Packet) p := e.Value.(packet.Packet)
s.statistics.PktSndBuf-- s.statistics.PktBuf--
s.statistics.ByteSndBuf -= p.Len() s.statistics.ByteBuf -= p.Len()
s.lossList.Remove(e) s.lossList.Remove(e)
@ -195,6 +211,26 @@ func (s *liveSend) Tick(now uint64) {
p.Decommission() p.Decommission()
} }
s.lock.Unlock() s.lock.Unlock()
s.lock.Lock()
tdiff := now - s.rate.last
if tdiff > s.rate.period {
s.rate.estimatedInputBW = float64(s.rate.bytes) / (float64(tdiff) / 1000 / 1000)
s.rate.estimatedSentBW = float64(s.rate.bytesSent) / (float64(tdiff) / 1000 / 1000)
if s.rate.bytesSent != 0 {
s.rate.pktLossRate = float64(s.rate.bytesRetrans) / float64(s.rate.bytesSent) * 100
} else {
s.rate.pktLossRate = 0
}
s.rate.bytes = 0
s.rate.bytesSent = 0
s.rate.bytesRetrans = 0
s.rate.last = now
}
s.lock.Unlock()
} }
func (s *liveSend) ACK(sequenceNumber circular.Number) { func (s *liveSend) ACK(sequenceNumber circular.Number) {
@ -216,8 +252,8 @@ func (s *liveSend) ACK(sequenceNumber circular.Number) {
for _, e := range removeList { for _, e := range removeList {
p := e.Value.(packet.Packet) p := e.Value.(packet.Packet)
s.statistics.PktSndBuf-- s.statistics.PktBuf--
s.statistics.ByteSndBuf -= p.Len() s.statistics.ByteBuf -= p.Len()
s.lossList.Remove(e) s.lossList.Remove(e)
@ -242,16 +278,19 @@ func (s *liveSend) NAK(sequenceNumbers []circular.Number) {
for i := 0; i < len(sequenceNumbers); i += 2 { for i := 0; i < len(sequenceNumbers); i += 2 {
if p.Header().PacketSequenceNumber.Gte(sequenceNumbers[i]) && p.Header().PacketSequenceNumber.Lte(sequenceNumbers[i+1]) { if p.Header().PacketSequenceNumber.Gte(sequenceNumbers[i]) && p.Header().PacketSequenceNumber.Lte(sequenceNumbers[i+1]) {
s.statistics.PktRetrans++ s.statistics.PktRetrans++
s.statistics.PktSent++ s.statistics.Pkt++
s.statistics.PktSndLoss++ s.statistics.PktLoss++
s.statistics.ByteRetrans += p.Len() s.statistics.ByteRetrans += p.Len()
s.statistics.ByteSent += p.Len() s.statistics.Byte += p.Len()
s.statistics.ByteSndLoss += p.Len() s.statistics.ByteLoss += p.Len()
// 5.1.2. SRT's Default LiveCC Algorithm // 5.1.2. SRT's Default LiveCC Algorithm
s.avgPayloadSize = 0.875*s.avgPayloadSize + 0.125*float64(p.Len()) s.avgPayloadSize = 0.875*s.avgPayloadSize + 0.125*float64(p.Len())
s.rate.bytesSent += p.Len()
s.rate.bytesRetrans += p.Len()
p.Header().RetransmittedPacketFlag = true p.Header().RetransmittedPacketFlag = true
s.deliver(p) s.deliver(p)
} }
@ -259,6 +298,13 @@ func (s *liveSend) NAK(sequenceNumbers []circular.Number) {
} }
} }
func (s *liveSend) SetDropThreshold(threshold uint64) {
s.lock.Lock()
defer s.lock.Unlock()
s.dropThreshold = threshold
}
// liveReceive implements the Receiver interface // liveReceive implements the Receiver interface
type liveReceive struct { type liveReceive struct {
maxSeenSequenceNumber circular.Number maxSeenSequenceNumber circular.Number
@ -275,21 +321,26 @@ type liveReceive struct {
lastPeriodicACK uint64 lastPeriodicACK uint64
lastPeriodicNAK uint64 lastPeriodicNAK uint64
avgPayloadSize float64 // bytes avgPayloadSize float64 // bytes
avgLinkCapacity float64 // packets per second
probeTime time.Time
probeNextSeq circular.Number
statistics ReceiveStats statistics ReceiveStats
rate struct { rate struct {
last time.Time last uint64 // microseconds
period time.Duration period uint64
packets uint64 packets uint64
prevPackets uint64 bytes uint64
bytes uint64 bytesRetrans uint64
prevBytes uint64
pps uint32 packetsPerSecond float64
bps uint32 bytesPerSecond float64
pktLossRate float64
} }
sendACK func(seq circular.Number, light bool) sendACK func(seq circular.Number, light bool)
@ -327,8 +378,8 @@ func NewLiveReceive(config ReceiveConfig) Receiver {
r.deliver = func(p packet.Packet) {} r.deliver = func(p packet.Packet) {}
} }
r.rate.last = time.Now() r.rate.last = 0
r.rate.period = time.Second r.rate.period = uint64(time.Second.Microseconds())
return r return r
} }
@ -338,34 +389,20 @@ func (r *liveReceive) Stats() ReceiveStats {
defer r.lock.RUnlock() defer r.lock.RUnlock()
r.statistics.BytePayload = uint64(r.avgPayloadSize) r.statistics.BytePayload = uint64(r.avgPayloadSize)
r.statistics.MbpsEstimatedRecvBandwidth = r.rate.bytesPerSecond * 8 / 1024 / 1024
r.statistics.MbpsEstimatedLinkCapacity = r.avgLinkCapacity * packet.MAX_PAYLOAD_SIZE * 8 / 1024 / 1024
r.statistics.PktLossRate = r.rate.pktLossRate
return r.statistics return r.statistics
} }
func (r *liveReceive) PacketRate() (pps, bps uint32) { func (r *liveReceive) PacketRate() (pps, bps, capacity float64) {
r.lock.Lock() r.lock.Lock()
defer r.lock.Unlock() defer r.lock.Unlock()
tdiff := time.Since(r.rate.last) pps = r.rate.packetsPerSecond
bps = r.rate.bytesPerSecond
if tdiff < r.rate.period { capacity = r.avgLinkCapacity
pps = r.rate.pps
bps = r.rate.bps
return
}
pdiff := r.rate.packets - r.rate.prevPackets
bdiff := r.rate.bytes - r.rate.prevBytes
r.rate.pps = uint32(float64(pdiff) / tdiff.Seconds())
r.rate.bps = uint32(float64(bdiff) / tdiff.Seconds())
r.rate.prevPackets, r.rate.prevBytes = r.rate.packets, r.rate.bytes
r.rate.last = time.Now()
pps = r.rate.pps
bps = r.rate.bps
return return
} }
@ -385,6 +422,28 @@ func (r *liveReceive) Push(pkt packet.Packet) {
return return
} }
// This is not really well (not at all) described in the specs. See core.cpp and window.h
// and search for PUMASK_SEQNO_PROBE (0xF). Every 16th and 17th packet are
// sent in pairs. This is used as a probe for the theoretical capacity of the link.
if !pkt.Header().RetransmittedPacketFlag {
probe := pkt.Header().PacketSequenceNumber.Val() & 0xF
if probe == 0 {
r.probeTime = time.Now()
r.probeNextSeq = pkt.Header().PacketSequenceNumber.Inc()
} else if probe == 1 && pkt.Header().PacketSequenceNumber.Equals(r.probeNextSeq) && !r.probeTime.IsZero() && pkt.Len() != 0 {
// The time between packets scaled to a fully loaded packet
diff := float64(time.Since(r.probeTime).Microseconds()) * (packet.MAX_PAYLOAD_SIZE / float64(pkt.Len()))
if diff != 0 {
// Here we're doing an average of the measurements.
r.avgLinkCapacity = 0.875*r.avgLinkCapacity + 0.125*1_000_000/diff
}
} else {
r.probeTime = time.Time{}
}
} else {
r.probeTime = time.Time{}
}
r.nPackets++ r.nPackets++
pktLen := pkt.Len() pktLen := pkt.Len()
@ -392,13 +451,15 @@ func (r *liveReceive) Push(pkt packet.Packet) {
r.rate.packets++ r.rate.packets++
r.rate.bytes += pktLen r.rate.bytes += pktLen
r.statistics.PktRecv++ r.statistics.Pkt++
r.statistics.ByteRecv += pktLen r.statistics.Byte += pktLen
//pkt.PktTsbpdTime = pkt.Timestamp + r.delay //pkt.PktTsbpdTime = pkt.Timestamp + r.delay
if pkt.Header().RetransmittedPacketFlag { if pkt.Header().RetransmittedPacketFlag {
r.statistics.PktRcvRetrans++ r.statistics.PktRetrans++
r.statistics.ByteRcvRetrans += pktLen r.statistics.ByteRetrans += pktLen
r.rate.bytesRetrans += pktLen
} }
// 5.1.2. SRT's Default LiveCC Algorithm // 5.1.2. SRT's Default LiveCC Algorithm
@ -406,16 +467,19 @@ func (r *liveReceive) Push(pkt packet.Packet) {
if pkt.Header().PacketSequenceNumber.Lte(r.lastDeliveredSequenceNumber) { if pkt.Header().PacketSequenceNumber.Lte(r.lastDeliveredSequenceNumber) {
// too old, because up until r.lastDeliveredSequenceNumber, we already delivered // too old, because up until r.lastDeliveredSequenceNumber, we already delivered
r.statistics.PktRcvDrop++ r.statistics.PktBelated++
r.statistics.ByteRcvDrop += pktLen r.statistics.ByteBelated += pktLen
r.statistics.PktDrop++
r.statistics.ByteDrop += pktLen
return return
} }
if pkt.Header().PacketSequenceNumber.Lt(r.lastACKSequenceNumber) { if pkt.Header().PacketSequenceNumber.Lt(r.lastACKSequenceNumber) {
// already acknowledged, ignoring // already acknowledged, ignoring
r.statistics.PktRcvDrop++ r.statistics.PktDrop++
r.statistics.ByteRcvDrop += pktLen r.statistics.ByteDrop += pktLen
return return
} }
@ -430,17 +494,17 @@ func (r *liveReceive) Push(pkt packet.Packet) {
if p.Header().PacketSequenceNumber == pkt.Header().PacketSequenceNumber { if p.Header().PacketSequenceNumber == pkt.Header().PacketSequenceNumber {
// already received (has been sent more than once), ignoring // already received (has been sent more than once), ignoring
r.statistics.PktRcvDrop++ r.statistics.PktDrop++
r.statistics.ByteRcvDrop += pktLen r.statistics.ByteDrop += pktLen
break break
} else if p.Header().PacketSequenceNumber.Gt(pkt.Header().PacketSequenceNumber) { } else if p.Header().PacketSequenceNumber.Gt(pkt.Header().PacketSequenceNumber) {
// late arrival, this fills a gap // late arrival, this fills a gap
r.statistics.PktRcvBuf++ r.statistics.PktBuf++
r.statistics.PktRecvUnique++ r.statistics.PktUnique++
r.statistics.ByteRcvBuf += pktLen r.statistics.ByteBuf += pktLen
r.statistics.ByteRecvUnique += pktLen r.statistics.ByteUnique += pktLen
r.packetList.InsertBefore(pkt, e) r.packetList.InsertBefore(pkt, e)
@ -455,17 +519,17 @@ func (r *liveReceive) Push(pkt packet.Packet) {
r.sendNAK(r.maxSeenSequenceNumber.Inc(), pkt.Header().PacketSequenceNumber.Dec()) r.sendNAK(r.maxSeenSequenceNumber.Inc(), pkt.Header().PacketSequenceNumber.Dec())
len := uint64(pkt.Header().PacketSequenceNumber.Distance(r.maxSeenSequenceNumber)) len := uint64(pkt.Header().PacketSequenceNumber.Distance(r.maxSeenSequenceNumber))
r.statistics.PktRcvLoss += len r.statistics.PktLoss += len
r.statistics.ByteRcvLoss += len * uint64(r.avgPayloadSize) r.statistics.ByteLoss += len * uint64(r.avgPayloadSize)
r.maxSeenSequenceNumber = pkt.Header().PacketSequenceNumber r.maxSeenSequenceNumber = pkt.Header().PacketSequenceNumber
} }
r.statistics.PktRcvBuf++ r.statistics.PktBuf++
r.statistics.PktRecvUnique++ r.statistics.PktUnique++
r.statistics.ByteRcvBuf += pktLen r.statistics.ByteBuf += pktLen
r.statistics.ByteRecvUnique += pktLen r.statistics.ByteUnique += pktLen
r.packetList.PushBack(pkt) r.packetList.PushBack(pkt)
} }
@ -521,7 +585,7 @@ func (r *liveReceive) periodicACK(now uint64) (ok bool, sequenceNumber circular.
r.lastPeriodicACK = now r.lastPeriodicACK = now
r.nPackets = 0 r.nPackets = 0
r.statistics.MsRcvBuf = (maxPktTsbpdTime - minPktTsbpdTime) / 1_000 r.statistics.MsBuf = (maxPktTsbpdTime - minPktTsbpdTime) / 1_000
return return
} }
@ -572,15 +636,13 @@ func (r *liveReceive) Tick(now uint64) {
// deliver packets whose PktTsbpdTime is ripe // deliver packets whose PktTsbpdTime is ripe
r.lock.Lock() r.lock.Lock()
defer r.lock.Unlock()
removeList := make([]*list.Element, 0, r.packetList.Len()) removeList := make([]*list.Element, 0, r.packetList.Len())
for e := r.packetList.Front(); e != nil; e = e.Next() { for e := r.packetList.Front(); e != nil; e = e.Next() {
p := e.Value.(packet.Packet) p := e.Value.(packet.Packet)
if p.Header().PacketSequenceNumber.Lte(r.lastACKSequenceNumber) && p.Header().PktTsbpdTime <= now { if p.Header().PacketSequenceNumber.Lte(r.lastACKSequenceNumber) && p.Header().PktTsbpdTime <= now {
r.statistics.PktRcvBuf-- r.statistics.PktBuf--
r.statistics.ByteRcvBuf -= p.Len() r.statistics.ByteBuf -= p.Len()
r.lastDeliveredSequenceNumber = p.Header().PacketSequenceNumber r.lastDeliveredSequenceNumber = p.Header().PacketSequenceNumber
@ -594,6 +656,27 @@ func (r *liveReceive) Tick(now uint64) {
for _, e := range removeList { for _, e := range removeList {
r.packetList.Remove(e) r.packetList.Remove(e)
} }
r.lock.Unlock()
r.lock.Lock()
tdiff := now - r.rate.last // microseconds
if tdiff > r.rate.period {
r.rate.packetsPerSecond = float64(r.rate.packets) / (float64(tdiff) / 1000 / 1000)
r.rate.bytesPerSecond = float64(r.rate.bytes) / (float64(tdiff) / 1000 / 1000)
if r.rate.bytes != 0 {
r.rate.pktLossRate = float64(r.rate.bytesRetrans) / float64(r.rate.bytes) * 100
} else {
r.rate.bytes = 0
}
r.rate.packets = 0
r.rate.bytes = 0
r.rate.bytesRetrans = 0
r.rate.last = now
}
r.lock.Unlock()
} }
func (r *liveReceive) SetNAKInterval(nakInterval uint64) { func (r *liveReceive) SetNAKInterval(nakInterval uint64) {
@ -630,7 +713,6 @@ type fakeLiveReceive struct {
periodicNAKInterval uint64 // config periodicNAKInterval uint64 // config
lastPeriodicACK uint64 lastPeriodicACK uint64
lastPeriodicNAK uint64
avgPayloadSize float64 // bytes avgPayloadSize float64 // bytes
@ -638,13 +720,11 @@ type fakeLiveReceive struct {
last time.Time last time.Time
period time.Duration period time.Duration
packets uint64 packets uint64
prevPackets uint64 bytes uint64
bytes uint64
prevBytes uint64
pps uint32 pps float64
bps uint32 bps float64
} }
sendACK func(seq circular.Number, light bool) sendACK func(seq circular.Number, light bool)
@ -689,7 +769,7 @@ func NewFakeLiveReceive(config ReceiveConfig) Receiver {
} }
func (r *fakeLiveReceive) Stats() ReceiveStats { return ReceiveStats{} } func (r *fakeLiveReceive) Stats() ReceiveStats { return ReceiveStats{} }
func (r *fakeLiveReceive) PacketRate() (pps, bps uint32) { func (r *fakeLiveReceive) PacketRate() (pps, bps, capacity float64) {
r.lock.Lock() r.lock.Lock()
defer r.lock.Unlock() defer r.lock.Unlock()
@ -702,13 +782,10 @@ func (r *fakeLiveReceive) PacketRate() (pps, bps uint32) {
return return
} }
pdiff := r.rate.packets - r.rate.prevPackets r.rate.pps = float64(r.rate.packets) / tdiff.Seconds()
bdiff := r.rate.bytes - r.rate.prevBytes r.rate.bps = float64(r.rate.bytes) / tdiff.Seconds()
r.rate.pps = uint32(float64(pdiff) / tdiff.Seconds()) r.rate.packets, r.rate.bytes = 0, 0
r.rate.bps = uint32(float64(bdiff) / tdiff.Seconds())
r.rate.prevPackets, r.rate.prevBytes = r.rate.packets, r.rate.bytes
r.rate.last = time.Now() r.rate.last = time.Now()
pps = r.rate.pps pps = r.rate.pps

View file

@ -18,6 +18,7 @@ import (
const MAX_SEQUENCENUMBER uint32 = 0b01111111_11111111_11111111_11111111 const MAX_SEQUENCENUMBER uint32 = 0b01111111_11111111_11111111_11111111
const MAX_TIMESTAMP uint32 = 0b11111111_11111111_11111111_11111111 const MAX_TIMESTAMP uint32 = 0b11111111_11111111_11111111_11111111
const MAX_PAYLOAD_SIZE = 1456
// Table 1: SRT Control Packet Types // Table 1: SRT Control Packet Types
const ( const (
@ -279,7 +280,7 @@ func (p *pkt) Decommission() {
func (p pkt) String() string { func (p pkt) String() string {
var b strings.Builder var b strings.Builder
fmt.Fprintf(&b, "timestamp=%#08x, destId=%#08x\n", p.header.Timestamp, p.header.DestinationSocketId) fmt.Fprintf(&b, "timestamp=%#08x (%d), destId=%#08x\n", p.header.Timestamp, p.header.Timestamp, p.header.DestinationSocketId)
if p.header.IsControlPacket { if p.header.IsControlPacket {
fmt.Fprintf(&b, "control packet:\n") fmt.Fprintf(&b, "control packet:\n")

View file

@ -312,15 +312,16 @@ func (ln *listener) Accept(acceptFn AcceptFunc) (Conn, ConnType, error) {
// Create a new socket ID // Create a new socket ID
socketId := uint32(time.Since(ln.start).Microseconds()) socketId := uint32(time.Since(ln.start).Microseconds())
// Select the largest TSBPD delay advertised by the caller, but at // Select the largest TSBPD delay advertised by the listener, but at least 120ms
// least 120ms recvTsbpdDelay := uint16(ln.config.ReceiverLatency.Milliseconds())
tsbpdDelay := uint16(120) sendTsbpdDelay := uint16(ln.config.PeerLatency.Milliseconds())
if request.handshake.RecvTSBPDDelay > tsbpdDelay {
tsbpdDelay = request.handshake.RecvTSBPDDelay if request.handshake.SendTSBPDDelay > recvTsbpdDelay {
recvTsbpdDelay = request.handshake.SendTSBPDDelay
} }
if request.handshake.SendTSBPDDelay > tsbpdDelay { if request.handshake.RecvTSBPDDelay > sendTsbpdDelay {
tsbpdDelay = request.handshake.SendTSBPDDelay sendTsbpdDelay = request.handshake.RecvTSBPDDelay
} }
ln.config.StreamId = request.handshake.StreamId ln.config.StreamId = request.handshake.StreamId
@ -335,7 +336,8 @@ func (ln *listener) Accept(acceptFn AcceptFunc) (Conn, ConnType, error) {
socketId: socketId, socketId: socketId,
peerSocketId: request.handshake.SRTSocketId, peerSocketId: request.handshake.SRTSocketId,
tsbpdTimeBase: uint64(request.timestamp), tsbpdTimeBase: uint64(request.timestamp),
tsbpdDelay: uint64(tsbpdDelay) * 1000, tsbpdDelay: uint64(recvTsbpdDelay) * 1000,
peerTsbpdDelay: uint64(sendTsbpdDelay) * 1000,
initialPacketSequenceNumber: request.handshake.InitialPacketSequenceNumber, initialPacketSequenceNumber: request.handshake.InitialPacketSequenceNumber,
crypto: request.crypto, crypto: request.crypto,
keyBaseEncryption: packet.EvenKeyEncrypted, keyBaseEncryption: packet.EvenKeyEncrypted,
@ -359,6 +361,8 @@ func (ln *listener) Accept(acceptFn AcceptFunc) (Conn, ConnType, error) {
request.handshake.SRTFlags.REXMITFLG = true request.handshake.SRTFlags.REXMITFLG = true
request.handshake.SRTFlags.STREAM = false request.handshake.SRTFlags.STREAM = false
request.handshake.SRTFlags.PACKET_FILTER = false request.handshake.SRTFlags.PACKET_FILTER = false
request.handshake.RecvTSBPDDelay = recvTsbpdDelay
request.handshake.SendTSBPDDelay = sendTsbpdDelay
ln.accept(request) ln.accept(request)

View file

@ -7,55 +7,111 @@ type Statistics struct {
MsTimeStamp uint64 // The time elapsed, in milliseconds, since the SRT socket has been created MsTimeStamp uint64 // The time elapsed, in milliseconds, since the SRT socket has been created
// Accumulated // Accumulated
Accumulated StatisticsAccumulated
PktSent uint64 // The total number of sent DATA packets, including retransmitted packets // Interval
PktRecv uint64 // The total number of received DATA packets, including retransmitted packets Interval StatisticsInterval
PktSentUnique uint64 // The total number of unique DATA packets sent by the SRT sender
PktRecvUnique uint64 // The total number of unique original, retransmitted or recovered by the packet filter DATA packets received in time, decrypted without errors and, as a result, scheduled for delivery to the upstream application by the SRT receiver.
PktSndLoss uint64 // The total number of data packets considered or reported as lost at the sender side. Does not correspond to the packets detected as lost at the receiver side.
PktRcvLoss uint64 // The total number of SRT DATA packets detected as presently missing (either reordered or lost) at the receiver side
PktRetrans uint64 // The total number of retransmitted packets sent by the SRT sender
PktRcvRetrans uint64 // The total number of retransmitted packets registered at the receiver side
PktSentACK uint64 // The total number of sent ACK (Acknowledgement) control packets
PktRecvACK uint64 // The total number of received ACK (Acknowledgement) control packets
PktSentNAK uint64 // The total number of sent NAK (Negative Acknowledgement) control packets
PktRecvNAK uint64 // The total number of received NAK (Negative Acknowledgement) control packets
PktSentKM uint64 // The total number of sent KM (Key Material) control packets
PktRecvKM uint64 // The total number of received KM (Key Material) control packets
UsSndDuration uint64 // The total accumulated time in microseconds, during which the SRT sender has some data to transmit, including packets that have been sent, but not yet acknowledged
PktSndDrop uint64 // The total number of dropped by the SRT sender DATA packets that have no chance to be delivered in time
PktRcvDrop uint64 // The total number of dropped by the SRT receiver and, as a result, not delivered to the upstream application DATA packets
PktRcvUndecrypt uint64 // The total number of packets that failed to be decrypted at the receiver side
ByteSent uint64 // Same as pktSent, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecv uint64 // Same as pktRecv, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteSentUnique uint64 // Same as pktSentUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecvUnique uint64 // Same as pktRecvUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRcvLoss uint64 // Same as pktRcvLoss, but expressed in bytes, including payload and all the headers (IP, TCP, SRT), bytes for the presently missing (either reordered or lost) packets' payloads are estimated based on the average packet size
ByteRetrans uint64 // Same as pktRetrans, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteSndDrop uint64 // Same as pktSndDrop, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRcvDrop uint64 // Same as pktRcvDrop, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRcvUndecrypt uint64 // Same as pktRcvUndecrypt, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
// Instantaneous // Instantaneous
Instantaneous StatisticsInstantaneous
UsPktSndPeriod float64 // Current minimum time interval between which consecutive packets are sent, in microseconds }
PktFlowWindow uint64 // The maximum number of packets that can be "in flight"
PktFlightSize uint64 // The number of packets in flight type StatisticsAccumulated struct {
MsRTT float64 // Smoothed round-trip time (SRTT), an exponentially-weighted moving average (EWMA) of an endpoint's RTT samples, in milliseconds PktSent uint64 // The total number of sent DATA packets, including retransmitted packets
MbpsBandwidth float64 // Estimated bandwidth of the network link, in Mbps PktRecv uint64 // The total number of received DATA packets, including retransmitted packets
ByteAvailSndBuf uint64 // The available space in the sender's buffer, in bytes PktSentUnique uint64 // The total number of unique DATA packets sent by the SRT sender
ByteAvailRcvBuf uint64 // The available space in the receiver's buffer, in bytes PktRecvUnique uint64 // The total number of unique original, retransmitted or recovered by the packet filter DATA packets received in time, decrypted without errors and, as a result, scheduled for delivery to the upstream application by the SRT receiver.
MbpsMaxBW float64 // Transmission bandwidth limit, in Mbps PktSendLoss uint64 // The total number of data packets considered or reported as lost at the sender side. Does not correspond to the packets detected as lost at the receiver side.
ByteMSS uint64 // Maximum Segment Size (MSS), in bytes PktRecvLoss uint64 // The total number of SRT DATA packets detected as presently missing (either reordered or lost) at the receiver side
PktSndBuf uint64 // The number of packets in the sender's buffer that are already scheduled for sending or even possibly sent, but not yet acknowledged PktRetrans uint64 // The total number of retransmitted packets sent by the SRT sender
ByteSndBuf uint64 // Instantaneous (current) value of pktSndBuf, but expressed in bytes, including payload and all headers (IP, TCP, SRT) PktRecvRetrans uint64 // The total number of retransmitted packets registered at the receiver side
MsSndBuf uint64 // The timespan (msec) of packets in the sender's buffer (unacknowledged packets) PktSentACK uint64 // The total number of sent ACK (Acknowledgement) control packets
MsSndTsbPdDelay uint64 // Timestamp-based Packet Delivery Delay value of the peer PktRecvACK uint64 // The total number of received ACK (Acknowledgement) control packets
PktRcvBuf uint64 // The number of acknowledged packets in receiver's buffer PktSentNAK uint64 // The total number of sent NAK (Negative Acknowledgement) control packets
ByteRcvBuf uint64 // Instantaneous (current) value of pktRcvBuf, expressed in bytes, including payload and all headers (IP, TCP, SRT) PktRecvNAK uint64 // The total number of received NAK (Negative Acknowledgement) control packets
MsRcvBuf uint64 // The timespan (msec) of acknowledged packets in the receiver's buffer PktSentKM uint64 // The total number of sent KM (Key Material) control packets
MsRcvTsbPdDelay uint64 // Timestamp-based Packet Delivery Delay value set on the socket via SRTO_RCVLATENCY or SRTO_LATENCY PktRecvKM uint64 // The total number of received KM (Key Material) control packets
PktReorderTolerance uint64 // Instant value of the packet reorder tolerance UsSndDuration uint64 // The total accumulated time in microseconds, during which the SRT sender has some data to transmit, including packets that have been sent, but not yet acknowledged
PktRcvAvgBelatedTime uint64 // Accumulated difference between the current time and the time-to-play of a packet that is received late PktRecvBelated uint64
PktSendDrop uint64 // The total number of dropped by the SRT sender DATA packets that have no chance to be delivered in time
PktRecvDrop uint64 // The total number of dropped by the SRT receiver and, as a result, not delivered to the upstream application DATA packets
PktRecvUndecrypt uint64 // The total number of packets that failed to be decrypted at the receiver side
ByteSent uint64 // Same as pktSent, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecv uint64 // Same as pktRecv, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteSentUnique uint64 // Same as pktSentUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecvUnique uint64 // Same as pktRecvUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecvLoss uint64 // Same as pktRecvLoss, but expressed in bytes, including payload and all the headers (IP, TCP, SRT), bytes for the presently missing (either reordered or lost) packets' payloads are estimated based on the average packet size
ByteRetrans uint64 // Same as pktRetrans, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecvRetrans uint64 // Same as pktRecvRetrans, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecvBelated uint64
ByteSendDrop uint64 // Same as pktSendDrop, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecvDrop uint64 // Same as pktRecvDrop, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecvUndecrypt uint64 // Same as pktRecvUndecrypt, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
}
type StatisticsInterval struct {
MsInterval uint64 // Length of the interval, in milliseconds
PktSent uint64 // Number of sent DATA packets, including retransmitted packets
PktRecv uint64 // Number of received DATA packets, including retransmitted packets
PktSentUnique uint64 // Number of unique DATA packets sent by the SRT sender
PktRecvUnique uint64 // Number of unique original, retransmitted or recovered by the packet filter DATA packets received in time, decrypted without errors and, as a result, scheduled for delivery to the upstream application by the SRT receiver.
PktSendLoss uint64 // Number of data packets considered or reported as lost at the sender side. Does not correspond to the packets detected as lost at the receiver side.
PktRecvLoss uint64 // Number of SRT DATA packets detected as presently missing (either reordered or lost) at the receiver side
PktRetrans uint64 // Number of retransmitted packets sent by the SRT sender
PktRecvRetrans uint64 // Number of retransmitted packets registered at the receiver side
PktSentACK uint64 // Number of sent ACK (Acknowledgement) control packets
PktRecvACK uint64 // Number of received ACK (Acknowledgement) control packets
PktSentNAK uint64 // Number of sent NAK (Negative Acknowledgement) control packets
PktRecvNAK uint64 // Number of received NAK (Negative Acknowledgement) control packets
MbpsSendRate float64 // Sending rate, in Mbps
MbpsRecvRate float64 // Receiving rate, in Mbps
UsSndDuration uint64 // Accumulated time in microseconds, during which the SRT sender has some data to transmit, including packets that have been sent, but not yet acknowledged
PktReorderDistance uint64
PktRecvBelated uint64 // Number of packets that arrive too late
PktSndDrop uint64 // Number of dropped by the SRT sender DATA packets that have no chance to be delivered in time
PktRecvDrop uint64 // Number of dropped by the SRT receiver and, as a result, not delivered to the upstream application DATA packets
PktRecvUndecrypt uint64 // Number of packets that failed to be decrypted at the receiver side
ByteSent uint64 // Same as pktSent, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecv uint64 // Same as pktRecv, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteSentUnique uint64 // Same as pktSentUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecvUnique uint64 // Same as pktRecvUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecvLoss uint64 // Same as pktRecvLoss, but expressed in bytes, including payload and all the headers (IP, TCP, SRT), bytes for the presently missing (either reordered or lost) packets' payloads are estimated based on the average packet size
ByteRetrans uint64 // Same as pktRetrans, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecvRetrans uint64 // Same as pktRecvRetrans, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecvBelated uint64 // Same as pktRecvBelated, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteSendDrop uint64 // Same as pktSendDrop, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecvDrop uint64 // Same as pktRecvDrop, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
ByteRecvUndecrypt uint64 // Same as pktRecvUndecrypt, but expressed in bytes, including payload and all the headers (IP, TCP, SRT)
}
type StatisticsInstantaneous struct {
UsPktSendPeriod float64 // Current minimum time interval between which consecutive packets are sent, in microseconds
PktFlowWindow uint64 // The maximum number of packets that can be "in flight"
PktFlightSize uint64 // The number of packets in flight
MsRTT float64 // Smoothed round-trip time (SRTT), an exponentially-weighted moving average (EWMA) of an endpoint's RTT samples, in milliseconds
MbpsSentRate float64 // Current transmission bandwidth, in Mbps
MbpsRecvRate float64 // Current receiving bandwidth, in Mbps
MbpsLinkCapacity float64 // Estimated capacity of the network link, in Mbps
ByteAvailSendBuf uint64 // The available space in the sender's buffer, in bytes
ByteAvailRecvBuf uint64 // The available space in the receiver's buffer, in bytes
MbpsMaxBW float64 // Transmission bandwidth limit, in Mbps
ByteMSS uint64 // Maximum Segment Size (MSS), in bytes
PktSendBuf uint64 // The number of packets in the sender's buffer that are already scheduled for sending or even possibly sent, but not yet acknowledged
ByteSendBuf uint64 // Instantaneous (current) value of pktSndBuf, but expressed in bytes, including payload and all headers (IP, TCP, SRT)
MsSendBuf uint64 // The timespan (msec) of packets in the sender's buffer (unacknowledged packets)
MsSendTsbPdDelay uint64 // Timestamp-based Packet Delivery Delay value of the peer
PktRecvBuf uint64 // The number of acknowledged packets in receiver's buffer
ByteRecvBuf uint64 // Instantaneous (current) value of pktRcvBuf, expressed in bytes, including payload and all headers (IP, TCP, SRT)
MsRecvBuf uint64 // The timespan (msec) of acknowledged packets in the receiver's buffer
MsRecvTsbPdDelay uint64 // Timestamp-based Packet Delivery Delay value set on the socket via SRTO_RCVLATENCY or SRTO_LATENCY
PktReorderTolerance uint64 // Instant value of the packet reorder tolerance
PktRecvAvgBelatedTime uint64 // Accumulated difference between the current time and the time-to-play of a packet that is received late
PktSendLossRate float64 // Percentage of resent data vs. sent data
PktRecvLossRate float64 // Percentage of retransmitted data vs. received data
} }

View file

@ -1,7 +1,7 @@
Package validator Package validator
================= =================
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v9/logo.png">[![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) <img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v9/logo.png">[![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
![Project status](https://img.shields.io/badge/version-10.11.0-green.svg) ![Project status](https://img.shields.io/badge/version-10.11.1-green.svg)
[![Build Status](https://travis-ci.org/go-playground/validator.svg?branch=master)](https://travis-ci.org/go-playground/validator) [![Build Status](https://travis-ci.org/go-playground/validator.svg?branch=master)](https://travis-ci.org/go-playground/validator)
[![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-playground/validator?branch=master) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-playground/validator?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)

View file

@ -1484,10 +1484,15 @@ func isAlphaUnicode(fl FieldLevel) bool {
return alphaUnicodeRegex.MatchString(fl.Field().String()) return alphaUnicodeRegex.MatchString(fl.Field().String())
} }
// isBoolean is the validation function for validating if the current field's value can be safely converted to a boolean. // isBoolean is the validation function for validating if the current field's value is a valid boolean value or can be safely converted to a boolean value.
func isBoolean(fl FieldLevel) bool { func isBoolean(fl FieldLevel) bool {
_, err := strconv.ParseBool(fl.Field().String()) switch fl.Field().Kind() {
return err == nil case reflect.Bool:
return true
default:
_, err := strconv.ParseBool(fl.Field().String())
return err == nil
}
} }
// isDefault is the opposite of required aka hasValue // isDefault is the opposite of required aka hasValue

View file

@ -132,6 +132,127 @@ func main() {
} }
``` ```
## commandline
Download as binary from: https://github.com/klauspost/cpuid/releases
Install from source:
`go install github.com/klauspost/cpuid/v2/cmd/cpuid@latest`
### Example
```
λ cpuid
Name: AMD Ryzen 9 3950X 16-Core Processor
Vendor String: AuthenticAMD
Vendor ID: AMD
PhysicalCores: 16
Threads Per Core: 2
Logical Cores: 32
CPU Family 23 Model: 113
Features: ADX,AESNI,AVX,AVX2,BMI1,BMI2,CLMUL,CLZERO,CMOV,CMPXCHG8,CPBOOST,CX16,F16C,FMA3,FXSR,FXSROPT,HTT,HYPERVISOR,LAHF,LZCNT,MCAOVERFLOW,MMX,MMXEXT,MOVBE,NX,OSXSAVE,POPCNT,RDRAND,RDSEED,RDTSCP,SCE,SHA,SSE,SSE2,SSE3,SSE4,SSE42,SSE4A,SSSE3,SUCCOR,X87,XSAVE
Microarchitecture level: 3
Cacheline bytes: 64
L1 Instruction Cache: 32768 bytes
L1 Data Cache: 32768 bytes
L2 Cache: 524288 bytes
L3 Cache: 16777216 bytes
```
### JSON Output:
```
λ cpuid --json
{
"BrandName": "AMD Ryzen 9 3950X 16-Core Processor",
"VendorID": 2,
"VendorString": "AuthenticAMD",
"PhysicalCores": 16,
"ThreadsPerCore": 2,
"LogicalCores": 32,
"Family": 23,
"Model": 113,
"CacheLine": 64,
"Hz": 0,
"BoostFreq": 0,
"Cache": {
"L1I": 32768,
"L1D": 32768,
"L2": 524288,
"L3": 16777216
},
"SGX": {
"Available": false,
"LaunchControl": false,
"SGX1Supported": false,
"SGX2Supported": false,
"MaxEnclaveSizeNot64": 0,
"MaxEnclaveSize64": 0,
"EPCSections": null
},
"Features": [
"ADX",
"AESNI",
"AVX",
"AVX2",
"BMI1",
"BMI2",
"CLMUL",
"CLZERO",
"CMOV",
"CMPXCHG8",
"CPBOOST",
"CX16",
"F16C",
"FMA3",
"FXSR",
"FXSROPT",
"HTT",
"HYPERVISOR",
"LAHF",
"LZCNT",
"MCAOVERFLOW",
"MMX",
"MMXEXT",
"MOVBE",
"NX",
"OSXSAVE",
"POPCNT",
"RDRAND",
"RDSEED",
"RDTSCP",
"SCE",
"SHA",
"SSE",
"SSE2",
"SSE3",
"SSE4",
"SSE42",
"SSE4A",
"SSSE3",
"SUCCOR",
"X87",
"XSAVE"
],
"X64Level": 3
}
```
### Check CPU microarch level
```
λ cpuid --check-level=3
2022/03/18 17:04:40 AMD Ryzen 9 3950X 16-Core Processor
2022/03/18 17:04:40 Microarchitecture level 3 is supported. Max level is 3.
Exit Code 0
λ cpuid --check-level=4
2022/03/18 17:06:18 AMD Ryzen 9 3950X 16-Core Processor
2022/03/18 17:06:18 Microarchitecture level 4 not supported. Max level is 3.
Exit Code 1
```
# license # license
This code is published under an MIT license. See LICENSE file for more information. This code is published under an MIT license. See LICENSE file for more information.

View file

@ -14,6 +14,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"math" "math"
"math/bits"
"os" "os"
"runtime" "runtime"
"strings" "strings"
@ -92,7 +93,8 @@ const (
AVX512VNNI // AVX-512 Vector Neural Network Instructions AVX512VNNI // AVX-512 Vector Neural Network Instructions
AVX512VP2INTERSECT // AVX-512 Intersect for D/Q AVX512VP2INTERSECT // AVX-512 Intersect for D/Q
AVX512VPOPCNTDQ // AVX-512 Vector Population Count Doubleword and Quadword AVX512VPOPCNTDQ // AVX-512 Vector Population Count Doubleword and Quadword
AVXSLOW // Indicates the CPU performs 2 128 bit operations instead of one. AVXSLOW // Indicates the CPU performs 2 128 bit operations instead of one
AVXVNNI // AVX (VEX encoded) VNNI neural network instructions
BMI1 // Bit Manipulation Instruction Set 1 BMI1 // Bit Manipulation Instruction Set 1
BMI2 // Bit Manipulation Instruction Set 2 BMI2 // Bit Manipulation Instruction Set 2
CETIBT // Intel CET Indirect Branch Tracking CETIBT // Intel CET Indirect Branch Tracking
@ -101,21 +103,28 @@ const (
CLMUL // Carry-less Multiplication CLMUL // Carry-less Multiplication
CLZERO // CLZERO instruction supported CLZERO // CLZERO instruction supported
CMOV // i686 CMOV CMOV // i686 CMOV
CMPSB_SCADBS_SHORT // Fast short CMPSB and SCASB
CMPXCHG8 // CMPXCHG8 instruction CMPXCHG8 // CMPXCHG8 instruction
CPBOOST // Core Performance Boost CPBOOST // Core Performance Boost
CX16 // CMPXCHG16B Instruction CX16 // CMPXCHG16B Instruction
ENQCMD // Enqueue Command ENQCMD // Enqueue Command
ERMS // Enhanced REP MOVSB/STOSB ERMS // Enhanced REP MOVSB/STOSB
F16C // Half-precision floating-point conversion F16C // Half-precision floating-point conversion
FLUSH_L1D // Flush L1D cache
FMA3 // Intel FMA 3. Does not imply AVX. FMA3 // Intel FMA 3. Does not imply AVX.
FMA4 // Bulldozer FMA4 functions FMA4 // Bulldozer FMA4 functions
FSRM // Fast Short Rep Mov
FXSR // FXSAVE, FXRESTOR instructions, CR4 bit 9 FXSR // FXSAVE, FXRESTOR instructions, CR4 bit 9
FXSROPT // FXSAVE/FXRSTOR optimizations FXSROPT // FXSAVE/FXRSTOR optimizations
GFNI // Galois Field New Instructions GFNI // Galois Field New Instructions. May require other features (AVX, AVX512VL,AVX512F) based on usage.
HLE // Hardware Lock Elision HLE // Hardware Lock Elision
HRESET // If set CPU supports history reset and the IA32_HRESET_ENABLE MSR
HTT // Hyperthreading (enabled) HTT // Hyperthreading (enabled)
HWA // Hardware assert supported. Indicates support for MSRC001_10 HWA // Hardware assert supported. Indicates support for MSRC001_10
HYBRID_CPU // This part has CPUs of more than one type.
HYPERVISOR // This bit has been reserved by Intel & AMD for use by hypervisors HYPERVISOR // This bit has been reserved by Intel & AMD for use by hypervisors
IA32_ARCH_CAP // IA32_ARCH_CAPABILITIES MSR (Intel)
IA32_CORE_CAP // IA32_CORE_CAPABILITIES MSR
IBPB // Indirect Branch Restricted Speculation (IBRS) and Indirect Branch Predictor Barrier (IBPB) IBPB // Indirect Branch Restricted Speculation (IBRS) and Indirect Branch Predictor Barrier (IBPB)
IBS // Instruction Based Sampling (AMD) IBS // Instruction Based Sampling (AMD)
IBSBRNTRGT // Instruction Based Sampling Feature (AMD) IBSBRNTRGT // Instruction Based Sampling Feature (AMD)
@ -126,21 +135,30 @@ const (
IBSOPSAM // Instruction Based Sampling Feature (AMD) IBSOPSAM // Instruction Based Sampling Feature (AMD)
IBSRDWROPCNT // Instruction Based Sampling Feature (AMD) IBSRDWROPCNT // Instruction Based Sampling Feature (AMD)
IBSRIPINVALIDCHK // Instruction Based Sampling Feature (AMD) IBSRIPINVALIDCHK // Instruction Based Sampling Feature (AMD)
IBS_PREVENTHOST // Disallowing IBS use by the host supported
INT_WBINVD // WBINVD/WBNOINVD are interruptible. INT_WBINVD // WBINVD/WBNOINVD are interruptible.
INVLPGB // NVLPGB and TLBSYNC instruction supported INVLPGB // NVLPGB and TLBSYNC instruction supported
LAHF // LAHF/SAHF in long mode LAHF // LAHF/SAHF in long mode
LAM // If set, CPU supports Linear Address Masking
LBRVIRT // LBR virtualization
LZCNT // LZCNT instruction LZCNT // LZCNT instruction
MCAOVERFLOW // MCA overflow recovery support. MCAOVERFLOW // MCA overflow recovery support.
MCDT_NO // Processor do not exhibit MXCSR Configuration Dependent Timing behavior and do not need to mitigate it.
MCOMMIT // MCOMMIT instruction supported MCOMMIT // MCOMMIT instruction supported
MD_CLEAR // VERW clears CPU buffers
MMX // standard MMX MMX // standard MMX
MMXEXT // SSE integer functions or AMD MMX ext MMXEXT // SSE integer functions or AMD MMX ext
MOVBE // MOVBE instruction (big-endian) MOVBE // MOVBE instruction (big-endian)
MOVDIR64B // Move 64 Bytes as Direct Store MOVDIR64B // Move 64 Bytes as Direct Store
MOVDIRI // Move Doubleword as Direct Store MOVDIRI // Move Doubleword as Direct Store
MOVSB_ZL // Fast Zero-Length MOVSB
MPX // Intel MPX (Memory Protection Extensions) MPX // Intel MPX (Memory Protection Extensions)
MSRIRC // Instruction Retired Counter MSR available MSRIRC // Instruction Retired Counter MSR available
MSR_PAGEFLUSH // Page Flush MSR available
NRIPS // Indicates support for NRIP save on VMEXIT
NX // NX (No-Execute) bit NX // NX (No-Execute) bit
OSXSAVE // XSAVE enabled by OS OSXSAVE // XSAVE enabled by OS
PCONFIG // PCONFIG for Intel Multi-Key Total Memory Encryption
POPCNT // POPCNT instruction POPCNT // POPCNT instruction
RDPRU // RDPRU instruction supported RDPRU // RDPRU instruction supported
RDRAND // RDRAND instruction is available RDRAND // RDRAND instruction is available
@ -148,11 +166,21 @@ const (
RDTSCP // RDTSCP Instruction RDTSCP // RDTSCP Instruction
RTM // Restricted Transactional Memory RTM // Restricted Transactional Memory
RTM_ALWAYS_ABORT // Indicates that the loaded microcode is forcing RTM abort. RTM_ALWAYS_ABORT // Indicates that the loaded microcode is forcing RTM abort.
SCE // SYSENTER and SYSEXIT instructions
SERIALIZE // Serialize Instruction Execution SERIALIZE // Serialize Instruction Execution
SEV // AMD Secure Encrypted Virtualization supported
SEV_64BIT // AMD SEV guest execution only allowed from a 64-bit host
SEV_ALTERNATIVE // AMD SEV Alternate Injection supported
SEV_DEBUGSWAP // Full debug state swap supported for SEV-ES guests
SEV_ES // AMD SEV Encrypted State supported
SEV_RESTRICTED // AMD SEV Restricted Injection supported
SEV_SNP // AMD SEV Secure Nested Paging supported
SGX // Software Guard Extensions SGX // Software Guard Extensions
SGXLC // Software Guard Extensions Launch Control SGXLC // Software Guard Extensions Launch Control
SHA // Intel SHA Extensions SHA // Intel SHA Extensions
SME // AMD Secure Memory Encryption supported
SME_COHERENT // AMD Hardware cache coherency across encryption domains enforced
SPEC_CTRL_SSBD // Speculative Store Bypass Disable
SRBDS_CTRL // SRBDS mitigation MSR available
SSE // SSE functions SSE // SSE functions
SSE2 // P4 SSE functions SSE2 // P4 SSE functions
SSE3 // Prescott SSE3 functions SSE3 // Prescott SSE3 functions
@ -161,17 +189,38 @@ const (
SSE4A // AMD Barcelona microarchitecture SSE4a instructions SSE4A // AMD Barcelona microarchitecture SSE4a instructions
SSSE3 // Conroe SSSE3 functions SSSE3 // Conroe SSSE3 functions
STIBP // Single Thread Indirect Branch Predictors STIBP // Single Thread Indirect Branch Predictors
STOSB_SHORT // Fast short STOSB
SUCCOR // Software uncorrectable error containment and recovery capability. SUCCOR // Software uncorrectable error containment and recovery capability.
SVM // AMD Secure Virtual Machine
SVMDA // Indicates support for the SVM decode assists.
SVMFBASID // SVM, Indicates that TLB flush events, including CR3 writes and CR4.PGE toggles, flush only the current ASID's TLB entries. Also indicates support for the extended VMCBTLB_Control
SVML // AMD SVM lock. Indicates support for SVM-Lock.
SVMNP // AMD SVM nested paging
SVMPF // SVM pause intercept filter. Indicates support for the pause intercept filter
SVMPFT // SVM PAUSE filter threshold. Indicates support for the PAUSE filter cycle count threshold
SYSCALL // System-Call Extension (SCE): SYSCALL and SYSRET instructions.
SYSEE // SYSENTER and SYSEXIT instructions
TBM // AMD Trailing Bit Manipulation TBM // AMD Trailing Bit Manipulation
TME // Intel Total Memory Encryption. The following MSRs are supported: IA32_TME_CAPABILITY, IA32_TME_ACTIVATE, IA32_TME_EXCLUDE_MASK, and IA32_TME_EXCLUDE_BASE.
TOPEXT // TopologyExtensions: topology extensions support. Indicates support for CPUID Fn8000_001D_EAX_x[N:0]-CPUID Fn8000_001E_EDX.
TSCRATEMSR // MSR based TSC rate control. Indicates support for MSR TSC ratio MSRC000_0104
TSXLDTRK // Intel TSX Suspend Load Address Tracking TSXLDTRK // Intel TSX Suspend Load Address Tracking
VAES // Vector AES VAES // Vector AES. AVX(512) versions requires additional checks.
VMCBCLEAN // VMCB clean bits. Indicates support for VMCB clean bits.
VMPL // AMD VM Permission Levels supported
VMSA_REGPROT // AMD VMSA Register Protection supported
VMX // Virtual Machine Extensions VMX // Virtual Machine Extensions
VPCLMULQDQ // Carry-Less Multiplication Quadword VPCLMULQDQ // Carry-Less Multiplication Quadword. Requires AVX for 3 register versions.
VTE // AMD Virtual Transparent Encryption supported
WAITPKG // TPAUSE, UMONITOR, UMWAIT WAITPKG // TPAUSE, UMONITOR, UMWAIT
WBNOINVD // Write Back and Do Not Invalidate Cache WBNOINVD // Write Back and Do Not Invalidate Cache
X87 // FPU X87 // FPU
XGETBV1 // Supports XGETBV with ECX = 1
XOP // Bulldozer XOP functions XOP // Bulldozer XOP functions
XSAVE // XSAVE, XRESTOR, XSETBV, XGETBV XSAVE // XSAVE, XRESTOR, XSETBV, XGETBV
XSAVEC // Supports XSAVEC and the compacted form of XRSTOR.
XSAVEOPT // XSAVEOPT available
XSAVES // Supports XSAVES/XRSTORS and IA32_XSS
// ARM features: // ARM features:
AESARM // AES instructions AESARM // AES instructions
@ -198,7 +247,6 @@ const (
SM3 // SM3 instructions SM3 // SM3 instructions
SM4 // SM4 instructions SM4 // SM4 instructions
SVE // Scalable Vector Extension SVE // Scalable Vector Extension
// Keep it last. It automatically defines the size of []flagSet // Keep it last. It automatically defines the size of []flagSet
lastID lastID
@ -216,6 +264,7 @@ type CPUInfo struct {
LogicalCores int // Number of physical cores times threads that can run on each core through the use of hyperthreading. Will be 0 if undetectable. LogicalCores int // Number of physical cores times threads that can run on each core through the use of hyperthreading. Will be 0 if undetectable.
Family int // CPU family number Family int // CPU family number
Model int // CPU model number Model int // CPU model number
Stepping int // CPU stepping info
CacheLine int // Cache line size in bytes. Will be 0 if undetectable. CacheLine int // Cache line size in bytes. Will be 0 if undetectable.
Hz int64 // Clock speed, if known, 0 otherwise. Will attempt to contain base clock speed. Hz int64 // Clock speed, if known, 0 otherwise. Will attempt to contain base clock speed.
BoostFreq int64 // Max clock speed, if known, 0 otherwise BoostFreq int64 // Max clock speed, if known, 0 otherwise
@ -322,11 +371,21 @@ func (c CPUInfo) Has(id FeatureID) bool {
return c.featureSet.inSet(id) return c.featureSet.inSet(id)
} }
// AnyOf returns whether the CPU supports one or more of the requested features.
func (c CPUInfo) AnyOf(ids ...FeatureID) bool {
for _, id := range ids {
if c.featureSet.inSet(id) {
return true
}
}
return false
}
// https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels // https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels
var level1Features = flagSetWith(CMOV, CMPXCHG8, X87, FXSR, MMX, SCE, SSE, SSE2) var level1Features = flagSetWith(CMOV, CMPXCHG8, X87, FXSR, MMX, SYSCALL, SSE, SSE2)
var level2Features = flagSetWith(CMOV, CMPXCHG8, X87, FXSR, MMX, SCE, SSE, SSE2, CX16, LAHF, POPCNT, SSE3, SSE4, SSE42, SSSE3) var level2Features = flagSetWith(CMOV, CMPXCHG8, X87, FXSR, MMX, SYSCALL, SSE, SSE2, CX16, LAHF, POPCNT, SSE3, SSE4, SSE42, SSSE3)
var level3Features = flagSetWith(CMOV, CMPXCHG8, X87, FXSR, MMX, SCE, SSE, SSE2, CX16, LAHF, POPCNT, SSE3, SSE4, SSE42, SSSE3, AVX, AVX2, BMI1, BMI2, F16C, FMA3, LZCNT, MOVBE, OSXSAVE) var level3Features = flagSetWith(CMOV, CMPXCHG8, X87, FXSR, MMX, SYSCALL, SSE, SSE2, CX16, LAHF, POPCNT, SSE3, SSE4, SSE42, SSSE3, AVX, AVX2, BMI1, BMI2, F16C, FMA3, LZCNT, MOVBE, OSXSAVE)
var level4Features = flagSetWith(CMOV, CMPXCHG8, X87, FXSR, MMX, SCE, SSE, SSE2, CX16, LAHF, POPCNT, SSE3, SSE4, SSE42, SSSE3, AVX, AVX2, BMI1, BMI2, F16C, FMA3, LZCNT, MOVBE, OSXSAVE, AVX512F, AVX512BW, AVX512CD, AVX512DQ, AVX512VL) var level4Features = flagSetWith(CMOV, CMPXCHG8, X87, FXSR, MMX, SYSCALL, SSE, SSE2, CX16, LAHF, POPCNT, SSE3, SSE4, SSE42, SSSE3, AVX, AVX2, BMI1, BMI2, F16C, FMA3, LZCNT, MOVBE, OSXSAVE, AVX512F, AVX512BW, AVX512CD, AVX512DQ, AVX512VL)
// X64Level returns the microarchitecture level detected on the CPU. // X64Level returns the microarchitecture level detected on the CPU.
// If features are lacking or non x64 mode, 0 is returned. // If features are lacking or non x64 mode, 0 is returned.
@ -369,8 +428,9 @@ func (c CPUInfo) IsVendor(v Vendor) bool {
return c.VendorID == v return c.VendorID == v
} }
// FeatureSet returns all available features as strings.
func (c CPUInfo) FeatureSet() []string { func (c CPUInfo) FeatureSet() []string {
s := make([]string, 0) s := make([]string, 0, c.featureSet.nEnabled())
s = append(s, c.featureSet.Strings()...) s = append(s, c.featureSet.Strings()...)
return s return s
} }
@ -543,6 +603,14 @@ func (s flagSet) hasSet(other flagSet) bool {
return true return true
} }
// nEnabled will return the number of enabled flags.
func (s flagSet) nEnabled() (n int) {
for _, v := range s[:] {
n += bits.OnesCount64(uint64(v))
}
return n
}
func flagSetWith(feat ...FeatureID) flagSet { func flagSetWith(feat ...FeatureID) flagSet {
var res flagSet var res flagSet
for _, f := range feat { for _, f := range feat {
@ -631,7 +699,7 @@ func threadsPerCore() int {
if vend == AMD { if vend == AMD {
// Workaround for AMD returning 0, assume 2 if >= Zen 2 // Workaround for AMD returning 0, assume 2 if >= Zen 2
// It will be more correct than not. // It will be more correct than not.
fam, _ := familyModel() fam, _, _ := familyModel()
_, _, _, d := cpuid(1) _, _, _, d := cpuid(1)
if (d&(1<<28)) != 0 && fam >= 23 { if (d&(1<<28)) != 0 && fam >= 23 {
return 2 return 2
@ -669,14 +737,27 @@ func logicalCores() int {
} }
} }
func familyModel() (int, int) { func familyModel() (family, model, stepping int) {
if maxFunctionID() < 0x1 { if maxFunctionID() < 0x1 {
return 0, 0 return 0, 0, 0
} }
eax, _, _, _ := cpuid(1) eax, _, _, _ := cpuid(1)
family := ((eax >> 8) & 0xf) + ((eax >> 20) & 0xff) // If BaseFamily[3:0] is less than Fh then ExtendedFamily[7:0] is reserved and Family is equal to BaseFamily[3:0].
model := ((eax >> 4) & 0xf) + ((eax >> 12) & 0xf0) family = int((eax >> 8) & 0xf)
return int(family), int(model) extFam := family == 0x6 // Intel is 0x6, needs extended model.
if family == 0xf {
// Add ExtFamily
family += int((eax >> 20) & 0xff)
extFam = true
}
// If BaseFamily[3:0] is less than 0Fh then ExtendedModel[3:0] is reserved and Model is equal to BaseModel[3:0].
model = int((eax >> 4) & 0xf)
if extFam {
// Add ExtModel
model += int((eax >> 12) & 0xf0)
}
stepping = int(eax & 0xf)
return family, model, stepping
} }
func physicalCores() int { func physicalCores() int {
@ -811,9 +892,14 @@ func (c *CPUInfo) cacheSize() {
c.Cache.L2 = int(((ecx >> 16) & 0xFFFF) * 1024) c.Cache.L2 = int(((ecx >> 16) & 0xFFFF) * 1024)
// CPUID Fn8000_001D_EAX_x[N:0] Cache Properties // CPUID Fn8000_001D_EAX_x[N:0] Cache Properties
if maxExtendedFunction() < 0x8000001D { if maxExtendedFunction() < 0x8000001D || !c.Has(TOPEXT) {
return return
} }
// Xen Hypervisor is buggy and returns the same entry no matter ECX value.
// Hack: When we encounter the same entry 100 times we break.
nSame := 0
var last uint32
for i := uint32(0); i < math.MaxUint32; i++ { for i := uint32(0); i < math.MaxUint32; i++ {
eax, ebx, ecx, _ := cpuidex(0x8000001D, i) eax, ebx, ecx, _ := cpuidex(0x8000001D, i)
@ -829,6 +915,16 @@ func (c *CPUInfo) cacheSize() {
return return
} }
// Check for the same value repeated.
comb := eax ^ ebx ^ ecx
if comb == last {
nSame++
if nSame == 100 {
return
}
}
last = comb
switch level { switch level {
case 1: case 1:
switch typ { switch typ {
@ -913,14 +1009,13 @@ func support() flagSet {
if mfi < 0x1 { if mfi < 0x1 {
return fs return fs
} }
family, model := familyModel() family, model, _ := familyModel()
_, _, c, d := cpuid(1) _, _, c, d := cpuid(1)
fs.setIf((d&(1<<0)) != 0, X87) fs.setIf((d&(1<<0)) != 0, X87)
fs.setIf((d&(1<<8)) != 0, CMPXCHG8) fs.setIf((d&(1<<8)) != 0, CMPXCHG8)
fs.setIf((d&(1<<11)) != 0, SCE) fs.setIf((d&(1<<11)) != 0, SYSEE)
fs.setIf((d&(1<<15)) != 0, CMOV) fs.setIf((d&(1<<15)) != 0, CMOV)
fs.setIf((d&(1<<22)) != 0, MMXEXT)
fs.setIf((d&(1<<23)) != 0, MMX) fs.setIf((d&(1<<23)) != 0, MMX)
fs.setIf((d&(1<<24)) != 0, FXSR) fs.setIf((d&(1<<24)) != 0, FXSR)
fs.setIf((d&(1<<25)) != 0, FXSROPT) fs.setIf((d&(1<<25)) != 0, FXSROPT)
@ -928,9 +1023,9 @@ func support() flagSet {
fs.setIf((d&(1<<26)) != 0, SSE2) fs.setIf((d&(1<<26)) != 0, SSE2)
fs.setIf((c&1) != 0, SSE3) fs.setIf((c&1) != 0, SSE3)
fs.setIf((c&(1<<5)) != 0, VMX) fs.setIf((c&(1<<5)) != 0, VMX)
fs.setIf((c&0x00000200) != 0, SSSE3) fs.setIf((c&(1<<9)) != 0, SSSE3)
fs.setIf((c&0x00080000) != 0, SSE4) fs.setIf((c&(1<<19)) != 0, SSE4)
fs.setIf((c&0x00100000) != 0, SSE42) fs.setIf((c&(1<<20)) != 0, SSE42)
fs.setIf((c&(1<<25)) != 0, AESNI) fs.setIf((c&(1<<25)) != 0, AESNI)
fs.setIf((c&(1<<1)) != 0, CLMUL) fs.setIf((c&(1<<1)) != 0, CLMUL)
fs.setIf(c&(1<<22) != 0, MOVBE) fs.setIf(c&(1<<22) != 0, MOVBE)
@ -976,7 +1071,6 @@ func support() flagSet {
// Check AVX2, AVX2 requires OS support, but BMI1/2 don't. // Check AVX2, AVX2 requires OS support, but BMI1/2 don't.
if mfi >= 7 { if mfi >= 7 {
_, ebx, ecx, edx := cpuidex(7, 0) _, ebx, ecx, edx := cpuidex(7, 0)
eax1, _, _, _ := cpuidex(7, 1)
if fs.inSet(AVX) && (ebx&0x00000020) != 0 { if fs.inSet(AVX) && (ebx&0x00000020) != 0 {
fs.set(AVX2) fs.set(AVX2)
} }
@ -993,21 +1087,45 @@ func support() flagSet {
fs.setIf(ebx&(1<<18) != 0, RDSEED) fs.setIf(ebx&(1<<18) != 0, RDSEED)
fs.setIf(ebx&(1<<19) != 0, ADX) fs.setIf(ebx&(1<<19) != 0, ADX)
fs.setIf(ebx&(1<<29) != 0, SHA) fs.setIf(ebx&(1<<29) != 0, SHA)
// CPUID.(EAX=7, ECX=0).ECX // CPUID.(EAX=7, ECX=0).ECX
fs.setIf(ecx&(1<<5) != 0, WAITPKG) fs.setIf(ecx&(1<<5) != 0, WAITPKG)
fs.setIf(ecx&(1<<7) != 0, CETSS) fs.setIf(ecx&(1<<7) != 0, CETSS)
fs.setIf(ecx&(1<<8) != 0, GFNI)
fs.setIf(ecx&(1<<9) != 0, VAES)
fs.setIf(ecx&(1<<10) != 0, VPCLMULQDQ)
fs.setIf(ecx&(1<<13) != 0, TME)
fs.setIf(ecx&(1<<25) != 0, CLDEMOTE) fs.setIf(ecx&(1<<25) != 0, CLDEMOTE)
fs.setIf(ecx&(1<<27) != 0, MOVDIRI) fs.setIf(ecx&(1<<27) != 0, MOVDIRI)
fs.setIf(ecx&(1<<28) != 0, MOVDIR64B) fs.setIf(ecx&(1<<28) != 0, MOVDIR64B)
fs.setIf(ecx&(1<<29) != 0, ENQCMD) fs.setIf(ecx&(1<<29) != 0, ENQCMD)
fs.setIf(ecx&(1<<30) != 0, SGXLC) fs.setIf(ecx&(1<<30) != 0, SGXLC)
// CPUID.(EAX=7, ECX=0).EDX // CPUID.(EAX=7, ECX=0).EDX
fs.setIf(edx&(1<<4) != 0, FSRM)
fs.setIf(edx&(1<<9) != 0, SRBDS_CTRL)
fs.setIf(edx&(1<<10) != 0, MD_CLEAR)
fs.setIf(edx&(1<<11) != 0, RTM_ALWAYS_ABORT) fs.setIf(edx&(1<<11) != 0, RTM_ALWAYS_ABORT)
fs.setIf(edx&(1<<14) != 0, SERIALIZE) fs.setIf(edx&(1<<14) != 0, SERIALIZE)
fs.setIf(edx&(1<<15) != 0, HYBRID_CPU)
fs.setIf(edx&(1<<16) != 0, TSXLDTRK) fs.setIf(edx&(1<<16) != 0, TSXLDTRK)
fs.setIf(edx&(1<<18) != 0, PCONFIG)
fs.setIf(edx&(1<<20) != 0, CETIBT) fs.setIf(edx&(1<<20) != 0, CETIBT)
fs.setIf(edx&(1<<26) != 0, IBPB) fs.setIf(edx&(1<<26) != 0, IBPB)
fs.setIf(edx&(1<<27) != 0, STIBP) fs.setIf(edx&(1<<27) != 0, STIBP)
fs.setIf(edx&(1<<28) != 0, FLUSH_L1D)
fs.setIf(edx&(1<<29) != 0, IA32_ARCH_CAP)
fs.setIf(edx&(1<<30) != 0, IA32_CORE_CAP)
fs.setIf(edx&(1<<31) != 0, SPEC_CTRL_SSBD)
// CPUID.(EAX=7, ECX=1)
eax1, _, _, _ := cpuidex(7, 1)
fs.setIf(fs.inSet(AVX) && eax1&(1<<4) != 0, AVXVNNI)
fs.setIf(eax1&(1<<10) != 0, MOVSB_ZL)
fs.setIf(eax1&(1<<11) != 0, STOSB_SHORT)
fs.setIf(eax1&(1<<12) != 0, CMPSB_SCADBS_SHORT)
fs.setIf(eax1&(1<<22) != 0, HRESET)
fs.setIf(eax1&(1<<26) != 0, LAM)
// Only detect AVX-512 features if XGETBV is supported // Only detect AVX-512 features if XGETBV is supported
if c&((1<<26)|(1<<27)) == (1<<26)|(1<<27) { if c&((1<<26)|(1<<27)) == (1<<26)|(1<<27) {
@ -1033,9 +1151,6 @@ func support() flagSet {
// ecx // ecx
fs.setIf(ecx&(1<<1) != 0, AVX512VBMI) fs.setIf(ecx&(1<<1) != 0, AVX512VBMI)
fs.setIf(ecx&(1<<6) != 0, AVX512VBMI2) fs.setIf(ecx&(1<<6) != 0, AVX512VBMI2)
fs.setIf(ecx&(1<<8) != 0, GFNI)
fs.setIf(ecx&(1<<9) != 0, VAES)
fs.setIf(ecx&(1<<10) != 0, VPCLMULQDQ)
fs.setIf(ecx&(1<<11) != 0, AVX512VNNI) fs.setIf(ecx&(1<<11) != 0, AVX512VNNI)
fs.setIf(ecx&(1<<12) != 0, AVX512BITALG) fs.setIf(ecx&(1<<12) != 0, AVX512BITALG)
fs.setIf(ecx&(1<<14) != 0, AVX512VPOPCNTDQ) fs.setIf(ecx&(1<<14) != 0, AVX512VPOPCNTDQ)
@ -1049,29 +1164,63 @@ func support() flagSet {
fs.setIf(eax1&(1<<5) != 0, AVX512BF16) fs.setIf(eax1&(1<<5) != 0, AVX512BF16)
} }
} }
// CPUID.(EAX=7, ECX=2)
_, _, _, edx = cpuidex(7, 2)
fs.setIf(edx&(1<<5) != 0, MCDT_NO)
} }
// Processor Extended State Enumeration Sub-leaf (EAX = 0DH, ECX = 1)
// EAX
// Bit 00: XSAVEOPT is available.
// Bit 01: Supports XSAVEC and the compacted form of XRSTOR if set.
// Bit 02: Supports XGETBV with ECX = 1 if set.
// Bit 03: Supports XSAVES/XRSTORS and IA32_XSS if set.
// Bits 31 - 04: Reserved.
// EBX
// Bits 31 - 00: The size in bytes of the XSAVE area containing all states enabled by XCRO | IA32_XSS.
// ECX
// Bits 31 - 00: Reports the supported bits of the lower 32 bits of the IA32_XSS MSR. IA32_XSS[n] can be set to 1 only if ECX[n] is 1.
// EDX?
// Bits 07 - 00: Used for XCR0. Bit 08: PT state. Bit 09: Used for XCR0. Bits 12 - 10: Reserved. Bit 13: HWP state. Bits 31 - 14: Reserved.
if mfi >= 0xd {
if fs.inSet(XSAVE) {
eax, _, _, _ := cpuidex(0xd, 1)
fs.setIf(eax&(1<<0) != 0, XSAVEOPT)
fs.setIf(eax&(1<<1) != 0, XSAVEC)
fs.setIf(eax&(1<<2) != 0, XGETBV1)
fs.setIf(eax&(1<<3) != 0, XSAVES)
}
}
if maxExtendedFunction() >= 0x80000001 { if maxExtendedFunction() >= 0x80000001 {
_, _, c, d := cpuid(0x80000001) _, _, c, d := cpuid(0x80000001)
if (c & (1 << 5)) != 0 { if (c & (1 << 5)) != 0 {
fs.set(LZCNT) fs.set(LZCNT)
fs.set(POPCNT) fs.set(POPCNT)
} }
// ECX
fs.setIf((c&(1<<0)) != 0, LAHF) fs.setIf((c&(1<<0)) != 0, LAHF)
fs.setIf((c&(1<<10)) != 0, IBS) fs.setIf((c&(1<<2)) != 0, SVM)
fs.setIf((d&(1<<31)) != 0, AMD3DNOW)
fs.setIf((d&(1<<30)) != 0, AMD3DNOWEXT)
fs.setIf((d&(1<<23)) != 0, MMX)
fs.setIf((d&(1<<22)) != 0, MMXEXT)
fs.setIf((c&(1<<6)) != 0, SSE4A) fs.setIf((c&(1<<6)) != 0, SSE4A)
fs.setIf((c&(1<<10)) != 0, IBS)
fs.setIf((c&(1<<22)) != 0, TOPEXT)
// EDX
fs.setIf(d&(1<<11) != 0, SYSCALL)
fs.setIf(d&(1<<20) != 0, NX) fs.setIf(d&(1<<20) != 0, NX)
fs.setIf(d&(1<<22) != 0, MMXEXT)
fs.setIf(d&(1<<23) != 0, MMX)
fs.setIf(d&(1<<24) != 0, FXSR)
fs.setIf(d&(1<<25) != 0, FXSROPT)
fs.setIf(d&(1<<27) != 0, RDTSCP) fs.setIf(d&(1<<27) != 0, RDTSCP)
fs.setIf(d&(1<<30) != 0, AMD3DNOWEXT)
fs.setIf(d&(1<<31) != 0, AMD3DNOW)
/* XOP and FMA4 use the AVX instruction coding scheme, so they can't be /* XOP and FMA4 use the AVX instruction coding scheme, so they can't be
* used unless the OS has AVX support. */ * used unless the OS has AVX support. */
if fs.inSet(AVX) { if fs.inSet(AVX) {
fs.setIf((c&0x00000800) != 0, XOP) fs.setIf((c&(1<<11)) != 0, XOP)
fs.setIf((c&0x00010000) != 0, FMA4) fs.setIf((c&(1<<16)) != 0, FMA4)
} }
} }
@ -1094,6 +1243,20 @@ func support() flagSet {
fs.setIf((b&(1<<0)) != 0, CLZERO) fs.setIf((b&(1<<0)) != 0, CLZERO)
} }
if fs.inSet(SVM) && maxExtendedFunction() >= 0x8000000A {
_, _, _, edx := cpuid(0x8000000A)
fs.setIf((edx>>0)&1 == 1, SVMNP)
fs.setIf((edx>>1)&1 == 1, LBRVIRT)
fs.setIf((edx>>2)&1 == 1, SVML)
fs.setIf((edx>>3)&1 == 1, NRIPS)
fs.setIf((edx>>4)&1 == 1, TSCRATEMSR)
fs.setIf((edx>>5)&1 == 1, VMCBCLEAN)
fs.setIf((edx>>6)&1 == 1, SVMFBASID)
fs.setIf((edx>>7)&1 == 1, SVMDA)
fs.setIf((edx>>10)&1 == 1, SVMPF)
fs.setIf((edx>>12)&1 == 1, SVMPFT)
}
if maxExtendedFunction() >= 0x8000001b && fs.inSet(IBS) { if maxExtendedFunction() >= 0x8000001b && fs.inSet(IBS) {
eax, _, _, _ := cpuid(0x8000001b) eax, _, _, _ := cpuid(0x8000001b)
fs.setIf((eax>>0)&1 == 1, IBSFFV) fs.setIf((eax>>0)&1 == 1, IBSFFV)
@ -1106,6 +1269,24 @@ func support() flagSet {
fs.setIf((eax>>7)&1 == 1, IBSRIPINVALIDCHK) fs.setIf((eax>>7)&1 == 1, IBSRIPINVALIDCHK)
} }
if maxExtendedFunction() >= 0x8000001f && vend == AMD {
a, _, _, _ := cpuid(0x8000001f)
fs.setIf((a>>0)&1 == 1, SME)
fs.setIf((a>>1)&1 == 1, SEV)
fs.setIf((a>>2)&1 == 1, MSR_PAGEFLUSH)
fs.setIf((a>>3)&1 == 1, SEV_ES)
fs.setIf((a>>4)&1 == 1, SEV_SNP)
fs.setIf((a>>5)&1 == 1, VMPL)
fs.setIf((a>>10)&1 == 1, SME_COHERENT)
fs.setIf((a>>11)&1 == 1, SEV_64BIT)
fs.setIf((a>>12)&1 == 1, SEV_RESTRICTED)
fs.setIf((a>>13)&1 == 1, SEV_ALTERNATIVE)
fs.setIf((a>>14)&1 == 1, SEV_DEBUGSWAP)
fs.setIf((a>>15)&1 == 1, IBS_PREVENTHOST)
fs.setIf((a>>16)&1 == 1, VTE)
fs.setIf((a>>24)&1 == 1, VMSA_REGPROT)
}
return fs return fs
} }

View file

@ -24,7 +24,7 @@ func addInfo(c *CPUInfo, safe bool) {
c.maxExFunc = maxExtendedFunction() c.maxExFunc = maxExtendedFunction()
c.BrandName = brandName() c.BrandName = brandName()
c.CacheLine = cacheLine() c.CacheLine = cacheLine()
c.Family, c.Model = familyModel() c.Family, c.Model, c.Stepping = familyModel()
c.featureSet = support() c.featureSet = support()
c.SGX = hasSGX(c.featureSet.inSet(SGX), c.featureSet.inSet(SGXLC)) c.SGX = hasSGX(c.featureSet.inSet(SGX), c.featureSet.inSet(SGXLC))
c.ThreadsPerCore = threadsPerCore() c.ThreadsPerCore = threadsPerCore()

View file

@ -34,116 +34,164 @@ func _() {
_ = x[AVX512VP2INTERSECT-24] _ = x[AVX512VP2INTERSECT-24]
_ = x[AVX512VPOPCNTDQ-25] _ = x[AVX512VPOPCNTDQ-25]
_ = x[AVXSLOW-26] _ = x[AVXSLOW-26]
_ = x[BMI1-27] _ = x[AVXVNNI-27]
_ = x[BMI2-28] _ = x[BMI1-28]
_ = x[CETIBT-29] _ = x[BMI2-29]
_ = x[CETSS-30] _ = x[CETIBT-30]
_ = x[CLDEMOTE-31] _ = x[CETSS-31]
_ = x[CLMUL-32] _ = x[CLDEMOTE-32]
_ = x[CLZERO-33] _ = x[CLMUL-33]
_ = x[CMOV-34] _ = x[CLZERO-34]
_ = x[CMPXCHG8-35] _ = x[CMOV-35]
_ = x[CPBOOST-36] _ = x[CMPSB_SCADBS_SHORT-36]
_ = x[CX16-37] _ = x[CMPXCHG8-37]
_ = x[ENQCMD-38] _ = x[CPBOOST-38]
_ = x[ERMS-39] _ = x[CX16-39]
_ = x[F16C-40] _ = x[ENQCMD-40]
_ = x[FMA3-41] _ = x[ERMS-41]
_ = x[FMA4-42] _ = x[F16C-42]
_ = x[FXSR-43] _ = x[FLUSH_L1D-43]
_ = x[FXSROPT-44] _ = x[FMA3-44]
_ = x[GFNI-45] _ = x[FMA4-45]
_ = x[HLE-46] _ = x[FSRM-46]
_ = x[HTT-47] _ = x[FXSR-47]
_ = x[HWA-48] _ = x[FXSROPT-48]
_ = x[HYPERVISOR-49] _ = x[GFNI-49]
_ = x[IBPB-50] _ = x[HLE-50]
_ = x[IBS-51] _ = x[HRESET-51]
_ = x[IBSBRNTRGT-52] _ = x[HTT-52]
_ = x[IBSFETCHSAM-53] _ = x[HWA-53]
_ = x[IBSFFV-54] _ = x[HYBRID_CPU-54]
_ = x[IBSOPCNT-55] _ = x[HYPERVISOR-55]
_ = x[IBSOPCNTEXT-56] _ = x[IA32_ARCH_CAP-56]
_ = x[IBSOPSAM-57] _ = x[IA32_CORE_CAP-57]
_ = x[IBSRDWROPCNT-58] _ = x[IBPB-58]
_ = x[IBSRIPINVALIDCHK-59] _ = x[IBS-59]
_ = x[INT_WBINVD-60] _ = x[IBSBRNTRGT-60]
_ = x[INVLPGB-61] _ = x[IBSFETCHSAM-61]
_ = x[LAHF-62] _ = x[IBSFFV-62]
_ = x[LZCNT-63] _ = x[IBSOPCNT-63]
_ = x[MCAOVERFLOW-64] _ = x[IBSOPCNTEXT-64]
_ = x[MCOMMIT-65] _ = x[IBSOPSAM-65]
_ = x[MMX-66] _ = x[IBSRDWROPCNT-66]
_ = x[MMXEXT-67] _ = x[IBSRIPINVALIDCHK-67]
_ = x[MOVBE-68] _ = x[IBS_PREVENTHOST-68]
_ = x[MOVDIR64B-69] _ = x[INT_WBINVD-69]
_ = x[MOVDIRI-70] _ = x[INVLPGB-70]
_ = x[MPX-71] _ = x[LAHF-71]
_ = x[MSRIRC-72] _ = x[LAM-72]
_ = x[NX-73] _ = x[LBRVIRT-73]
_ = x[OSXSAVE-74] _ = x[LZCNT-74]
_ = x[POPCNT-75] _ = x[MCAOVERFLOW-75]
_ = x[RDPRU-76] _ = x[MCDT_NO-76]
_ = x[RDRAND-77] _ = x[MCOMMIT-77]
_ = x[RDSEED-78] _ = x[MD_CLEAR-78]
_ = x[RDTSCP-79] _ = x[MMX-79]
_ = x[RTM-80] _ = x[MMXEXT-80]
_ = x[RTM_ALWAYS_ABORT-81] _ = x[MOVBE-81]
_ = x[SCE-82] _ = x[MOVDIR64B-82]
_ = x[SERIALIZE-83] _ = x[MOVDIRI-83]
_ = x[SGX-84] _ = x[MOVSB_ZL-84]
_ = x[SGXLC-85] _ = x[MPX-85]
_ = x[SHA-86] _ = x[MSRIRC-86]
_ = x[SSE-87] _ = x[MSR_PAGEFLUSH-87]
_ = x[SSE2-88] _ = x[NRIPS-88]
_ = x[SSE3-89] _ = x[NX-89]
_ = x[SSE4-90] _ = x[OSXSAVE-90]
_ = x[SSE42-91] _ = x[PCONFIG-91]
_ = x[SSE4A-92] _ = x[POPCNT-92]
_ = x[SSSE3-93] _ = x[RDPRU-93]
_ = x[STIBP-94] _ = x[RDRAND-94]
_ = x[SUCCOR-95] _ = x[RDSEED-95]
_ = x[TBM-96] _ = x[RDTSCP-96]
_ = x[TSXLDTRK-97] _ = x[RTM-97]
_ = x[VAES-98] _ = x[RTM_ALWAYS_ABORT-98]
_ = x[VMX-99] _ = x[SERIALIZE-99]
_ = x[VPCLMULQDQ-100] _ = x[SEV-100]
_ = x[WAITPKG-101] _ = x[SEV_64BIT-101]
_ = x[WBNOINVD-102] _ = x[SEV_ALTERNATIVE-102]
_ = x[X87-103] _ = x[SEV_DEBUGSWAP-103]
_ = x[XOP-104] _ = x[SEV_ES-104]
_ = x[XSAVE-105] _ = x[SEV_RESTRICTED-105]
_ = x[AESARM-106] _ = x[SEV_SNP-106]
_ = x[ARMCPUID-107] _ = x[SGX-107]
_ = x[ASIMD-108] _ = x[SGXLC-108]
_ = x[ASIMDDP-109] _ = x[SHA-109]
_ = x[ASIMDHP-110] _ = x[SME-110]
_ = x[ASIMDRDM-111] _ = x[SME_COHERENT-111]
_ = x[ATOMICS-112] _ = x[SPEC_CTRL_SSBD-112]
_ = x[CRC32-113] _ = x[SRBDS_CTRL-113]
_ = x[DCPOP-114] _ = x[SSE-114]
_ = x[EVTSTRM-115] _ = x[SSE2-115]
_ = x[FCMA-116] _ = x[SSE3-116]
_ = x[FP-117] _ = x[SSE4-117]
_ = x[FPHP-118] _ = x[SSE42-118]
_ = x[GPA-119] _ = x[SSE4A-119]
_ = x[JSCVT-120] _ = x[SSSE3-120]
_ = x[LRCPC-121] _ = x[STIBP-121]
_ = x[PMULL-122] _ = x[STOSB_SHORT-122]
_ = x[SHA1-123] _ = x[SUCCOR-123]
_ = x[SHA2-124] _ = x[SVM-124]
_ = x[SHA3-125] _ = x[SVMDA-125]
_ = x[SHA512-126] _ = x[SVMFBASID-126]
_ = x[SM3-127] _ = x[SVML-127]
_ = x[SM4-128] _ = x[SVMNP-128]
_ = x[SVE-129] _ = x[SVMPF-129]
_ = x[lastID-130] _ = x[SVMPFT-130]
_ = x[SYSCALL-131]
_ = x[SYSEE-132]
_ = x[TBM-133]
_ = x[TME-134]
_ = x[TOPEXT-135]
_ = x[TSCRATEMSR-136]
_ = x[TSXLDTRK-137]
_ = x[VAES-138]
_ = x[VMCBCLEAN-139]
_ = x[VMPL-140]
_ = x[VMSA_REGPROT-141]
_ = x[VMX-142]
_ = x[VPCLMULQDQ-143]
_ = x[VTE-144]
_ = x[WAITPKG-145]
_ = x[WBNOINVD-146]
_ = x[X87-147]
_ = x[XGETBV1-148]
_ = x[XOP-149]
_ = x[XSAVE-150]
_ = x[XSAVEC-151]
_ = x[XSAVEOPT-152]
_ = x[XSAVES-153]
_ = x[AESARM-154]
_ = x[ARMCPUID-155]
_ = x[ASIMD-156]
_ = x[ASIMDDP-157]
_ = x[ASIMDHP-158]
_ = x[ASIMDRDM-159]
_ = x[ATOMICS-160]
_ = x[CRC32-161]
_ = x[DCPOP-162]
_ = x[EVTSTRM-163]
_ = x[FCMA-164]
_ = x[FP-165]
_ = x[FPHP-166]
_ = x[GPA-167]
_ = x[JSCVT-168]
_ = x[LRCPC-169]
_ = x[PMULL-170]
_ = x[SHA1-171]
_ = x[SHA2-172]
_ = x[SHA3-173]
_ = x[SHA512-174]
_ = x[SM3-175]
_ = x[SM4-176]
_ = x[SVE-177]
_ = x[lastID-178]
_ = x[firstID-0] _ = x[firstID-0]
} }
const _FeatureID_name = "firstIDADXAESNIAMD3DNOWAMD3DNOWEXTAMXBF16AMXINT8AMXTILEAVXAVX2AVX512BF16AVX512BITALGAVX512BWAVX512CDAVX512DQAVX512ERAVX512FAVX512FP16AVX512IFMAAVX512PFAVX512VBMIAVX512VBMI2AVX512VLAVX512VNNIAVX512VP2INTERSECTAVX512VPOPCNTDQAVXSLOWBMI1BMI2CETIBTCETSSCLDEMOTECLMULCLZEROCMOVCMPXCHG8CPBOOSTCX16ENQCMDERMSF16CFMA3FMA4FXSRFXSROPTGFNIHLEHTTHWAHYPERVISORIBPBIBSIBSBRNTRGTIBSFETCHSAMIBSFFVIBSOPCNTIBSOPCNTEXTIBSOPSAMIBSRDWROPCNTIBSRIPINVALIDCHKINT_WBINVDINVLPGBLAHFLZCNTMCAOVERFLOWMCOMMITMMXMMXEXTMOVBEMOVDIR64BMOVDIRIMPXMSRIRCNXOSXSAVEPOPCNTRDPRURDRANDRDSEEDRDTSCPRTMRTM_ALWAYS_ABORTSCESERIALIZESGXSGXLCSHASSESSE2SSE3SSE4SSE42SSE4ASSSE3STIBPSUCCORTBMTSXLDTRKVAESVMXVPCLMULQDQWAITPKGWBNOINVDX87XOPXSAVEAESARMARMCPUIDASIMDASIMDDPASIMDHPASIMDRDMATOMICSCRC32DCPOPEVTSTRMFCMAFPFPHPGPAJSCVTLRCPCPMULLSHA1SHA2SHA3SHA512SM3SM4SVElastID" const _FeatureID_name = "firstIDADXAESNIAMD3DNOWAMD3DNOWEXTAMXBF16AMXINT8AMXTILEAVXAVX2AVX512BF16AVX512BITALGAVX512BWAVX512CDAVX512DQAVX512ERAVX512FAVX512FP16AVX512IFMAAVX512PFAVX512VBMIAVX512VBMI2AVX512VLAVX512VNNIAVX512VP2INTERSECTAVX512VPOPCNTDQAVXSLOWAVXVNNIBMI1BMI2CETIBTCETSSCLDEMOTECLMULCLZEROCMOVCMPSB_SCADBS_SHORTCMPXCHG8CPBOOSTCX16ENQCMDERMSF16CFLUSH_L1DFMA3FMA4FSRMFXSRFXSROPTGFNIHLEHRESETHTTHWAHYBRID_CPUHYPERVISORIA32_ARCH_CAPIA32_CORE_CAPIBPBIBSIBSBRNTRGTIBSFETCHSAMIBSFFVIBSOPCNTIBSOPCNTEXTIBSOPSAMIBSRDWROPCNTIBSRIPINVALIDCHKIBS_PREVENTHOSTINT_WBINVDINVLPGBLAHFLAMLBRVIRTLZCNTMCAOVERFLOWMCDT_NOMCOMMITMD_CLEARMMXMMXEXTMOVBEMOVDIR64BMOVDIRIMOVSB_ZLMPXMSRIRCMSR_PAGEFLUSHNRIPSNXOSXSAVEPCONFIGPOPCNTRDPRURDRANDRDSEEDRDTSCPRTMRTM_ALWAYS_ABORTSERIALIZESEVSEV_64BITSEV_ALTERNATIVESEV_DEBUGSWAPSEV_ESSEV_RESTRICTEDSEV_SNPSGXSGXLCSHASMESME_COHERENTSPEC_CTRL_SSBDSRBDS_CTRLSSESSE2SSE3SSE4SSE42SSE4ASSSE3STIBPSTOSB_SHORTSUCCORSVMSVMDASVMFBASIDSVMLSVMNPSVMPFSVMPFTSYSCALLSYSEETBMTMETOPEXTTSCRATEMSRTSXLDTRKVAESVMCBCLEANVMPLVMSA_REGPROTVMXVPCLMULQDQVTEWAITPKGWBNOINVDX87XGETBV1XOPXSAVEXSAVECXSAVEOPTXSAVESAESARMARMCPUIDASIMDASIMDDPASIMDHPASIMDRDMATOMICSCRC32DCPOPEVTSTRMFCMAFPFPHPGPAJSCVTLRCPCPMULLSHA1SHA2SHA3SHA512SM3SM4SVElastID"
var _FeatureID_index = [...]uint16{0, 7, 10, 15, 23, 34, 41, 48, 55, 58, 62, 72, 84, 92, 100, 108, 116, 123, 133, 143, 151, 161, 172, 180, 190, 208, 223, 230, 234, 238, 244, 249, 257, 262, 268, 272, 280, 287, 291, 297, 301, 305, 309, 313, 317, 324, 328, 331, 334, 337, 347, 351, 354, 364, 375, 381, 389, 400, 408, 420, 436, 446, 453, 457, 462, 473, 480, 483, 489, 494, 503, 510, 513, 519, 521, 528, 534, 539, 545, 551, 557, 560, 576, 579, 588, 591, 596, 599, 602, 606, 610, 614, 619, 624, 629, 634, 640, 643, 651, 655, 658, 668, 675, 683, 686, 689, 694, 700, 708, 713, 720, 727, 735, 742, 747, 752, 759, 763, 765, 769, 772, 777, 782, 787, 791, 795, 799, 805, 808, 811, 814, 820} var _FeatureID_index = [...]uint16{0, 7, 10, 15, 23, 34, 41, 48, 55, 58, 62, 72, 84, 92, 100, 108, 116, 123, 133, 143, 151, 161, 172, 180, 190, 208, 223, 230, 237, 241, 245, 251, 256, 264, 269, 275, 279, 297, 305, 312, 316, 322, 326, 330, 339, 343, 347, 351, 355, 362, 366, 369, 375, 378, 381, 391, 401, 414, 427, 431, 434, 444, 455, 461, 469, 480, 488, 500, 516, 531, 541, 548, 552, 555, 562, 567, 578, 585, 592, 600, 603, 609, 614, 623, 630, 638, 641, 647, 660, 665, 667, 674, 681, 687, 692, 698, 704, 710, 713, 729, 738, 741, 750, 765, 778, 784, 798, 805, 808, 813, 816, 819, 831, 845, 855, 858, 862, 866, 870, 875, 880, 885, 890, 901, 907, 910, 915, 924, 928, 933, 938, 944, 951, 956, 959, 962, 968, 978, 986, 990, 999, 1003, 1015, 1018, 1028, 1031, 1038, 1046, 1049, 1056, 1059, 1064, 1070, 1078, 1084, 1090, 1098, 1103, 1110, 1117, 1125, 1132, 1137, 1142, 1149, 1153, 1155, 1159, 1162, 1167, 1172, 1177, 1181, 1185, 1189, 1195, 1198, 1201, 1204, 1210}
func (i FeatureID) String() string { func (i FeatureID) String() string {
if i < 0 || i >= FeatureID(len(_FeatureID_index)-1) { if i < 0 || i >= FeatureID(len(_FeatureID_index)-1) {

View file

@ -2,18 +2,120 @@
package cpuid package cpuid
import "runtime" import (
"runtime"
"strings"
"golang.org/x/sys/unix"
)
func detectOS(c *CPUInfo) bool { func detectOS(c *CPUInfo) bool {
if runtime.GOOS != "ios" {
tryToFillCPUInfoFomSysctl(c)
}
// There are no hw.optional sysctl values for the below features on Mac OS 11.0 // There are no hw.optional sysctl values for the below features on Mac OS 11.0
// to detect their supported state dynamically. Assume the CPU features that // to detect their supported state dynamically. Assume the CPU features that
// Apple Silicon M1 supports to be available as a minimal set of features // Apple Silicon M1 supports to be available as a minimal set of features
// to all Go programs running on darwin/arm64. // to all Go programs running on darwin/arm64.
// TODO: Add more if we know them. // TODO: Add more if we know them.
c.featureSet.setIf(runtime.GOOS != "ios", AESARM, PMULL, SHA1, SHA2) c.featureSet.setIf(runtime.GOOS != "ios", AESARM, PMULL, SHA1, SHA2)
c.PhysicalCores = runtime.NumCPU()
// For now assuming 1 thread per core...
c.ThreadsPerCore = 1
c.LogicalCores = c.PhysicalCores
return true return true
} }
func sysctlGetBool(name string) bool {
value, err := unix.SysctlUint32(name)
if err != nil {
return false
}
return value != 0
}
func sysctlGetString(name string) string {
value, err := unix.Sysctl(name)
if err != nil {
return ""
}
return value
}
func sysctlGetInt(unknown int, names ...string) int {
for _, name := range names {
value, err := unix.SysctlUint32(name)
if err != nil {
continue
}
if value != 0 {
return int(value)
}
}
return unknown
}
func sysctlGetInt64(unknown int, names ...string) int {
for _, name := range names {
value64, err := unix.SysctlUint64(name)
if err != nil {
continue
}
if int(value64) != unknown {
return int(value64)
}
}
return unknown
}
func setFeature(c *CPUInfo, name string, feature FeatureID) {
c.featureSet.setIf(sysctlGetBool(name), feature)
}
func tryToFillCPUInfoFomSysctl(c *CPUInfo) {
c.BrandName = sysctlGetString("machdep.cpu.brand_string")
if len(c.BrandName) != 0 {
c.VendorString = strings.Fields(c.BrandName)[0]
}
c.PhysicalCores = sysctlGetInt(runtime.NumCPU(), "hw.physicalcpu")
c.ThreadsPerCore = sysctlGetInt(1, "machdep.cpu.thread_count", "kern.num_threads") /
sysctlGetInt(1, "hw.physicalcpu")
c.LogicalCores = sysctlGetInt(runtime.NumCPU(), "machdep.cpu.core_count")
c.Family = sysctlGetInt(0, "machdep.cpu.family", "hw.cpufamily")
c.Model = sysctlGetInt(0, "machdep.cpu.model")
c.CacheLine = sysctlGetInt64(0, "hw.cachelinesize")
c.Cache.L1I = sysctlGetInt64(-1, "hw.l1icachesize")
c.Cache.L1D = sysctlGetInt64(-1, "hw.l1icachesize")
c.Cache.L2 = sysctlGetInt64(-1, "hw.l2cachesize")
c.Cache.L3 = sysctlGetInt64(-1, "hw.l3cachesize")
// from https://developer.arm.com/downloads/-/exploration-tools/feature-names-for-a-profile
setFeature(c, "hw.optional.arm.FEAT_AES", AESARM)
setFeature(c, "hw.optional.AdvSIMD", ASIMD)
setFeature(c, "hw.optional.arm.FEAT_DotProd", ASIMDDP)
setFeature(c, "hw.optional.arm.FEAT_RDM", ASIMDRDM)
setFeature(c, "hw.optional.FEAT_CRC32", CRC32)
setFeature(c, "hw.optional.arm.FEAT_DPB", DCPOP)
// setFeature(c, "", EVTSTRM)
setFeature(c, "hw.optional.arm.FEAT_FCMA", FCMA)
setFeature(c, "hw.optional.arm.FEAT_FP", FP)
setFeature(c, "hw.optional.arm.FEAT_FP16", FPHP)
setFeature(c, "hw.optional.arm.FEAT_PAuth", GPA)
setFeature(c, "hw.optional.arm.FEAT_JSCVT", JSCVT)
setFeature(c, "hw.optional.arm.FEAT_LRCPC", LRCPC)
setFeature(c, "hw.optional.arm.FEAT_PMULL", PMULL)
setFeature(c, "hw.optional.arm.FEAT_SHA1", SHA1)
setFeature(c, "hw.optional.arm.FEAT_SHA256", SHA2)
setFeature(c, "hw.optional.arm.FEAT_SHA3", SHA3)
setFeature(c, "hw.optional.arm.FEAT_SHA512", SHA512)
// setFeature(c, "", SM3)
// setFeature(c, "", SM4)
setFeature(c, "hw.optional.arm.FEAT_SVE", SVE)
// from empirical observation
setFeature(c, "hw.optional.AdvSIMD_HPFPCvt", ASIMDHP)
setFeature(c, "hw.optional.armv8_1_atomics", ATOMICS)
setFeature(c, "hw.optional.floatingpoint", FP)
setFeature(c, "hw.optional.armv8_2_sha3", SHA3)
setFeature(c, "hw.optional.armv8_2_sha512", SHA512)
setFeature(c, "hw.optional.armv8_3_compnum", FCMA)
setFeature(c, "hw.optional.armv8_crc32", CRC32)
}

View file

@ -1,5 +1,19 @@
# Changelog # Changelog
## v4.9.1 - 2022-10-12
**Fixes**
* Fix logger panicing (when template is set to empty) by bumping dependency version [#2295](https://github.com/labstack/echo/issues/2295)
**Enhancements**
* Improve CORS documentation [#2272](https://github.com/labstack/echo/pull/2272)
* Update readme about supported Go versions [#2291](https://github.com/labstack/echo/pull/2291)
* Tests: improve error handling on closing body [#2254](https://github.com/labstack/echo/pull/2254)
* Tests: refactor some of the assertions in tests [#2275](https://github.com/labstack/echo/pull/2275)
* Tests: refactor assertions [#2301](https://github.com/labstack/echo/pull/2301)
## v4.9.0 - 2022-09-04 ## v4.9.0 - 2022-09-04
**Security** **Security**

View file

@ -11,12 +11,11 @@
## Supported Go versions ## Supported Go versions
Latest version of Echo supports last four Go major [releases](https://go.dev/doc/devel/release) and might work with older versions.
As of version 4.0.0, Echo is available as a [Go module](https://github.com/golang/go/wiki/Modules). As of version 4.0.0, Echo is available as a [Go module](https://github.com/golang/go/wiki/Modules).
Therefore a Go version capable of understanding /vN suffixed imports is required: Therefore a Go version capable of understanding /vN suffixed imports is required:
- 1.9.7+
- 1.10.3+
- 1.14+
Any of these versions will allow you to import Echo as `github.com/labstack/echo/v4` which is the recommended Any of these versions will allow you to import Echo as `github.com/labstack/echo/v4` which is the recommended
way of using Echo going forward. way of using Echo going forward.

View file

@ -181,7 +181,7 @@ type (
// Logger returns the `Logger` instance. // Logger returns the `Logger` instance.
Logger() Logger Logger() Logger
// Set the logger // SetLogger Set the logger
SetLogger(l Logger) SetLogger(l Logger)
// Echo returns the `Echo` instance. // Echo returns the `Echo` instance.

View file

@ -15,46 +15,85 @@ type (
// Skipper defines a function to skip middleware. // Skipper defines a function to skip middleware.
Skipper Skipper Skipper Skipper
// AllowOrigin defines a list of origins that may access the resource. // AllowOrigins determines the value of the Access-Control-Allow-Origin
// response header. This header defines a list of origins that may access the
// resource. The wildcard characters '*' and '?' are supported and are
// converted to regex fragments '.*' and '.' accordingly.
//
// Security: use extreme caution when handling the origin, and carefully
// validate any logic. Remember that attackers may register hostile domain names.
// See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
//
// Optional. Default value []string{"*"}. // Optional. Default value []string{"*"}.
//
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
AllowOrigins []string `yaml:"allow_origins"` AllowOrigins []string `yaml:"allow_origins"`
// AllowOriginFunc is a custom function to validate the origin. It takes the // AllowOriginFunc is a custom function to validate the origin. It takes the
// origin as an argument and returns true if allowed or false otherwise. If // origin as an argument and returns true if allowed or false otherwise. If
// an error is returned, it is returned by the handler. If this option is // an error is returned, it is returned by the handler. If this option is
// set, AllowOrigins is ignored. // set, AllowOrigins is ignored.
//
// Security: use extreme caution when handling the origin, and carefully
// validate any logic. Remember that attackers may register hostile domain names.
// See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
//
// Optional. // Optional.
AllowOriginFunc func(origin string) (bool, error) `yaml:"allow_origin_func"` AllowOriginFunc func(origin string) (bool, error) `yaml:"allow_origin_func"`
// AllowMethods defines a list methods allowed when accessing the resource. // AllowMethods determines the value of the Access-Control-Allow-Methods
// This is used in response to a preflight request. // response header. This header specified the list of methods allowed when
// accessing the resource. This is used in response to a preflight request.
//
// Optional. Default value DefaultCORSConfig.AllowMethods. // Optional. Default value DefaultCORSConfig.AllowMethods.
// If `allowMethods` is left empty will fill for preflight request `Access-Control-Allow-Methods` header value // If `allowMethods` is left empty, this middleware will fill for preflight
// request `Access-Control-Allow-Methods` header value
// from `Allow` header that echo.Router set into context. // from `Allow` header that echo.Router set into context.
//
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
AllowMethods []string `yaml:"allow_methods"` AllowMethods []string `yaml:"allow_methods"`
// AllowHeaders defines a list of request headers that can be used when // AllowHeaders determines the value of the Access-Control-Allow-Headers
// making the actual request. This is in response to a preflight request. // response header. This header is used in response to a preflight request to
// indicate which HTTP headers can be used when making the actual request.
//
// Optional. Default value []string{}. // Optional. Default value []string{}.
//
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
AllowHeaders []string `yaml:"allow_headers"` AllowHeaders []string `yaml:"allow_headers"`
// AllowCredentials indicates whether or not the response to the request // AllowCredentials determines the value of the
// can be exposed when the credentials flag is true. When used as part of // Access-Control-Allow-Credentials response header. This header indicates
// a response to a preflight request, this indicates whether or not the // whether or not the response to the request can be exposed when the
// actual request can be made using credentials. // credentials mode (Request.credentials) is true. When used as part of a
// Optional. Default value false. // response to a preflight request, this indicates whether or not the actual
// request can be made using credentials. See also
// [MDN: Access-Control-Allow-Credentials].
//
// Optional. Default value false, in which case the header is not set.
//
// Security: avoid using `AllowCredentials = true` with `AllowOrigins = *`. // Security: avoid using `AllowCredentials = true` with `AllowOrigins = *`.
// See http://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html // See "Exploiting CORS misconfigurations for Bitcoins and bounties",
// https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
//
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
AllowCredentials bool `yaml:"allow_credentials"` AllowCredentials bool `yaml:"allow_credentials"`
// ExposeHeaders defines a whitelist headers that clients are allowed to // ExposeHeaders determines the value of Access-Control-Expose-Headers, which
// access. // defines a list of headers that clients are allowed to access.
// Optional. Default value []string{}. //
// Optional. Default value []string{}, in which case the header is not set.
//
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Header
ExposeHeaders []string `yaml:"expose_headers"` ExposeHeaders []string `yaml:"expose_headers"`
// MaxAge indicates how long (in seconds) the results of a preflight request // MaxAge determines the value of the Access-Control-Max-Age response header.
// can be cached. // This header indicates how long (in seconds) the results of a preflight
// Optional. Default value 0. // request can be cached.
//
// Optional. Default value 0. The header is set only if MaxAge > 0.
//
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
MaxAge int `yaml:"max_age"` MaxAge int `yaml:"max_age"`
} }
) )
@ -69,13 +108,22 @@ var (
) )
// CORS returns a Cross-Origin Resource Sharing (CORS) middleware. // CORS returns a Cross-Origin Resource Sharing (CORS) middleware.
// See: https://developer.mozilla.org/en/docs/Web/HTTP/Access_control_CORS // See also [MDN: Cross-Origin Resource Sharing (CORS)].
//
// Security: Poorly configured CORS can compromise security because it allows
// relaxation of the browser's Same-Origin policy. See [Exploiting CORS
// misconfigurations for Bitcoins and bounties] and [Portswigger: Cross-origin
// resource sharing (CORS)] for more details.
//
// [MDN: Cross-Origin Resource Sharing (CORS)]: https://developer.mozilla.org/en/docs/Web/HTTP/Access_control_CORS
// [Exploiting CORS misconfigurations for Bitcoins and bounties]: https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
// [Portswigger: Cross-origin resource sharing (CORS)]: https://portswigger.net/web-security/cors
func CORS() echo.MiddlewareFunc { func CORS() echo.MiddlewareFunc {
return CORSWithConfig(DefaultCORSConfig) return CORSWithConfig(DefaultCORSConfig)
} }
// CORSWithConfig returns a CORS middleware with config. // CORSWithConfig returns a CORS middleware with config.
// See: `CORS()`. // See: [CORS].
func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc { func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
// Defaults // Defaults
if config.Skipper == nil { if config.Skipper == nil {

View file

@ -12,19 +12,31 @@ type (
Bytes struct{} Bytes struct{}
) )
// binary units (IEC 60027)
const ( const (
_ = 1.0 << (10 * iota) // ignore first value by assigning to blank identifier _ = 1.0 << (10 * iota) // ignore first value by assigning to blank identifier
KB KiB
MB MiB
GB GiB
TB TiB
PB PiB
EB EiB
)
// decimal units (SI international system of units)
const (
KB = 1000
MB = KB * 1000
GB = MB * 1000
TB = GB * 1000
PB = TB * 1000
EB = PB * 1000
) )
var ( var (
pattern = regexp.MustCompile(`(?i)^(-?\d+(?:\.\d+)?)\s?([KMGTPE]B?|B?)$`) patternBinary = regexp.MustCompile(`(?i)^(-?\d+(?:\.\d+)?)\s?([KMGTPE]iB?)$`)
global = New() patternDecimal = regexp.MustCompile(`(?i)^(-?\d+(?:\.\d+)?)\s?([KMGTPE]B?|B?)$`)
global = New()
) )
// New creates a Bytes instance. // New creates a Bytes instance.
@ -32,44 +44,97 @@ func New() *Bytes {
return &Bytes{} return &Bytes{}
} }
// Format formats bytes integer to human readable string. // Format formats bytes integer to human readable string according to IEC 60027.
// For example, 31323 bytes will return 30.59KB. // For example, 31323 bytes will return 30.59KB.
func (*Bytes) Format(b int64) string { func (b *Bytes) Format(value int64) string {
return b.FormatBinary(value)
}
// FormatBinary formats bytes integer to human readable string according to IEC 60027.
// For example, 31323 bytes will return 30.59KB.
func (*Bytes) FormatBinary(value int64) string {
multiple := "" multiple := ""
value := float64(b) val := float64(value)
switch { switch {
case b >= EB: case value >= EiB:
value /= EB val /= EiB
multiple = "EB" multiple = "EiB"
case b >= PB: case value >= PiB:
value /= PB val /= PiB
multiple = "PB" multiple = "PiB"
case b >= TB: case value >= TiB:
value /= TB val /= TiB
multiple = "TB" multiple = "TiB"
case b >= GB: case value >= GiB:
value /= GB val /= GiB
multiple = "GB" multiple = "GiB"
case b >= MB: case value >= MiB:
value /= MB val /= MiB
multiple = "MB" multiple = "MiB"
case b >= KB: case value >= KiB:
value /= KB val /= KiB
multiple = "KB" multiple = "KiB"
case b == 0: case value == 0:
return "0" return "0"
default: default:
return strconv.FormatInt(b, 10) + "B" return strconv.FormatInt(value, 10) + "B"
} }
return fmt.Sprintf("%.2f%s", value, multiple) return fmt.Sprintf("%.2f%s", val, multiple)
}
// FormatDecimal formats bytes integer to human readable string according to SI international system of units.
// For example, 31323 bytes will return 31.32KB.
func (*Bytes) FormatDecimal(value int64) string {
multiple := ""
val := float64(value)
switch {
case value >= EB:
val /= EB
multiple = "EB"
case value >= PB:
val /= PB
multiple = "PB"
case value >= TB:
val /= TB
multiple = "TB"
case value >= GB:
val /= GB
multiple = "GB"
case value >= MB:
val /= MB
multiple = "MB"
case value >= KB:
val /= KB
multiple = "KB"
case value == 0:
return "0"
default:
return strconv.FormatInt(value, 10) + "B"
}
return fmt.Sprintf("%.2f%s", val, multiple)
} }
// Parse parses human readable bytes string to bytes integer. // Parse parses human readable bytes string to bytes integer.
// For example, 6GB (6G is also valid) will return 6442450944. // For example, 6GiB (6Gi is also valid) will return 6442450944, and
func (*Bytes) Parse(value string) (i int64, err error) { // 6GB (6G is also valid) will return 6000000000.
parts := pattern.FindStringSubmatch(value) func (b *Bytes) Parse(value string) (int64, error) {
i, err := b.ParseBinary(value)
if err == nil {
return i, err
}
return b.ParseDecimal(value)
}
// ParseBinary parses human readable bytes string to bytes integer.
// For example, 6GiB (6Gi is also valid) will return 6442450944.
func (*Bytes) ParseBinary(value string) (i int64, err error) {
parts := patternBinary.FindStringSubmatch(value)
if len(parts) < 3 { if len(parts) < 3 {
return 0, fmt.Errorf("error parsing value=%s", value) return 0, fmt.Errorf("error parsing value=%s", value)
} }
@ -81,8 +146,38 @@ func (*Bytes) Parse(value string) (i int64, err error) {
} }
switch multiple { switch multiple {
case "KI", "KIB":
return int64(bytes * KiB), nil
case "MI", "MIB":
return int64(bytes * MiB), nil
case "GI", "GIB":
return int64(bytes * GiB), nil
case "TI", "TIB":
return int64(bytes * TiB), nil
case "PI", "PIB":
return int64(bytes * PiB), nil
case "EI", "EIB":
return int64(bytes * EiB), nil
default: default:
return int64(bytes), nil return int64(bytes), nil
}
}
// ParseDecimal parses human readable bytes string to bytes integer.
// For example, 6GB (6G is also valid) will return 6000000000.
func (*Bytes) ParseDecimal(value string) (i int64, err error) {
parts := patternDecimal.FindStringSubmatch(value)
if len(parts) < 3 {
return 0, fmt.Errorf("error parsing value=%s", value)
}
bytesString := parts[1]
multiple := strings.ToUpper(parts[2])
bytes, err := strconv.ParseFloat(bytesString, 64)
if err != nil {
return
}
switch multiple {
case "K", "KB": case "K", "KB":
return int64(bytes * KB), nil return int64(bytes * KB), nil
case "M", "MB": case "M", "MB":
@ -95,15 +190,27 @@ func (*Bytes) Parse(value string) (i int64, err error) {
return int64(bytes * PB), nil return int64(bytes * PB), nil
case "E", "EB": case "E", "EB":
return int64(bytes * EB), nil return int64(bytes * EB), nil
default:
return int64(bytes), nil
} }
} }
// Format wraps global Bytes's Format function. // Format wraps global Bytes's Format function.
func Format(b int64) string { func Format(value int64) string {
return global.Format(b) return global.Format(value)
}
// FormatBinary wraps global Bytes's FormatBinary function.
func FormatBinary(value int64) string {
return global.FormatBinary(value)
}
// FormatDecimal wraps global Bytes's FormatDecimal function.
func FormatDecimal(value int64) string {
return global.FormatDecimal(value)
} }
// Parse wraps global Bytes's Parse function. // Parse wraps global Bytes's Parse function.
func Parse(val string) (int64, error) { func Parse(value string) (int64, error) {
return global.Parse(val) return global.Parse(value)
} }

View file

@ -391,7 +391,7 @@ func (l *Logger) log(level Lvl, format string, args ...interface{}) {
if err == nil { if err == nil {
s := buf.String() s := buf.String()
i := buf.Len() - 1 i := buf.Len() - 1
if s[i] == '}' { if i >= 0 && s[i] == '}' {
// JSON header // JSON header
buf.Truncate(i) buf.Truncate(i)
buf.WriteByte(',') buf.WriteByte(',')
@ -404,7 +404,9 @@ func (l *Logger) log(level Lvl, format string, args ...interface{}) {
} }
} else { } else {
// Text header // Text header
buf.WriteByte(' ') if len(s) > 0 {
buf.WriteByte(' ')
}
buf.WriteString(message) buf.WriteString(message)
} }
buf.WriteByte('\n') buf.WriteByte('\n')

View file

@ -24,7 +24,7 @@ func isPacketConn(c net.Conn) bool {
} }
if ua, ok := c.LocalAddr().(*net.UnixAddr); ok { if ua, ok := c.LocalAddr().(*net.UnixAddr); ok {
return ua.Net == "unixgram" return ua.Net == "unixgram" || ua.Net == "unixpacket"
} }
return true return true
@ -280,7 +280,7 @@ func (co *Conn) ReadMsg() (*Msg, error) {
} }
if t := m.IsTsig(); t != nil { if t := m.IsTsig(); t != nil {
// Need to work on the original message p, as that was used to calculate the tsig. // Need to work on the original message p, as that was used to calculate the tsig.
err = tsigVerifyProvider(p, co.tsigProvider(), co.tsigRequestMAC, false) err = TsigVerifyWithProvider(p, co.tsigProvider(), co.tsigRequestMAC, false)
} }
return m, err return m, err
} }
@ -358,7 +358,7 @@ func (co *Conn) WriteMsg(m *Msg) (err error) {
var out []byte var out []byte
if t := m.IsTsig(); t != nil { if t := m.IsTsig(); t != nil {
// Set tsigRequestMAC for the next read, although only used in zone transfers. // Set tsigRequestMAC for the next read, although only used in zone transfers.
out, co.tsigRequestMAC, err = tsigGenerateProvider(m, co.tsigProvider(), co.tsigRequestMAC, false) out, co.tsigRequestMAC, err = TsigGenerateWithProvider(m, co.tsigProvider(), co.tsigRequestMAC, false)
} else { } else {
out, err = m.Pack() out, err = m.Pack()
} }

View file

@ -218,6 +218,11 @@ func IsDomainName(s string) (labels int, ok bool) {
wasDot = false wasDot = false
case '.': case '.':
if i == 0 && len(s) > 1 {
// leading dots are not legal except for the root zone
return labels, false
}
if wasDot { if wasDot {
// two dots back to back is not legal // two dots back to back is not legal
return labels, false return labels, false

View file

@ -65,6 +65,9 @@ var AlgorithmToString = map[uint8]string{
} }
// AlgorithmToHash is a map of algorithm crypto hash IDs to crypto.Hash's. // AlgorithmToHash is a map of algorithm crypto hash IDs to crypto.Hash's.
// For newer algorithm that do their own hashing (i.e. ED25519) the returned value
// is 0, implying no (external) hashing should occur. The non-exported identityHash is then
// used.
var AlgorithmToHash = map[uint8]crypto.Hash{ var AlgorithmToHash = map[uint8]crypto.Hash{
RSAMD5: crypto.MD5, // Deprecated in RFC 6725 RSAMD5: crypto.MD5, // Deprecated in RFC 6725
DSA: crypto.SHA1, DSA: crypto.SHA1,
@ -74,7 +77,7 @@ var AlgorithmToHash = map[uint8]crypto.Hash{
ECDSAP256SHA256: crypto.SHA256, ECDSAP256SHA256: crypto.SHA256,
ECDSAP384SHA384: crypto.SHA384, ECDSAP384SHA384: crypto.SHA384,
RSASHA512: crypto.SHA512, RSASHA512: crypto.SHA512,
ED25519: crypto.Hash(0), ED25519: 0,
} }
// DNSSEC hashing algorithm codes. // DNSSEC hashing algorithm codes.
@ -137,12 +140,12 @@ func (k *DNSKEY) KeyTag() uint16 {
var keytag int var keytag int
switch k.Algorithm { switch k.Algorithm {
case RSAMD5: case RSAMD5:
// Look at the bottom two bytes of the modules, which the last
// item in the pubkey.
// This algorithm has been deprecated, but keep this key-tag calculation. // This algorithm has been deprecated, but keep this key-tag calculation.
// Look at the bottom two bytes of the modules, which the last item in the pubkey.
// See https://www.rfc-editor.org/errata/eid193 .
modulus, _ := fromBase64([]byte(k.PublicKey)) modulus, _ := fromBase64([]byte(k.PublicKey))
if len(modulus) > 1 { if len(modulus) > 1 {
x := binary.BigEndian.Uint16(modulus[len(modulus)-2:]) x := binary.BigEndian.Uint16(modulus[len(modulus)-3:])
keytag = int(x) keytag = int(x)
} }
default: default:
@ -296,35 +299,20 @@ func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
return err return err
} }
hash, ok := AlgorithmToHash[rr.Algorithm] h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
if !ok { if err != nil {
return ErrAlg return err
} }
switch rr.Algorithm { switch rr.Algorithm {
case ED25519:
// ed25519 signs the raw message and performs hashing internally.
// All other supported signature schemes operate over the pre-hashed
// message, and thus ed25519 must be handled separately here.
//
// The raw message is passed directly into sign and crypto.Hash(0) is
// used to signal to the crypto.Signer that the data has not been hashed.
signature, err := sign(k, append(signdata, wire...), crypto.Hash(0), rr.Algorithm)
if err != nil {
return err
}
rr.Signature = toBase64(signature)
return nil
case RSAMD5, DSA, DSANSEC3SHA1: case RSAMD5, DSA, DSANSEC3SHA1:
// See RFC 6944. // See RFC 6944.
return ErrAlg return ErrAlg
default: default:
h := hash.New()
h.Write(signdata) h.Write(signdata)
h.Write(wire) h.Write(wire)
signature, err := sign(k, h.Sum(nil), hash, rr.Algorithm) signature, err := sign(k, h.Sum(nil), cryptohash, rr.Algorithm)
if err != nil { if err != nil {
return err return err
} }
@ -341,7 +329,7 @@ func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte,
} }
switch alg { switch alg {
case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512: case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, ED25519:
return signature, nil return signature, nil
case ECDSAP256SHA256, ECDSAP384SHA384: case ECDSAP256SHA256, ECDSAP384SHA384:
ecdsaSignature := &struct { ecdsaSignature := &struct {
@ -362,8 +350,6 @@ func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte,
signature := intToBytes(ecdsaSignature.R, intlen) signature := intToBytes(ecdsaSignature.R, intlen)
signature = append(signature, intToBytes(ecdsaSignature.S, intlen)...) signature = append(signature, intToBytes(ecdsaSignature.S, intlen)...)
return signature, nil return signature, nil
case ED25519:
return signature, nil
default: default:
return nil, ErrAlg return nil, ErrAlg
} }
@ -437,9 +423,9 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
// remove the domain name and assume its ours? // remove the domain name and assume its ours?
} }
hash, ok := AlgorithmToHash[rr.Algorithm] h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
if !ok { if err != nil {
return ErrAlg return err
} }
switch rr.Algorithm { switch rr.Algorithm {
@ -450,10 +436,9 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
return ErrKey return ErrKey
} }
h := hash.New()
h.Write(signeddata) h.Write(signeddata)
h.Write(wire) h.Write(wire)
return rsa.VerifyPKCS1v15(pubkey, hash, h.Sum(nil), sigbuf) return rsa.VerifyPKCS1v15(pubkey, cryptohash, h.Sum(nil), sigbuf)
case ECDSAP256SHA256, ECDSAP384SHA384: case ECDSAP256SHA256, ECDSAP384SHA384:
pubkey := k.publicKeyECDSA() pubkey := k.publicKeyECDSA()
@ -465,7 +450,6 @@ func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
r := new(big.Int).SetBytes(sigbuf[:len(sigbuf)/2]) r := new(big.Int).SetBytes(sigbuf[:len(sigbuf)/2])
s := new(big.Int).SetBytes(sigbuf[len(sigbuf)/2:]) s := new(big.Int).SetBytes(sigbuf[len(sigbuf)/2:])
h := hash.New()
h.Write(signeddata) h.Write(signeddata)
h.Write(wire) h.Write(wire)
if ecdsa.Verify(pubkey, h.Sum(nil), r, s) { if ecdsa.Verify(pubkey, h.Sum(nil), r, s) {

16
vendor/github.com/miekg/dns/edns.go generated vendored
View file

@ -584,14 +584,17 @@ func (e *EDNS0_N3U) copy() EDNS0 { return &EDNS0_N3U{e.Code, e.AlgCode} }
type EDNS0_EXPIRE struct { type EDNS0_EXPIRE struct {
Code uint16 // Always EDNS0EXPIRE Code uint16 // Always EDNS0EXPIRE
Expire uint32 Expire uint32
Empty bool // Empty is used to signal an empty Expire option in a backwards compatible way, it's not used on the wire.
} }
// Option implements the EDNS0 interface. // Option implements the EDNS0 interface.
func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE } func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE }
func (e *EDNS0_EXPIRE) String() string { return strconv.FormatUint(uint64(e.Expire), 10) } func (e *EDNS0_EXPIRE) copy() EDNS0 { return &EDNS0_EXPIRE{e.Code, e.Expire, e.Empty} }
func (e *EDNS0_EXPIRE) copy() EDNS0 { return &EDNS0_EXPIRE{e.Code, e.Expire} }
func (e *EDNS0_EXPIRE) pack() ([]byte, error) { func (e *EDNS0_EXPIRE) pack() ([]byte, error) {
if e.Empty {
return []byte{}, nil
}
b := make([]byte, 4) b := make([]byte, 4)
binary.BigEndian.PutUint32(b, e.Expire) binary.BigEndian.PutUint32(b, e.Expire)
return b, nil return b, nil
@ -600,15 +603,24 @@ func (e *EDNS0_EXPIRE) pack() ([]byte, error) {
func (e *EDNS0_EXPIRE) unpack(b []byte) error { func (e *EDNS0_EXPIRE) unpack(b []byte) error {
if len(b) == 0 { if len(b) == 0 {
// zero-length EXPIRE query, see RFC 7314 Section 2 // zero-length EXPIRE query, see RFC 7314 Section 2
e.Empty = true
return nil return nil
} }
if len(b) < 4 { if len(b) < 4 {
return ErrBuf return ErrBuf
} }
e.Expire = binary.BigEndian.Uint32(b) e.Expire = binary.BigEndian.Uint32(b)
e.Empty = false
return nil return nil
} }
func (e *EDNS0_EXPIRE) String() (s string) {
if e.Empty {
return ""
}
return strconv.FormatUint(uint64(e.Expire), 10)
}
// The EDNS0_LOCAL option is used for local/experimental purposes. The option // The EDNS0_LOCAL option is used for local/experimental purposes. The option
// code is recommended to be within the range [EDNS0LOCALSTART, EDNS0LOCALEND] // code is recommended to be within the range [EDNS0LOCALSTART, EDNS0LOCALEND]
// (RFC6891), although any unassigned code can actually be used. The content of // (RFC6891), although any unassigned code can actually be used. The content of

31
vendor/github.com/miekg/dns/hash.go generated vendored Normal file
View file

@ -0,0 +1,31 @@
package dns
import (
"bytes"
"crypto"
"hash"
)
// identityHash will not hash, it only buffers the data written into it and returns it as-is.
type identityHash struct {
b *bytes.Buffer
}
// Implement the hash.Hash interface.
func (i identityHash) Write(b []byte) (int, error) { return i.b.Write(b) }
func (i identityHash) Size() int { return i.b.Len() }
func (i identityHash) BlockSize() int { return 1024 }
func (i identityHash) Reset() { i.b.Reset() }
func (i identityHash) Sum(b []byte) []byte { return append(b, i.b.Bytes()...) }
func hashFromAlgorithm(alg uint8) (hash.Hash, crypto.Hash, error) {
hashnumber, ok := AlgorithmToHash[alg]
if !ok {
return nil, 0, ErrAlg
}
if hashnumber == 0 {
return identityHash{b: &bytes.Buffer{}}, hashnumber, nil
}
return hashnumber.New(), hashnumber, nil
}

5
vendor/github.com/miekg/dns/msg.go generated vendored
View file

@ -265,6 +265,11 @@ loop:
wasDot = false wasDot = false
case '.': case '.':
if i == 0 && len(s) > 1 {
// leading dots are not legal except for the root zone
return len(msg), ErrRdata
}
if wasDot { if wasDot {
// two dots back to back is not legal // two dots back to back is not legal
return len(msg), ErrRdata return len(msg), ErrRdata

View file

@ -476,7 +476,7 @@ func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) {
length, window, lastwindow := 0, 0, -1 length, window, lastwindow := 0, 0, -1
for off < len(msg) { for off < len(msg) {
if off+2 > len(msg) { if off+2 > len(msg) {
return nsec, len(msg), &Error{err: "overflow unpacking nsecx"} return nsec, len(msg), &Error{err: "overflow unpacking NSEC(3)"}
} }
window = int(msg[off]) window = int(msg[off])
length = int(msg[off+1]) length = int(msg[off+1])
@ -484,17 +484,17 @@ func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) {
if window <= lastwindow { if window <= lastwindow {
// RFC 4034: Blocks are present in the NSEC RR RDATA in // RFC 4034: Blocks are present in the NSEC RR RDATA in
// increasing numerical order. // increasing numerical order.
return nsec, len(msg), &Error{err: "out of order NSEC block"} return nsec, len(msg), &Error{err: "out of order NSEC(3) block in type bitmap"}
} }
if length == 0 { if length == 0 {
// RFC 4034: Blocks with no types present MUST NOT be included. // RFC 4034: Blocks with no types present MUST NOT be included.
return nsec, len(msg), &Error{err: "empty NSEC block"} return nsec, len(msg), &Error{err: "empty NSEC(3) block in type bitmap"}
} }
if length > 32 { if length > 32 {
return nsec, len(msg), &Error{err: "NSEC block too long"} return nsec, len(msg), &Error{err: "NSEC(3) block too long in type bitmap"}
} }
if off+length > len(msg) { if off+length > len(msg) {
return nsec, len(msg), &Error{err: "overflowing NSEC block"} return nsec, len(msg), &Error{err: "overflowing NSEC(3) block in type bitmap"}
} }
// Walk the bytes in the window and extract the type bits // Walk the bytes in the window and extract the type bits
@ -558,6 +558,16 @@ func packDataNsec(bitmap []uint16, msg []byte, off int) (int, error) {
if len(bitmap) == 0 { if len(bitmap) == 0 {
return off, nil return off, nil
} }
if off > len(msg) {
return off, &Error{err: "overflow packing nsec"}
}
toZero := msg[off:]
if maxLen := typeBitMapLen(bitmap); maxLen < len(toZero) {
toZero = toZero[:maxLen]
}
for i := range toZero {
toZero[i] = 0
}
var lastwindow, lastlength uint16 var lastwindow, lastlength uint16
for _, t := range bitmap { for _, t := range bitmap {
window := t / 256 window := t / 256

View file

@ -646,7 +646,7 @@ func (srv *Server) serveDNS(m []byte, w *response) {
w.tsigStatus = nil w.tsigStatus = nil
if w.tsigProvider != nil { if w.tsigProvider != nil {
if t := req.IsTsig(); t != nil { if t := req.IsTsig(); t != nil {
w.tsigStatus = tsigVerifyProvider(m, w.tsigProvider, "", false) w.tsigStatus = TsigVerifyWithProvider(m, w.tsigProvider, "", false)
w.tsigTimersOnly = false w.tsigTimersOnly = false
w.tsigRequestMAC = t.MAC w.tsigRequestMAC = t.MAC
} }
@ -728,7 +728,7 @@ func (w *response) WriteMsg(m *Msg) (err error) {
var data []byte var data []byte
if w.tsigProvider != nil { // if no provider, dont check for the tsig (which is a longer check) if w.tsigProvider != nil { // if no provider, dont check for the tsig (which is a longer check)
if t := m.IsTsig(); t != nil { if t := m.IsTsig(); t != nil {
data, w.tsigRequestMAC, err = tsigGenerateProvider(m, w.tsigProvider, w.tsigRequestMAC, w.tsigTimersOnly) data, w.tsigRequestMAC, err = TsigGenerateWithProvider(m, w.tsigProvider, w.tsigRequestMAC, w.tsigTimersOnly)
if err != nil { if err != nil {
return err return err
} }

51
vendor/github.com/miekg/dns/sig0.go generated vendored
View file

@ -3,6 +3,7 @@ package dns
import ( import (
"crypto" "crypto"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa" "crypto/rsa"
"encoding/binary" "encoding/binary"
"math/big" "math/big"
@ -38,18 +39,17 @@ func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) {
} }
buf = buf[:off:cap(buf)] buf = buf[:off:cap(buf)]
hash, ok := AlgorithmToHash[rr.Algorithm] h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
if !ok { if err != nil {
return nil, ErrAlg return nil, err
} }
hasher := hash.New()
// Write SIG rdata // Write SIG rdata
hasher.Write(buf[len(mbuf)+1+2+2+4+2:]) h.Write(buf[len(mbuf)+1+2+2+4+2:])
// Write message // Write message
hasher.Write(buf[:len(mbuf)]) h.Write(buf[:len(mbuf)])
signature, err := sign(k, hasher.Sum(nil), hash, rr.Algorithm) signature, err := sign(k, h.Sum(nil), cryptohash, rr.Algorithm)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -82,20 +82,10 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
return ErrKey return ErrKey
} }
var hash crypto.Hash h, cryptohash, err := hashFromAlgorithm(rr.Algorithm)
switch rr.Algorithm { if err != nil {
case RSASHA1: return err
hash = crypto.SHA1
case RSASHA256, ECDSAP256SHA256:
hash = crypto.SHA256
case ECDSAP384SHA384:
hash = crypto.SHA384
case RSASHA512:
hash = crypto.SHA512
default:
return ErrAlg
} }
hasher := hash.New()
buflen := len(buf) buflen := len(buf)
qdc := binary.BigEndian.Uint16(buf[4:]) qdc := binary.BigEndian.Uint16(buf[4:])
@ -103,7 +93,6 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
auc := binary.BigEndian.Uint16(buf[8:]) auc := binary.BigEndian.Uint16(buf[8:])
adc := binary.BigEndian.Uint16(buf[10:]) adc := binary.BigEndian.Uint16(buf[10:])
offset := headerSize offset := headerSize
var err error
for i := uint16(0); i < qdc && offset < buflen; i++ { for i := uint16(0); i < qdc && offset < buflen; i++ {
_, offset, err = UnpackDomainName(buf, offset) _, offset, err = UnpackDomainName(buf, offset)
if err != nil { if err != nil {
@ -166,21 +155,21 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
return &Error{err: "signer name doesn't match key name"} return &Error{err: "signer name doesn't match key name"}
} }
sigend := offset sigend := offset
hasher.Write(buf[sigstart:sigend]) h.Write(buf[sigstart:sigend])
hasher.Write(buf[:10]) h.Write(buf[:10])
hasher.Write([]byte{ h.Write([]byte{
byte((adc - 1) << 8), byte((adc - 1) << 8),
byte(adc - 1), byte(adc - 1),
}) })
hasher.Write(buf[12:bodyend]) h.Write(buf[12:bodyend])
hashed := hasher.Sum(nil) hashed := h.Sum(nil)
sig := buf[sigend:] sig := buf[sigend:]
switch k.Algorithm { switch k.Algorithm {
case RSASHA1, RSASHA256, RSASHA512: case RSASHA1, RSASHA256, RSASHA512:
pk := k.publicKeyRSA() pk := k.publicKeyRSA()
if pk != nil { if pk != nil {
return rsa.VerifyPKCS1v15(pk, hash, hashed, sig) return rsa.VerifyPKCS1v15(pk, cryptohash, hashed, sig)
} }
case ECDSAP256SHA256, ECDSAP384SHA384: case ECDSAP256SHA256, ECDSAP384SHA384:
pk := k.publicKeyECDSA() pk := k.publicKeyECDSA()
@ -192,6 +181,14 @@ func (rr *SIG) Verify(k *KEY, buf []byte) error {
} }
return ErrSig return ErrSig
} }
case ED25519:
pk := k.publicKeyED25519()
if pk != nil {
if ed25519.Verify(pk, hashed, sig) {
return nil
}
return ErrSig
}
} }
return ErrKeyAlg return ErrKeyAlg
} }

330
vendor/github.com/miekg/dns/svcb.go generated vendored
View file

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"net" "net"
"sort" "sort"
"strconv" "strconv"
@ -13,16 +14,18 @@ import (
// SVCBKey is the type of the keys used in the SVCB RR. // SVCBKey is the type of the keys used in the SVCB RR.
type SVCBKey uint16 type SVCBKey uint16
// Keys defined in draft-ietf-dnsop-svcb-https-01 Section 12.3.2. // Keys defined in draft-ietf-dnsop-svcb-https-08 Section 14.3.2.
const ( const (
SVCB_MANDATORY SVCBKey = 0 SVCB_MANDATORY SVCBKey = iota
SVCB_ALPN SVCBKey = 1 SVCB_ALPN
SVCB_NO_DEFAULT_ALPN SVCBKey = 2 SVCB_NO_DEFAULT_ALPN
SVCB_PORT SVCBKey = 3 SVCB_PORT
SVCB_IPV4HINT SVCBKey = 4 SVCB_IPV4HINT
SVCB_ECHCONFIG SVCBKey = 5 SVCB_ECHCONFIG
SVCB_IPV6HINT SVCBKey = 6 SVCB_IPV6HINT
svcb_RESERVED SVCBKey = 65535 SVCB_DOHPATH // draft-ietf-add-svcb-dns-02 Section 9
svcb_RESERVED SVCBKey = 65535
) )
var svcbKeyToStringMap = map[SVCBKey]string{ var svcbKeyToStringMap = map[SVCBKey]string{
@ -31,8 +34,9 @@ var svcbKeyToStringMap = map[SVCBKey]string{
SVCB_NO_DEFAULT_ALPN: "no-default-alpn", SVCB_NO_DEFAULT_ALPN: "no-default-alpn",
SVCB_PORT: "port", SVCB_PORT: "port",
SVCB_IPV4HINT: "ipv4hint", SVCB_IPV4HINT: "ipv4hint",
SVCB_ECHCONFIG: "echconfig", SVCB_ECHCONFIG: "ech",
SVCB_IPV6HINT: "ipv6hint", SVCB_IPV6HINT: "ipv6hint",
SVCB_DOHPATH: "dohpath",
} }
var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap) var svcbStringToKeyMap = reverseSVCBKeyMap(svcbKeyToStringMap)
@ -167,10 +171,14 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
} }
l, _ = c.Next() l, _ = c.Next()
} }
// "In AliasMode, records SHOULD NOT include any SvcParams, and recipients MUST
// ignore any SvcParams that are present."
// However, we don't check rr.Priority == 0 && len(xs) > 0 here
// It is the responsibility of the user of the library to check this.
// This is to encourage the fixing of the source of this error.
rr.Value = xs rr.Value = xs
if rr.Priority == 0 && len(xs) > 0 {
return &ParseError{l.token, "SVCB aliasform can't have values", l}
}
return nil return nil
} }
@ -191,6 +199,8 @@ func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue {
return new(SVCBECHConfig) return new(SVCBECHConfig)
case SVCB_IPV6HINT: case SVCB_IPV6HINT:
return new(SVCBIPv6Hint) return new(SVCBIPv6Hint)
case SVCB_DOHPATH:
return new(SVCBDoHPath)
case svcb_RESERVED: case svcb_RESERVED:
return nil return nil
default: default:
@ -200,16 +210,24 @@ func makeSVCBKeyValue(key SVCBKey) SVCBKeyValue {
} }
} }
// SVCB RR. See RFC xxxx (https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-01). // SVCB RR. See RFC xxxx (https://tools.ietf.org/html/draft-ietf-dnsop-svcb-https-08).
//
// NOTE: The HTTPS/SVCB RFCs are in the draft stage.
// The API, including constants and types related to SVCBKeyValues, may
// change in future versions in accordance with the latest drafts.
type SVCB struct { type SVCB struct {
Hdr RR_Header Hdr RR_Header
Priority uint16 Priority uint16 // If zero, Value must be empty or discarded by the user of this library
Target string `dns:"domain-name"` Target string `dns:"domain-name"`
Value []SVCBKeyValue `dns:"pairs"` // Value must be empty if Priority is zero. Value []SVCBKeyValue `dns:"pairs"`
} }
// HTTPS RR. Everything valid for SVCB applies to HTTPS as well. // HTTPS RR. Everything valid for SVCB applies to HTTPS as well.
// Except that the HTTPS record is intended for use with the HTTP and HTTPS protocols. // Except that the HTTPS record is intended for use with the HTTP and HTTPS protocols.
//
// NOTE: The HTTPS/SVCB RFCs are in the draft stage.
// The API, including constants and types related to SVCBKeyValues, may
// change in future versions in accordance with the latest drafts.
type HTTPS struct { type HTTPS struct {
SVCB SVCB
} }
@ -235,15 +253,29 @@ type SVCBKeyValue interface {
} }
// SVCBMandatory pair adds to required keys that must be interpreted for the RR // SVCBMandatory pair adds to required keys that must be interpreted for the RR
// to be functional. // to be functional. If ignored, the whole RRSet must be ignored.
// "port" and "no-default-alpn" are mandatory by default if present,
// so they shouldn't be included here.
//
// It is incumbent upon the user of this library to reject the RRSet if
// or avoid constructing such an RRSet that:
// - "mandatory" is included as one of the keys of mandatory
// - no key is listed multiple times in mandatory
// - all keys listed in mandatory are present
// - escape sequences are not used in mandatory
// - mandatory, when present, lists at least one key
//
// Basic use pattern for creating a mandatory option: // Basic use pattern for creating a mandatory option:
// //
// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}} // s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
// e := new(dns.SVCBMandatory) // e := new(dns.SVCBMandatory)
// e.Code = []uint16{65403} // e.Code = []uint16{dns.SVCB_ALPN}
// s.Value = append(s.Value, e) // s.Value = append(s.Value, e)
// t := new(dns.SVCBAlpn)
// t.Alpn = []string{"xmpp-client"}
// s.Value = append(s.Value, t)
type SVCBMandatory struct { type SVCBMandatory struct {
Code []SVCBKey // Must not include mandatory Code []SVCBKey
} }
func (*SVCBMandatory) Key() SVCBKey { return SVCB_MANDATORY } func (*SVCBMandatory) Key() SVCBKey { return SVCB_MANDATORY }
@ -302,7 +334,8 @@ func (s *SVCBMandatory) copy() SVCBKeyValue {
} }
// SVCBAlpn pair is used to list supported connection protocols. // SVCBAlpn pair is used to list supported connection protocols.
// Protocol ids can be found at: // The user of this library must ensure that at least one protocol is listed when alpn is present.
// Protocol IDs can be found at:
// https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
// Basic use pattern for creating an alpn option: // Basic use pattern for creating an alpn option:
// //
@ -310,13 +343,57 @@ func (s *SVCBMandatory) copy() SVCBKeyValue {
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET} // h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
// e := new(dns.SVCBAlpn) // e := new(dns.SVCBAlpn)
// e.Alpn = []string{"h2", "http/1.1"} // e.Alpn = []string{"h2", "http/1.1"}
// h.Value = append(o.Value, e) // h.Value = append(h.Value, e)
type SVCBAlpn struct { type SVCBAlpn struct {
Alpn []string Alpn []string
} }
func (*SVCBAlpn) Key() SVCBKey { return SVCB_ALPN } func (*SVCBAlpn) Key() SVCBKey { return SVCB_ALPN }
func (s *SVCBAlpn) String() string { return strings.Join(s.Alpn, ",") }
func (s *SVCBAlpn) String() string {
// An ALPN value is a comma-separated list of values, each of which can be
// an arbitrary binary value. In order to allow parsing, the comma and
// backslash characters are themselves excaped.
//
// However, this escaping is done in addition to the normal escaping which
// happens in zone files, meaning that these values must be
// double-escaped. This looks terrible, so if you see a never-ending
// sequence of backslash in a zone file this may be why.
//
// https://datatracker.ietf.org/doc/html/draft-ietf-dnsop-svcb-https-08#appendix-A.1
var str strings.Builder
for i, alpn := range s.Alpn {
// 4*len(alpn) is the worst case where we escape every character in the alpn as \123, plus 1 byte for the ',' separating the alpn from others
str.Grow(4*len(alpn) + 1)
if i > 0 {
str.WriteByte(',')
}
for j := 0; j < len(alpn); j++ {
e := alpn[j]
if ' ' > e || e > '~' {
str.WriteString(escapeByte(e))
continue
}
switch e {
// We escape a few characters which may confuse humans or parsers.
case '"', ';', ' ':
str.WriteByte('\\')
str.WriteByte(e)
// The comma and backslash characters themselves must be
// doubly-escaped. We use `\\` for the first backslash and
// the escaped numeric value for the other value. We especially
// don't want a comma in the output.
case ',':
str.WriteString(`\\\044`)
case '\\':
str.WriteString(`\\\092`)
default:
str.WriteByte(e)
}
}
}
return str.String()
}
func (s *SVCBAlpn) pack() ([]byte, error) { func (s *SVCBAlpn) pack() ([]byte, error) {
// Liberally estimate the size of an alpn as 10 octets // Liberally estimate the size of an alpn as 10 octets
@ -351,7 +428,47 @@ func (s *SVCBAlpn) unpack(b []byte) error {
} }
func (s *SVCBAlpn) parse(b string) error { func (s *SVCBAlpn) parse(b string) error {
s.Alpn = strings.Split(b, ",") if len(b) == 0 {
s.Alpn = []string{}
return nil
}
alpn := []string{}
a := []byte{}
for p := 0; p < len(b); {
c, q := nextByte(b, p)
if q == 0 {
return errors.New("dns: svcbalpn: unterminated escape")
}
p += q
// If we find a comma, we have finished reading an alpn.
if c == ',' {
if len(a) == 0 {
return errors.New("dns: svcbalpn: empty protocol identifier")
}
alpn = append(alpn, string(a))
a = []byte{}
continue
}
// If it's a backslash, we need to handle a comma-separated list.
if c == '\\' {
dc, dq := nextByte(b, p)
if dq == 0 {
return errors.New("dns: svcbalpn: unterminated escape decoding comma-separated list")
}
if dc != '\\' && dc != ',' {
return errors.New("dns: svcbalpn: bad escaped character decoding comma-separated list")
}
p += dq
c = dc
}
a = append(a, c)
}
// Add the final alpn.
if len(a) == 0 {
return errors.New("dns: svcbalpn: last protocol identifier empty")
}
s.Alpn = append(alpn, string(a))
return nil return nil
} }
@ -370,9 +487,13 @@ func (s *SVCBAlpn) copy() SVCBKeyValue {
} }
// SVCBNoDefaultAlpn pair signifies no support for default connection protocols. // SVCBNoDefaultAlpn pair signifies no support for default connection protocols.
// Should be used in conjunction with alpn.
// Basic use pattern for creating a no-default-alpn option: // Basic use pattern for creating a no-default-alpn option:
// //
// s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}} // s := &dns.SVCB{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}}
// t := new(dns.SVCBAlpn)
// t.Alpn = []string{"xmpp-client"}
// s.Value = append(s.Value, t)
// e := new(dns.SVCBNoDefaultAlpn) // e := new(dns.SVCBNoDefaultAlpn)
// s.Value = append(s.Value, e) // s.Value = append(s.Value, e)
type SVCBNoDefaultAlpn struct{} type SVCBNoDefaultAlpn struct{}
@ -385,14 +506,14 @@ func (*SVCBNoDefaultAlpn) len() int { return 0 }
func (*SVCBNoDefaultAlpn) unpack(b []byte) error { func (*SVCBNoDefaultAlpn) unpack(b []byte) error {
if len(b) != 0 { if len(b) != 0 {
return errors.New("dns: svcbnodefaultalpn: no_default_alpn must have no value") return errors.New("dns: svcbnodefaultalpn: no-default-alpn must have no value")
} }
return nil return nil
} }
func (*SVCBNoDefaultAlpn) parse(b string) error { func (*SVCBNoDefaultAlpn) parse(b string) error {
if b != "" { if b != "" {
return errors.New("dns: svcbnodefaultalpn: no_default_alpn must have no value") return errors.New("dns: svcbnodefaultalpn: no-default-alpn must have no value")
} }
return nil return nil
} }
@ -523,7 +644,7 @@ func (s *SVCBIPv4Hint) copy() SVCBKeyValue {
} }
// SVCBECHConfig pair contains the ECHConfig structure defined in draft-ietf-tls-esni [RFC xxxx]. // SVCBECHConfig pair contains the ECHConfig structure defined in draft-ietf-tls-esni [RFC xxxx].
// Basic use pattern for creating an echconfig option: // Basic use pattern for creating an ech option:
// //
// h := new(dns.HTTPS) // h := new(dns.HTTPS)
// h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET} // h.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeHTTPS, Class: dns.ClassINET}
@ -531,7 +652,7 @@ func (s *SVCBIPv4Hint) copy() SVCBKeyValue {
// e.ECH = []byte{0xfe, 0x08, ...} // e.ECH = []byte{0xfe, 0x08, ...}
// h.Value = append(h.Value, e) // h.Value = append(h.Value, e)
type SVCBECHConfig struct { type SVCBECHConfig struct {
ECH []byte ECH []byte // Specifically ECHConfigList including the redundant length prefix
} }
func (*SVCBECHConfig) Key() SVCBKey { return SVCB_ECHCONFIG } func (*SVCBECHConfig) Key() SVCBKey { return SVCB_ECHCONFIG }
@ -555,7 +676,7 @@ func (s *SVCBECHConfig) unpack(b []byte) error {
func (s *SVCBECHConfig) parse(b string) error { func (s *SVCBECHConfig) parse(b string) error {
x, err := fromBase64([]byte(b)) x, err := fromBase64([]byte(b))
if err != nil { if err != nil {
return errors.New("dns: svcbechconfig: bad base64 echconfig") return errors.New("dns: svcbech: bad base64 ech")
} }
s.ECH = x s.ECH = x
return nil return nil
@ -618,9 +739,6 @@ func (s *SVCBIPv6Hint) String() string {
} }
func (s *SVCBIPv6Hint) parse(b string) error { func (s *SVCBIPv6Hint) parse(b string) error {
if strings.Contains(b, ".") {
return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4")
}
str := strings.Split(b, ",") str := strings.Split(b, ",")
dst := make([]net.IP, len(str)) dst := make([]net.IP, len(str))
for i, e := range str { for i, e := range str {
@ -628,6 +746,9 @@ func (s *SVCBIPv6Hint) parse(b string) error {
if ip == nil { if ip == nil {
return errors.New("dns: svcbipv6hint: bad ip") return errors.New("dns: svcbipv6hint: bad ip")
} }
if ip.To4() != nil {
return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4-mapped-ipv6")
}
dst[i] = ip dst[i] = ip
} }
s.Hint = dst s.Hint = dst
@ -645,6 +766,54 @@ func (s *SVCBIPv6Hint) copy() SVCBKeyValue {
} }
} }
// SVCBDoHPath pair is used to indicate the URI template that the
// clients may use to construct a DNS over HTTPS URI.
//
// See RFC xxxx (https://datatracker.ietf.org/doc/html/draft-ietf-add-svcb-dns-02)
// and RFC yyyy (https://datatracker.ietf.org/doc/html/draft-ietf-add-ddr-06).
//
// A basic example of using the dohpath option together with the alpn
// option to indicate support for DNS over HTTPS on a certain path:
//
// s := new(dns.SVCB)
// s.Hdr = dns.RR_Header{Name: ".", Rrtype: dns.TypeSVCB, Class: dns.ClassINET}
// e := new(dns.SVCBAlpn)
// e.Alpn = []string{"h2", "h3"}
// p := new(dns.SVCBDoHPath)
// p.Template = "/dns-query{?dns}"
// s.Value = append(s.Value, e, p)
//
// The parsing currently doesn't validate that Template is a valid
// RFC 6570 URI template.
type SVCBDoHPath struct {
Template string
}
func (*SVCBDoHPath) Key() SVCBKey { return SVCB_DOHPATH }
func (s *SVCBDoHPath) String() string { return svcbParamToStr([]byte(s.Template)) }
func (s *SVCBDoHPath) len() int { return len(s.Template) }
func (s *SVCBDoHPath) pack() ([]byte, error) { return []byte(s.Template), nil }
func (s *SVCBDoHPath) unpack(b []byte) error {
s.Template = string(b)
return nil
}
func (s *SVCBDoHPath) parse(b string) error {
template, err := svcbParseParam(b)
if err != nil {
return fmt.Errorf("dns: svcbdohpath: %w", err)
}
s.Template = string(template)
return nil
}
func (s *SVCBDoHPath) copy() SVCBKeyValue {
return &SVCBDoHPath{
Template: s.Template,
}
}
// SVCBLocal pair is intended for experimental/private use. The key is recommended // SVCBLocal pair is intended for experimental/private use. The key is recommended
// to be in the range [SVCB_PRIVATE_LOWER, SVCB_PRIVATE_UPPER]. // to be in the range [SVCB_PRIVATE_LOWER, SVCB_PRIVATE_UPPER].
// Basic use pattern for creating a keyNNNNN option: // Basic use pattern for creating a keyNNNNN option:
@ -661,6 +830,7 @@ type SVCBLocal struct {
} }
func (s *SVCBLocal) Key() SVCBKey { return s.KeyCode } func (s *SVCBLocal) Key() SVCBKey { return s.KeyCode }
func (s *SVCBLocal) String() string { return svcbParamToStr(s.Data) }
func (s *SVCBLocal) pack() ([]byte, error) { return append([]byte(nil), s.Data...), nil } func (s *SVCBLocal) pack() ([]byte, error) { return append([]byte(nil), s.Data...), nil }
func (s *SVCBLocal) len() int { return len(s.Data) } func (s *SVCBLocal) len() int { return len(s.Data) }
@ -669,50 +839,10 @@ func (s *SVCBLocal) unpack(b []byte) error {
return nil return nil
} }
func (s *SVCBLocal) String() string {
var str strings.Builder
str.Grow(4 * len(s.Data))
for _, e := range s.Data {
if ' ' <= e && e <= '~' {
switch e {
case '"', ';', ' ', '\\':
str.WriteByte('\\')
str.WriteByte(e)
default:
str.WriteByte(e)
}
} else {
str.WriteString(escapeByte(e))
}
}
return str.String()
}
func (s *SVCBLocal) parse(b string) error { func (s *SVCBLocal) parse(b string) error {
data := make([]byte, 0, len(b)) data, err := svcbParseParam(b)
for i := 0; i < len(b); { if err != nil {
if b[i] != '\\' { return fmt.Errorf("dns: svcblocal: svcb private/experimental key %w", err)
data = append(data, b[i])
i++
continue
}
if i+1 == len(b) {
return errors.New("dns: svcblocal: svcb private/experimental key escape unterminated")
}
if isDigit(b[i+1]) {
if i+3 < len(b) && isDigit(b[i+2]) && isDigit(b[i+3]) {
a, err := strconv.ParseUint(b[i+1:i+4], 10, 8)
if err == nil {
i += 4
data = append(data, byte(a))
continue
}
}
return errors.New("dns: svcblocal: svcb private/experimental key bad escaped octet")
} else {
data = append(data, b[i+1])
i += 2
}
} }
s.Data = data s.Data = data
return nil return nil
@ -753,3 +883,53 @@ func areSVCBPairArraysEqual(a []SVCBKeyValue, b []SVCBKeyValue) bool {
} }
return true return true
} }
// svcbParamStr converts the value of an SVCB parameter into a DNS presentation-format string.
func svcbParamToStr(s []byte) string {
var str strings.Builder
str.Grow(4 * len(s))
for _, e := range s {
if ' ' <= e && e <= '~' {
switch e {
case '"', ';', ' ', '\\':
str.WriteByte('\\')
str.WriteByte(e)
default:
str.WriteByte(e)
}
} else {
str.WriteString(escapeByte(e))
}
}
return str.String()
}
// svcbParseParam parses a DNS presentation-format string into an SVCB parameter value.
func svcbParseParam(b string) ([]byte, error) {
data := make([]byte, 0, len(b))
for i := 0; i < len(b); {
if b[i] != '\\' {
data = append(data, b[i])
i++
continue
}
if i+1 == len(b) {
return nil, errors.New("escape unterminated")
}
if isDigit(b[i+1]) {
if i+3 < len(b) && isDigit(b[i+2]) && isDigit(b[i+3]) {
a, err := strconv.ParseUint(b[i+1:i+4], 10, 8)
if err == nil {
i += 4
data = append(data, byte(a))
continue
}
}
return nil, errors.New("bad escaped octet")
} else {
data = append(data, b[i+1])
i += 2
}
}
return data, nil
}

26
vendor/github.com/miekg/dns/tsig.go generated vendored
View file

@ -158,18 +158,17 @@ type timerWireFmt struct {
} }
// TsigGenerate fills out the TSIG record attached to the message. // TsigGenerate fills out the TSIG record attached to the message.
// The message should contain // The message should contain a "stub" TSIG RR with the algorithm, key name
// a "stub" TSIG RR with the algorithm, key name (owner name of the RR), // (owner name of the RR), time fudge (defaults to 300 seconds) and the current
// time fudge (defaults to 300 seconds) and the current time // time The TSIG MAC is saved in that Tsig RR. When TsigGenerate is called for
// The TSIG MAC is saved in that Tsig RR. // the first time requestMAC should be set to the empty string and timersOnly to
// When TsigGenerate is called for the first time requestMAC is set to the empty string and // false.
// timersOnly is false.
// If something goes wrong an error is returned, otherwise it is nil.
func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) { func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) {
return tsigGenerateProvider(m, tsigHMACProvider(secret), requestMAC, timersOnly) return TsigGenerateWithProvider(m, tsigHMACProvider(secret), requestMAC, timersOnly)
} }
func tsigGenerateProvider(m *Msg, provider TsigProvider, requestMAC string, timersOnly bool) ([]byte, string, error) { // TsigGenerateWithProvider is similar to TsigGenerate, but allows for a custom TsigProvider.
func TsigGenerateWithProvider(m *Msg, provider TsigProvider, requestMAC string, timersOnly bool) ([]byte, string, error) {
if m.IsTsig() == nil { if m.IsTsig() == nil {
panic("dns: TSIG not last RR in additional") panic("dns: TSIG not last RR in additional")
} }
@ -216,14 +215,15 @@ func tsigGenerateProvider(m *Msg, provider TsigProvider, requestMAC string, time
return mbuf, t.MAC, nil return mbuf, t.MAC, nil
} }
// TsigVerify verifies the TSIG on a message. // TsigVerify verifies the TSIG on a message. If the signature does not
// If the signature does not validate err contains the // validate the returned error contains the cause. If the signature is OK, the
// error, otherwise it is nil. // error is nil.
func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error { func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error {
return tsigVerify(msg, tsigHMACProvider(secret), requestMAC, timersOnly, uint64(time.Now().Unix())) return tsigVerify(msg, tsigHMACProvider(secret), requestMAC, timersOnly, uint64(time.Now().Unix()))
} }
func tsigVerifyProvider(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool) error { // TsigVerifyWithProvider is similar to TsigVerify, but allows for a custom TsigProvider.
func TsigVerifyWithProvider(msg []byte, provider TsigProvider, requestMAC string, timersOnly bool) error {
return tsigVerify(msg, provider, requestMAC, timersOnly, uint64(time.Now().Unix())) return tsigVerify(msg, provider, requestMAC, timersOnly, uint64(time.Now().Unix()))
} }

View file

@ -3,7 +3,7 @@ package dns
import "fmt" import "fmt"
// Version is current version of this library. // Version is current version of this library.
var Version = v{1, 1, 46} var Version = v{1, 1, 50}
// v holds the version of this library. // v holds the version of this library.
type v struct { type v struct {

4
vendor/github.com/miekg/dns/xfr.go generated vendored
View file

@ -237,7 +237,7 @@ func (t *Transfer) ReadMsg() (*Msg, error) {
} }
if ts, tp := m.IsTsig(), t.tsigProvider(); ts != nil && tp != nil { if ts, tp := m.IsTsig(), t.tsigProvider(); ts != nil && tp != nil {
// Need to work on the original message p, as that was used to calculate the tsig. // Need to work on the original message p, as that was used to calculate the tsig.
err = tsigVerifyProvider(p, tp, t.tsigRequestMAC, t.tsigTimersOnly) err = TsigVerifyWithProvider(p, tp, t.tsigRequestMAC, t.tsigTimersOnly)
t.tsigRequestMAC = ts.MAC t.tsigRequestMAC = ts.MAC
} }
return m, err return m, err
@ -247,7 +247,7 @@ func (t *Transfer) ReadMsg() (*Msg, error) {
func (t *Transfer) WriteMsg(m *Msg) (err error) { func (t *Transfer) WriteMsg(m *Msg) (err error) {
var out []byte var out []byte
if ts, tp := m.IsTsig(), t.tsigProvider(); ts != nil && tp != nil { if ts, tp := m.IsTsig(), t.tsigProvider(); ts != nil && tp != nil {
out, t.tsigRequestMAC, err = tsigGenerateProvider(m, tp, t.tsigRequestMAC, t.tsigTimersOnly) out, t.tsigRequestMAC, err = TsigGenerateWithProvider(m, tp, t.tsigRequestMAC, t.tsigTimersOnly)
} else { } else {
out, err = m.Pack() out, err = m.Pack()
} }

View file

@ -1,5 +1,5 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// source: metrics.proto // source: io/prometheus/client/metrics.proto
package io_prometheus_client package io_prometheus_client
@ -24,11 +24,18 @@ const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type MetricType int32 type MetricType int32
const ( const (
MetricType_COUNTER MetricType = 0 // COUNTER must use the Metric field "counter".
MetricType_GAUGE MetricType = 1 MetricType_COUNTER MetricType = 0
MetricType_SUMMARY MetricType = 2 // GAUGE must use the Metric field "gauge".
MetricType_UNTYPED MetricType = 3 MetricType_GAUGE MetricType = 1
// SUMMARY must use the Metric field "summary".
MetricType_SUMMARY MetricType = 2
// UNTYPED must use the Metric field "untyped".
MetricType_UNTYPED MetricType = 3
// HISTOGRAM must use the Metric field "histogram".
MetricType_HISTOGRAM MetricType = 4 MetricType_HISTOGRAM MetricType = 4
// GAUGE_HISTOGRAM must use the Metric field "histogram".
MetricType_GAUGE_HISTOGRAM MetricType = 5
) )
var MetricType_name = map[int32]string{ var MetricType_name = map[int32]string{
@ -37,14 +44,16 @@ var MetricType_name = map[int32]string{
2: "SUMMARY", 2: "SUMMARY",
3: "UNTYPED", 3: "UNTYPED",
4: "HISTOGRAM", 4: "HISTOGRAM",
5: "GAUGE_HISTOGRAM",
} }
var MetricType_value = map[string]int32{ var MetricType_value = map[string]int32{
"COUNTER": 0, "COUNTER": 0,
"GAUGE": 1, "GAUGE": 1,
"SUMMARY": 2, "SUMMARY": 2,
"UNTYPED": 3, "UNTYPED": 3,
"HISTOGRAM": 4, "HISTOGRAM": 4,
"GAUGE_HISTOGRAM": 5,
} }
func (x MetricType) Enum() *MetricType { func (x MetricType) Enum() *MetricType {
@ -67,7 +76,7 @@ func (x *MetricType) UnmarshalJSON(data []byte) error {
} }
func (MetricType) EnumDescriptor() ([]byte, []int) { func (MetricType) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_6039342a2ba47b72, []int{0} return fileDescriptor_d1e5ddb18987a258, []int{0}
} }
type LabelPair struct { type LabelPair struct {
@ -82,7 +91,7 @@ func (m *LabelPair) Reset() { *m = LabelPair{} }
func (m *LabelPair) String() string { return proto.CompactTextString(m) } func (m *LabelPair) String() string { return proto.CompactTextString(m) }
func (*LabelPair) ProtoMessage() {} func (*LabelPair) ProtoMessage() {}
func (*LabelPair) Descriptor() ([]byte, []int) { func (*LabelPair) Descriptor() ([]byte, []int) {
return fileDescriptor_6039342a2ba47b72, []int{0} return fileDescriptor_d1e5ddb18987a258, []int{0}
} }
func (m *LabelPair) XXX_Unmarshal(b []byte) error { func (m *LabelPair) XXX_Unmarshal(b []byte) error {
@ -128,7 +137,7 @@ func (m *Gauge) Reset() { *m = Gauge{} }
func (m *Gauge) String() string { return proto.CompactTextString(m) } func (m *Gauge) String() string { return proto.CompactTextString(m) }
func (*Gauge) ProtoMessage() {} func (*Gauge) ProtoMessage() {}
func (*Gauge) Descriptor() ([]byte, []int) { func (*Gauge) Descriptor() ([]byte, []int) {
return fileDescriptor_6039342a2ba47b72, []int{1} return fileDescriptor_d1e5ddb18987a258, []int{1}
} }
func (m *Gauge) XXX_Unmarshal(b []byte) error { func (m *Gauge) XXX_Unmarshal(b []byte) error {
@ -168,7 +177,7 @@ func (m *Counter) Reset() { *m = Counter{} }
func (m *Counter) String() string { return proto.CompactTextString(m) } func (m *Counter) String() string { return proto.CompactTextString(m) }
func (*Counter) ProtoMessage() {} func (*Counter) ProtoMessage() {}
func (*Counter) Descriptor() ([]byte, []int) { func (*Counter) Descriptor() ([]byte, []int) {
return fileDescriptor_6039342a2ba47b72, []int{2} return fileDescriptor_d1e5ddb18987a258, []int{2}
} }
func (m *Counter) XXX_Unmarshal(b []byte) error { func (m *Counter) XXX_Unmarshal(b []byte) error {
@ -215,7 +224,7 @@ func (m *Quantile) Reset() { *m = Quantile{} }
func (m *Quantile) String() string { return proto.CompactTextString(m) } func (m *Quantile) String() string { return proto.CompactTextString(m) }
func (*Quantile) ProtoMessage() {} func (*Quantile) ProtoMessage() {}
func (*Quantile) Descriptor() ([]byte, []int) { func (*Quantile) Descriptor() ([]byte, []int) {
return fileDescriptor_6039342a2ba47b72, []int{3} return fileDescriptor_d1e5ddb18987a258, []int{3}
} }
func (m *Quantile) XXX_Unmarshal(b []byte) error { func (m *Quantile) XXX_Unmarshal(b []byte) error {
@ -263,7 +272,7 @@ func (m *Summary) Reset() { *m = Summary{} }
func (m *Summary) String() string { return proto.CompactTextString(m) } func (m *Summary) String() string { return proto.CompactTextString(m) }
func (*Summary) ProtoMessage() {} func (*Summary) ProtoMessage() {}
func (*Summary) Descriptor() ([]byte, []int) { func (*Summary) Descriptor() ([]byte, []int) {
return fileDescriptor_6039342a2ba47b72, []int{4} return fileDescriptor_d1e5ddb18987a258, []int{4}
} }
func (m *Summary) XXX_Unmarshal(b []byte) error { func (m *Summary) XXX_Unmarshal(b []byte) error {
@ -316,7 +325,7 @@ func (m *Untyped) Reset() { *m = Untyped{} }
func (m *Untyped) String() string { return proto.CompactTextString(m) } func (m *Untyped) String() string { return proto.CompactTextString(m) }
func (*Untyped) ProtoMessage() {} func (*Untyped) ProtoMessage() {}
func (*Untyped) Descriptor() ([]byte, []int) { func (*Untyped) Descriptor() ([]byte, []int) {
return fileDescriptor_6039342a2ba47b72, []int{5} return fileDescriptor_d1e5ddb18987a258, []int{5}
} }
func (m *Untyped) XXX_Unmarshal(b []byte) error { func (m *Untyped) XXX_Unmarshal(b []byte) error {
@ -345,9 +354,34 @@ func (m *Untyped) GetValue() float64 {
} }
type Histogram struct { type Histogram struct {
SampleCount *uint64 `protobuf:"varint,1,opt,name=sample_count,json=sampleCount" json:"sample_count,omitempty"` SampleCount *uint64 `protobuf:"varint,1,opt,name=sample_count,json=sampleCount" json:"sample_count,omitempty"`
SampleSum *float64 `protobuf:"fixed64,2,opt,name=sample_sum,json=sampleSum" json:"sample_sum,omitempty"` SampleCountFloat *float64 `protobuf:"fixed64,4,opt,name=sample_count_float,json=sampleCountFloat" json:"sample_count_float,omitempty"`
Bucket []*Bucket `protobuf:"bytes,3,rep,name=bucket" json:"bucket,omitempty"` SampleSum *float64 `protobuf:"fixed64,2,opt,name=sample_sum,json=sampleSum" json:"sample_sum,omitempty"`
// Buckets for the conventional histogram.
Bucket []*Bucket `protobuf:"bytes,3,rep,name=bucket" json:"bucket,omitempty"`
// schema defines the bucket schema. Currently, valid numbers are -4 <= n <= 8.
// They are all for base-2 bucket schemas, where 1 is a bucket boundary in each case, and
// then each power of two is divided into 2^n logarithmic buckets.
// Or in other words, each bucket boundary is the previous boundary times 2^(2^-n).
// In the future, more bucket schemas may be added using numbers < -4 or > 8.
Schema *int32 `protobuf:"zigzag32,5,opt,name=schema" json:"schema,omitempty"`
ZeroThreshold *float64 `protobuf:"fixed64,6,opt,name=zero_threshold,json=zeroThreshold" json:"zero_threshold,omitempty"`
ZeroCount *uint64 `protobuf:"varint,7,opt,name=zero_count,json=zeroCount" json:"zero_count,omitempty"`
ZeroCountFloat *float64 `protobuf:"fixed64,8,opt,name=zero_count_float,json=zeroCountFloat" json:"zero_count_float,omitempty"`
// Negative buckets for the native histogram.
NegativeSpan []*BucketSpan `protobuf:"bytes,9,rep,name=negative_span,json=negativeSpan" json:"negative_span,omitempty"`
// Use either "negative_delta" or "negative_count", the former for
// regular histograms with integer counts, the latter for float
// histograms.
NegativeDelta []int64 `protobuf:"zigzag64,10,rep,name=negative_delta,json=negativeDelta" json:"negative_delta,omitempty"`
NegativeCount []float64 `protobuf:"fixed64,11,rep,name=negative_count,json=negativeCount" json:"negative_count,omitempty"`
// Positive buckets for the native histogram.
PositiveSpan []*BucketSpan `protobuf:"bytes,12,rep,name=positive_span,json=positiveSpan" json:"positive_span,omitempty"`
// Use either "positive_delta" or "positive_count", the former for
// regular histograms with integer counts, the latter for float
// histograms.
PositiveDelta []int64 `protobuf:"zigzag64,13,rep,name=positive_delta,json=positiveDelta" json:"positive_delta,omitempty"`
PositiveCount []float64 `protobuf:"fixed64,14,rep,name=positive_count,json=positiveCount" json:"positive_count,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"` XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"` XXX_sizecache int32 `json:"-"`
@ -357,7 +391,7 @@ func (m *Histogram) Reset() { *m = Histogram{} }
func (m *Histogram) String() string { return proto.CompactTextString(m) } func (m *Histogram) String() string { return proto.CompactTextString(m) }
func (*Histogram) ProtoMessage() {} func (*Histogram) ProtoMessage() {}
func (*Histogram) Descriptor() ([]byte, []int) { func (*Histogram) Descriptor() ([]byte, []int) {
return fileDescriptor_6039342a2ba47b72, []int{6} return fileDescriptor_d1e5ddb18987a258, []int{6}
} }
func (m *Histogram) XXX_Unmarshal(b []byte) error { func (m *Histogram) XXX_Unmarshal(b []byte) error {
@ -385,6 +419,13 @@ func (m *Histogram) GetSampleCount() uint64 {
return 0 return 0
} }
func (m *Histogram) GetSampleCountFloat() float64 {
if m != nil && m.SampleCountFloat != nil {
return *m.SampleCountFloat
}
return 0
}
func (m *Histogram) GetSampleSum() float64 { func (m *Histogram) GetSampleSum() float64 {
if m != nil && m.SampleSum != nil { if m != nil && m.SampleSum != nil {
return *m.SampleSum return *m.SampleSum
@ -399,8 +440,81 @@ func (m *Histogram) GetBucket() []*Bucket {
return nil return nil
} }
func (m *Histogram) GetSchema() int32 {
if m != nil && m.Schema != nil {
return *m.Schema
}
return 0
}
func (m *Histogram) GetZeroThreshold() float64 {
if m != nil && m.ZeroThreshold != nil {
return *m.ZeroThreshold
}
return 0
}
func (m *Histogram) GetZeroCount() uint64 {
if m != nil && m.ZeroCount != nil {
return *m.ZeroCount
}
return 0
}
func (m *Histogram) GetZeroCountFloat() float64 {
if m != nil && m.ZeroCountFloat != nil {
return *m.ZeroCountFloat
}
return 0
}
func (m *Histogram) GetNegativeSpan() []*BucketSpan {
if m != nil {
return m.NegativeSpan
}
return nil
}
func (m *Histogram) GetNegativeDelta() []int64 {
if m != nil {
return m.NegativeDelta
}
return nil
}
func (m *Histogram) GetNegativeCount() []float64 {
if m != nil {
return m.NegativeCount
}
return nil
}
func (m *Histogram) GetPositiveSpan() []*BucketSpan {
if m != nil {
return m.PositiveSpan
}
return nil
}
func (m *Histogram) GetPositiveDelta() []int64 {
if m != nil {
return m.PositiveDelta
}
return nil
}
func (m *Histogram) GetPositiveCount() []float64 {
if m != nil {
return m.PositiveCount
}
return nil
}
// A Bucket of a conventional histogram, each of which is treated as
// an individual counter-like time series by Prometheus.
type Bucket struct { type Bucket struct {
CumulativeCount *uint64 `protobuf:"varint,1,opt,name=cumulative_count,json=cumulativeCount" json:"cumulative_count,omitempty"` CumulativeCount *uint64 `protobuf:"varint,1,opt,name=cumulative_count,json=cumulativeCount" json:"cumulative_count,omitempty"`
CumulativeCountFloat *float64 `protobuf:"fixed64,4,opt,name=cumulative_count_float,json=cumulativeCountFloat" json:"cumulative_count_float,omitempty"`
UpperBound *float64 `protobuf:"fixed64,2,opt,name=upper_bound,json=upperBound" json:"upper_bound,omitempty"` UpperBound *float64 `protobuf:"fixed64,2,opt,name=upper_bound,json=upperBound" json:"upper_bound,omitempty"`
Exemplar *Exemplar `protobuf:"bytes,3,opt,name=exemplar" json:"exemplar,omitempty"` Exemplar *Exemplar `protobuf:"bytes,3,opt,name=exemplar" json:"exemplar,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_NoUnkeyedLiteral struct{} `json:"-"`
@ -412,7 +526,7 @@ func (m *Bucket) Reset() { *m = Bucket{} }
func (m *Bucket) String() string { return proto.CompactTextString(m) } func (m *Bucket) String() string { return proto.CompactTextString(m) }
func (*Bucket) ProtoMessage() {} func (*Bucket) ProtoMessage() {}
func (*Bucket) Descriptor() ([]byte, []int) { func (*Bucket) Descriptor() ([]byte, []int) {
return fileDescriptor_6039342a2ba47b72, []int{7} return fileDescriptor_d1e5ddb18987a258, []int{7}
} }
func (m *Bucket) XXX_Unmarshal(b []byte) error { func (m *Bucket) XXX_Unmarshal(b []byte) error {
@ -440,6 +554,13 @@ func (m *Bucket) GetCumulativeCount() uint64 {
return 0 return 0
} }
func (m *Bucket) GetCumulativeCountFloat() float64 {
if m != nil && m.CumulativeCountFloat != nil {
return *m.CumulativeCountFloat
}
return 0
}
func (m *Bucket) GetUpperBound() float64 { func (m *Bucket) GetUpperBound() float64 {
if m != nil && m.UpperBound != nil { if m != nil && m.UpperBound != nil {
return *m.UpperBound return *m.UpperBound
@ -454,6 +575,59 @@ func (m *Bucket) GetExemplar() *Exemplar {
return nil return nil
} }
// A BucketSpan defines a number of consecutive buckets in a native
// histogram with their offset. Logically, it would be more
// straightforward to include the bucket counts in the Span. However,
// the protobuf representation is more compact in the way the data is
// structured here (with all the buckets in a single array separate
// from the Spans).
type BucketSpan struct {
Offset *int32 `protobuf:"zigzag32,1,opt,name=offset" json:"offset,omitempty"`
Length *uint32 `protobuf:"varint,2,opt,name=length" json:"length,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *BucketSpan) Reset() { *m = BucketSpan{} }
func (m *BucketSpan) String() string { return proto.CompactTextString(m) }
func (*BucketSpan) ProtoMessage() {}
func (*BucketSpan) Descriptor() ([]byte, []int) {
return fileDescriptor_d1e5ddb18987a258, []int{8}
}
func (m *BucketSpan) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_BucketSpan.Unmarshal(m, b)
}
func (m *BucketSpan) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_BucketSpan.Marshal(b, m, deterministic)
}
func (m *BucketSpan) XXX_Merge(src proto.Message) {
xxx_messageInfo_BucketSpan.Merge(m, src)
}
func (m *BucketSpan) XXX_Size() int {
return xxx_messageInfo_BucketSpan.Size(m)
}
func (m *BucketSpan) XXX_DiscardUnknown() {
xxx_messageInfo_BucketSpan.DiscardUnknown(m)
}
var xxx_messageInfo_BucketSpan proto.InternalMessageInfo
func (m *BucketSpan) GetOffset() int32 {
if m != nil && m.Offset != nil {
return *m.Offset
}
return 0
}
func (m *BucketSpan) GetLength() uint32 {
if m != nil && m.Length != nil {
return *m.Length
}
return 0
}
type Exemplar struct { type Exemplar struct {
Label []*LabelPair `protobuf:"bytes,1,rep,name=label" json:"label,omitempty"` Label []*LabelPair `protobuf:"bytes,1,rep,name=label" json:"label,omitempty"`
Value *float64 `protobuf:"fixed64,2,opt,name=value" json:"value,omitempty"` Value *float64 `protobuf:"fixed64,2,opt,name=value" json:"value,omitempty"`
@ -467,7 +641,7 @@ func (m *Exemplar) Reset() { *m = Exemplar{} }
func (m *Exemplar) String() string { return proto.CompactTextString(m) } func (m *Exemplar) String() string { return proto.CompactTextString(m) }
func (*Exemplar) ProtoMessage() {} func (*Exemplar) ProtoMessage() {}
func (*Exemplar) Descriptor() ([]byte, []int) { func (*Exemplar) Descriptor() ([]byte, []int) {
return fileDescriptor_6039342a2ba47b72, []int{8} return fileDescriptor_d1e5ddb18987a258, []int{9}
} }
func (m *Exemplar) XXX_Unmarshal(b []byte) error { func (m *Exemplar) XXX_Unmarshal(b []byte) error {
@ -526,7 +700,7 @@ func (m *Metric) Reset() { *m = Metric{} }
func (m *Metric) String() string { return proto.CompactTextString(m) } func (m *Metric) String() string { return proto.CompactTextString(m) }
func (*Metric) ProtoMessage() {} func (*Metric) ProtoMessage() {}
func (*Metric) Descriptor() ([]byte, []int) { func (*Metric) Descriptor() ([]byte, []int) {
return fileDescriptor_6039342a2ba47b72, []int{9} return fileDescriptor_d1e5ddb18987a258, []int{10}
} }
func (m *Metric) XXX_Unmarshal(b []byte) error { func (m *Metric) XXX_Unmarshal(b []byte) error {
@ -610,7 +784,7 @@ func (m *MetricFamily) Reset() { *m = MetricFamily{} }
func (m *MetricFamily) String() string { return proto.CompactTextString(m) } func (m *MetricFamily) String() string { return proto.CompactTextString(m) }
func (*MetricFamily) ProtoMessage() {} func (*MetricFamily) ProtoMessage() {}
func (*MetricFamily) Descriptor() ([]byte, []int) { func (*MetricFamily) Descriptor() ([]byte, []int) {
return fileDescriptor_6039342a2ba47b72, []int{10} return fileDescriptor_d1e5ddb18987a258, []int{11}
} }
func (m *MetricFamily) XXX_Unmarshal(b []byte) error { func (m *MetricFamily) XXX_Unmarshal(b []byte) error {
@ -669,55 +843,72 @@ func init() {
proto.RegisterType((*Untyped)(nil), "io.prometheus.client.Untyped") proto.RegisterType((*Untyped)(nil), "io.prometheus.client.Untyped")
proto.RegisterType((*Histogram)(nil), "io.prometheus.client.Histogram") proto.RegisterType((*Histogram)(nil), "io.prometheus.client.Histogram")
proto.RegisterType((*Bucket)(nil), "io.prometheus.client.Bucket") proto.RegisterType((*Bucket)(nil), "io.prometheus.client.Bucket")
proto.RegisterType((*BucketSpan)(nil), "io.prometheus.client.BucketSpan")
proto.RegisterType((*Exemplar)(nil), "io.prometheus.client.Exemplar") proto.RegisterType((*Exemplar)(nil), "io.prometheus.client.Exemplar")
proto.RegisterType((*Metric)(nil), "io.prometheus.client.Metric") proto.RegisterType((*Metric)(nil), "io.prometheus.client.Metric")
proto.RegisterType((*MetricFamily)(nil), "io.prometheus.client.MetricFamily") proto.RegisterType((*MetricFamily)(nil), "io.prometheus.client.MetricFamily")
} }
func init() { proto.RegisterFile("metrics.proto", fileDescriptor_6039342a2ba47b72) } func init() {
proto.RegisterFile("io/prometheus/client/metrics.proto", fileDescriptor_d1e5ddb18987a258)
var fileDescriptor_6039342a2ba47b72 = []byte{ }
// 665 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0xcd, 0x6e, 0xd3, 0x4c, var fileDescriptor_d1e5ddb18987a258 = []byte{
0x14, 0xfd, 0xdc, 0x38, 0x3f, 0xbe, 0x69, 0x3f, 0xa2, 0x51, 0x17, 0x56, 0xa1, 0x24, 0x78, 0x55, // 896 bytes of a gzipped FileDescriptorProto
0x58, 0x38, 0xa2, 0x6a, 0x05, 0x2a, 0xb0, 0x68, 0x4b, 0x48, 0x91, 0x48, 0x5b, 0x26, 0xc9, 0xa2, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0xdd, 0x8e, 0xdb, 0x44,
0xb0, 0x88, 0x1c, 0x77, 0x70, 0x2c, 0x3c, 0xb1, 0xb1, 0x67, 0x2a, 0xb2, 0x66, 0xc1, 0x16, 0x5e, 0x18, 0xc5, 0x9b, 0x5f, 0x7f, 0xd9, 0x6c, 0xd3, 0x61, 0x55, 0x59, 0x0b, 0xcb, 0x06, 0x4b, 0x48,
0x81, 0x17, 0x05, 0xcd, 0x8f, 0x6d, 0x2a, 0xb9, 0x95, 0x40, 0xec, 0x66, 0xee, 0x3d, 0xe7, 0xfa, 0x0b, 0x42, 0x8e, 0x40, 0x5b, 0x81, 0x0a, 0x5c, 0xec, 0xb6, 0xe9, 0x16, 0x89, 0xb4, 0x65, 0x92,
0xcc, 0xf8, 0x9c, 0x81, 0x0d, 0x4a, 0x58, 0x1a, 0xfa, 0x99, 0x9b, 0xa4, 0x31, 0x8b, 0xd1, 0x66, 0x5c, 0x14, 0x2e, 0xac, 0x49, 0x32, 0xeb, 0x58, 0x78, 0x3c, 0xc6, 0x1e, 0x57, 0x2c, 0x2f, 0xc0,
0x18, 0x8b, 0x15, 0x25, 0x6c, 0x41, 0x78, 0xe6, 0xfa, 0x51, 0x48, 0x96, 0x6c, 0xab, 0x1b, 0xc4, 0x35, 0xaf, 0xc0, 0xc3, 0xf0, 0x22, 0x3c, 0x08, 0x68, 0xfe, 0xec, 0xdd, 0xe2, 0x94, 0xd2, 0x3b,
0x71, 0x10, 0x91, 0xbe, 0xc4, 0xcc, 0xf9, 0x87, 0x3e, 0x0b, 0x29, 0xc9, 0x98, 0x47, 0x13, 0x45, 0x7f, 0x67, 0xce, 0xf7, 0xcd, 0x39, 0xe3, 0xc9, 0x71, 0xc0, 0x8f, 0xf9, 0x24, 0xcb, 0x39, 0xa3,
0x73, 0xf6, 0xc1, 0x7a, 0xe3, 0xcd, 0x49, 0x74, 0xee, 0x85, 0x29, 0x42, 0x60, 0x2e, 0x3d, 0x4a, 0x62, 0x4b, 0xcb, 0x62, 0xb2, 0x4e, 0x62, 0x9a, 0x8a, 0x09, 0xa3, 0x22, 0x8f, 0xd7, 0x45, 0x90,
0x6c, 0xa3, 0x67, 0xec, 0x58, 0x58, 0xae, 0xd1, 0x26, 0xd4, 0xaf, 0xbc, 0x88, 0x13, 0x7b, 0x4d, 0xe5, 0x5c, 0x70, 0x74, 0x18, 0xf3, 0xa0, 0xe6, 0x04, 0x9a, 0x73, 0x74, 0x12, 0x71, 0x1e, 0x25,
0x16, 0xd5, 0xc6, 0xd9, 0x86, 0xfa, 0xd0, 0xe3, 0xc1, 0x6f, 0x6d, 0xc1, 0x31, 0xf2, 0xf6, 0x7b, 0x74, 0xa2, 0x38, 0xab, 0xf2, 0x6a, 0x22, 0x62, 0x46, 0x0b, 0x41, 0x58, 0xa6, 0xdb, 0xfc, 0xfb,
0x68, 0x1e, 0xc7, 0x7c, 0xc9, 0x48, 0x5a, 0x0d, 0x40, 0x07, 0xd0, 0x22, 0x9f, 0x09, 0x4d, 0x22, 0xe0, 0x7e, 0x47, 0x56, 0x34, 0x79, 0x4e, 0xe2, 0x1c, 0x21, 0x68, 0xa7, 0x84, 0x51, 0xcf, 0x19,
0x2f, 0x95, 0x83, 0xdb, 0xbb, 0xf7, 0xdd, 0xaa, 0x03, 0xb8, 0x03, 0x8d, 0xc2, 0x05, 0xde, 0x79, 0x3b, 0xa7, 0x2e, 0x56, 0xcf, 0xe8, 0x10, 0x3a, 0x2f, 0x49, 0x52, 0x52, 0x6f, 0x4f, 0x81, 0xba,
0x0e, 0xad, 0xb7, 0xdc, 0x5b, 0xb2, 0x30, 0x22, 0x68, 0x0b, 0x5a, 0x9f, 0xf4, 0x5a, 0x7f, 0xa0, 0xf0, 0x8f, 0xa1, 0x73, 0x49, 0xca, 0xe8, 0xc6, 0xb2, 0xec, 0x71, 0xec, 0xf2, 0x8f, 0xd0, 0x7b,
0xd8, 0x5f, 0x57, 0x5e, 0x48, 0xfb, 0x6a, 0x40, 0x73, 0xcc, 0x29, 0xf5, 0xd2, 0x15, 0x7a, 0x00, 0xc8, 0xcb, 0x54, 0xd0, 0xbc, 0x99, 0x80, 0x1e, 0x40, 0x9f, 0xfe, 0x42, 0x59, 0x96, 0x90, 0x5c,
0xeb, 0x99, 0x47, 0x93, 0x88, 0xcc, 0x7c, 0xa1, 0x56, 0x4e, 0x30, 0x71, 0x5b, 0xd5, 0xe4, 0x01, 0x0d, 0x1e, 0x7c, 0xfe, 0x41, 0xd0, 0x64, 0x20, 0x98, 0x1a, 0x16, 0xae, 0xf8, 0xfe, 0xd7, 0xd0,
0xd0, 0x36, 0x80, 0x86, 0x64, 0x9c, 0xea, 0x49, 0x96, 0xaa, 0x8c, 0x39, 0x15, 0xe7, 0x28, 0xbe, 0xff, 0xbe, 0x24, 0xa9, 0x88, 0x13, 0x8a, 0x8e, 0xa0, 0xff, 0xb3, 0x79, 0x36, 0x1b, 0x54, 0xf5,
0x5f, 0xeb, 0xd5, 0x6e, 0x3e, 0x47, 0xae, 0xb8, 0xd4, 0xe7, 0x74, 0xa1, 0x39, 0x5d, 0xb2, 0x55, 0x6d, 0xe5, 0x95, 0xb4, 0xdf, 0x1c, 0xe8, 0xcd, 0x4b, 0xc6, 0x48, 0x7e, 0x8d, 0x3e, 0x84, 0xfd,
0x42, 0x2e, 0x6f, 0xb8, 0xc5, 0x2f, 0x06, 0x58, 0x27, 0x61, 0xc6, 0xe2, 0x20, 0xf5, 0xe8, 0x3f, 0x82, 0xb0, 0x2c, 0xa1, 0xe1, 0x5a, 0xaa, 0x55, 0x13, 0xda, 0x78, 0xa0, 0x31, 0x65, 0x00, 0x1d,
0x10, 0xbb, 0x07, 0x8d, 0x39, 0xf7, 0x3f, 0x12, 0xa6, 0xa5, 0xde, 0xab, 0x96, 0x7a, 0x24, 0x31, 0x03, 0x18, 0x4a, 0x51, 0x32, 0x33, 0xc9, 0xd5, 0xc8, 0xbc, 0x64, 0xd2, 0x47, 0xb5, 0x7f, 0x6b,
0x58, 0x63, 0x9d, 0x6f, 0x06, 0x34, 0x54, 0x09, 0x3d, 0x84, 0x8e, 0xcf, 0x29, 0x8f, 0x3c, 0x16, 0xdc, 0xda, 0xed, 0xc3, 0x2a, 0xae, 0xf5, 0xf9, 0x27, 0xd0, 0x5b, 0xa6, 0xe2, 0x3a, 0xa3, 0x9b,
0x5e, 0x5d, 0x97, 0x71, 0xa7, 0xac, 0x2b, 0x29, 0x5d, 0x68, 0xf3, 0x24, 0x21, 0xe9, 0x6c, 0x1e, 0x1d, 0xa7, 0xf8, 0x57, 0x1b, 0xdc, 0x27, 0x71, 0x21, 0x78, 0x94, 0x13, 0xf6, 0x26, 0x62, 0x3f,
0xf3, 0xe5, 0xa5, 0xd6, 0x02, 0xb2, 0x74, 0x24, 0x2a, 0xd7, 0x1c, 0x50, 0xfb, 0x43, 0x07, 0x7c, 0x05, 0x74, 0x93, 0x12, 0x5e, 0x25, 0x9c, 0x08, 0xaf, 0xad, 0x66, 0x8e, 0x6e, 0x10, 0x1f, 0x4b,
0x37, 0xa0, 0x95, 0x97, 0xd1, 0x3e, 0xd4, 0x23, 0xe1, 0x60, 0xdb, 0x90, 0x87, 0xea, 0x56, 0x4f, 0xfc, 0xbf, 0xac, 0x9d, 0x41, 0x77, 0x55, 0xae, 0x7f, 0xa2, 0xc2, 0x18, 0x7b, 0xbf, 0xd9, 0xd8,
0x29, 0x4c, 0x8e, 0x15, 0xba, 0xda, 0x1d, 0xe8, 0x29, 0x58, 0x45, 0x42, 0xb4, 0xac, 0x2d, 0x57, 0x85, 0xe2, 0x60, 0xc3, 0x45, 0xf7, 0xa0, 0x5b, 0xac, 0xb7, 0x94, 0x11, 0xaf, 0x33, 0x76, 0x4e,
0x65, 0xc8, 0xcd, 0x33, 0xe4, 0x4e, 0x72, 0x04, 0x2e, 0xc1, 0xce, 0xcf, 0x35, 0x68, 0x8c, 0x64, 0xef, 0x62, 0x53, 0xa1, 0x8f, 0xe0, 0xe0, 0x57, 0x9a, 0xf3, 0x50, 0x6c, 0x73, 0x5a, 0x6c, 0x79,
0x22, 0xff, 0x56, 0xd1, 0x63, 0xa8, 0x07, 0x22, 0x53, 0x3a, 0x10, 0x77, 0xab, 0x69, 0x32, 0x76, 0xb2, 0xf1, 0xba, 0x6a, 0xc3, 0xa1, 0x44, 0x17, 0x16, 0x94, 0x9a, 0x14, 0x4d, 0x5b, 0xec, 0x29,
0x58, 0x21, 0xd1, 0x13, 0x68, 0xfa, 0x2a, 0x67, 0x5a, 0xec, 0x76, 0x35, 0x49, 0x87, 0x11, 0xe7, 0x8b, 0xae, 0x44, 0xb4, 0xc1, 0x53, 0x18, 0xd5, 0xcb, 0xc6, 0x5e, 0x5f, 0xcd, 0x39, 0xa8, 0x48,
0x68, 0x41, 0xcc, 0x54, 0x08, 0x6c, 0xf3, 0x36, 0xa2, 0x4e, 0x0a, 0xce, 0xd1, 0x82, 0xc8, 0x95, 0xda, 0xdc, 0x14, 0x86, 0x29, 0x8d, 0x88, 0x88, 0x5f, 0xd2, 0xb0, 0xc8, 0x48, 0xea, 0xb9, 0xca,
0x69, 0xed, 0xfa, 0x6d, 0x44, 0xed, 0x6c, 0x9c, 0xa3, 0xd1, 0x0b, 0xb0, 0x16, 0xb9, 0x97, 0xed, 0xc4, 0xf8, 0x75, 0x26, 0xe6, 0x19, 0x49, 0xf1, 0xbe, 0x6d, 0x93, 0x95, 0x94, 0x5d, 0x8d, 0xd9,
0xa6, 0xa4, 0xde, 0x70, 0x31, 0x85, 0xe5, 0x71, 0xc9, 0x10, 0xee, 0x2f, 0xee, 0x7a, 0x46, 0x33, 0xd0, 0x44, 0x10, 0x0f, 0xc6, 0xad, 0x53, 0x84, 0xab, 0xe1, 0x8f, 0x24, 0x78, 0x8b, 0xa6, 0xa5,
0xbb, 0xd1, 0x33, 0x76, 0x6a, 0xb8, 0x5d, 0xd4, 0x46, 0x99, 0xf3, 0xc3, 0x80, 0x75, 0xf5, 0x07, 0x0f, 0xc6, 0x2d, 0xe9, 0xce, 0xa2, 0x5a, 0xfe, 0x14, 0x86, 0x19, 0x2f, 0xe2, 0x5a, 0xd4, 0xfe,
0x5e, 0x79, 0x34, 0x8c, 0x56, 0x95, 0xcf, 0x19, 0x02, 0x73, 0x41, 0xa2, 0x44, 0xbf, 0x66, 0x72, 0x9b, 0x8a, 0xb2, 0x6d, 0x56, 0x54, 0x35, 0x46, 0x8b, 0x1a, 0x6a, 0x51, 0x16, 0xad, 0x44, 0x55,
0x8d, 0xf6, 0xc0, 0x14, 0x1a, 0xe5, 0x15, 0xfe, 0xbf, 0xdb, 0xab, 0x56, 0xa5, 0x26, 0x4f, 0x56, 0x34, 0x2d, 0xea, 0x40, 0x8b, 0xb2, 0xa8, 0x12, 0xe5, 0xff, 0xe9, 0x40, 0x57, 0x6f, 0x85, 0x3e,
0x09, 0xc1, 0x12, 0x2d, 0xd2, 0xa4, 0x5e, 0x60, 0xdb, 0xbc, 0x2d, 0x4d, 0x8a, 0x87, 0x35, 0xf6, 0x86, 0xd1, 0xba, 0x64, 0x65, 0x72, 0xd3, 0x88, 0xbe, 0x66, 0x77, 0x6a, 0x5c, 0x5b, 0x39, 0x83,
0xd1, 0x08, 0xa0, 0x9c, 0x84, 0xda, 0xd0, 0x3c, 0x3e, 0x9b, 0x9e, 0x4e, 0x06, 0xb8, 0xf3, 0x1f, 0x7b, 0xaf, 0x52, 0x6f, 0x5d, 0xb7, 0xc3, 0x57, 0x1a, 0xf4, 0x5b, 0x39, 0x81, 0x41, 0x99, 0x65,
0xb2, 0xa0, 0x3e, 0x3c, 0x9c, 0x0e, 0x07, 0x1d, 0x43, 0xd4, 0xc7, 0xd3, 0xd1, 0xe8, 0x10, 0x5f, 0x34, 0x0f, 0x57, 0xbc, 0x4c, 0x37, 0xe6, 0xce, 0x81, 0x82, 0x2e, 0x24, 0x72, 0x2b, 0x17, 0x5a,
0x74, 0xd6, 0xc4, 0x66, 0x7a, 0x3a, 0xb9, 0x38, 0x1f, 0xbc, 0xec, 0xd4, 0xd0, 0x06, 0x58, 0x27, 0xff, 0x3b, 0x17, 0xa0, 0x3e, 0x32, 0x79, 0x11, 0xf9, 0xd5, 0x55, 0x41, 0xb5, 0x83, 0xbb, 0xd8,
0xaf, 0xc7, 0x93, 0xb3, 0x21, 0x3e, 0x1c, 0x75, 0xcc, 0x23, 0x0c, 0x95, 0xef, 0xfe, 0xbb, 0x83, 0x54, 0x12, 0x4f, 0x68, 0x1a, 0x89, 0xad, 0xda, 0x7d, 0x88, 0x4d, 0xe5, 0xff, 0xee, 0x40, 0xdf,
0x20, 0x64, 0x0b, 0x3e, 0x77, 0xfd, 0x98, 0xf6, 0xcb, 0x6e, 0x5f, 0x75, 0x67, 0x34, 0xbe, 0x24, 0x0e, 0x45, 0xf7, 0xa1, 0x93, 0xc8, 0x54, 0xf4, 0x1c, 0xf5, 0x82, 0x4e, 0x9a, 0x35, 0x54, 0xc1,
0x51, 0x3f, 0x88, 0x9f, 0x85, 0xf1, 0xac, 0xec, 0xce, 0x54, 0xf7, 0x57, 0x00, 0x00, 0x00, 0xff, 0x89, 0x35, 0xbb, 0x39, 0x71, 0xd0, 0x97, 0xe0, 0x56, 0xa9, 0x6b, 0x4c, 0x1d, 0x05, 0x3a, 0x97,
0xff, 0xd0, 0x84, 0x91, 0x73, 0x59, 0x06, 0x00, 0x00, 0x03, 0x9b, 0xcb, 0xc1, 0xc2, 0x32, 0x70, 0x4d, 0xf6, 0xff, 0xde, 0x83, 0xee, 0x4c, 0xa5, 0xfc,
0xdb, 0x2a, 0xfa, 0x0c, 0x3a, 0x91, 0xcc, 0x69, 0x13, 0xb2, 0xef, 0x35, 0xb7, 0xa9, 0x28, 0xc7,
0x9a, 0x89, 0xbe, 0x80, 0xde, 0x5a, 0x67, 0xb7, 0x11, 0x7b, 0xdc, 0xdc, 0x64, 0x02, 0x1e, 0x5b,
0xb6, 0x6c, 0x2c, 0x74, 0xb0, 0xaa, 0x3b, 0xb0, 0xb3, 0xd1, 0xa4, 0x2f, 0xb6, 0x6c, 0xd9, 0x58,
0xea, 0x20, 0x54, 0xa1, 0xb1, 0xb3, 0xd1, 0xa4, 0x25, 0xb6, 0x6c, 0xf4, 0x0d, 0xb8, 0x5b, 0x9b,
0x8f, 0x2a, 0x2c, 0x76, 0x1e, 0x4c, 0x15, 0xa3, 0xb8, 0xee, 0x90, 0x89, 0x5a, 0x9d, 0x75, 0xc8,
0x0a, 0x95, 0x48, 0x2d, 0x3c, 0xa8, 0xb0, 0x59, 0xe1, 0xff, 0xe1, 0xc0, 0xbe, 0x7e, 0x03, 0x8f,
0x09, 0x8b, 0x93, 0xeb, 0xc6, 0x4f, 0x24, 0x82, 0xf6, 0x96, 0x26, 0x99, 0xf9, 0x42, 0xaa, 0x67,
0x74, 0x06, 0x6d, 0xa9, 0x51, 0x1d, 0xe1, 0xc1, 0xae, 0x5f, 0xb8, 0x9e, 0xbc, 0xb8, 0xce, 0x28,
0x56, 0x6c, 0x99, 0xb9, 0xfa, 0xab, 0xee, 0xb5, 0x5f, 0x97, 0xb9, 0xba, 0x0f, 0x1b, 0xee, 0x27,
0x2b, 0x80, 0x7a, 0x12, 0x1a, 0x40, 0xef, 0xe1, 0xb3, 0xe5, 0xd3, 0xc5, 0x14, 0x8f, 0xde, 0x41,
0x2e, 0x74, 0x2e, 0xcf, 0x97, 0x97, 0xd3, 0x91, 0x23, 0xf1, 0xf9, 0x72, 0x36, 0x3b, 0xc7, 0x2f,
0x46, 0x7b, 0xb2, 0x58, 0x3e, 0x5d, 0xbc, 0x78, 0x3e, 0x7d, 0x34, 0x6a, 0xa1, 0x21, 0xb8, 0x4f,
0xbe, 0x9d, 0x2f, 0x9e, 0x5d, 0xe2, 0xf3, 0xd9, 0xa8, 0x8d, 0xde, 0x85, 0x3b, 0xaa, 0x27, 0xac,
0xc1, 0xce, 0x05, 0x86, 0xc6, 0x3f, 0x18, 0x3f, 0x3c, 0x88, 0x62, 0xb1, 0x2d, 0x57, 0xc1, 0x9a,
0xb3, 0x7f, 0xff, 0x45, 0x09, 0x19, 0xdf, 0xd0, 0x64, 0x12, 0xf1, 0xaf, 0x62, 0x1e, 0xd6, 0xab,
0xa1, 0x5e, 0xfd, 0x27, 0x00, 0x00, 0xff, 0xff, 0x16, 0x77, 0x81, 0x98, 0xd7, 0x08, 0x00, 0x00,
} }

View file

@ -5,14 +5,78 @@ package disk
import ( import (
"context" "context"
"regexp"
"strings"
"golang.org/x/sys/unix"
"github.com/shirou/gopsutil/v3/internal/common" "github.com/shirou/gopsutil/v3/internal/common"
) )
var whiteSpaces = regexp.MustCompile(`\s+`)
var startBlank = regexp.MustCompile(`^\s+`)
var ignoreFSType = map[string]bool{"procfs": true}
var FSType = map[int]string{
0: "jfs2", 1: "namefs", 2: "nfs", 3: "jfs", 5: "cdrom", 6: "proc",
16: "special-fs", 17: "cache-fs", 18: "nfs3", 19: "automount-fs", 20: "pool-fs", 32: "vxfs",
33: "veritas-fs", 34: "udfs", 35: "nfs4", 36: "nfs4-pseudo", 37: "smbfs", 38: "mcr-pseudofs",
39: "ahafs", 40: "sterm-nfs", 41: "asmfs",
}
func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, error) { func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, error) {
return []PartitionStat{}, common.ErrNotImplementedError var ret []PartitionStat
out, err := invoke.CommandWithContext(ctx, "mount")
if err != nil {
return nil, err
}
// parse head lines for column names
colidx := make(map[string]int)
lines := strings.Split(string(out), "\n")
if len(lines) < 3 {
return nil, common.ErrNotImplementedError
}
idx := 0
start := 0
finished := false
for pos, ch := range lines[1] {
if ch == ' ' && ! finished {
name := strings.TrimSpace(lines[0][start:pos])
colidx[name] = idx
finished = true
} else if ch == '-' && finished {
idx++
start = pos
finished = false
}
}
name := strings.TrimSpace(lines[0][start:len(lines[1])])
colidx[name] = idx
for idx := 2; idx < len(lines); idx++ {
line := lines[idx]
if startBlank.MatchString(line) {
line = "localhost" + line
}
p := whiteSpaces.Split(lines[idx], 6)
if len(p) < 5 || ignoreFSType[p[colidx["vfs"]]] {
continue
}
d := PartitionStat{
Device: p[colidx["mounted"]],
Mountpoint: p[colidx["mounted over"]],
Fstype: p[colidx["vfs"]],
Opts: strings.Split(p[colidx["options"]], ","),
}
ret = append(ret, d)
}
return ret, nil
} }
func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) { func getFsType(stat unix.Statfs_t) string {
return nil, common.ErrNotImplementedError return FSType[int(stat.Vfstype)]
} }

View file

@ -1,5 +1,5 @@
//go:build freebsd || linux || darwin //go:build freebsd || linux || darwin || (aix && !cgo)
// +build freebsd linux darwin // +build freebsd linux darwin aix,!cgo
package disk package disk
@ -27,20 +27,8 @@ func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
InodesFree: (uint64(stat.Ffree)), InodesFree: (uint64(stat.Ffree)),
} }
// if could not get InodesTotal, return empty
if ret.InodesTotal < ret.InodesFree {
return ret, nil
}
ret.InodesUsed = (ret.InodesTotal - ret.InodesFree)
ret.Used = (uint64(stat.Blocks) - uint64(stat.Bfree)) * uint64(bsize) ret.Used = (uint64(stat.Blocks) - uint64(stat.Bfree)) * uint64(bsize)
if ret.InodesTotal == 0 {
ret.InodesUsedPercent = 0
} else {
ret.InodesUsedPercent = (float64(ret.InodesUsed) / float64(ret.InodesTotal)) * 100.0
}
if (ret.Used + ret.Free) == 0 { if (ret.Used + ret.Free) == 0 {
ret.UsedPercent = 0 ret.UsedPercent = 0
} else { } else {
@ -49,6 +37,19 @@ func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
ret.UsedPercent = (float64(ret.Used) / float64(ret.Used+ret.Free)) * 100.0 ret.UsedPercent = (float64(ret.Used) / float64(ret.Used+ret.Free)) * 100.0
} }
// if could not get InodesTotal, return empty
if ret.InodesTotal < ret.InodesFree {
return ret, nil
}
ret.InodesUsed = (ret.InodesTotal - ret.InodesFree)
if ret.InodesTotal == 0 {
ret.InodesUsedPercent = 0
} else {
ret.InodesUsedPercent = (float64(ret.InodesUsed) / float64(ret.InodesTotal)) * 100.0
}
return ret, nil return ret, nil
} }

View file

@ -114,8 +114,8 @@ func ReadLines(filename string) ([]string, error) {
// ReadLinesOffsetN reads contents from file and splits them by new line. // ReadLinesOffsetN reads contents from file and splits them by new line.
// The offset tells at which line number to start. // The offset tells at which line number to start.
// The count determines the number of lines to read (starting from offset): // The count determines the number of lines to read (starting from offset):
// n >= 0: at most n lines // n >= 0: at most n lines
// n < 0: whole file // n < 0: whole file
func ReadLinesOffsetN(filename string, offset uint, n int) ([]string, error) { func ReadLinesOffsetN(filename string, offset uint, n int) ([]string, error) {
f, err := os.Open(filename) f, err := os.Open(filename)
if err != nil { if err != nil {

View file

@ -149,6 +149,9 @@ func VirtualizationWithContext(ctx context.Context) (string, string, error) {
if StringsContains(contents, "kvm") { if StringsContains(contents, "kvm") {
system = "kvm" system = "kvm"
role = "host" role = "host"
} else if StringsContains(contents, "hv_util") {
system = "hyperv"
role = "guest"
} else if StringsContains(contents, "vboxdrv") { } else if StringsContains(contents, "vboxdrv") {
system = "vbox" system = "vbox"
role = "host" role = "host"

View file

@ -161,7 +161,7 @@ var netProtocols = []string{
// If protocols is empty then all protocols are returned, otherwise // If protocols is empty then all protocols are returned, otherwise
// just the protocols in the list are returned. // just the protocols in the list are returned.
// Available protocols: // Available protocols:
// ip,icmp,icmpmsg,tcp,udp,udplite // [ip,icmp,icmpmsg,tcp,udp,udplite]
func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) { func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) {
return ProtoCountersWithContext(context.Background(), protocols) return ProtoCountersWithContext(context.Background(), protocols)
} }

View file

@ -408,7 +408,7 @@ func (p *Process) CwdWithContext(_ context.Context) (string, error) {
} }
if userProcParams.CurrentDirectoryPathNameLength > 0 { if userProcParams.CurrentDirectoryPathNameLength > 0 {
cwd := readProcessMemory(syscall.Handle(h), procIs32Bits, uint64(userProcParams.CurrentDirectoryPathAddress), uint(userProcParams.CurrentDirectoryPathNameLength)) cwd := readProcessMemory(syscall.Handle(h), procIs32Bits, uint64(userProcParams.CurrentDirectoryPathAddress), uint(userProcParams.CurrentDirectoryPathNameLength))
if len(cwd) != int(userProcParams.CurrentDirectoryPathAddress) { if len(cwd) != int(userProcParams.CurrentDirectoryPathNameLength) {
return "", errors.New("cannot read current working directory") return "", errors.New("cannot read current working directory")
} }

View file

@ -22,6 +22,7 @@ type Config struct {
InstanceName string InstanceName string
DeepLinking bool DeepLinking bool
PersistAuthorization bool PersistAuthorization bool
SyntaxHighlight bool
// The information for OAuth2 integration, if any. // The information for OAuth2 integration, if any.
OAuth *OAuthConfig OAuth *OAuthConfig
@ -54,6 +55,13 @@ func DeepLinking(deepLinking bool) func(*Config) {
} }
} }
// SyntaxHighlight true, false.
func SyntaxHighlight(syntaxHighlight bool) func(*Config) {
return func(c *Config) {
c.SyntaxHighlight = syntaxHighlight
}
}
// DocExpansion list, full, none. // DocExpansion list, full, none.
func DocExpansion(docExpansion string) func(*Config) { func DocExpansion(docExpansion string) func(*Config) {
return func(c *Config) { return func(c *Config) {
@ -97,6 +105,7 @@ func newConfig(configFns ...func(*Config)) *Config {
InstanceName: "swagger", InstanceName: "swagger",
DeepLinking: true, DeepLinking: true,
PersistAuthorization: false, PersistAuthorization: false,
SyntaxHighlight: true,
} }
for _, fn := range configFns { for _, fn := range configFns {
@ -257,6 +266,7 @@ window.onload = function() {
// Build a system // Build a system
const ui = SwaggerUIBundle({ const ui = SwaggerUIBundle({
url: "{{.URL}}", url: "{{.URL}}",
syntaxHighlight: {{.SyntaxHighlight}},
deepLinking: {{.DeepLinking}}, deepLinking: {{.DeepLinking}},
docExpansion: "{{.DocExpansion}}", docExpansion: "{{.DocExpansion}}",
persistAuthorization: {{.PersistAuthorization}}, persistAuthorization: {{.PersistAuthorization}},

View file

@ -52,12 +52,9 @@ Swag converts Go annotations to Swagger Documentation 2.0. We've created a varie
2. Download swag by using: 2. Download swag by using:
```sh ```sh
$ go get -u github.com/swaggo/swag/cmd/swag
# 1.16 or newer
$ go install github.com/swaggo/swag/cmd/swag@latest $ go install github.com/swaggo/swag/cmd/swag@latest
``` ```
To build from source you need [Go](https://golang.org/dl/) (1.15 or newer). To build from source you need [Go](https://golang.org/dl/) (1.16 or newer).
Or download a pre-compiled binary from the [release page](https://github.com/swaggo/swag/releases). Or download a pre-compiled binary from the [release page](https://github.com/swaggo/swag/releases).

View file

@ -47,13 +47,10 @@ Swag将Go的注释转换为Swagger2.0文档。我们为流行的 [Go Web Framewo
2. 使用如下命令下载swag 2. 使用如下命令下载swag
```bash ```bash
$ go get -u github.com/swaggo/swag/cmd/swag
# 1.16 及以上版本
$ go install github.com/swaggo/swag/cmd/swag@latest $ go install github.com/swaggo/swag/cmd/swag@latest
``` ```
从源码开始构建的话需要有Go环境1.15及以上版本)。 从源码开始构建的话需要有Go环境1.16及以上版本)。
或者从github的release页面下载预编译好的二进制文件。 或者从github的release页面下载预编译好的二进制文件。

View file

@ -2,21 +2,18 @@ package swag
import ( import (
"bytes" "bytes"
"crypto/md5"
"fmt" "fmt"
"go/ast" "go/ast"
goparser "go/parser" goparser "go/parser"
"go/token" "go/token"
"io"
"log" "log"
"os" "os"
"regexp" "regexp"
"sort"
"strings" "strings"
"text/tabwriter" "text/tabwriter"
) )
const splitTag = "&*"
// Check of @Param @Success @Failure @Response @Header // Check of @Param @Success @Failure @Response @Header
var specialTagForSplit = map[string]bool{ var specialTagForSplit = map[string]bool{
paramAttr: true, paramAttr: true,
@ -55,48 +52,93 @@ func (f *Formatter) Format(fileName string, contents []byte) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
formattedComments := bytes.Buffer{}
oldComments := map[string]string{}
if ast.Comments != nil { // Formatting changes are described as an edit list of byte range
for _, comment := range ast.Comments { // replacements. We make these content-level edits directly rather than
formatFuncDoc(comment.List, &formattedComments, oldComments) // changing the AST nodes and writing those out (via [go/printer] or
} // [go/format]) so that we only change the formatting of Swag attribute
// comments. This won't touch the formatting of any other comments, or of
// functions, etc.
maxEdits := 0
for _, comment := range ast.Comments {
maxEdits += len(comment.List)
} }
return formatComments(fileName, contents, formattedComments.Bytes(), oldComments), nil edits := make(edits, 0, maxEdits)
for _, comment := range ast.Comments {
formatFuncDoc(fileSet, comment.List, &edits)
}
return edits.apply(contents), nil
} }
func formatComments(fileName string, contents []byte, formattedComments []byte, oldComments map[string]string) []byte { type edit struct {
for _, comment := range bytes.Split(formattedComments, []byte("\n")) { begin int
splits := bytes.SplitN(comment, []byte(splitTag), 2) end int
if len(splits) == 2 { replacement []byte
hash, line := splits[0], splits[1] }
contents = bytes.Replace(contents, []byte(oldComments[string(hash)]), line, 1)
} type edits []edit
func (edits edits) apply(contents []byte) []byte {
// Apply the edits with the highest offset first, so that earlier edits
// don't affect the offsets of later edits.
sort.Slice(edits, func(i, j int) bool {
return edits[i].begin > edits[j].begin
})
for _, edit := range edits {
prefix := contents[:edit.begin]
suffix := contents[edit.end:]
contents = append(prefix, append(edit.replacement, suffix...)...)
} }
return contents return contents
} }
func formatFuncDoc(commentList []*ast.Comment, formattedComments io.Writer, oldCommentsMap map[string]string) { // formatFuncDoc reformats the comment lines in commentList, and appends any
w := tabwriter.NewWriter(formattedComments, 0, 0, 1, ' ', 0) // changes to the edit list.
func formatFuncDoc(fileSet *token.FileSet, commentList []*ast.Comment, edits *edits) {
// Building the edit list to format a comment block is a two-step process.
// First, we iterate over each comment line looking for Swag attributes. In
// each one we find, we replace alignment whitespace with a tab character,
// then write the result into a tab writer.
for _, comment := range commentList { linesToComments := make(map[int]int, len(commentList))
buffer := &bytes.Buffer{}
w := tabwriter.NewWriter(buffer, 0, 0, 1, ' ', 0)
for commentIndex, comment := range commentList {
text := comment.Text text := comment.Text
if attr, body, found := swagComment(text); found { if attr, body, found := swagComment(text); found {
cmd5 := fmt.Sprintf("%x", md5.Sum([]byte(text)))
oldCommentsMap[cmd5] = text
formatted := "// " + attr formatted := "// " + attr
if body != "" { if body != "" {
formatted += "\t" + splitComment2(attr, body) formatted += "\t" + splitComment2(attr, body)
} }
// md5 + splitTag + srcCommentLine _, _ = fmt.Fprintln(w, formatted)
// eg. xxx&*@Description get struct array linesToComments[len(linesToComments)] = commentIndex
_, _ = fmt.Fprintln(w, cmd5+splitTag+formatted)
} }
} }
// format by tabwriter
// Once we've loaded all of the comment lines to be aligned into the tab
// writer, flushing it causes the aligned text to be written out to the
// backing buffer.
_ = w.Flush() _ = w.Flush()
// Now the second step: we iterate over the aligned comment lines that were
// written into the backing buffer, pair each one up to its original
// comment line, and use the combination to describe the edit that needs to
// be made to the original input.
formattedComments := bytes.Split(buffer.Bytes(), []byte("\n"))
for lineIndex, commentIndex := range linesToComments {
comment := commentList[commentIndex]
*edits = append(*edits, edit{
begin: fileSet.Position(comment.Pos()).Offset,
end: fileSet.Position(comment.End()).Offset,
replacement: formattedComments[lineIndex],
})
}
} }
func splitComment2(attr, body string) string { func splitComment2(attr, body string) string {

View file

@ -6,10 +6,14 @@ package swag
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/go-openapi/spec"
"go/ast" "go/ast"
"strings" "strings"
"sync"
"unicode"
) )
var genericDefinitionsMutex = &sync.RWMutex{}
var genericsDefinitions = map[*TypeSpecDef]map[string]*TypeSpecDef{} var genericsDefinitions = map[*TypeSpecDef]map[string]*TypeSpecDef{}
type genericTypeSpec struct { type genericTypeSpec struct {
@ -55,9 +59,12 @@ func typeSpecFullName(typeSpecDef *TypeSpecDef) string {
return fullName return fullName
} }
func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, fullGenericForm string, parseDependency bool) *TypeSpecDef { func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, original *TypeSpecDef, fullGenericForm string, parseDependency bool) *TypeSpecDef {
if spec, ok := genericsDefinitions[original][fullGenericForm]; ok { genericDefinitionsMutex.RLock()
return spec tSpec, ok := genericsDefinitions[original][fullGenericForm]
genericDefinitionsMutex.RUnlock()
if ok {
return tSpec
} }
pkgName := strings.Split(fullGenericForm, ".")[0] pkgName := strings.Split(fullGenericForm, ".")[0]
@ -81,7 +88,11 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful
arrayDepth++ arrayDepth++
} }
tdef := pkgDefs.FindTypeSpec(genericParam, original.File, parseDependency) tdef := pkgDefs.FindTypeSpec(genericParam, file, parseDependency)
if tdef != nil && !strings.Contains(genericParam, ".") {
genericParam = fullTypeName(file.Name.Name, genericParam)
}
genericParamTypeDefs[original.TypeSpec.TypeParams.List[i].Names[0].Name] = &genericTypeSpec{ genericParamTypeDefs[original.TypeSpec.TypeParams.List[i].Names[0].Name] = &genericTypeSpec{
ArrayDepth: arrayDepth, ArrayDepth: arrayDepth,
TypeSpec: tdef, TypeSpec: tdef,
@ -132,31 +143,12 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful
ident.Name = string(IgnoreNameOverridePrefix) + ident.Name ident.Name = string(IgnoreNameOverridePrefix) + ident.Name
parametrizedTypeSpec.TypeSpec.Name = ident parametrizedTypeSpec.TypeSpec.Name = ident
origStructType := original.TypeSpec.Type.(*ast.StructType)
newStructTypeDef := &ast.StructType{ newType := pkgDefs.resolveGenericType(original.File, original.TypeSpec.Type, genericParamTypeDefs, parseDependency)
Struct: origStructType.Struct,
Incomplete: origStructType.Incomplete,
Fields: &ast.FieldList{
Opening: origStructType.Fields.Opening,
Closing: origStructType.Fields.Closing,
},
}
for _, field := range origStructType.Fields.List { genericDefinitionsMutex.Lock()
newField := &ast.Field{ defer genericDefinitionsMutex.Unlock()
Doc: field.Doc, parametrizedTypeSpec.TypeSpec.Type = newType
Names: field.Names,
Tag: field.Tag,
Comment: field.Comment,
}
newField.Type = resolveType(field.Type, field, genericParamTypeDefs)
newStructTypeDef.Fields.List = append(newStructTypeDef.Fields.List, newField)
}
parametrizedTypeSpec.TypeSpec.Type = newStructTypeDef
if genericsDefinitions[original] == nil { if genericsDefinitions[original] == nil {
genericsDefinitions[original] = map[string]*TypeSpecDef{} genericsDefinitions[original] = map[string]*TypeSpecDef{}
} }
@ -166,12 +158,20 @@ func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, ful
// splitStructName splits a generic struct name in his parts // splitStructName splits a generic struct name in his parts
func splitStructName(fullGenericForm string) (string, []string) { func splitStructName(fullGenericForm string) (string, []string) {
//remove all spaces character
fullGenericForm = strings.Map(func(r rune) rune {
if unicode.IsSpace(r) {
return -1
}
return r
}, fullGenericForm)
// split only at the first '[' and remove the last ']' // split only at the first '[' and remove the last ']'
if fullGenericForm[len(fullGenericForm)-1] != ']' { if fullGenericForm[len(fullGenericForm)-1] != ']' {
return "", nil return "", nil
} }
genericParams := strings.SplitN(strings.TrimSpace(fullGenericForm)[:len(fullGenericForm)-1], "[", 2) genericParams := strings.SplitN(fullGenericForm[:len(fullGenericForm)-1], "[", 2)
if len(genericParams) == 1 { if len(genericParams) == 1 {
return "", nil return "", nil
} }
@ -179,73 +179,121 @@ func splitStructName(fullGenericForm string) (string, []string) {
// generic type name // generic type name
genericTypeName := genericParams[0] genericTypeName := genericParams[0]
// generic params depth := 0
insideBrackets := 0 genericParams = strings.FieldsFunc(genericParams[1], func(r rune) bool {
lastParam := "" if r == '[' {
params := strings.Split(genericParams[1], ",") depth++
genericParams = []string{} } else if r == ']' {
for _, p := range params { depth--
numOpened := strings.Count(p, "[") } else if r == ',' && depth == 0 {
numClosed := strings.Count(p, "]") return true
if numOpened == numClosed && insideBrackets == 0 {
genericParams = append(genericParams, strings.TrimSpace(p))
continue
}
insideBrackets += numOpened - numClosed
lastParam += p + ","
if insideBrackets == 0 {
genericParams = append(genericParams, strings.TrimSpace(strings.TrimRight(lastParam, ",")))
lastParam = ""
} }
return false
})
if depth != 0 {
return "", nil
} }
return genericTypeName, genericParams return genericTypeName, genericParams
} }
func resolveType(expr ast.Expr, field *ast.Field, genericParamTypeDefs map[string]*genericTypeSpec) ast.Expr { func (pkgDefs *PackagesDefinitions) resolveGenericType(file *ast.File, expr ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec, parseDependency bool) ast.Expr {
switch astExpr := expr.(type) { switch astExpr := expr.(type) {
case *ast.Ident: case *ast.Ident:
if genTypeSpec, ok := genericParamTypeDefs[astExpr.Name]; ok { if genTypeSpec, ok := genericParamTypeDefs[astExpr.Name]; ok {
if genTypeSpec.ArrayDepth > 0 { retType := genTypeSpec.Type()
genTypeSpec.ArrayDepth-- for i := 0; i < genTypeSpec.ArrayDepth; i++ {
return &ast.ArrayType{Elt: resolveType(expr, field, genericParamTypeDefs)} retType = &ast.ArrayType{Elt: retType}
} }
return genTypeSpec.Type() return retType
} }
case *ast.ArrayType: case *ast.ArrayType:
return &ast.ArrayType{ return &ast.ArrayType{
Elt: resolveType(astExpr.Elt, field, genericParamTypeDefs), Elt: pkgDefs.resolveGenericType(file, astExpr.Elt, genericParamTypeDefs, parseDependency),
Len: astExpr.Len, Len: astExpr.Len,
Lbrack: astExpr.Lbrack, Lbrack: astExpr.Lbrack,
} }
} case *ast.StarExpr:
return &ast.StarExpr{
Star: astExpr.Star,
X: pkgDefs.resolveGenericType(file, astExpr.X, genericParamTypeDefs, parseDependency),
}
case *ast.IndexExpr, *ast.IndexListExpr:
fullGenericName, _ := getGenericFieldType(file, expr, genericParamTypeDefs)
typeDef := pkgDefs.findGenericTypeSpec(fullGenericName, file, parseDependency)
if typeDef != nil {
return typeDef.TypeSpec.Type
}
case *ast.StructType:
newStructTypeDef := &ast.StructType{
Struct: astExpr.Struct,
Incomplete: astExpr.Incomplete,
Fields: &ast.FieldList{
Opening: astExpr.Fields.Opening,
Closing: astExpr.Fields.Closing,
},
}
return field.Type for _, field := range astExpr.Fields.List {
newField := &ast.Field{
Type: field.Type,
Doc: field.Doc,
Names: field.Names,
Tag: field.Tag,
Comment: field.Comment,
}
newField.Type = pkgDefs.resolveGenericType(file, field.Type, genericParamTypeDefs, parseDependency)
newStructTypeDef.Fields.List = append(newStructTypeDef.Fields.List, newField)
}
return newStructTypeDef
}
return expr
} }
func getGenericFieldType(file *ast.File, field ast.Expr) (string, error) { func getExtendedGenericFieldType(file *ast.File, field ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec) (string, error) {
switch fieldType := field.(type) {
case *ast.ArrayType:
fieldName, err := getExtendedGenericFieldType(file, fieldType.Elt, genericParamTypeDefs)
return "[]" + fieldName, err
case *ast.StarExpr:
return getExtendedGenericFieldType(file, fieldType.X, genericParamTypeDefs)
case *ast.Ident:
if genericParamTypeDefs != nil {
if typeSpec, ok := genericParamTypeDefs[fieldType.Name]; ok {
return typeSpec.Name, nil
}
}
if fieldType.Obj == nil {
return fieldType.Name, nil
}
tSpec := &TypeSpecDef{
File: file,
TypeSpec: fieldType.Obj.Decl.(*ast.TypeSpec),
PkgPath: file.Name.Name,
}
return tSpec.FullName(), nil
default:
return getFieldType(file, field)
}
}
func getGenericFieldType(file *ast.File, field ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec) (string, error) {
var fullName string
var baseName string
var err error
switch fieldType := field.(type) { switch fieldType := field.(type) {
case *ast.IndexListExpr: case *ast.IndexListExpr:
fullName, err := getGenericTypeName(file, fieldType.X) baseName, err = getGenericTypeName(file, fieldType.X)
if err != nil { if err != nil {
return "", err return "", err
} }
fullName += "[" fullName = baseName + "["
for _, index := range fieldType.Indices { for _, index := range fieldType.Indices {
var fieldName string fieldName, err := getExtendedGenericFieldType(file, index, genericParamTypeDefs)
var err error
switch item := index.(type) {
case *ast.ArrayType:
fieldName, err = getFieldType(file, item.Elt)
fieldName = "[]" + fieldName
default:
fieldName, err = getFieldType(file, index)
}
if err != nil { if err != nil {
return "", err return "", err
} }
@ -253,50 +301,85 @@ func getGenericFieldType(file *ast.File, field ast.Expr) (string, error) {
fullName += fieldName + "," fullName += fieldName + ","
} }
return strings.TrimRight(fullName, ",") + "]", nil fullName = strings.TrimRight(fullName, ",") + "]"
case *ast.IndexExpr: case *ast.IndexExpr:
x, err := getFieldType(file, fieldType.X) baseName, err = getGenericTypeName(file, fieldType.X)
if err != nil { if err != nil {
return "", err return "", err
} }
i, err := getFieldType(file, fieldType.Index) indexName, err := getExtendedGenericFieldType(file, fieldType.Index, genericParamTypeDefs)
if err != nil { if err != nil {
return "", err return "", err
} }
packageName := "" fullName = fmt.Sprintf("%s[%s]", baseName, indexName)
if !strings.Contains(x, ".") {
if file.Name == nil {
return "", errors.New("file name is nil")
}
packageName, _ = getFieldType(file, file.Name)
}
return strings.TrimLeft(fmt.Sprintf("%s.%s[%s]", packageName, x, i), "."), nil
} }
return "", fmt.Errorf("unknown field type %#v", field) if fullName == "" {
return "", fmt.Errorf("unknown field type %#v", field)
}
var packageName string
if !strings.Contains(baseName, ".") {
if file.Name == nil {
return "", errors.New("file name is nil")
}
packageName, _ = getFieldType(file, file.Name)
}
return strings.TrimLeft(fmt.Sprintf("%s.%s", packageName, fullName), "."), nil
} }
func getGenericTypeName(file *ast.File, field ast.Expr) (string, error) { func getGenericTypeName(file *ast.File, field ast.Expr) (string, error) {
switch indexType := field.(type) { switch fieldType := field.(type) {
case *ast.Ident: case *ast.Ident:
spec := &TypeSpecDef{ if fieldType.Obj == nil {
return fieldType.Name, nil
}
tSpec := &TypeSpecDef{
File: file, File: file,
TypeSpec: indexType.Obj.Decl.(*ast.TypeSpec), TypeSpec: fieldType.Obj.Decl.(*ast.TypeSpec),
PkgPath: file.Name.Name, PkgPath: file.Name.Name,
} }
return spec.FullName(), nil return tSpec.FullName(), nil
case *ast.ArrayType: case *ast.ArrayType:
spec := &TypeSpecDef{ tSpec := &TypeSpecDef{
File: file, File: file,
TypeSpec: indexType.Elt.(*ast.Ident).Obj.Decl.(*ast.TypeSpec), TypeSpec: fieldType.Elt.(*ast.Ident).Obj.Decl.(*ast.TypeSpec),
PkgPath: file.Name.Name, PkgPath: file.Name.Name,
} }
return spec.FullName(), nil return tSpec.FullName(), nil
case *ast.SelectorExpr: case *ast.SelectorExpr:
return fmt.Sprintf("%s.%s", indexType.X.(*ast.Ident).Name, indexType.Sel.Name), nil return fmt.Sprintf("%s.%s", fieldType.X.(*ast.Ident).Name, fieldType.Sel.Name), nil
} }
return "", fmt.Errorf("unknown type %#v", field) return "", fmt.Errorf("unknown type %#v", field)
} }
func (parser *Parser) parseGenericTypeExpr(file *ast.File, typeExpr ast.Expr) (*spec.Schema, error) {
switch expr := typeExpr.(type) {
// suppress debug messages for these types
case *ast.InterfaceType:
case *ast.StructType:
case *ast.Ident:
case *ast.StarExpr:
case *ast.SelectorExpr:
case *ast.ArrayType:
case *ast.MapType:
case *ast.FuncType:
case *ast.IndexExpr:
name, err := getExtendedGenericFieldType(file, expr, nil)
if err == nil {
if schema, err := parser.getTypeSchema(name, file, false); err == nil {
return schema, nil
}
}
parser.debug.Printf("Type definition of type '%T' is not supported yet. Using 'object' instead. (%s)\n", typeExpr, err)
default:
parser.debug.Printf("Type definition of type '%T' is not supported yet. Using 'object' instead.\n", typeExpr)
}
return PrimitiveSchema(OBJECT), nil
}

View file

@ -5,17 +5,42 @@ package swag
import ( import (
"fmt" "fmt"
"github.com/go-openapi/spec"
"go/ast" "go/ast"
) )
type genericTypeSpec struct {
ArrayDepth int
TypeSpec *TypeSpecDef
Name string
}
func typeSpecFullName(typeSpecDef *TypeSpecDef) string { func typeSpecFullName(typeSpecDef *TypeSpecDef) string {
return typeSpecDef.FullName() return typeSpecDef.FullName()
} }
func (pkgDefs *PackagesDefinitions) parametrizeStruct(original *TypeSpecDef, fullGenericForm string, parseDependency bool) *TypeSpecDef { func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, original *TypeSpecDef, fullGenericForm string, parseDependency bool) *TypeSpecDef {
return original return original
} }
func getGenericFieldType(file *ast.File, field ast.Expr) (string, error) { func getGenericFieldType(file *ast.File, field ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec) (string, error) {
return "", fmt.Errorf("unknown field type %#v", field) return "", fmt.Errorf("unknown field type %#v", field)
} }
func (parser *Parser) parseGenericTypeExpr(file *ast.File, typeExpr ast.Expr) (*spec.Schema, error) {
switch typeExpr.(type) {
// suppress debug messages for these types
case *ast.InterfaceType:
case *ast.StructType:
case *ast.Ident:
case *ast.StarExpr:
case *ast.SelectorExpr:
case *ast.ArrayType:
case *ast.MapType:
case *ast.FuncType:
default:
parser.debug.Printf("Type definition of type '%T' is not supported yet. Using 'object' instead.\n", typeExpr)
}
return PrimitiveSchema(OBJECT), nil
}

View file

@ -6,7 +6,6 @@ import (
"go/ast" "go/ast"
goparser "go/parser" goparser "go/parser"
"go/token" "go/token"
"io/ioutil"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -226,7 +225,7 @@ func findInSlice(arr []string, target string) bool {
} }
func (operation *Operation) parseArrayParam(param *spec.Parameter, paramType, refType, objectType string) error { func (operation *Operation) parseArrayParam(param *spec.Parameter, paramType, refType, objectType string) error {
if !IsPrimitiveType(refType) { if !IsPrimitiveType(refType) && !(refType == "file" && paramType == "formData") {
return fmt.Errorf("%s is not supported array type for %s", refType, paramType) return fmt.Errorf("%s is not supported array type for %s", refType, paramType)
} }
@ -270,7 +269,9 @@ func (operation *Operation) parseArrayParam(param *spec.Parameter, paramType, re
// ParseParamComment parses params return []string of param properties // ParseParamComment parses params return []string of param properties
// E.g. @Param queryText formData string true "The email for login" // E.g. @Param queryText formData string true "The email for login"
// [param name] [paramType] [data type] [is mandatory?] [Comment] //
// [param name] [paramType] [data type] [is mandatory?] [Comment]
//
// E.g. @Param some_id path int true "Some ID". // E.g. @Param some_id path int true "Some ID".
func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.File) error { func (operation *Operation) ParseParamComment(commentLine string, astFile *ast.File) error {
matches := paramPattern.FindStringSubmatch(commentLine) matches := paramPattern.FindStringSubmatch(commentLine)
@ -824,6 +825,10 @@ var responsePattern = regexp.MustCompile(`^([\w,]+)\s+([\w{}]+)\s+([\w\-.\\{}=,\
var combinedPattern = regexp.MustCompile(`^([\w\-./\[\]]+){(.*)}$`) var combinedPattern = regexp.MustCompile(`^([\w\-./\[\]]+){(.*)}$`)
func (operation *Operation) parseObjectSchema(refType string, astFile *ast.File) (*spec.Schema, error) { func (operation *Operation) parseObjectSchema(refType string, astFile *ast.File) (*spec.Schema, error) {
return parseObjectSchema(operation.parser, refType, astFile)
}
func parseObjectSchema(parser *Parser, refType string, astFile *ast.File) (*spec.Schema, error) {
switch { switch {
case refType == NIL: case refType == NIL:
return nil, nil return nil, nil
@ -838,7 +843,7 @@ func (operation *Operation) parseObjectSchema(refType string, astFile *ast.File)
case IsPrimitiveType(refType): case IsPrimitiveType(refType):
return PrimitiveSchema(refType), nil return PrimitiveSchema(refType), nil
case strings.HasPrefix(refType, "[]"): case strings.HasPrefix(refType, "[]"):
schema, err := operation.parseObjectSchema(refType[2:], astFile) schema, err := parseObjectSchema(parser, refType[2:], astFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -856,17 +861,17 @@ func (operation *Operation) parseObjectSchema(refType string, astFile *ast.File)
return spec.MapProperty(nil), nil return spec.MapProperty(nil), nil
} }
schema, err := operation.parseObjectSchema(refType, astFile) schema, err := parseObjectSchema(parser, refType, astFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return spec.MapProperty(schema), nil return spec.MapProperty(schema), nil
case strings.Contains(refType, "{"): case strings.Contains(refType, "{"):
return operation.parseCombinedObjectSchema(refType, astFile) return parseCombinedObjectSchema(parser, refType, astFile)
default: default:
if operation.parser != nil { // checking refType has existing in 'TypeDefinitions' if parser != nil { // checking refType has existing in 'TypeDefinitions'
schema, err := operation.parser.getTypeSchema(refType, astFile, true) schema, err := parser.getTypeSchema(refType, astFile, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -896,13 +901,13 @@ func parseFields(s string) []string {
}) })
} }
func (operation *Operation) parseCombinedObjectSchema(refType string, astFile *ast.File) (*spec.Schema, error) { func parseCombinedObjectSchema(parser *Parser, refType string, astFile *ast.File) (*spec.Schema, error) {
matches := combinedPattern.FindStringSubmatch(refType) matches := combinedPattern.FindStringSubmatch(refType)
if len(matches) != 3 { if len(matches) != 3 {
return nil, fmt.Errorf("invalid type: %s", refType) return nil, fmt.Errorf("invalid type: %s", refType)
} }
schema, err := operation.parseObjectSchema(matches[1], astFile) schema, err := parseObjectSchema(parser, matches[1], astFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -912,7 +917,7 @@ func (operation *Operation) parseCombinedObjectSchema(refType string, astFile *a
for _, field := range fields { for _, field := range fields {
keyVal := strings.SplitN(field, "=", 2) keyVal := strings.SplitN(field, "=", 2)
if len(keyVal) == 2 { if len(keyVal) == 2 {
schema, err := operation.parseObjectSchema(keyVal[1], astFile) schema, err := parseObjectSchema(parser, keyVal[1], astFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1193,17 +1198,17 @@ func createParameter(paramType, description, paramName, schemaType string, requi
} }
func getCodeExampleForSummary(summaryName string, dirPath string) ([]byte, error) { func getCodeExampleForSummary(summaryName string, dirPath string) ([]byte, error) {
filesInfos, err := ioutil.ReadDir(dirPath) dirEntries, err := os.ReadDir(dirPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, fileInfo := range filesInfos { for _, entry := range dirEntries {
if fileInfo.IsDir() { if entry.IsDir() {
continue continue
} }
fileName := fileInfo.Name() fileName := entry.Name()
if !strings.Contains(fileName, ".json") { if !strings.Contains(fileName, ".json") {
continue continue
@ -1212,7 +1217,7 @@ func getCodeExampleForSummary(summaryName string, dirPath string) ([]byte, error
if strings.Contains(fileName, summaryName) { if strings.Contains(fileName, summaryName) {
fullPath := filepath.Join(dirPath, fileName) fullPath := filepath.Join(dirPath, fileName)
commentInfo, err := ioutil.ReadFile(fullPath) commentInfo, err := os.ReadFile(fullPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to read code example file %s error: %s ", fullPath, err) return nil, fmt.Errorf("Failed to read code example file %s error: %s ", fullPath, err)
} }

View file

@ -164,7 +164,8 @@ func (pkgDefs *PackagesDefinitions) parseTypesFromFile(astFile *ast.File, packag
func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *ast.File, packagePath string, parsedSchemas map[*TypeSpecDef]*Schema) { func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *ast.File, packagePath string, parsedSchemas map[*TypeSpecDef]*Schema) {
for _, astDeclaration := range astFile.Decls { for _, astDeclaration := range astFile.Decls {
if funcDeclaration, ok := astDeclaration.(*ast.FuncDecl); ok { funcDeclaration, ok := astDeclaration.(*ast.FuncDecl)
if ok && funcDeclaration.Body != nil {
for _, stmt := range funcDeclaration.Body.List { for _, stmt := range funcDeclaration.Body.List {
if declStmt, ok := (stmt).(*ast.DeclStmt); ok { if declStmt, ok := (stmt).(*ast.DeclStmt); ok {
if genDecl, ok := (declStmt.Decl).(*ast.GenDecl); ok && genDecl.Tok == token.TYPE { if genDecl, ok := (declStmt.Decl).(*ast.GenDecl); ok && genDecl.Tok == token.TYPE {
@ -388,25 +389,17 @@ func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File
} }
} }
if strings.Contains(typeName, "[") { if def := pkgDefs.findGenericTypeSpec(typeName, file, parseDependency); def != nil {
// joinedParts differs from typeName in that it does not contain any type parameters return def
joinedParts := strings.Join(parts, ".")
for tName, tSpec := range pkgDefs.uniqueDefinitions {
if !strings.Contains(tName, "[") {
continue
}
if strings.Contains(tName, joinedParts) {
if parametrized := pkgDefs.parametrizeStruct(tSpec, typeName, parseDependency); parametrized != nil {
return parametrized
}
}
}
} }
return pkgDefs.findTypeSpec(pkgPath, parts[1]) return pkgDefs.findTypeSpec(pkgPath, parts[1])
} }
if def := pkgDefs.findGenericTypeSpec(fullTypeName(file.Name.Name, typeName), file, parseDependency); def != nil {
return def
}
typeDef, ok := pkgDefs.uniqueDefinitions[fullTypeName(file.Name.Name, typeName)] typeDef, ok := pkgDefs.uniqueDefinitions[fullTypeName(file.Name.Name, typeName)]
if ok { if ok {
return typeDef return typeDef
@ -428,3 +421,22 @@ func (pkgDefs *PackagesDefinitions) FindTypeSpec(typeName string, file *ast.File
return nil return nil
} }
func (pkgDefs *PackagesDefinitions) findGenericTypeSpec(typeName string, file *ast.File, parseDependency bool) *TypeSpecDef {
if strings.Contains(typeName, "[") {
// genericName differs from typeName in that it does not contain any type parameters
genericName := strings.SplitN(typeName, "[", 2)[0]
for tName, tSpec := range pkgDefs.uniqueDefinitions {
if !strings.Contains(tName, "[") {
continue
}
if strings.Contains(tName, genericName) {
if parametrized := pkgDefs.parametrizeGenericType(file, tSpec, typeName, parseDependency); parametrized != nil {
return parametrized
}
}
}
}
return nil
}

View file

@ -9,7 +9,6 @@ import (
"go/build" "go/build"
goparser "go/parser" goparser "go/parser"
"go/token" "go/token"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"net/url" "net/url"
@ -733,17 +732,17 @@ func isGeneralAPIComment(comments []string) bool {
} }
func getMarkdownForTag(tagName string, dirPath string) ([]byte, error) { func getMarkdownForTag(tagName string, dirPath string) ([]byte, error) {
filesInfos, err := ioutil.ReadDir(dirPath) dirEntries, err := os.ReadDir(dirPath)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, fileInfo := range filesInfos { for _, entry := range dirEntries {
if fileInfo.IsDir() { if entry.IsDir() {
continue continue
} }
fileName := fileInfo.Name() fileName := entry.Name()
if !strings.Contains(fileName, ".md") { if !strings.Contains(fileName, ".md") {
continue continue
@ -752,7 +751,7 @@ func getMarkdownForTag(tagName string, dirPath string) ([]byte, error) {
if strings.Contains(fileName, tagName) { if strings.Contains(fileName, tagName) {
fullPath := filepath.Join(dirPath, fileName) fullPath := filepath.Join(dirPath, fileName)
commentInfo, err := ioutil.ReadFile(fullPath) commentInfo, err := os.ReadFile(fullPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to read markdown file %s error: %s ", fullPath, err) return nil, fmt.Errorf("Failed to read markdown file %s error: %s ", fullPath, err)
} }
@ -873,7 +872,7 @@ func convertFromSpecificToPrimitive(typeName string) (string, error) {
func (parser *Parser) getTypeSchema(typeName string, file *ast.File, ref bool) (*spec.Schema, error) { func (parser *Parser) getTypeSchema(typeName string, file *ast.File, ref bool) (*spec.Schema, error) {
if override, ok := parser.Overrides[typeName]; ok { if override, ok := parser.Overrides[typeName]; ok {
parser.debug.Printf("Override detected for %s: using %s instead", typeName, override) parser.debug.Printf("Override detected for %s: using %s instead", typeName, override)
typeName = override return parseObjectSchema(parser, override, file)
} }
if IsInterfaceLike(typeName) { if IsInterfaceLike(typeName) {
@ -1186,12 +1185,10 @@ func (parser *Parser) parseTypeExpr(file *ast.File, typeExpr ast.Expr, ref bool)
case *ast.FuncType: case *ast.FuncType:
return nil, ErrFuncTypeField return nil, ErrFuncTypeField
// ... // ...
default:
parser.debug.Printf("Type definition of type '%T' is not supported yet. Using 'object' instead.\n", typeExpr)
} }
return PrimitiveSchema(OBJECT), nil return parser.parseGenericTypeExpr(file, typeExpr)
} }
func (parser *Parser) parseStruct(file *ast.File, fields *ast.FieldList) (*spec.Schema, error) { func (parser *Parser) parseStruct(file *ast.File, fields *ast.FieldList) (*spec.Schema, error) {
@ -1334,7 +1331,7 @@ func getFieldType(file *ast.File, field ast.Expr) (string, error) {
return fullName, nil return fullName, nil
default: default:
return getGenericFieldType(file, field) return getGenericFieldType(file, field, nil)
} }
} }
@ -1490,7 +1487,7 @@ func (parser *Parser) getAllGoFileInfoFromDeps(pkg *depth.Pkg) error {
srcDir := pkg.Raw.Dir srcDir := pkg.Raw.Dir
files, err := ioutil.ReadDir(srcDir) // only parsing files in the dir(don't contain sub dir files) files, err := os.ReadDir(srcDir) // only parsing files in the dir(don't contain sub dir files)
if err != nil { if err != nil {
return err return err
} }

View file

@ -1,4 +1,4 @@
package swag package swag
// Version of swag. // Version of swag.
const Version = "v1.8.5" const Version = "v1.8.7"

View file

@ -112,8 +112,7 @@ func ExecuteFuncString(template, startTag, endTag string, f TagFunc) string {
// but when f returns an error, ExecuteFuncStringWithErr won't panic like ExecuteFuncString // but when f returns an error, ExecuteFuncStringWithErr won't panic like ExecuteFuncString
// it just returns an empty string and the error f returned // it just returns an empty string and the error f returned
func ExecuteFuncStringWithErr(template, startTag, endTag string, f TagFunc) (string, error) { func ExecuteFuncStringWithErr(template, startTag, endTag string, f TagFunc) (string, error) {
tagsCount := bytes.Count(unsafeString2Bytes(template), unsafeString2Bytes(startTag)) if n := bytes.Index(unsafeString2Bytes(template), unsafeString2Bytes(startTag)); n < 0 {
if tagsCount == 0 {
return template, nil return template, nil
} }

View file

@ -336,7 +336,6 @@ func (p *parser) parseTypeReference() *Type {
} }
if p.skip(lexer.Bang) { if p.skip(lexer.Bang) {
typ.Position = p.peekPos()
typ.NonNull = true typ.NonNull = true
} }
return &typ return &typ

View file

@ -10,3 +10,6 @@ lint.log
# Profiling output # Profiling output
*.prof *.prof
# Output of fossa analyzer
/fossa

View file

@ -1,27 +0,0 @@
sudo: false
language: go
go_import_path: go.uber.org/atomic
env:
global:
- GO111MODULE=on
matrix:
include:
- go: oldstable
- go: stable
env: LINT=1
cache:
directories:
- vendor
before_install:
- go version
script:
- test -z "$LINT" || make lint
- make cover
after_success:
- bash <(curl -s https://codecov.io/bash)

View file

@ -4,6 +4,37 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.10.0] - 2022-08-11
### Added
- Add `atomic.Float32` type for atomic operations on `float32`.
- Add `CompareAndSwap` and `Swap` methods to `atomic.String`, `atomic.Error`,
and `atomic.Value`.
- Add generic `atomic.Pointer[T]` type for atomic operations on pointers of any
type. This is present only for Go 1.18 or higher, and is a drop-in for
replacement for the standard library's `sync/atomic.Pointer` type.
### Changed
- Deprecate `CAS` methods on all types in favor of corresponding
`CompareAndSwap` methods.
Thanks to @eNV25 and @icpd for their contributions to this release.
[1.10.0]: https://github.com/uber-go/atomic/compare/v1.9.0...v1.10.0
## [1.9.0] - 2021-07-15
### Added
- Add `Float64.Swap` to match int atomic operations.
- Add `atomic.Time` type for atomic operations on `time.Time` values.
[1.9.0]: https://github.com/uber-go/atomic/compare/v1.8.0...v1.9.0
## [1.8.0] - 2021-06-09
### Added
- Add `atomic.Uintptr` type for atomic operations on `uintptr` values.
- Add `atomic.UnsafePointer` type for atomic operations on `unsafe.Pointer` values.
[1.8.0]: https://github.com/uber-go/atomic/compare/v1.7.0...v1.8.0
## [1.7.0] - 2020-09-14 ## [1.7.0] - 2020-09-14
### Added ### Added
- Support JSON serialization and deserialization of primitive atomic types. - Support JSON serialization and deserialization of primitive atomic types.
@ -15,32 +46,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed ### Removed
- Remove dependency on `golang.org/x/{lint, tools}`. - Remove dependency on `golang.org/x/{lint, tools}`.
[1.7.0]: https://github.com/uber-go/atomic/compare/v1.6.0...v1.7.0
## [1.6.0] - 2020-02-24 ## [1.6.0] - 2020-02-24
### Changed ### Changed
- Drop library dependency on `golang.org/x/{lint, tools}`. - Drop library dependency on `golang.org/x/{lint, tools}`.
[1.6.0]: https://github.com/uber-go/atomic/compare/v1.5.1...v1.6.0
## [1.5.1] - 2019-11-19 ## [1.5.1] - 2019-11-19
- Fix bug where `Bool.CAS` and `Bool.Toggle` do work correctly together - Fix bug where `Bool.CAS` and `Bool.Toggle` do work correctly together
causing `CAS` to fail even though the old value matches. causing `CAS` to fail even though the old value matches.
[1.5.1]: https://github.com/uber-go/atomic/compare/v1.5.0...v1.5.1
## [1.5.0] - 2019-10-29 ## [1.5.0] - 2019-10-29
### Changed ### Changed
- With Go modules, only the `go.uber.org/atomic` import path is supported now. - With Go modules, only the `go.uber.org/atomic` import path is supported now.
If you need to use the old import path, please add a `replace` directive to If you need to use the old import path, please add a `replace` directive to
your `go.mod`. your `go.mod`.
[1.5.0]: https://github.com/uber-go/atomic/compare/v1.4.0...v1.5.0
## [1.4.0] - 2019-05-01 ## [1.4.0] - 2019-05-01
### Added ### Added
- Add `atomic.Error` type for atomic operations on `error` values. - Add `atomic.Error` type for atomic operations on `error` values.
[1.4.0]: https://github.com/uber-go/atomic/compare/v1.3.2...v1.4.0
## [1.3.2] - 2018-05-02 ## [1.3.2] - 2018-05-02
### Added ### Added
- Add `atomic.Duration` type for atomic operations on `time.Duration` values. - Add `atomic.Duration` type for atomic operations on `time.Duration` values.
[1.3.2]: https://github.com/uber-go/atomic/compare/v1.3.1...v1.3.2
## [1.3.1] - 2017-11-14 ## [1.3.1] - 2017-11-14
### Fixed ### Fixed
- Revert optimization for `atomic.String.Store("")` which caused data races. - Revert optimization for `atomic.String.Store("")` which caused data races.
[1.3.1]: https://github.com/uber-go/atomic/compare/v1.3.0...v1.3.1
## [1.3.0] - 2017-11-13 ## [1.3.0] - 2017-11-13
### Added ### Added
- Add `atomic.Bool.CAS` for compare-and-swap semantics on bools. - Add `atomic.Bool.CAS` for compare-and-swap semantics on bools.
@ -48,10 +93,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Optimize `atomic.String.Store("")` by avoiding an allocation. - Optimize `atomic.String.Store("")` by avoiding an allocation.
[1.3.0]: https://github.com/uber-go/atomic/compare/v1.2.0...v1.3.0
## [1.2.0] - 2017-04-12 ## [1.2.0] - 2017-04-12
### Added ### Added
- Shadow `atomic.Value` from `sync/atomic`. - Shadow `atomic.Value` from `sync/atomic`.
[1.2.0]: https://github.com/uber-go/atomic/compare/v1.1.0...v1.2.0
## [1.1.0] - 2017-03-10 ## [1.1.0] - 2017-03-10
### Added ### Added
- Add atomic `Float64` type. - Add atomic `Float64` type.
@ -59,18 +108,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Support new `go.uber.org/atomic` import path. - Support new `go.uber.org/atomic` import path.
[1.1.0]: https://github.com/uber-go/atomic/compare/v1.0.0...v1.1.0
## [1.0.0] - 2016-07-18 ## [1.0.0] - 2016-07-18
- Initial release. - Initial release.
[1.7.0]: https://github.com/uber-go/atomic/compare/v1.6.0...v1.7.0
[1.6.0]: https://github.com/uber-go/atomic/compare/v1.5.1...v1.6.0
[1.5.1]: https://github.com/uber-go/atomic/compare/v1.5.0...v1.5.1
[1.5.0]: https://github.com/uber-go/atomic/compare/v1.4.0...v1.5.0
[1.4.0]: https://github.com/uber-go/atomic/compare/v1.3.2...v1.4.0
[1.3.2]: https://github.com/uber-go/atomic/compare/v1.3.1...v1.3.2
[1.3.1]: https://github.com/uber-go/atomic/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/uber-go/atomic/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/uber-go/atomic/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/uber-go/atomic/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/uber-go/atomic/releases/tag/v1.0.0 [1.0.0]: https://github.com/uber-go/atomic/releases/tag/v1.0.0

1
vendor/go.uber.org/atomic/Makefile generated vendored
View file

@ -69,6 +69,7 @@ generate: $(GEN_ATOMICINT) $(GEN_ATOMICWRAPPER)
generatenodirty: generatenodirty:
@[ -z "$$(git status --porcelain)" ] || ( \ @[ -z "$$(git status --porcelain)" ] || ( \
echo "Working tree is dirty. Commit your changes first."; \ echo "Working tree is dirty. Commit your changes first."; \
git status; \
exit 1 ) exit 1 )
@make generate @make generate
@status=$$(git status --porcelain); \ @status=$$(git status --porcelain); \

View file

@ -55,8 +55,8 @@ Released under the [MIT License](LICENSE.txt).
[doc-img]: https://godoc.org/github.com/uber-go/atomic?status.svg [doc-img]: https://godoc.org/github.com/uber-go/atomic?status.svg
[doc]: https://godoc.org/go.uber.org/atomic [doc]: https://godoc.org/go.uber.org/atomic
[ci-img]: https://travis-ci.com/uber-go/atomic.svg?branch=master [ci-img]: https://github.com/uber-go/atomic/actions/workflows/go.yml/badge.svg
[ci]: https://travis-ci.com/uber-go/atomic [ci]: https://github.com/uber-go/atomic/actions/workflows/go.yml
[cov-img]: https://codecov.io/gh/uber-go/atomic/branch/master/graph/badge.svg [cov-img]: https://codecov.io/gh/uber-go/atomic/branch/master/graph/badge.svg
[cov]: https://codecov.io/gh/uber-go/atomic [cov]: https://codecov.io/gh/uber-go/atomic
[reportcard-img]: https://goreportcard.com/badge/go.uber.org/atomic [reportcard-img]: https://goreportcard.com/badge/go.uber.org/atomic

27
vendor/go.uber.org/atomic/bool.go generated vendored
View file

@ -1,6 +1,6 @@
// @generated Code generated by gen-atomicwrapper. // @generated Code generated by gen-atomicwrapper.
// Copyright (c) 2020 Uber Technologies, Inc. // Copyright (c) 2020-2022 Uber Technologies, Inc.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -36,10 +36,10 @@ type Bool struct {
var _zeroBool bool var _zeroBool bool
// NewBool creates a new Bool. // NewBool creates a new Bool.
func NewBool(v bool) *Bool { func NewBool(val bool) *Bool {
x := &Bool{} x := &Bool{}
if v != _zeroBool { if val != _zeroBool {
x.Store(v) x.Store(val)
} }
return x return x
} }
@ -50,19 +50,26 @@ func (x *Bool) Load() bool {
} }
// Store atomically stores the passed bool. // Store atomically stores the passed bool.
func (x *Bool) Store(v bool) { func (x *Bool) Store(val bool) {
x.v.Store(boolToInt(v)) x.v.Store(boolToInt(val))
} }
// CAS is an atomic compare-and-swap for bool values. // CAS is an atomic compare-and-swap for bool values.
func (x *Bool) CAS(o, n bool) bool { //
return x.v.CAS(boolToInt(o), boolToInt(n)) // Deprecated: Use CompareAndSwap.
func (x *Bool) CAS(old, new bool) (swapped bool) {
return x.CompareAndSwap(old, new)
}
// CompareAndSwap is an atomic compare-and-swap for bool values.
func (x *Bool) CompareAndSwap(old, new bool) (swapped bool) {
return x.v.CompareAndSwap(boolToInt(old), boolToInt(new))
} }
// Swap atomically stores the given bool and returns the old // Swap atomically stores the given bool and returns the old
// value. // value.
func (x *Bool) Swap(o bool) bool { func (x *Bool) Swap(val bool) (old bool) {
return truthy(x.v.Swap(boolToInt(o))) return truthy(x.v.Swap(boolToInt(val)))
} }
// MarshalJSON encodes the wrapped bool into JSON. // MarshalJSON encodes the wrapped bool into JSON.

View file

@ -38,7 +38,7 @@ func boolToInt(b bool) uint32 {
} }
// Toggle atomically negates the Boolean and returns the previous value. // Toggle atomically negates the Boolean and returns the previous value.
func (b *Bool) Toggle() bool { func (b *Bool) Toggle() (old bool) {
for { for {
old := b.Load() old := b.Load()
if b.CAS(old, !old) { if b.CAS(old, !old) {

View file

@ -1,6 +1,6 @@
// @generated Code generated by gen-atomicwrapper. // @generated Code generated by gen-atomicwrapper.
// Copyright (c) 2020 Uber Technologies, Inc. // Copyright (c) 2020-2022 Uber Technologies, Inc.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -37,10 +37,10 @@ type Duration struct {
var _zeroDuration time.Duration var _zeroDuration time.Duration
// NewDuration creates a new Duration. // NewDuration creates a new Duration.
func NewDuration(v time.Duration) *Duration { func NewDuration(val time.Duration) *Duration {
x := &Duration{} x := &Duration{}
if v != _zeroDuration { if val != _zeroDuration {
x.Store(v) x.Store(val)
} }
return x return x
} }
@ -51,19 +51,26 @@ func (x *Duration) Load() time.Duration {
} }
// Store atomically stores the passed time.Duration. // Store atomically stores the passed time.Duration.
func (x *Duration) Store(v time.Duration) { func (x *Duration) Store(val time.Duration) {
x.v.Store(int64(v)) x.v.Store(int64(val))
} }
// CAS is an atomic compare-and-swap for time.Duration values. // CAS is an atomic compare-and-swap for time.Duration values.
func (x *Duration) CAS(o, n time.Duration) bool { //
return x.v.CAS(int64(o), int64(n)) // Deprecated: Use CompareAndSwap.
func (x *Duration) CAS(old, new time.Duration) (swapped bool) {
return x.CompareAndSwap(old, new)
}
// CompareAndSwap is an atomic compare-and-swap for time.Duration values.
func (x *Duration) CompareAndSwap(old, new time.Duration) (swapped bool) {
return x.v.CompareAndSwap(int64(old), int64(new))
} }
// Swap atomically stores the given time.Duration and returns the old // Swap atomically stores the given time.Duration and returns the old
// value. // value.
func (x *Duration) Swap(o time.Duration) time.Duration { func (x *Duration) Swap(val time.Duration) (old time.Duration) {
return time.Duration(x.v.Swap(int64(o))) return time.Duration(x.v.Swap(int64(val)))
} }
// MarshalJSON encodes the wrapped time.Duration into JSON. // MarshalJSON encodes the wrapped time.Duration into JSON.

View file

@ -25,13 +25,13 @@ import "time"
//go:generate bin/gen-atomicwrapper -name=Duration -type=time.Duration -wrapped=Int64 -pack=int64 -unpack=time.Duration -cas -swap -json -imports time -file=duration.go //go:generate bin/gen-atomicwrapper -name=Duration -type=time.Duration -wrapped=Int64 -pack=int64 -unpack=time.Duration -cas -swap -json -imports time -file=duration.go
// Add atomically adds to the wrapped time.Duration and returns the new value. // Add atomically adds to the wrapped time.Duration and returns the new value.
func (d *Duration) Add(n time.Duration) time.Duration { func (d *Duration) Add(delta time.Duration) time.Duration {
return time.Duration(d.v.Add(int64(n))) return time.Duration(d.v.Add(int64(delta)))
} }
// Sub atomically subtracts from the wrapped time.Duration and returns the new value. // Sub atomically subtracts from the wrapped time.Duration and returns the new value.
func (d *Duration) Sub(n time.Duration) time.Duration { func (d *Duration) Sub(delta time.Duration) time.Duration {
return time.Duration(d.v.Sub(int64(n))) return time.Duration(d.v.Sub(int64(delta)))
} }
// String encodes the wrapped value as a string. // String encodes the wrapped value as a string.

23
vendor/go.uber.org/atomic/error.go generated vendored
View file

@ -1,6 +1,6 @@
// @generated Code generated by gen-atomicwrapper. // @generated Code generated by gen-atomicwrapper.
// Copyright (c) 2020 Uber Technologies, Inc. // Copyright (c) 2020-2022 Uber Technologies, Inc.
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy // Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal // of this software and associated documentation files (the "Software"), to deal
@ -32,10 +32,10 @@ type Error struct {
var _zeroError error var _zeroError error
// NewError creates a new Error. // NewError creates a new Error.
func NewError(v error) *Error { func NewError(val error) *Error {
x := &Error{} x := &Error{}
if v != _zeroError { if val != _zeroError {
x.Store(v) x.Store(val)
} }
return x return x
} }
@ -46,6 +46,17 @@ func (x *Error) Load() error {
} }
// Store atomically stores the passed error. // Store atomically stores the passed error.
func (x *Error) Store(v error) { func (x *Error) Store(val error) {
x.v.Store(packError(v)) x.v.Store(packError(val))
}
// CompareAndSwap is an atomic compare-and-swap for error values.
func (x *Error) CompareAndSwap(old, new error) (swapped bool) {
return x.v.CompareAndSwap(packError(old), packError(new))
}
// Swap atomically stores the given error and returns the old
// value.
func (x *Error) Swap(val error) (old error) {
return unpackError(x.v.Swap(packError(val)))
} }

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