diff --git a/go.mod b/go.mod index 77bbfe263..725a1f730 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/aws/aws-sdk-go-v2/credentials v1.16.16 github.com/aws/aws-sdk-go-v2/service/sns v1.26.7 github.com/aws/aws-sdk-go-v2/service/sqs v1.29.7 - github.com/eclipse/paho.golang v0.12.0 + github.com/eclipse/paho.golang v0.21.0 github.com/elastic/elastic-transport-go/v8 v8.4.0 github.com/elastic/go-elasticsearch/v8 v8.12.0 github.com/go-redis/redis/extra/rediscmd v0.2.0 @@ -81,10 +81,10 @@ require ( go.opentelemetry.io/otel/metric v1.21.0 // indirect go.opentelemetry.io/otel/trace v1.21.0 // indirect go.uber.org/atomic v1.11.0 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/net v0.18.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/net v0.21.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 17c3174f2..249d7b55a 100644 --- a/go.sum +++ b/go.sum @@ -49,8 +49,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4A github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/eclipse/paho.golang v0.12.0 h1:EXQFJbJklDnUqW6lyAknMWRhM2NgpHxwrrL8riUmp3Q= -github.com/eclipse/paho.golang v0.12.0/go.mod h1:TSDCUivu9JnoR9Hl+H7sQMcHkejWH2/xKK1NJGtLbIE= +github.com/eclipse/paho.golang v0.21.0 h1:cxxEReu+iFbA5RrHfRGxJOh8tXZKDywuehneoeBeyn8= +github.com/eclipse/paho.golang v0.21.0/go.mod h1:GHF6vy7SvDbDHBguaUpfuBkEB5G6j0zKxMG4gbh6QRQ= github.com/elastic/elastic-transport-go/v8 v8.4.0 h1:EKYiH8CHd33BmMna2Bos1rDNMM89+hdgcymI+KzJCGE= github.com/elastic/elastic-transport-go/v8 v8.4.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk= github.com/elastic/go-elasticsearch/v8 v8.12.0 h1:krkiCf4peJa7bZwGegy01b5xWWaYpik78wvisTeRO1U= @@ -223,8 +223,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -237,8 +237,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= -golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -259,8 +259,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/vendor/github.com/eclipse/paho.golang/LICENSE b/vendor/github.com/eclipse/paho.golang/LICENSE index d3087e4c5..f55c39539 100644 --- a/vendor/github.com/eclipse/paho.golang/LICENSE +++ b/vendor/github.com/eclipse/paho.golang/LICENSE @@ -1,3 +1,20 @@ +Eclipse Public License - v 2.0 (EPL-2.0) + +This program and the accompanying materials +are made available under the terms of the Eclipse Public License v2.0 +and Eclipse Distribution License v1.0 which accompany this distribution. + +The Eclipse Public License is available at + https://www.eclipse.org/legal/epl-2.0/ +and the Eclipse Distribution License is available at + http://www.eclipse.org/org/documents/edl-v10.php. + +For an explanation of what dual-licensing means to you, see: +https://www.eclipse.org/legal/eplfaq.php#DUALLIC + +**** +The epl-2.0 is copied below in order to pass the pkg.go.dev license check (https://pkg.go.dev/license-policy). +**** Eclipse Public License - v 2.0 THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE @@ -261,8 +278,8 @@ No third-party beneficiary rights are created under this Agreement. Exhibit A - Form of Secondary Licenses Notice -"This Source Code may also be made available under the following -Secondary Licenses when the conditions for such availability set forth +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}." diff --git a/vendor/github.com/eclipse/paho.golang/autopaho/auto.go b/vendor/github.com/eclipse/paho.golang/autopaho/auto.go index 80a4436aa..7e574463d 100644 --- a/vendor/github.com/eclipse/paho.golang/autopaho/auto.go +++ b/vendor/github.com/eclipse/paho.golang/autopaho/auto.go @@ -1,6 +1,22 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package autopaho import ( + "bytes" "context" "crypto/tls" "errors" @@ -11,6 +27,11 @@ import ( "sync" "time" + "github.com/eclipse/paho.golang/autopaho/queue" + "github.com/eclipse/paho.golang/autopaho/queue/memory" + "github.com/eclipse/paho.golang/packets" + "github.com/eclipse/paho.golang/paho/log" + "github.com/eclipse/paho.golang/paho/session/state" "github.com/gorilla/websocket" "github.com/eclipse/paho.golang/paho" @@ -19,15 +40,15 @@ import ( // AutoPaho is a wrapper around github.com/eclipse/paho.golang that simplifies the connection process; it automates // connections (retrying until the connection comes up) and will attempt to re-establish the connection if it is lost. // -// The aim is to cover a common requirement (connect to the broker and try to keep the connection up); if your +// The aim is to cover a common requirement (connect to the server and try to keep the connection up); if your // requirements differ then please consider using github.com/eclipse/paho.golang directly (perhaps using the // code in this file as a base; a secondary aim is to provide example code!). -// ConnectionDownError Down will be returned when a request is made but the connection to the broker is down -// Note: It is possible that the connection will drop between the request being made and a response being received in +// ConnectionDownError Down will be returned when a request is made but the connection to the server is down +// Note: It is possible that the connection will drop between the request being made and a response being received, in // which case a different error will be received (this is only returned if the connection is down at the time the // request is made). -var ConnectionDownError = errors.New("connection with the MQTT broker is currently down") +var ConnectionDownError = errors.New("connection with the MQTT server is currently down") // WebSocketConfig enables customisation of the websocket connection type WebSocketConfig struct { @@ -35,16 +56,29 @@ type WebSocketConfig struct { Header func(url *url.URL, tlsCfg *tls.Config) http.Header // If non-nil this will be called before each connection attempt to get headers to include with request } +type PublishReceived struct { + paho.PublishReceived + ConnectionManager *ConnectionManager +} + // ClientConfig adds a few values, required to manage the connection, to the standard paho.ClientConfig (note that // conn will be ignored) type ClientConfig struct { - BrokerUrls []*url.URL // URL(s) for the broker (schemes supported include 'mqtt' and 'tls') - TlsCfg *tls.Config // Configuration used when connecting using TLS - KeepAlive uint16 // Keepalive period in seconds (the maximum time interval that is permitted to elapse between the point at which the Client finishes transmitting one MQTT Control Packet and the point it starts sending the next) + ServerUrls []*url.URL // URL(s) for the MQTT server (schemes supported include 'mqtt' and 'tls') + TlsCfg *tls.Config // Configuration used when connecting using TLS + KeepAlive uint16 // Keepalive period in seconds (the maximum time interval that is permitted to elapse between the point at which the Client finishes transmitting one MQTT Control Packet and the point it starts sending the next) + CleanStartOnInitialConnection bool // Clean Start flag, if true, existing session information will be cleared on the first connection (it will be false for subsequent connections) + SessionExpiryInterval uint32 // Session Expiry Interval in seconds (if 0 the Session ends when the Network Connection is closed) + ConnectRetryDelay time.Duration // How long to wait between connection attempts (defaults to 10s) ConnectTimeout time.Duration // How long to wait for the connection process to complete (defaults to 10s) WebSocketCfg *WebSocketConfig // Enables customisation of the websocket connection + Queue queue.Queue // Used to queue up publish messages (if nil an error will be returned if publish could not be transmitted) + + // Depreciated: Use ServerUrls instead (this will be used if ServerUrls is empty). Will be removed in a future release. + BrokerUrls []*url.URL + // AttemptConnection, if provided, will be called to establish a network connection. // The returned `conn` must support thread safe writing; most wrapped net.Conn implementations like tls.Conn // are not thread safe for writing. @@ -54,120 +88,147 @@ type ClientConfig struct { OnConnectionUp func(*ConnectionManager, *paho.Connack) // Called (within a goroutine) when a connection is made (including reconnection). Connection Manager passed to simplify subscriptions. OnConnectError func(error) // Called (within a goroutine) whenever a connection attempt fails. Will wrap autopaho.ConnackError on server deny. - Debug paho.Logger // By default set to NOOPLogger{},set to a logger for debugging info - PahoDebug paho.Logger // debugger passed to the paho package (will default to NOOPLogger{}) - PahoErrors paho.Logger // error logger passed to the paho package (will default to NOOPLogger{}) + Debug log.Logger // By default set to NOOPLogger{},set to a logger for debugging info + Errors log.Logger // By default set to NOOPLogger{},set to a logger for errors + PahoDebug log.Logger // debugger passed to the paho package (will default to NOOPLogger{}) + PahoErrors log.Logger // error logger passed to the paho package (will default to NOOPLogger{}) - connectUsername string - connectPassword []byte + ConnectUsername string + ConnectPassword []byte - willTopic string - willPayload []byte - willQos byte - willRetain bool - willPayloadFormat byte - willMessageExpiry uint32 - willContentType string - willResponseTopic string - willCorrelationData []byte + WillMessage *paho.WillMessage + WillProperties *paho.WillProperties - connectPacketBuilder func(*paho.Connect) *paho.Connect + ConnectPacketBuilder func(*paho.Connect, *url.URL) *paho.Connect // called prior to connection allowing customisation of the CONNECT packet + + // DisconnectPacketBuilder - called prior to disconnection allowing customisation of the DISCONNECT + // packet. If the function returns nil, then no DISCONNECT packet will be passed; if nil a default packet is sent. + DisconnectPacketBuilder func() *paho.Disconnect // We include the full paho.ClientConfig in order to simplify moving between the two packages. // Note that Conn will be ignored. paho.ClientConfig } -// ConnectionManager manages the connection with the broker and provides thew ability to publish messages +// ConnectionManager manages the connection with the server and provides the ability to publish messages type ConnectionManager struct { - cli *paho.Client // The client will only be set when the connection is up (only updated within NewBrokerConnection goRoutine) - connUp chan struct{} // Channel is closed when the connection is up - mu sync.Mutex // protects both of the above + cli *paho.Client // The client will only be set when the connection is up (only updated within NewServerConnection goRoutine) + connUp chan struct{} // Channel is closed when the connection is up (only valid if cli == nil; must lock Mu to read) + connDown chan struct{} // Channel is closed when the connection is down (only valid if cli != nil; must lock Mu to read) + mu sync.Mutex // protects all of the above + cfg ClientConfig // The config passed to NewConnection (stored to enable getters) cancelCtx context.CancelFunc // Calling this will shut things down cleanly + queue queue.Queue // In not nil, this will be used to queue publish requests + queueWg sync.WaitGroup // Waits on goroutine that monitors Queue + done chan struct{} // Channel that will be closed when the process has cleanly shutdown + + debug log.Logger // By default set to NOOPLogger{},set to a logger for debugging info + errors log.Logger // By default set to NOOPLogger{},set to a logger for errors } // ResetUsernamePassword clears any configured username and password on the client configuration +// +// Set ConnectUsername and ConnectPassword directly instead. func (cfg *ClientConfig) ResetUsernamePassword() { - cfg.connectPassword = []byte{} - cfg.connectUsername = "" + cfg.ConnectPassword = []byte{} + cfg.ConnectUsername = "" } // SetUsernamePassword configures username and password properties for the Connect packets // These values are staged in the ClientConfig, and preparation of the Connect packet is deferred. +// +// Deprecated: Set ConnectUsername and ConnectPassword directly instead. func (cfg *ClientConfig) SetUsernamePassword(username string, password []byte) { if len(username) > 0 { - cfg.connectUsername = username + cfg.ConnectUsername = username } if len(password) > 0 { - cfg.connectPassword = password + cfg.ConnectPassword = password } } // SetWillMessage configures the Will topic, payload, QOS and Retain facets of the client connection // These values are staged in the ClientConfig, for later preparation of the Connect packet. +// +// Deprecated: Set WillMessage and WillProperties directly instead. func (cfg *ClientConfig) SetWillMessage(topic string, payload []byte, qos byte, retain bool) { - cfg.willTopic = topic - cfg.willPayload = payload - cfg.willQos = qos - cfg.willRetain = retain + cfg.WillMessage = &paho.WillMessage{ + Retain: retain, + Payload: payload, + Topic: topic, + QoS: qos, + } + + // Default Will Properties will match the values used in previous versions for compatibility + willDelayInterval := uint32(2 * cfg.KeepAlive) + + cfg.WillProperties = &paho.WillProperties{ + // Most of these are nil/empty or defaults until related methods are exposed for configuration + WillDelayInterval: &willDelayInterval, + } } // SetConnectPacketConfigurator assigns a callback for modification of the Connect packet, called before the connection is opened, allowing the application to adjust its configuration before establishing a connection. // This function should be treated as asynchronous, and expected to have no side effects. +// +// Deprecated: Set ConnectPacketBuilder directly instead. This function exists for +// backwards compatibility only (and may be removed in the future). func (cfg *ClientConfig) SetConnectPacketConfigurator(fn func(*paho.Connect) *paho.Connect) bool { - cfg.connectPacketBuilder = fn + cfg.ConnectPacketBuilder = func(pc *paho.Connect, u *url.URL) *paho.Connect { + return fn(pc) + } return fn != nil } +// SetDisConnectPacketConfigurator assigns a callback for the provision of a DISCONNECT packet. By default, a DISCONNECT +// is sent to the server when Disconnect is called; setting a callback allows a custom packet to be provided, or no +// packet (by returning nil). +// +// Deprecated: Set DisconnectPacketBuilder directly instead. This function exists for +// backwards compatibility only (and may be removed in the future). +func (cfg *ClientConfig) SetDisConnectPacketConfigurator(fn func() *paho.Disconnect) { + cfg.DisconnectPacketBuilder = fn +} + // buildConnectPacket constructs a Connect packet for the paho client, based on staged configuration. // If the program uses SetConnectPacketConfigurator, the provided callback will be executed with the preliminary Connect packet representation. -func (cfg *ClientConfig) buildConnectPacket() *paho.Connect { +func (cfg *ClientConfig) buildConnectPacket(firstConnection bool, serverURL *url.URL) *paho.Connect { cp := &paho.Connect{ KeepAlive: cfg.KeepAlive, ClientID: cfg.ClientID, - CleanStart: true, // while persistence is not supported we should probably start clean... + CleanStart: cfg.CleanStartOnInitialConnection && firstConnection, } - if len(cfg.connectUsername) > 0 { + if len(cfg.ConnectUsername) > 0 { cp.UsernameFlag = true - cp.Username = cfg.connectUsername + cp.Username = cfg.ConnectUsername } - if len(cfg.connectPassword) > 0 { + if len(cfg.ConnectPassword) > 0 { cp.PasswordFlag = true - cp.Password = cfg.connectPassword + cp.Password = cfg.ConnectPassword } - if len(cfg.willTopic) > 0 && len(cfg.willPayload) > 0 { - cp.WillMessage = &paho.WillMessage{ - Retain: cfg.willRetain, - Payload: cfg.willPayload, - Topic: cfg.willTopic, - QoS: cfg.willQos, + if cfg.WillMessage != nil { + cp.WillMessage = cfg.WillMessage + if cfg.WillProperties != nil { + cp.WillProperties = cfg.WillProperties + } else { + cp.WillProperties = &paho.WillProperties{} } + } - // how the broker should wait before considering the client disconnected - // hopefully this default is sensible for most applications, tolerating short interruptions - willDelayInterval := uint32(2 * cfg.KeepAlive) - - cp.WillProperties = &paho.WillProperties{ - // Most of these are nil/empty or defaults until related methods are exposed for configuration - WillDelayInterval: &willDelayInterval, - PayloadFormat: &cfg.willPayloadFormat, - MessageExpiry: &cfg.willMessageExpiry, - ContentType: cfg.willContentType, - ResponseTopic: cfg.willResponseTopic, - CorrelationData: cfg.willCorrelationData, - } + if cfg.SessionExpiryInterval != 0 { + cp.Properties = &paho.ConnectProperties{SessionExpiryInterval: &cfg.SessionExpiryInterval} } - if nil != cfg.connectPacketBuilder { - cp = cfg.connectPacketBuilder(cp) + if cfg.ConnectPacketBuilder != nil { + cp = cfg.ConnectPacketBuilder(cp, serverURL) } return cp @@ -176,7 +237,10 @@ func (cfg *ClientConfig) buildConnectPacket() *paho.Connect { // NewConnection creates a connection manager and begins the connection process (will retry until the context is cancelled) func NewConnection(ctx context.Context, cfg ClientConfig) (*ConnectionManager, error) { if cfg.Debug == nil { - cfg.Debug = paho.NOOPLogger{} + cfg.Debug = log.NOOPLogger{} + } + if cfg.Errors == nil { + cfg.Errors = log.NOOPLogger{} } if cfg.ConnectRetryDelay == 0 { cfg.ConnectRetryDelay = 10 * time.Second @@ -184,18 +248,37 @@ func NewConnection(ctx context.Context, cfg ClientConfig) (*ConnectionManager, e if cfg.ConnectTimeout == 0 { cfg.ConnectTimeout = 10 * time.Second } - + if len(cfg.ServerUrls) == 0 { // backwards compatibility + cfg.ServerUrls = cfg.BrokerUrls + } + if len(cfg.ServerUrls) == 0 { // This would cause an infinite loop + return nil, errors.New("no server urls provided") + } + if cfg.Queue == nil { + cfg.Queue = memory.New() + } + if cfg.Session == nil { // Must create this, or it will be recreated upon reconnection, and we will lose the session info + cfg.Session = state.NewInMemory() + } innerCtx, cancel := context.WithCancel(ctx) c := ConnectionManager{ cli: nil, connUp: make(chan struct{}), + cfg: cfg, cancelCtx: cancel, + queue: cfg.Queue, done: make(chan struct{}), + errors: cfg.Errors, + debug: cfg.Debug, } errChan := make(chan error, 1) // Will be sent one, and only one error per connection (buffered to prevent deadlock) + firstConnection := true // Set to false after we have successfully connected go func() { - defer close(c.done) + defer func() { + c.queueWg.Wait() // Separate goroutine handling queue may be running + close(c.done) + }() mainLoop: for { @@ -210,49 +293,67 @@ func NewConnection(ctx context.Context, cfg ClientConfig) (*ConnectionManager, e cliCfg := cfg cliCfg.OnClientError = eh.onClientError cliCfg.OnServerDisconnect = eh.onServerDisconnect - - cli, connAck := establishBrokerConnection(innerCtx, cliCfg) + cli, connAck := establishServerConnection(innerCtx, cliCfg, firstConnection) if cli == nil { break mainLoop // Only occurs when context is cancelled } + c.mu.Lock() c.cli = cli - c.mu.Unlock() + c.connDown = make(chan struct{}) close(c.connUp) + c.mu.Unlock() if cfg.OnConnectionUp != nil { cfg.OnConnectionUp(&c, connAck) } + if firstConnection { + c.queueWg.Add(1) + go func(ctx context.Context) { + _ = c.managePublishQueue(ctx) + c.queueWg.Done() + }(innerCtx) + firstConnection = false + } + var err error select { - case err = <-errChan: // Message on the error channel indicates connection has (or will) drop. + case err = <-errChan: // Message on the error channel indicates the connection has, or will, drop. case <-innerCtx.Done(): + cfg.Debug.Println("innerCtx Done") eh.shutdown() // Prevent any errors triggered by closure of context from reaching user // As the connection is up, we call disconnect to shut things down cleanly - if err = c.cli.Disconnect(&paho.Disconnect{ReasonCode: 0}); err != nil { - cfg.Debug.Printf("disconnect returned error: %s\n", err) + dp := &paho.Disconnect{ReasonCode: 0} + if cfg.DisconnectPacketBuilder != nil { + dp = cfg.DisconnectPacketBuilder() + } + if dp != nil { + if err = c.cli.Disconnect(dp); err != nil { + cfg.Debug.Printf("mainLoop: disconnect returned error: %s\n", err) + } } if ctx.Err() != nil { // If this is due to outer context being cancelled, then this will have happened before the inner one gets cancelled. - cfg.Debug.Printf("broker connection handler exiting due to context: %s\n", ctx.Err()) + cfg.Debug.Printf("mainLoop: server connection handler exiting due to context: %s\n", ctx.Err()) } else { - cfg.Debug.Printf("broker connection handler exiting due to Disconnect call: %s\n", innerCtx.Err()) + cfg.Debug.Printf("mainLoop: server connection handler exiting due to Disconnect call: %s\n", innerCtx.Err()) } break mainLoop } + <-cli.Done() // Wait for the client to fully shutdown c.mu.Lock() c.cli = nil + close(c.connDown) c.connUp = make(chan struct{}) c.mu.Unlock() - cfg.Debug.Printf("connection to broker lost (%s); will reconnect\n", err) + cfg.Debug.Printf("mainLoop: connection to server lost (%s); will reconnect\n", err) } - cfg.Debug.Println("connection manager has terminated") + cfg.Debug.Println("mainLoop: connection manager has terminated") }() return &c, nil } // Disconnect closes the connection (if one is up) and shuts down any active processes before returning -// Note: We cannot currently tell when the mqtt has fully shutdown (so it may still be in the process of closing down) func (c *ConnectionManager) Disconnect(ctx context.Context) error { c.cancelCtx() select { @@ -287,6 +388,22 @@ func (c *ConnectionManager) AwaitConnection(ctx context.Context) error { } } +// Authenticate is used to initiate a reauthentication of credentials with the +// server. This function sends the initial Auth packet to start the reauthentication +// then relies on the client AuthHandler managing any further requests from the +// server until either a successful Auth packet is passed back, or a Disconnect +// is received. +func (c *ConnectionManager) Authenticate(ctx context.Context, a *paho.Auth) (*paho.AuthResponse, error) { + c.mu.Lock() + cli := c.cli + c.mu.Unlock() + + if cli == nil { + return nil, ConnectionDownError + } + return cli.Authenticate(ctx, a) +} + // Subscribe is used to send a Subscription request to the MQTT server. // It is passed a pre-prepared Subscribe packet and blocks waiting for // a response Suback, or for the timeout to fire. Any response Suback @@ -318,8 +435,8 @@ func (c *ConnectionManager) Unsubscribe(ctx context.Context, u *paho.Unsubscribe } // Publish is used to send a publication to the MQTT server. -// It is passed a pre-prepared Publish packet and blocks waiting for -// the appropriate response, or for the timeout to fire. +// It is passed a pre-prepared `PUBLISH` packet and blocks waiting for the appropriate response, +// or for the timeout to fire. // Any response message is returned from the function, along with any errors. func (c *ConnectionManager) Publish(ctx context.Context, p *paho.Publish) (*paho.PublishResponse, error) { c.mu.Lock() @@ -331,3 +448,194 @@ func (c *ConnectionManager) Publish(ctx context.Context, p *paho.Publish) (*paho } return cli.Publish(ctx, p) } + +// QueuePublish holds info required to publish a message. A separate struct is used so options can be added in the future +// without breaking existing code +type QueuePublish struct { + *paho.Publish +} + +// PublishViaQueue is used to send a publication to the MQTT server via a queue (by default memory based). +// An error will be returned if the message could not be added to the queue, otherwise the message will be delivered +// in the background with no status updates available. +// Use this function when you wish to rely upon the libraries best-effort to transmit the message; it is anticipated +// that this will generally be in situations where the network link or power supply is unreliable. +// Messages will be written to a queue (configuring a disk-based queue is recommended) and transmitted where possible. +// To maximise the chance of a successful delivery: +// - Leave CleanStartOnInitialConnection set to false +// - Set SessionExpiryInterval such that sessions will outlive anticipated outages (this impacts inflight messages only) +// - Set ClientConfig.Session to a session manager with persistent storage +// - Set ClientConfig.Queue to a queue with persistent storage +func (c *ConnectionManager) PublishViaQueue(ctx context.Context, p *QueuePublish) error { + var b bytes.Buffer + if _, err := p.Packet().WriteTo(&b); err != nil { + return err + } + return c.queue.Enqueue(&b) +} + +// TerminateConnectionForTest closes the active connection (if any). This function is intended for testing only, it +// simulates connection loss which supports testing QOS1 and 2 message delivery. +func (c *ConnectionManager) TerminateConnectionForTest() { + c.mu.Lock() + if c.cli != nil { + c.cli.TerminateConnectionForTest() + } + c.mu.Unlock() +} + +// AddOnPublishReceived adds a function that will be called when a PUBLISH is received +// The new function will be called after any functions already in the list +// Returns a function that can be called to remove the callback +func (c *ConnectionManager) AddOnPublishReceived(f func(PublishReceived) (bool, error)) func() { + // Removing a handler is a bit tricky because the connection may have dropped and been reestablished + // The current approach is to leave the handler in place but turn it into a no-op (this could be improved!) + isActive := true + fn := func(pr paho.PublishReceived) (bool, error) { + if !isActive { + return false, nil + } + return f(PublishReceived{ + PublishReceived: pr, + ConnectionManager: c, + }) + } + var removeFromClient func() + c.mu.Lock() + defer c.mu.Unlock() + if c.cli != nil { + removeFromClient = c.cli.AddOnPublishReceived(fn) + } + + return func() { + c.mu.Lock() + defer c.mu.Unlock() + if removeFromClient != nil { + removeFromClient() + } + isActive = false + } +} + +// managePublishQueue sends messages from the publish queue. +// blocks until the context is cancelled. +func (c *ConnectionManager) managePublishQueue(ctx context.Context) error { +connectionLoop: + for { + c.debug.Println("queue AwaitConnection") + if err := c.AwaitConnection(ctx); err != nil { + return err + } + + c.debug.Println("queue got connection") + c.mu.Lock() + cli := c.cli + connDown := c.connDown + c.mu.Unlock() + if cli == nil { // Possible connection dropped immediately + continue + } + + queueLoop: + for { + select { + case <-ctx.Done(): + c.debug.Println("queue done") + return ctx.Err() + case <-connDown: + c.debug.Println("connection down") + continue connectionLoop + case <-c.queue.Wait(): + } + + // Connection is up, and we have at least one thing to send + for { + entry, err := c.queue.Peek() // If this succeeds, we MUST call Remove, Quarantine or Leave + if errors.Is(err, queue.ErrEmpty) { + c.debug.Println("everything in queue transmitted") + continue queueLoop + } else if err != nil { + // if Peek() keeps returning errors, we will loop forever. + // see https://github.com/eclipse/paho.golang/issues/234 + c.errors.Printf("error retrieving queue entry: %s", err) + time.Sleep(1 * time.Second) + continue + } + r, err := entry.Reader() + if err != nil { + c.errors.Printf("error retrieving reader for queue entry: %s", err) + if err := entry.Leave(); err != nil { + c.errors.Printf("error leaving queue entry: %s", err) + } + time.Sleep(1 * time.Second) + continue + } + + p, err := packets.ReadPacket(r) + if err != nil { + c.errors.Printf("error retrieving packet from queue: %s", err) + // If the packet cannot be processed, then we need to remove it from the queue + // (ideally into an error queue). + if err := entry.Quarantine(); err != nil { + c.errors.Printf("error moving queue entry to quarantine: %s", err) + } + continue + } + + pub, ok := p.Content.(*packets.Publish) + if !ok { + c.errors.Printf("packet from queue is not a Publish") + if qErr := entry.Quarantine(); qErr != nil { + c.errors.Printf("error moving queue entry to quarantine: %s", err) + } + continue + } + pub2 := paho.Publish{ + PacketID: 0, + QoS: pub.QoS, + Retain: pub.Retain, + Topic: pub.Topic, + Payload: pub.Payload, + } + pub2.InitProperties(pub.Properties) + + // PublishWithOptions using PublishMethod_AsyncSend will block until the packet has been transmitted + // and then return (at this point any pub1+ publish will be in the session so will be retried) + c.debug.Printf("publishing message from queue with topic %s", pub2.Topic) + if _, err = cli.PublishWithOptions(ctx, &pub2, paho.PublishOptions{Method: paho.PublishMethod_AsyncSend}); err != nil { + c.errors.Printf("error publishing from queue: %s", err) + if errors.Is(err, paho.ErrInvalidArguments) { // Some errors should not be retried + if err := entry.Remove(); err != nil { + c.errors.Printf("error removing queue entry: %s", err) + } + // Need a way to notify the user of this + } else if errors.Is(err, paho.ErrNetworkErrorAfterStored) { // Message in session so remove from queue + if err := entry.Remove(); err != nil { + c.errors.Printf("error removing queue entry: %s", err) + } + } else { + if err := entry.Leave(); err != nil { // the message was not sent, so leave it in the queue + c.errors.Printf("error leaving queue entry: %s", err) + } + time.Sleep(1 * time.Second) + } + + // The error might be fatal (connection will drop) or could be temporary (i.e. PacketTimeout exceeded) + // as a result we currently retry unless we know the connection has dropped, or it's time to exit + select { + case <-ctx.Done(): + return ctx.Err() + case <-connDown: + continue connectionLoop + default: // retry + continue + } + } + if err := entry.Remove(); err != nil { // successfully published + c.errors.Printf("error removing queue entry: %s", err) + continue + } + } + } + } +} diff --git a/vendor/github.com/eclipse/paho.golang/autopaho/error.go b/vendor/github.com/eclipse/paho.golang/autopaho/error.go index 69677f000..b22f7327a 100644 --- a/vendor/github.com/eclipse/paho.golang/autopaho/error.go +++ b/vendor/github.com/eclipse/paho.golang/autopaho/error.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package autopaho import ( @@ -5,6 +20,7 @@ import ( "sync" "github.com/eclipse/paho.golang/paho" + "github.com/eclipse/paho.golang/paho/log" ) // errorHandler provides the onClientError callback function that will be called by the Paho library. The sole aim @@ -14,7 +30,7 @@ import ( // userOnClientError will not be called (but there is a small chance that userOnClientError will be called followed // by userOnServerDisconnect (if we encounter an error sending but there is a DISCONNECT in the queue). type errorHandler struct { - debug paho.Logger + debug log.Logger mu sync.Mutex errChan chan error // receives connection errors @@ -38,7 +54,7 @@ func (e *errorHandler) onClientError(err error) { } // onClientError called by the paho library when the server requests a disconnection (for example, as part of a -// clean broker shutdown). We want to begin attempting to reconnect when this occurs (and pass a detectable error +// clean server shutdown). We want to begin attempting to reconnect when this occurs (and pass a detectable error // to the user) func (e *errorHandler) onServerDisconnect(d *paho.Disconnect) { e.handleError(&DisconnectError{err: fmt.Sprintf("server requested disconnect (reason: %d)", d.ReasonCode)}) @@ -55,11 +71,12 @@ func (e *errorHandler) handleError(err error) bool { e.errChan = nil e.mu.Unlock() if errChan != nil { - e.debug.Printf("received error: %s", err) + e.debug.Printf("handleError received error: %s", err) errChan <- err + e.debug.Printf("handleError passed error on: %s", err) return true } - e.debug.Printf("received extra error: %s", err) + e.debug.Printf("handleError received extra error: %s", err) return false } diff --git a/vendor/github.com/eclipse/paho.golang/autopaho/net.go b/vendor/github.com/eclipse/paho.golang/autopaho/net.go index 1d4a58973..149241a4a 100644 --- a/vendor/github.com/eclipse/paho.golang/autopaho/net.go +++ b/vendor/github.com/eclipse/paho.golang/autopaho/net.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package autopaho import ( @@ -23,14 +38,14 @@ import ( // Network (establishing connection) functionality for AutoPaho -// establishBrokerConnection - establishes a connection with the broker retrying until successful or the +// establishServerConnection - establishes a connection with the MQTT server retrying until successful or the // context is cancelled (in which case nil will be returned). -func establishBrokerConnection(ctx context.Context, cfg ClientConfig) (*paho.Client, *paho.Connack) { +func establishServerConnection(ctx context.Context, cfg ClientConfig, firstConnection bool) (*paho.Client, *paho.Connack) { // Note: We do not touch b.cli in order to avoid adding thread safety issues. var err error for { - for _, u := range cfg.BrokerUrls { + for _, u := range cfg.ServerUrls { connectionCtx, cancelConnCtx := context.WithTimeout(ctx, cfg.ConnectTimeout) if cfg.AttemptConnection != nil { // Use custom function if it is provided @@ -65,7 +80,7 @@ func establishBrokerConnection(ctx context.Context, cfg ClientConfig) (*paho.Cli cli.SetErrorLogger(cfg.PahoErrors) } - cp := cfg.buildConnectPacket() + cp := cfg.buildConnectPacket(firstConnection, u) connack, err = cli.Connect(connectionCtx, cp) // will return an error if the connection is unsuccessful (checks the reason code) if err == nil { // Successfully connected cancelConnCtx() @@ -78,6 +93,7 @@ func establishBrokerConnection(ctx context.Context, cfg ClientConfig) (*paho.Cli if ctx.Err() != nil { return nil, nil } + cfg.Debug.Printf("failed to connect to %s: %s", u.String(), err) if cfg.OnConnectError != nil { cerr := fmt.Errorf("failed to connect to %s: %w", u.String(), err) @@ -97,7 +113,7 @@ func establishBrokerConnection(ctx context.Context, cfg ClientConfig) (*paho.Cli } } -// attemptTCPConnection - makes a single attempt at establishing a TCP connection with the broker +// attemptTCPConnection - makes a single attempt at establishing a TCP connection with the server func attemptTCPConnection(ctx context.Context, address string) (net.Conn, error) { allProxy := os.Getenv("all_proxy") if len(allProxy) == 0 { @@ -108,7 +124,7 @@ func attemptTCPConnection(ctx context.Context, address string) (net.Conn, error) return proxyDialer.Dial("tcp", address) } -// attemptTLSConnection - makes a single attempt at establishing a TLS connection with the broker +// attemptTLSConnection - makes a single attempt at establishing a TLS connection with the server func attemptTLSConnection(ctx context.Context, tlsCfg *tls.Config, address string) (net.Conn, error) { allProxy := os.Getenv("all_proxy") if len(allProxy) == 0 { @@ -136,16 +152,16 @@ func attemptTLSConnection(ctx context.Context, tlsCfg *tls.Config, address strin return packets.NewThreadSafeConn(tlsConn), err } -// attemptWebsocketConnection - makes a single attempt at establishing a websocket connection with the broker -func attemptWebsocketConnection(ctx context.Context, tlsc *tls.Config, cfg *WebSocketConfig, brokerURL *url.URL) (net.Conn, error) { +// attemptWebsocketConnection - makes a single attempt at establishing a websocket connection with the server +func attemptWebsocketConnection(ctx context.Context, tlsc *tls.Config, cfg *WebSocketConfig, serverURL *url.URL) (net.Conn, error) { var dialer *websocket.Dialer var requestHeader http.Header if cfg != nil { if cfg.Dialer != nil { - dialer = cfg.Dialer(brokerURL, tlsc) + dialer = cfg.Dialer(serverURL, tlsc) } if cfg.Header != nil { - requestHeader = cfg.Header(brokerURL, tlsc) + requestHeader = cfg.Header(serverURL, tlsc) } } if dialer == nil { @@ -154,7 +170,7 @@ func attemptWebsocketConnection(ctx context.Context, tlsc *tls.Config, cfg *WebS d.Subprotocols = []string{"mqtt"} dialer = &d } - ws, _, err := dialer.DialContext(ctx, brokerURL.String(), requestHeader) + ws, _, err := dialer.DialContext(ctx, serverURL.String(), requestHeader) if err != nil { return nil, fmt.Errorf("websocket connection failed: %w", err) } diff --git a/vendor/github.com/eclipse/paho.golang/autopaho/queue/memory/queue.go b/vendor/github.com/eclipse/paho.golang/autopaho/queue/memory/queue.go new file mode 100644 index 000000000..92f44a978 --- /dev/null +++ b/vendor/github.com/eclipse/paho.golang/autopaho/queue/memory/queue.go @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package memory + +import ( + "bytes" + "fmt" + "io" + "sync" + + "github.com/eclipse/paho.golang/autopaho/queue" +) + +// A queue implementation that stores all data in RAM + +// Queue - basic memory based queue +type Queue struct { + mu sync.Mutex + messages [][]byte + waiting []chan<- struct{} // closed when something arrives in the queue + waitingForEmpty []chan<- struct{} // closed when queue is empty +} + +// New creates a new memory-based queue +func New() *Queue { + return &Queue{} +} + +// Wait returns a channel that is closed when there is something in the queue +func (q *Queue) Wait() chan struct{} { + c := make(chan struct{}) + q.mu.Lock() + if len(q.messages) > 0 { + q.mu.Unlock() + close(c) + return c + } + q.waiting = append(q.waiting, c) + q.mu.Unlock() + return c +} + +// WaitForEmpty returns a channel which will be closed when the queue is empty +func (q *Queue) WaitForEmpty() chan struct{} { + c := make(chan struct{}) + q.mu.Lock() + if len(q.messages) == 0 { + q.mu.Unlock() + close(c) + return c + } + q.waitingForEmpty = append(q.waitingForEmpty, c) + q.mu.Unlock() + return c +} + +// Enqueue add item to the queue. +func (q *Queue) Enqueue(p io.Reader) error { + var b bytes.Buffer + _, err := b.ReadFrom(p) + if err != nil { + return fmt.Errorf("Queue.Push failed to read into buffer: %w", err) + } + q.mu.Lock() + defer q.mu.Unlock() + q.messages = append(q.messages, b.Bytes()) + for _, c := range q.waiting { + close(c) + } + q.waiting = q.waiting[:0] + return nil +} + +// Peek retrieves the oldest item from the queue (without removing it) +func (q *Queue) Peek() (queue.Entry, error) { + q.mu.Lock() + defer q.mu.Unlock() + if len(q.messages) == 0 { + return nil, queue.ErrEmpty + } + // Queue implements Entry directly (as this always references q.messages[0] + return q, nil +} + +// Reader implements Entry.Reader - As the entry will always be the first item in the queue this is implemented +// against Queue rather than as a separate struct. +func (q *Queue) Reader() (io.Reader, error) { + q.mu.Lock() + defer q.mu.Unlock() + if len(q.messages) == 0 { + return nil, queue.ErrEmpty + } + return bytes.NewReader(q.messages[0]), nil +} + +// Leave implements Entry.Leave - the entry (will be returned on subsequent calls to Peek) +func (q *Queue) Leave() error { + return nil // No action (item is already in the queue and there is nothing to close) +} + +// Remove implements Entry.Remove this entry from the queue +func (q *Queue) Remove() error { + return q.remove() +} + +// Quarantine implements Entry.Quarantine - Flag that this entry has an error (remove from queue, potentially retaining data with error flagged) +func (q *Queue) Quarantine() error { + return q.remove() // No way for us to actually quarantine this, so we just remove the item from the queue +} + +// remove removes the first item in the queue. +func (q *Queue) remove() error { + q.mu.Lock() + defer q.mu.Unlock() + initialLen := len(q.messages) + if initialLen > 0 { + q.messages = q.messages[1:] + } + if initialLen <= 1 { // Queue is now, or was already, empty + for _, c := range q.waitingForEmpty { + close(c) + } + q.waitingForEmpty = q.waitingForEmpty[:0] + if initialLen == 0 { + return queue.ErrEmpty + } + } + return nil +} diff --git a/vendor/github.com/eclipse/paho.golang/autopaho/queue/queue.go b/vendor/github.com/eclipse/paho.golang/autopaho/queue/queue.go new file mode 100644 index 000000000..2fa4a91ed --- /dev/null +++ b/vendor/github.com/eclipse/paho.golang/autopaho/queue/queue.go @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package queue + +import ( + "errors" + "io" +) + +var ( + ErrEmpty = errors.New("empty queue") +) + +// Entry - permits access to a queue entry +// Users must call one of Leave, Remove, or Quarantine when done with the entry (and before calling Peek again) +// `Reader()` must not be called after calling Leave, Remove, or Quarantine (and any Reader previously requestes should be considered invalid) +type Entry interface { + Reader() (io.Reader, error) // Provides access to the file contents, subsequent calls may return the same reader + Leave() error // Leave the entry in the queue (same entry will be returned on subsequent calls to Peek). + Remove() error // Remove this entry from the queue. Returns queue.ErrEmpty if queue is empty after operation + Quarantine() error // Flag that this entry has an error (remove from queue, potentially retaining data with error flagged) +} + +// Queue provides the functionality needed to manage queued messages +type Queue interface { + // Wait returns a channel that is closed when there is something in the queue (will return a closed channel if the + // queue is empty at the time of the call) + Wait() chan struct{} + + // Enqueue add item to the queue. + Enqueue(p io.Reader) error + + // Peek retrieves the oldest item from the queue without removing it + // Users must call one of Close, Remove, or Quarantine when done with the entry, and before calling Peek again. + // Warning: Peek is not safe for concurrent use (it may return the same Entry leading to unpredictable results) + Peek() (Entry, error) +} diff --git a/vendor/github.com/eclipse/paho.golang/autopaho/readme.md b/vendor/github.com/eclipse/paho.golang/autopaho/readme.md index c54bcfe39..0173c62a9 100644 --- a/vendor/github.com/eclipse/paho.golang/autopaho/readme.md +++ b/vendor/github.com/eclipse/paho.golang/autopaho/readme.md @@ -3,59 +3,62 @@ AutoPaho AutoPaho has a number of aims: -* Provide an easy-to-use MQTT v5 client that provides commonly requested functionality (e.g. connection, automatic reconnection). +* Provide an easy-to-use MQTT v5 client that provides commonly requested functionality (e.g. connection, automatic reconnection, message queueing). * Demonstrate the use of `paho.golang/paho`. * Enable us to smoke test `paho.golang/paho` features (ensuring they are they usable in a real world situation) ## Basic Usage -```Go -package main - -import ( - "context" - "fmt" - "net/url" - "os" - "os/signal" - "syscall" - - "github.com/eclipse/paho.golang/autopaho" - "github.com/eclipse/paho.golang/paho" -) +The following code demonstrates basic usage; the full code is available under `examples/basics`: +```go func main() { // App will run until cancelled by user (e.g. ctrl-c) ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() - u, err := url.Parse("mqtt://test.mosquitto.org:1883") + // We will connect to the Eclipse test server (note that you may see messages that other users publish) + u, err := url.Parse("mqtt://mqtt.eclipseprojects.io:1883") if err != nil { panic(err) } cliCfg := autopaho.ClientConfig{ - BrokerUrls: []*url.URL{u}, + ServerUrls: []*url.URL{u}, KeepAlive: 20, // Keepalive message should be sent every 20 seconds + // CleanStartOnInitialConnection defaults to false. Setting this to true will clear the session on the first connection. + CleanStartOnInitialConnection: false, + // SessionExpiryInterval - Seconds that a session will survive after disconnection. + // It is important to set this because otherwise, any queued messages will be lost if the connection drops and + // the server will not queue messages while it is down. The specific setting will depend upon your needs + // (60 = 1 minute, 3600 = 1 hour, 86400 = one day, 0xFFFFFFFE = 136 years, 0xFFFFFFFF = don't expire) + SessionExpiryInterval: 60, OnConnectionUp: func(cm *autopaho.ConnectionManager, connAck *paho.Connack) { fmt.Println("mqtt connection up") + // Subscribing in the OnConnectionUp callback is recommended (ensures the subscription is reestablished if + // the connection drops) if _, err := cm.Subscribe(context.Background(), &paho.Subscribe{ Subscriptions: []paho.SubscribeOptions{ - {Topic: "testTopic", QoS: 1}, + {Topic: topic, QoS: 1}, }, }); err != nil { - fmt.Printf("failed to subscribe (%s). This is likely to mean no messages will be received!\n", err) + fmt.Printf("failed to subscribe (%s). This is likely to mean no messages will be received.", err) } fmt.Println("mqtt subscription made") }, OnConnectError: func(err error) { fmt.Printf("error whilst attempting connection: %s\n", err) }, // eclipse/paho.golang/paho provides base mqtt functionality, the below config will be passed in for each connection ClientConfig: paho.ClientConfig{ - ClientID: "TestClient", - Router: paho.NewSingleHandlerRouter(func(m *paho.Publish) { - fmt.Printf("received message on topic %s; body: %s (retain: %t)\n", m.Topic, m.Payload, m.Retain) - }), - OnClientError: func(err error) { fmt.Printf("server requested disconnect: %s\n", err) }, + // If you are using QOS 1/2, then it's important to specify a client id (which must be unique) + ClientID: clientID, + // OnPublishReceived is a slice of functions that will be called when a message is received. + // You can write the function(s) yourself or use the supplied Router + OnPublishReceived: []func(paho.PublishReceived) (bool, error){ + func(pr paho.PublishReceived) (bool, error) { + fmt.Printf("received message on topic %s; body: %s (retain: %t)\n", pr.Packet.Topic, pr.Packet.Payload, pr.Packet.Retain) + return true, nil + }}, + OnClientError: func(err error) { fmt.Printf("client error: %s\n", err) }, OnServerDisconnect: func(d *paho.Disconnect) { if d.Properties != nil { fmt.Printf("server requested disconnect: %s\n", d.Properties.ReasonString) @@ -66,7 +69,7 @@ func main() { }, } - c, err := autopaho.NewConnection(ctx, cliCfg) + c, err := autopaho.NewConnection(ctx, cliCfg) // starts process; will reconnect until context cancelled if err != nil { panic(err) } @@ -74,39 +77,84 @@ func main() { if err = c.AwaitConnection(ctx); err != nil { panic(err) } - // Publish a test message - if _, err = c.Publish(ctx, &paho.Publish{ - QoS: 1, - Topic: "testTopic", - Payload: []byte("TestMessage"), - }); err != nil { - panic(err) + + ticker := time.NewTicker(time.Second) + msgCount := 0 + defer ticker.Stop() + for { + select { + case <-ticker.C: + msgCount++ + // Publish a test message (use PublishViaQueue if you don't want to wait for a response) + if _, err = c.Publish(ctx, &paho.Publish{ + QoS: 1, + Topic: topic, + Payload: []byte("TestMessage: " + strconv.Itoa(msgCount)), + }); err != nil { + if ctx.Err() == nil { + panic(err) // Publish will exit when context cancelled or if something went wrong + } + } + continue + case <-ctx.Done(): + } + break } - <-ctx.Done() // Wait for user to trigger exit fmt.Println("signal caught - exiting") + <-c.Done() // Wait for clean shutdown (cancelling the context triggered the shutdown) } ``` +See the [other examples](https://github.com/eclipse/paho.golang/tree/master/autopaho/examples) for further information on usage. + ## QOS 1 & 2 -`paho.golang/paho` supports QOS 1 & 2 but does not maintain a ["Session State" ](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901230). This means that QOS1/2 messages will be sent but no attempt will be made to resend them if there is an issue. +QOS 1 & 2 provide assurances that messages will be delivered. To implement this a [session state](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901230) +is required that holds information on messages that have not been fully acknowledged. By default `autopaho` holds this +state in memory meaning that messages will not be lost following a reconnection, but may be lost if the program is +restarted (a file-based store can be used to avoid this). + +A range of settings impact message delivery; if you want guaranteed delivery, then remember to: + +* Use a unique, client ID (you need to ensue any subsequent connections use the same ID) +* Configure `CleanStartOnInitialConnection` and `SessionExpiryInterval` appropriately (e.g. `false`, `600`). +* Use file-based persistence if you wish the session to survive an application restart +* Specify QOS 1 or 2 when publishing/subscribing. + +When subscribing at QOS1/2: +* Remember that messages will not be queued until after the initial `Subscribe` call. +* If you subscribed previously (and the session is live) then expect to receive messages upon connection (you do not need +to call `Subscribe` when reconnecting; however this is recommended in case the session was lost). + +`example/docker` provides a demonstration of how this can work. You can confirm this yourself using two terminal windows: + +| Terminal1 | Terminal2 | +|---------------------------|-----------------------------------------------------------------------------------------------------------| +| `docker compose up -d` | | +| | `docker compose logs --follow` | +| | Wait until you see the subscriber receiving messages (e.g. `docker-sub-1 received message: {"Count":1}`) | +| `docker compose stop sub` | | +| Wait 20 seconds | | +| `docker compose up -d` | | +| | Verify that `sub` received all messages despite the stop/start. | + +Note: The logs can be easier fo follow if you comment out the `log_type all` in `mosquitto.conf`. -## Work in progress +## Queue -See [this issue](https://github.com/eclipse/paho.golang/issues/25) for more info (currently nearing completion). +When publishing a message, there are a number of things that can go wrong; for example: +* The connection to the server may drop (or not have even come up before your initial message is ready) +* The application might be restarted (but you still want messages previously published to be delivered) +* `ConnectionManager.Publish` may timeout because you are attempting to publish a lot of messages in a short space of time. -Use case: I want to regularly publish messages and leave it to autopaho to ensure they are delivered (regardless of the connection state). +With MQTT v3.1 this was generally handled by adding messages to the session; meaning they would be retried if the +connection droped and was reestablished. MQTT v5 introduces a [Receive Maximum](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901083) +which limits the number of messages that can be in flight (and, hence, in the session). -Acceptance tests: - * Publish a message before connection is established; it should be queued and delivered when connection comes up. - * Connection drops and messages are published whilst reconnection is in progress. They should be queued and delivered - when connection is available. - * Publish messages at a rate in excess of Receive Maximum; they should be queued and sent, in order, when possible. - * Application restarts during any of the above - queued messages are sent out when connection comes up. +`ConnectionManager.PublishViaQueue` provides a solution; messages passed to this function are added to a queue and +transmitted when possible. By default, this queue is held in memory but you can use an alternate `ClientConfig.Queue` +(e.g. `queue/disk`) if you wish the queue to survive an application restart. -Desired features: - * Fire and forget - async publish; we trust the library to deliver the message so once its in the store the client can forget about it. - * Minimal RAM use - the connection may be down for a long time and we may not have much ram. So messages should be on disk (ideally with - no trace of them in RAM) +See `examples/queue`. diff --git a/vendor/github.com/eclipse/paho.golang/packets/auth.go b/vendor/github.com/eclipse/paho.golang/packets/auth.go index 56237e00c..c97446f6a 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/auth.go +++ b/vendor/github.com/eclipse/paho.golang/packets/auth.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( diff --git a/vendor/github.com/eclipse/paho.golang/packets/connack.go b/vendor/github.com/eclipse/paho.golang/packets/connack.go index aa171eaba..493aa29e0 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/connack.go +++ b/vendor/github.com/eclipse/paho.golang/packets/connack.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( @@ -43,7 +58,7 @@ func (c *Connack) String() string { return fmt.Sprintf("CONNACK: ReasonCode:%d SessionPresent:%t\nProperties:\n%s", c.ReasonCode, c.SessionPresent, c.Properties) } -//Unpack is the implementation of the interface required function for a packet +// Unpack is the implementation of the interface required function for a packet func (c *Connack) Unpack(r *bytes.Buffer) error { connackFlags, err := r.ReadByte() if err != nil { diff --git a/vendor/github.com/eclipse/paho.golang/packets/connect.go b/vendor/github.com/eclipse/paho.golang/packets/connect.go index 31340f6bd..2394e7d01 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/connect.go +++ b/vendor/github.com/eclipse/paho.golang/packets/connect.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( @@ -85,7 +100,7 @@ func (c *Connect) UnpackFlags(b byte) { c.UsernameFlag = 1&(b>>7) > 0 } -//Unpack is the implementation of the interface required function for a packet +// Unpack is the implementation of the interface required function for a packet func (c *Connect) Unpack(r *bytes.Buffer) error { var err error diff --git a/vendor/github.com/eclipse/paho.golang/packets/disconnect.go b/vendor/github.com/eclipse/paho.golang/packets/disconnect.go index 9180207a6..693abfd3e 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/disconnect.go +++ b/vendor/github.com/eclipse/paho.golang/packets/disconnect.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( diff --git a/vendor/github.com/eclipse/paho.golang/packets/packets.go b/vendor/github.com/eclipse/paho.golang/packets/packets.go index 4f023e015..48dc5462e 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/packets.go +++ b/vendor/github.com/eclipse/paho.golang/packets/packets.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( diff --git a/vendor/github.com/eclipse/paho.golang/packets/pingreq.go b/vendor/github.com/eclipse/paho.golang/packets/pingreq.go index 27d39ee32..b6a4c7fa0 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/pingreq.go +++ b/vendor/github.com/eclipse/paho.golang/packets/pingreq.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( diff --git a/vendor/github.com/eclipse/paho.golang/packets/pingresp.go b/vendor/github.com/eclipse/paho.golang/packets/pingresp.go index fcf421a76..f5210f187 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/pingresp.go +++ b/vendor/github.com/eclipse/paho.golang/packets/pingresp.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( diff --git a/vendor/github.com/eclipse/paho.golang/packets/properties.go b/vendor/github.com/eclipse/paho.golang/packets/properties.go index 5a74e0561..9ddc9bdc7 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/properties.go +++ b/vendor/github.com/eclipse/paho.golang/packets/properties.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( @@ -285,11 +300,6 @@ func (i *Properties) Pack(p byte) []byte { writeUint16(*i.TopicAliasMaximum, &b) } - if i.MaximumQOS != nil { - b.WriteByte(PropMaximumQOS) - b.WriteByte(*i.MaximumQOS) - } - if i.MaximumPacketSize != nil { b.WriteByte(PropMaximumPacketSize) writeUint32(*i.MaximumPacketSize, &b) @@ -297,6 +307,11 @@ func (i *Properties) Pack(p byte) []byte { } if p == CONNACK { + if i.MaximumQOS != nil { + b.WriteByte(PropMaximumQOS) + b.WriteByte(*i.MaximumQOS) + } + if i.AssignedClientID != "" { b.WriteByte(PropAssignedClientID) writeString(i.AssignedClientID, &b) @@ -453,11 +468,6 @@ func (i *Properties) PackBuf(p byte) *bytes.Buffer { writeUint16(*i.TopicAliasMaximum, &b) } - if i.MaximumQOS != nil { - b.WriteByte(PropMaximumQOS) - b.WriteByte(*i.MaximumQOS) - } - if i.MaximumPacketSize != nil { b.WriteByte(PropMaximumPacketSize) writeUint32(*i.MaximumPacketSize, &b) @@ -465,6 +475,11 @@ func (i *Properties) PackBuf(p byte) *bytes.Buffer { } if p == CONNACK { + if i.MaximumQOS != nil { + b.WriteByte(PropMaximumQOS) + b.WriteByte(*i.MaximumQOS) + } + if i.AssignedClientID != "" { b.WriteByte(PropAssignedClientID) writeString(i.AssignedClientID, &b) @@ -792,7 +807,7 @@ var ValidProperties = map[byte]map[byte]struct{}{ PropReasonString: {CONNACK: {}, PUBACK: {}, PUBREC: {}, PUBREL: {}, PUBCOMP: {}, SUBACK: {}, UNSUBACK: {}, DISCONNECT: {}, AUTH: {}}, PropReceiveMaximum: {CONNECT: {}, CONNACK: {}}, PropTopicAliasMaximum: {CONNECT: {}, CONNACK: {}}, - PropMaximumQOS: {CONNECT: {}, CONNACK: {}}, + PropMaximumQOS: {CONNACK: {}}, PropMaximumPacketSize: {CONNECT: {}, CONNACK: {}}, PropUser: {CONNECT: {}, CONNACK: {}, PUBLISH: {}, PUBACK: {}, PUBREC: {}, PUBREL: {}, PUBCOMP: {}, SUBSCRIBE: {}, UNSUBSCRIBE: {}, SUBACK: {}, UNSUBACK: {}, DISCONNECT: {}, AUTH: {}}, } diff --git a/vendor/github.com/eclipse/paho.golang/packets/puback.go b/vendor/github.com/eclipse/paho.golang/packets/puback.go index 67f404ce6..57e6f34f7 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/puback.go +++ b/vendor/github.com/eclipse/paho.golang/packets/puback.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( @@ -41,7 +56,7 @@ func (p *Puback) String() string { return b.String() } -//Unpack is the implementation of the interface required function for a packet +// Unpack is the implementation of the interface required function for a packet func (p *Puback) Unpack(r *bytes.Buffer) error { var err error success := r.Len() == 2 diff --git a/vendor/github.com/eclipse/paho.golang/packets/pubcomp.go b/vendor/github.com/eclipse/paho.golang/packets/pubcomp.go index 1cdfe61e9..b193be037 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/pubcomp.go +++ b/vendor/github.com/eclipse/paho.golang/packets/pubcomp.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( @@ -34,7 +49,7 @@ func (p *Pubcomp) String() string { return b.String() } -//Unpack is the implementation of the interface required function for a packet +// Unpack is the implementation of the interface required function for a packet func (p *Pubcomp) Unpack(r *bytes.Buffer) error { var err error success := r.Len() == 2 diff --git a/vendor/github.com/eclipse/paho.golang/packets/publish.go b/vendor/github.com/eclipse/paho.golang/packets/publish.go index 24edb588b..150ea3417 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/publish.go +++ b/vendor/github.com/eclipse/paho.golang/packets/publish.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( diff --git a/vendor/github.com/eclipse/paho.golang/packets/pubrec.go b/vendor/github.com/eclipse/paho.golang/packets/pubrec.go index 7bd304510..f1627968b 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/pubrec.go +++ b/vendor/github.com/eclipse/paho.golang/packets/pubrec.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( diff --git a/vendor/github.com/eclipse/paho.golang/packets/pubrel.go b/vendor/github.com/eclipse/paho.golang/packets/pubrel.go index 27c48c240..1f8ea9447 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/pubrel.go +++ b/vendor/github.com/eclipse/paho.golang/packets/pubrel.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( @@ -28,7 +43,7 @@ func (p *Pubrel) String() string { return b.String() } -//Unpack is the implementation of the interface required function for a packet +// Unpack is the implementation of the interface required function for a packet func (p *Pubrel) Unpack(r *bytes.Buffer) error { var err error success := r.Len() == 2 diff --git a/vendor/github.com/eclipse/paho.golang/packets/suback.go b/vendor/github.com/eclipse/paho.golang/packets/suback.go index 2503aaf1a..be5eaaa2b 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/suback.go +++ b/vendor/github.com/eclipse/paho.golang/packets/suback.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( @@ -34,7 +49,7 @@ const ( SubackWildcardsubscriptionsnotsupported = 0xA2 ) -//Unpack is the implementation of the interface required function for a packet +// Unpack is the implementation of the interface required function for a packet func (s *Suback) Unpack(r *bytes.Buffer) error { var err error s.PacketID, err = readUint16(r) diff --git a/vendor/github.com/eclipse/paho.golang/packets/subscribe.go b/vendor/github.com/eclipse/paho.golang/packets/subscribe.go index 2abccbbd6..c179e4eb9 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/subscribe.go +++ b/vendor/github.com/eclipse/paho.golang/packets/subscribe.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( diff --git a/vendor/github.com/eclipse/paho.golang/packets/unsuback.go b/vendor/github.com/eclipse/paho.golang/packets/unsuback.go index ba5164b9f..264ebce1d 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/unsuback.go +++ b/vendor/github.com/eclipse/paho.golang/packets/unsuback.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( diff --git a/vendor/github.com/eclipse/paho.golang/packets/unsubscribe.go b/vendor/github.com/eclipse/paho.golang/packets/unsubscribe.go index 090d7ca72..4af1bc5fd 100644 --- a/vendor/github.com/eclipse/paho.golang/packets/unsubscribe.go +++ b/vendor/github.com/eclipse/paho.golang/packets/unsubscribe.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package packets import ( diff --git a/vendor/github.com/eclipse/paho.golang/paho/acks_tracker.go b/vendor/github.com/eclipse/paho.golang/paho/acks_tracker.go index 47f11cb67..d4077b0ee 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/acks_tracker.go +++ b/vendor/github.com/eclipse/paho.golang/paho/acks_tracker.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho import ( diff --git a/vendor/github.com/eclipse/paho.golang/paho/auth.go b/vendor/github.com/eclipse/paho.golang/paho/auth.go index 7d3a3c972..3ab649656 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/auth.go +++ b/vendor/github.com/eclipse/paho.golang/paho/auth.go @@ -1,8 +1,23 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho // Auther is the interface for something that implements the extended authentication // flows in MQTT v5 type Auther interface { - Authenticate(*Auth) *Auth - Authenticated() + Authenticate(*Auth) *Auth // Authenticate will be called when an AUTH packet is received. + Authenticated() // Authenticated will be called when CONNACK is received } diff --git a/vendor/github.com/eclipse/paho.golang/paho/client.go b/vendor/github.com/eclipse/paho.golang/paho/client.go index 76a987229..94b89b7b3 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/client.go +++ b/vendor/github.com/eclipse/paho.golang/paho/client.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho import ( @@ -11,7 +26,9 @@ import ( "time" "github.com/eclipse/paho.golang/packets" - "golang.org/x/sync/semaphore" + "github.com/eclipse/paho.golang/paho/log" + "github.com/eclipse/paho.golang/paho/session" + "github.com/eclipse/paho.golang/paho/session/state" ) type MQTTVersion byte @@ -25,10 +42,22 @@ const defaultSendAckInterval = 50 * time.Millisecond var ( ErrManualAcknowledgmentDisabled = errors.New("manual acknowledgments disabled") + ErrNetworkErrorAfterStored = errors.New("error after packet added to state") // Could not send packet but its stored (and response will be sent on chan at some point in the future) + ErrConnectionLost = errors.New("connection lost after request transmitted") // We don't know whether the server received the request or not + + ErrInvalidArguments = errors.New("invalid argument") // If included (errors.Join) in an error, there is a problem with the arguments passed. Retrying on the same connection with the same arguments will not succeed. ) type ( - // ClientConfig are the user configurable options for the client, an + PublishReceived struct { + Packet *Publish + Client *Client // The Client that received the message (note that the connection may have been lost post-receipt) + + AlreadyHandled bool // Set to true if a previous callback has returned true (indicating some action has already been taken re the message) + Errs []error // Errors returned by previous handlers (if any). + } + + // ClientConfig are the user-configurable options for the client, an // instance of this struct is passed into NewClient(), not all options // are required to be set, defaults are provided for Persistence, MIDs, // PingHandler, PacketTimeout and Router. @@ -38,12 +67,27 @@ type ( // BEWARE that most wrapped net.Conn implementations like tls.Conn are // not thread safe for writing. To fix, use packets.NewThreadSafeConn // wrapper or extend the custom net.Conn struct with sync.Locker. - Conn net.Conn - MIDs MIDService + Conn net.Conn + + Session session.SessionManager + autoCloseSession bool + AuthHandler Auther PingHandler Pinger - Router Router - Persistence Persistence + defaultPinger bool + + // Router - new inbound messages will be passed to the `Route(*packets.Publish)` function. + // + // Depreciated: If a router is provided, it will now be added to the end of the OnPublishReceived + // slice (which provides a more flexible approach to handling incoming messages). + Router Router + + // OnPublishReceived provides a slice of callbacks; additional handlers may be added after the client has been + // created via the AddOnPublishReceived function (Client holds a copy of the slice; OnPublishReceived will not change). + // When a `PUBLISH` is received, the callbacks will be called in order. If a callback processes the message, + // then it should return true. This boolean, and any errors, will be passed to subsequent handlers. + OnPublishReceived []func(PublishReceived) (bool, error) + PacketTimeout time.Duration // OnServerDisconnect is called only when a packets.DISCONNECT is received from server OnServerDisconnect func(*Disconnect) @@ -68,20 +112,30 @@ type ( } // Client is the struct representing an MQTT client Client struct { - mu sync.Mutex - ClientConfig - // raCtx is used for handling the MQTTv5 authentication exchange. - raCtx *CPContext - stop chan struct{} + config ClientConfig + + // OnPublishReceived copy of OnPublishReceived from ClientConfig (perhaps with added callback form Router) + onPublishReceived []func(PublishReceived) (bool, error) + onPublishReceivedTracker []int // Used to track positions in above + onPublishReceivedMu sync.Mutex + + // authResponse is used for handling the MQTTv5 authentication exchange (MUST be buffered) + authResponse chan<- packets.ControlPacket + authResponseMu sync.Mutex // protects the above + + cancelFunc func() + + connectCalled bool // if true `Connect` has been called and a connection is being managed + connectCalledMu sync.Mutex // protects the above + + done <-chan struct{} // closed when shutdown complete (only valid after Connect returns nil error) publishPackets chan *packets.Publish acksTracker acksTracker workers sync.WaitGroup serverProps CommsProperties clientProps CommsProperties - serverInflight *semaphore.Weighted - clientInflight *semaphore.Weighted - debug Logger - errors Logger + debug log.Logger + errors log.Logger } // CommsProperties is a struct of the communication properties that may @@ -97,11 +151,6 @@ type ( SubIDAvailable bool SharedSubAvailable bool } - - caContext struct { - Context context.Context - Return chan *packets.Connack - } ) // NewClient is used to create a new default instance of an MQTT client. @@ -129,30 +178,40 @@ func NewClient(conf ClientConfig) *Client { MaximumPacketSize: 0, TopicAliasMaximum: 0, }, - ClientConfig: conf, - errors: NOOPLogger{}, - debug: NOOPLogger{}, + config: conf, + onPublishReceived: conf.OnPublishReceived, + done: make(chan struct{}), + errors: log.NOOPLogger{}, + debug: log.NOOPLogger{}, } - if c.Persistence == nil { - c.Persistence = &noopPersistence{} + if c.config.Session == nil { + c.config.Session = state.NewInMemory() + c.config.autoCloseSession = true // We created `Session`, so need to close it when done (so handlers all return) } - if c.MIDs == nil { - c.MIDs = &MIDs{index: make([]*CPContext, int(midMax))} + if c.config.PacketTimeout == 0 { + c.config.PacketTimeout = 10 * time.Second } - if c.PacketTimeout == 0 { - c.PacketTimeout = 10 * time.Second + + if c.config.Router == nil && len(c.onPublishReceived) == 0 { + c.config.Router = NewStandardRouter() // Maintain backwards compatibility (for now!) } - if c.Router == nil { - c.Router = NewStandardRouter() + if c.config.Router != nil { + r := c.config.Router + c.onPublishReceived = append(c.onPublishReceived, + func(p PublishReceived) (bool, error) { + r.Route(p.Packet.Packet()) + return false, nil + }) } - if c.PingHandler == nil { - c.PingHandler = DefaultPingerWithCustomFailHandler(func(e error) { - go c.error(e) - }) + c.onPublishReceivedTracker = make([]int, len(c.onPublishReceived)) // Must have the same number of elements as onPublishReceived + + if c.config.PingHandler == nil { + c.config.PingHandler = NewDefaultPinger() + c.config.defaultPinger = true } - if c.OnClientError == nil { - c.OnClientError = func(e error) {} + if c.config.OnClientError == nil { + c.config.OnClientError = func(e error) {} } return c @@ -162,23 +221,38 @@ func NewClient(conf ClientConfig) *Client { // the Client instance already has a working network connection. // The function takes a pre-prepared Connect packet, and uses that to // establish an MQTT connection. Assuming the connection completes -// successfully the rest of the client is initiated and the Connack -// returned. Otherwise the failure Connack (if there is one) is returned +// successfully, the rest of the client is initiated and the Connack +// returned. Otherwise, the failure Connack (if there is one) is returned // along with an error indicating the reason for the failure to connect. func (c *Client) Connect(ctx context.Context, cp *Connect) (*Connack, error) { - if c.Conn == nil { + if c.config.Conn == nil { return nil, fmt.Errorf("client connection is nil") } + // The connection is in c.config.Conn which is inaccessible to the user. + // The end result of `Connect` (possibly some time after it returns) will be to close the connection so calling + // Connect twice is invalid. + c.connectCalledMu.Lock() + if c.connectCalled { + c.connectCalledMu.Unlock() + return nil, fmt.Errorf("connect must only be called once") + } + c.connectCalled = true + c.connectCalledMu.Unlock() + + // The passed in ctx applies to the connection process only. clientCtx applies to Client (signals that the + // client should shut down). + clientCtx, cancelFunc := context.WithCancel(context.Background()) + done := make(chan struct{}) cleanup := func() { - close(c.stop) + cancelFunc() close(c.publishPackets) - _ = c.Conn.Close() - c.mu.Unlock() + _ = c.config.Conn.Close() + close(done) } - c.mu.Lock() - c.stop = make(chan struct{}) + c.cancelFunc = cancelFunc + c.done = done var publishPacketsSize uint16 = math.MaxUint16 if cp.Properties != nil && cp.Properties.ReceiveMaximum != nil { @@ -187,14 +261,11 @@ func (c *Client) Connect(ctx context.Context, cp *Connect) (*Connack, error) { c.publishPackets = make(chan *packets.Publish, publishPacketsSize) keepalive := cp.KeepAlive - c.ClientID = cp.ClientID + c.config.ClientID = cp.ClientID if cp.Properties != nil { if cp.Properties.MaximumPacketSize != nil { c.clientProps.MaximumPacketSize = *cp.Properties.MaximumPacketSize } - if cp.Properties.MaximumQOS != nil { - c.clientProps.MaximumQoS = *cp.Properties.MaximumQOS - } if cp.Properties.ReceiveMaximum != nil { c.clientProps.ReceiveMaximum = *cp.Properties.ReceiveMaximum } @@ -204,7 +275,7 @@ func (c *Client) Connect(ctx context.Context, cp *Connect) (*Connack, error) { } c.debug.Println("connecting") - connCtx, cf := context.WithTimeout(ctx, c.PacketTimeout) + connCtx, cf := context.WithTimeout(ctx, c.config.PacketTimeout) defer cf() ccp := cp.Packet() @@ -212,14 +283,14 @@ func (c *Client) Connect(ctx context.Context, cp *Connect) (*Connack, error) { ccp.ProtocolVersion = 5 c.debug.Println("sending CONNECT") - if _, err := ccp.WriteTo(c.Conn); err != nil { + if _, err := ccp.WriteTo(c.config.Conn); err != nil { cleanup() return nil, err } c.debug.Println("waiting for CONNACK/AUTH") var ( - caPacket *packets.Connack + caPacket *packets.Connack // We use buffered channels to prevent goroutine leak. The Details are below. // - c.expectConnack waits to send data to caPacketCh or caPacketErr. // - If connCtx is cancelled (done) before c.expectConnack finishes to send data to either "unbuffered" channel, @@ -230,11 +301,10 @@ func (c *Client) Connect(ctx context.Context, cp *Connect) (*Connack, error) { go c.expectConnack(caPacketCh, caPacketErr) select { case <-connCtx.Done(): - if ctxErr := connCtx.Err(); ctxErr != nil { - c.debug.Println(fmt.Sprintf("terminated due to context: %v", ctxErr)) - } + ctxErr := connCtx.Err() + c.debug.Println(fmt.Sprintf("terminated due to context waiting for CONNACK: %v", ctxErr)) cleanup() - return nil, connCtx.Err() + return nil, ctxErr case err := <-caPacketErr: c.debug.Println(err) cleanup() @@ -254,15 +324,21 @@ func (c *Client) Connect(ctx context.Context, cp *Connect) (*Connack, error) { return ca, fmt.Errorf("failed to connect to server: %s", reason) } - // no more possible calls to cleanup(), defer an unlock - defer c.mu.Unlock() + if err := c.config.Session.ConAckReceived(c.config.Conn, ccp, caPacket); err != nil { + cleanup() + return ca, fmt.Errorf("session error: %w", err) + } + + // the connection is now fully up and a nil error will be returned. + // cleanup() must not be called past this point and will be handled by `shutdown` + context.AfterFunc(clientCtx, func() { c.shutdown(done) }) if ca.Properties != nil { if ca.Properties.ServerKeepAlive != nil { keepalive = *ca.Properties.ServerKeepAlive } if ca.Properties.AssignedClientID != "" { - c.ClientID = ca.Properties.AssignedClientID + c.config.ClientID = ca.Properties.AssignedClientID } if ca.Properties.ReceiveMaximum != nil { c.serverProps.ReceiveMaximum = *ca.Properties.ReceiveMaximum @@ -282,15 +358,14 @@ func (c *Client) Connect(ctx context.Context, cp *Connect) (*Connack, error) { c.serverProps.SharedSubAvailable = ca.Properties.SharedSubAvailable } - c.serverInflight = semaphore.NewWeighted(int64(c.serverProps.ReceiveMaximum)) - c.clientInflight = semaphore.NewWeighted(int64(c.clientProps.ReceiveMaximum)) - c.debug.Println("received CONNACK, starting PingHandler") c.workers.Add(1) go func() { defer c.workers.Done() defer c.debug.Println("returning from ping handler worker") - c.PingHandler.Start(c.Conn, time.Duration(keepalive)*time.Second) + if err := c.config.PingHandler.Run(clientCtx, c.config.Conn, keepalive); err != nil { + go c.error(fmt.Errorf("ping handler error: %w", err)) + } }() c.debug.Println("starting publish packets loop") @@ -298,6 +373,8 @@ func (c *Client) Connect(ctx context.Context, cp *Connect) (*Connack, error) { go func() { defer c.workers.Done() defer c.debug.Println("returning from publish packets loop worker") + // exits when `c.publishPackets` is closed (`c.incoming()` closes this). This is important because + // messages may be passed for processing after `c.stop` has been closed. c.routePublishPackets() }() @@ -306,16 +383,16 @@ func (c *Client) Connect(ctx context.Context, cp *Connect) (*Connack, error) { go func() { defer c.workers.Done() defer c.debug.Println("returning from incoming worker") - c.incoming() + c.incoming(clientCtx) }() - if c.EnableManualAcknowledgment { + if c.config.EnableManualAcknowledgment { c.debug.Println("starting acking routine") c.acksTracker.reset() sendAcksInterval := defaultSendAckInterval - if c.SendAcksInterval > 0 { - sendAcksInterval = c.SendAcksInterval + if c.config.SendAcksInterval > 0 { + sendAcksInterval = c.config.SendAcksInterval } c.workers.Add(1) @@ -325,7 +402,7 @@ func (c *Client) Connect(ctx context.Context, cp *Connect) (*Connack, error) { t := time.NewTicker(sendAcksInterval) for { select { - case <-c.stop: + case <-clientCtx.Done(): return case <-t.C: c.acksTracker.flush(func(pbs []*packets.Publish) { @@ -341,8 +418,17 @@ func (c *Client) Connect(ctx context.Context, cp *Connect) (*Connack, error) { return ca, nil } +// Done returns a channel that will be closed when Client has shutdown. Only valid after Connect has returned a +// nil error. +func (c *Client) Done() <-chan struct{} { + return c.done +} + +// Ack transmits an acknowledgement of the `Publish` packet. +// WARNING: Calling Ack after the connection is closed may have unpredictable results (particularly if the sessionState +// is being accessed by a new connection). See issue #160. func (c *Client) Ack(pb *Publish) error { - if !c.EnableManualAcknowledgment { + if !c.config.EnableManualAcknowledgment { return ErrManualAcknowledgmentDisabled } if pb.QoS == 0 { @@ -351,52 +437,45 @@ func (c *Client) Ack(pb *Publish) error { return c.acksTracker.markAsAcked(pb.Packet()) } +// ack acknowledges a message (note: called by acksTracker to ensure these are sent in order) func (c *Client) ack(pb *packets.Publish) { - switch pb.QoS { - case 1: - pa := packets.Puback{ - Properties: &packets.Properties{}, - PacketID: pb.PacketID, - } - c.debug.Println("sending PUBACK") - _, err := pa.WriteTo(c.Conn) - if err != nil { - c.errors.Printf("failed to send PUBACK for %d: %s", pb.PacketID, err) - } - case 2: - pr := packets.Pubrec{ - Properties: &packets.Properties{}, - PacketID: pb.PacketID, - } - c.debug.Printf("sending PUBREC") - _, err := pr.WriteTo(c.Conn) - if err != nil { - c.errors.Printf("failed to send PUBREC for %d: %s", pb.PacketID, err) - } - } + c.config.Session.Ack(pb) } +// routePublishPackets listens on c.publishPackets and passes received messages to the handlers +// terminates when publishPackets closed func (c *Client) routePublishPackets() { - for { - select { - case <-c.stop: - return - case pb, open := <-c.publishPackets: - if !open { - return - } + for pb := range c.publishPackets { + // Copy onPublishReceived so lock is only held briefly + c.onPublishReceivedMu.Lock() + handlers := make([]func(PublishReceived) (bool, error), len(c.onPublishReceived)) + for i := range c.onPublishReceived { + handlers[i] = c.onPublishReceived[i] + } + c.onPublishReceivedMu.Unlock() - if !c.ClientConfig.EnableManualAcknowledgment { - c.Router.Route(pb) - c.ack(pb) - continue - } + if c.config.EnableManualAcknowledgment && pb.QoS != 0 { + c.acksTracker.add(pb) + } - if pb.QoS != 0 { - c.acksTracker.add(pb) + var handled bool + var errs []error + pkt := PublishFromPacketPublish(pb) + for _, h := range handlers { + ha, err := h(PublishReceived{ + Packet: pkt, + Client: c, + AlreadyHandled: handled, + Errs: errs, + }) + if ha { + handled = true } + errs = append(errs, err) + } - c.Router.Route(pb) + if !c.config.EnableManualAcknowledgment { + c.ack(pb) } } } @@ -406,21 +485,24 @@ func (c *Client) routePublishPackets() { // from Connect(), it exits when it receives a server initiated // Disconnect, the Stop channel is closed or there is an error reading // a packet from the network connection -func (c *Client) incoming() { +// Closes `c.publishPackets` when done (should be the only thing sending on this channel) +func (c *Client) incoming(ctx context.Context) { defer c.debug.Println("client stopping, incoming stopping") + defer close(c.publishPackets) + for { select { - case <-c.stop: + case <-ctx.Done(): return default: - recv, err := packets.ReadPacket(c.Conn) + recv, err := packets.ReadPacket(c.config.Conn) if err != nil { go c.error(err) return } switch recv.Type { case packets.CONNACK: - c.debug.Println("received CONNACK") + c.debug.Println("received CONNACK (unexpected)") go c.error(fmt.Errorf("received unexpected CONNACK")) return case packets.AUTH: @@ -428,93 +510,55 @@ func (c *Client) incoming() { ap := recv.Content.(*packets.Auth) switch ap.ReasonCode { case packets.AuthSuccess: - if c.AuthHandler != nil { - go c.AuthHandler.Authenticated() + if c.config.AuthHandler != nil { + go c.config.AuthHandler.Authenticated() } - if c.raCtx != nil { - c.raCtx.Return <- *recv + c.authResponseMu.Lock() + if c.authResponse != nil { + select { // authResponse must be buffered, and we should only receive 1 AUTH packet a time + case c.authResponse <- *recv: + default: + } } + c.authResponseMu.Unlock() case packets.AuthContinueAuthentication: - if c.AuthHandler != nil { - if _, err := c.AuthHandler.Authenticate(AuthFromPacketAuth(ap)).Packet().WriteTo(c.Conn); err != nil { + if c.config.AuthHandler != nil { + if _, err := c.config.AuthHandler.Authenticate(AuthFromPacketAuth(ap)).Packet().WriteTo(c.config.Conn); err != nil { go c.error(err) return } + c.config.PingHandler.PacketSent() } } case packets.PUBLISH: pb := recv.Content.(*packets.Publish) - c.debug.Printf("received QoS%d PUBLISH", pb.QoS) - c.mu.Lock() - select { - case <-c.stop: - c.mu.Unlock() - return - default: - c.publishPackets <- pb - c.mu.Unlock() - } - case packets.PUBACK, packets.PUBCOMP, packets.SUBACK, packets.UNSUBACK: - c.debug.Printf("received %s packet with id %d", recv.PacketType(), recv.PacketID()) - if cpCtx := c.MIDs.Get(recv.PacketID()); cpCtx != nil { - cpCtx.Return <- *recv - } else { - c.debug.Println("received a response for a message ID we don't know:", recv.PacketID()) - } - case packets.PUBREC: - c.debug.Println("received PUBREC for", recv.PacketID()) - if cpCtx := c.MIDs.Get(recv.PacketID()); cpCtx == nil { - c.debug.Println("received a PUBREC for a message ID we don't know:", recv.PacketID()) - pl := packets.Pubrel{ - PacketID: recv.Content.(*packets.Pubrec).PacketID, - ReasonCode: 0x92, - } - c.debug.Println("sending PUBREL for", pl.PacketID) - _, err := pl.WriteTo(c.Conn) - if err != nil { - c.errors.Printf("failed to send PUBREL for %d: %s", pl.PacketID, err) - } - } else { - pr := recv.Content.(*packets.Pubrec) - if pr.ReasonCode >= 0x80 { - //Received a failure code, shortcut and return - cpCtx.Return <- *recv - } else { - pl := packets.Pubrel{ - PacketID: pr.PacketID, - } - c.debug.Println("sending PUBREL for", pl.PacketID) - _, err := pl.WriteTo(c.Conn) - if err != nil { - c.errors.Printf("failed to send PUBREL for %d: %s", pl.PacketID, err) - } - } - } - case packets.PUBREL: - c.debug.Println("received PUBREL for", recv.PacketID()) - //Auto respond to pubrels unless failure code - pr := recv.Content.(*packets.Pubrel) - if pr.ReasonCode >= 0x80 { - //Received a failure code, continue - continue + if pb.QoS > 0 { // QOS1 or 2 need to be recorded in session state + c.config.Session.PacketReceived(recv, c.publishPackets) } else { - pc := packets.Pubcomp{ - PacketID: pr.PacketID, - } - c.debug.Println("sending PUBCOMP for", pr.PacketID) - _, err := pc.WriteTo(c.Conn) - if err != nil { - c.errors.Printf("failed to send PUBCOMP for %d: %s", pc.PacketID, err) + c.debug.Printf("received QoS%d PUBLISH", pb.QoS) + select { + case <-ctx.Done(): + return + case c.publishPackets <- pb: } } + case packets.PUBACK, packets.PUBCOMP, packets.SUBACK, packets.UNSUBACK, packets.PUBREC, packets.PUBREL: + c.config.Session.PacketReceived(recv, c.publishPackets) case packets.DISCONNECT: + pd := recv.Content.(*packets.Disconnect) c.debug.Println("received DISCONNECT") - if c.raCtx != nil { - c.raCtx.Return <- *recv + c.authResponseMu.Lock() + if c.authResponse != nil { + select { // authResponse must be buffered, and we should only receive 1 AUTH packet a time + case c.authResponse <- *recv: + default: + } } + c.authResponseMu.Unlock() + c.config.Session.ConnectionLost(pd) // this may impact the session state go func() { - if c.OnServerDisconnect != nil { - go c.serverDisconnect(DisconnectFromPacketDisconnect(recv.Content.(*packets.Disconnect))) + if c.config.OnServerDisconnect != nil { + go c.serverDisconnect(DisconnectFromPacketDisconnect(pd)) } else { go c.error(fmt.Errorf("server initiated disconnect")) } @@ -522,33 +566,37 @@ func (c *Client) incoming() { return case packets.PINGRESP: c.debug.Println("received PINGRESP") - c.PingHandler.PingResp() + c.config.PingHandler.PingResp() } } } } +// close terminates the connection and waits for a clean shutdown +// may be called multiple times (subsequent calls will wait on previously requested shutdown) func (c *Client) close() { - c.mu.Lock() - defer c.mu.Unlock() - - select { - case <-c.stop: - //already shutting down, do nothing - return - default: - } - - close(c.stop) - close(c.publishPackets) + c.cancelFunc() // cleanup handled by AfterFunc defined in Connect + <-c.done +} - c.debug.Println("client stopped") - c.PingHandler.Stop() - c.debug.Println("ping stopped") - _ = c.Conn.Close() +// shutdown cleanly shutdown the client +// This should only be called via the AfterFunc in `Connect` (shutdown must not be called more than once) +func (c *Client) shutdown(done chan<- struct{}) { + c.debug.Println("client stop requested") + _ = c.config.Conn.Close() c.debug.Println("conn closed") c.acksTracker.reset() c.debug.Println("acks tracker reset") + c.config.Session.ConnectionLost(nil) + if c.config.autoCloseSession { + if err := c.config.Session.Close(); err != nil { + c.errors.Println("error closing session", err) + } + } + c.debug.Println("session updated, waiting on workers") + c.workers.Wait() + c.debug.Println("workers done") + close(done) } // error is called to signify that an error situation has occurred, this @@ -558,15 +606,13 @@ func (c *Client) close() { func (c *Client) error(e error) { c.debug.Println("error called:", e) c.close() - c.workers.Wait() - go c.OnClientError(e) + go c.config.OnClientError(e) } func (c *Client) serverDisconnect(d *Disconnect) { c.close() - c.workers.Wait() c.debug.Println("calling OnServerDisconnect") - go c.OnServerDisconnect(d) + go c.config.OnServerDisconnect(d) } // Authenticate is used to initiate a reauthentication of credentials with the @@ -576,39 +622,39 @@ func (c *Client) serverDisconnect(d *Disconnect) { // is received. func (c *Client) Authenticate(ctx context.Context, a *Auth) (*AuthResponse, error) { c.debug.Println("client initiated reauthentication") - - c.mu.Lock() - if c.raCtx != nil { - c.mu.Unlock() + authResp := make(chan packets.ControlPacket, 1) + c.authResponseMu.Lock() + if c.authResponse != nil { + c.authResponseMu.Unlock() return nil, fmt.Errorf("previous authentication is still in progress") } - c.raCtx = &CPContext{ctx, make(chan packets.ControlPacket, 1)} - c.mu.Unlock() + c.authResponse = authResp + c.authResponseMu.Unlock() defer func() { - c.mu.Lock() - c.raCtx = nil - c.mu.Unlock() + c.authResponseMu.Lock() + c.authResponse = nil + c.authResponseMu.Unlock() }() c.debug.Println("sending AUTH") - if _, err := a.Packet().WriteTo(c.Conn); err != nil { + if _, err := a.Packet().WriteTo(c.config.Conn); err != nil { return nil, err } + c.config.PingHandler.PacketSent() var rp packets.ControlPacket select { case <-ctx.Done(): - if ctxErr := ctx.Err(); ctxErr != nil { - c.debug.Println(fmt.Sprintf("terminated due to context: %v", ctxErr)) - return nil, ctxErr - } - case rp = <-c.raCtx.Return: + ctxErr := ctx.Err() + c.debug.Println(fmt.Sprintf("terminated due to context waiting for AUTH: %v", ctxErr)) + return nil, ctxErr + case rp = <-authResp: } switch rp.Type { case packets.AUTH: - //If we've received one here it must be successful, the only way - //to abort a reauth is a server initiated disconnect + // If we've received one here it must be successful, the only way + // to abort a reauth is a server initiated disconnect return AuthResponseFromPacketAuth(rp.Content.(*packets.Auth)), nil case packets.DISCONNECT: return AuthResponseFromPacketDisconnect(rp.Content.(*packets.Disconnect)), nil @@ -626,50 +672,52 @@ func (c *Client) Subscribe(ctx context.Context, s *Subscribe) (*Suback, error) { for _, sub := range s.Subscriptions { if strings.ContainsAny(sub.Topic, "#+") { // Using a wildcard in a subscription when not supported - return nil, fmt.Errorf("cannot subscribe to %s, server does not support wildcards", sub.Topic) + return nil, fmt.Errorf("%w: cannot subscribe to %s, server does not support wildcards", ErrInvalidArguments, sub.Topic) } } } if !c.serverProps.SubIDAvailable && s.Properties != nil && s.Properties.SubscriptionIdentifier != nil { - return nil, fmt.Errorf("cannot send subscribe with subID set, server does not support subID") + return nil, fmt.Errorf("%w: cannot send subscribe with subID set, server does not support subID", ErrInvalidArguments) } if !c.serverProps.SharedSubAvailable { for _, sub := range s.Subscriptions { if strings.HasPrefix(sub.Topic, "$share") { - return nil, fmt.Errorf("cannont subscribe to %s, server does not support shared subscriptions", sub.Topic) + return nil, fmt.Errorf("%w: cannont subscribe to %s, server does not support shared subscriptions", ErrInvalidArguments, sub.Topic) } } } c.debug.Printf("subscribing to %+v", s.Subscriptions) - subCtx, cf := context.WithTimeout(ctx, c.PacketTimeout) - defer cf() - cpCtx := &CPContext{subCtx, make(chan packets.ControlPacket, 1)} - + ret := make(chan packets.ControlPacket, 1) sp := s.Packet() - - mid, err := c.MIDs.Request(cpCtx) - if err != nil { + if err := c.config.Session.AddToSession(ctx, sp, ret); err != nil { return nil, err } - defer c.MIDs.Free(mid) - sp.PacketID = mid - c.debug.Println("sending SUBSCRIBE") - if _, err := sp.WriteTo(c.Conn); err != nil { + // From this point on the message is in store, and ret will receive something regardless of whether we succeed in + // writing the packet to the connection or not. + if _, err := sp.WriteTo(c.config.Conn); err != nil { + // The packet will remain in the session state until `Session` is notified of the disconnection. return nil, err } + c.config.PingHandler.PacketSent() + c.debug.Println("waiting for SUBACK") + subCtx, cf := context.WithTimeout(ctx, c.config.PacketTimeout) + defer cf() var sap packets.ControlPacket select { case <-subCtx.Done(): - if ctxErr := subCtx.Err(); ctxErr != nil { - c.debug.Println(fmt.Sprintf("terminated due to context: %v", ctxErr)) - return nil, ctxErr - } - case sap = <-cpCtx.Return: + ctxErr := subCtx.Err() + c.debug.Println(fmt.Sprintf("terminated due to context waiting for SUBACK: %v", ctxErr)) + return nil, ctxErr + case sap = <-ret: + } + + if sap.Type == 0 { // default ControlPacket indicates we are shutting down + return nil, ErrConnectionLost } if sap.Type != packets.SUBACK { @@ -706,33 +754,35 @@ func (c *Client) Subscribe(ctx context.Context, s *Subscribe) (*Suback, error) { // is returned from the function, along with any errors. func (c *Client) Unsubscribe(ctx context.Context, u *Unsubscribe) (*Unsuback, error) { c.debug.Printf("unsubscribing from %+v", u.Topics) - unsubCtx, cf := context.WithTimeout(ctx, c.PacketTimeout) - defer cf() - cpCtx := &CPContext{unsubCtx, make(chan packets.ControlPacket, 1)} - + ret := make(chan packets.ControlPacket, 1) up := u.Packet() - - mid, err := c.MIDs.Request(cpCtx) - if err != nil { + if err := c.config.Session.AddToSession(ctx, up, ret); err != nil { return nil, err } - defer c.MIDs.Free(mid) - up.PacketID = mid - c.debug.Println("sending UNSUBSCRIBE") - if _, err := up.WriteTo(c.Conn); err != nil { + // From this point on the message is in store, and ret will receive something regardless of whether we succeed in + // writing the packet to the connection or not + if _, err := up.WriteTo(c.config.Conn); err != nil { + // The packet will remain in the session state until `Session` is notified of the disconnection. return nil, err } - c.debug.Println("waiting for UNSUBACK") + c.config.PingHandler.PacketSent() + + unsubCtx, cf := context.WithTimeout(ctx, c.config.PacketTimeout) + defer cf() var uap packets.ControlPacket + c.debug.Println("waiting for UNSUBACK") select { case <-unsubCtx.Done(): - if ctxErr := unsubCtx.Err(); ctxErr != nil { - c.debug.Println(fmt.Sprintf("terminated due to context: %v", ctxErr)) - return nil, ctxErr - } - case uap = <-cpCtx.Return: + ctxErr := unsubCtx.Err() + c.debug.Println(fmt.Sprintf("terminated due to context waiting for UNSUBACK: %v", ctxErr)) + return nil, ctxErr + case uap = <-ret: + } + + if uap.Type == 0 { // default ControlPacket indicates we are shutting down + return nil, ErrConnectionLost } if uap.Type != packets.UNSUBACK { @@ -764,27 +814,52 @@ func (c *Client) Unsubscribe(ctx context.Context, u *Unsubscribe) (*Unsuback, er } // Publish is used to send a publication to the MQTT server. -// It is passed a pre-prepared Publish packet and blocks waiting for -// the appropriate response, or for the timeout to fire. +// It is passed a pre-prepared Publish packet and blocks waiting for the appropriate response, or for the timeout to fire. // Any response message is returned from the function, along with any errors. +// Note that a message may still be delivered even if Publish times out (once the message is part of the session state, +// it may even be delivered following an application restart). +// Warning: Publish may outlive the connection when QOS1+ (managed in `session_state`) func (c *Client) Publish(ctx context.Context, p *Publish) (*PublishResponse, error) { + return c.PublishWithOptions(ctx, p, PublishOptions{}) +} + +type PublishMethod int + +const ( + PublishMethod_Blocking PublishMethod = iota // by default PublishWithOptions will block until the publish transaction is complete + PublishMethod_AsyncSend // PublishWithOptions will add the message to the session and then return (no method to check status is provided) +) + +// PublishOptions enables the behaviour of Publish to be modified +type PublishOptions struct { + // Method enables a degree of control over how PublishWithOptions operates + Method PublishMethod +} + +// PublishWithOptions is used to send a publication to the MQTT server (with options to customise its behaviour) +// It is passed a pre-prepared Publish packet and, by default, blocks waiting for the appropriate response, or for the +// timeout to fire. +// Note that a message may still be delivered even if Publish times out (once the message is part of the session state, +// it may even be delivered following an application restart). +// Warning: Publish may outlive the connection when QOS1+ (managed in `session_state`) +func (c *Client) PublishWithOptions(ctx context.Context, p *Publish, o PublishOptions) (*PublishResponse, error) { if p.QoS > c.serverProps.MaximumQoS { - return nil, fmt.Errorf("cannot send Publish with QoS %d, server maximum QoS is %d", p.QoS, c.serverProps.MaximumQoS) + return nil, fmt.Errorf("%w: cannot send Publish with QoS %d, server maximum QoS is %d", ErrInvalidArguments, p.QoS, c.serverProps.MaximumQoS) } if p.Properties != nil && p.Properties.TopicAlias != nil { if c.serverProps.TopicAliasMaximum > 0 && *p.Properties.TopicAlias > c.serverProps.TopicAliasMaximum { - return nil, fmt.Errorf("cannot send publish with TopicAlias %d, server topic alias maximum is %d", *p.Properties.TopicAlias, c.serverProps.TopicAliasMaximum) + return nil, fmt.Errorf("%w: cannot send publish with TopicAlias %d, server topic alias maximum is %d", ErrInvalidArguments, *p.Properties.TopicAlias, c.serverProps.TopicAliasMaximum) } } if !c.serverProps.RetainAvailable && p.Retain { - return nil, fmt.Errorf("cannot send Publish with retain flag set, server does not support retained messages") + return nil, fmt.Errorf("%w: cannot send Publish with retain flag set, server does not support retained messages", ErrInvalidArguments) } if (p.Properties == nil || p.Properties.TopicAlias == nil) && p.Topic == "" { - return nil, fmt.Errorf("cannot send a publish with no TopicAlias and no Topic set") + return nil, fmt.Errorf("%w: cannot send a publish with no TopicAlias and no Topic set", ErrInvalidArguments) } - if c.ClientConfig.PublishHook != nil { - c.ClientConfig.PublishHook(p) + if c.config.PublishHook != nil { + c.config.PublishHook(p) } c.debug.Printf("sending message to %s", p.Topic) @@ -794,46 +869,54 @@ func (c *Client) Publish(ctx context.Context, p *Publish) (*PublishResponse, err switch p.QoS { case 0: c.debug.Println("sending QoS0 message") - if _, err := pb.WriteTo(c.Conn); err != nil { + if _, err := pb.WriteTo(c.config.Conn); err != nil { + go c.error(err) return nil, err } + c.config.PingHandler.PacketSent() return nil, nil case 1, 2: - return c.publishQoS12(ctx, pb) + return c.publishQoS12(ctx, pb, o) } - return nil, fmt.Errorf("QoS isn't 0, 1 or 2") + return nil, fmt.Errorf("%w: QoS isn't 0, 1 or 2", ErrInvalidArguments) } -func (c *Client) publishQoS12(ctx context.Context, pb *packets.Publish) (*PublishResponse, error) { +func (c *Client) publishQoS12(ctx context.Context, pb *packets.Publish, o PublishOptions) (*PublishResponse, error) { c.debug.Println("sending QoS12 message") - pubCtx, cf := context.WithTimeout(ctx, c.PacketTimeout) + pubCtx, cf := context.WithTimeout(ctx, c.config.PacketTimeout) defer cf() - if err := c.serverInflight.Acquire(pubCtx, 1); err != nil { + + ret := make(chan packets.ControlPacket, 1) + if err := c.config.Session.AddToSession(pubCtx, pb, ret); err != nil { return nil, err } - defer c.serverInflight.Release(1) - cpCtx := &CPContext{pubCtx, make(chan packets.ControlPacket, 1)} - mid, err := c.MIDs.Request(cpCtx) - if err != nil { - return nil, err + // From this point on the message is in store, and ret will receive something regardless of whether we succeed in + // writing the packet to the connection + if _, err := pb.WriteTo(c.config.Conn); err != nil { + c.debug.Printf("failed to write packet %d to connection: %s", pb.PacketID, err) + if o.Method == PublishMethod_AsyncSend { + return nil, ErrNetworkErrorAfterStored // Async send, so we don't wait for the response (may add callbacks in the future to enable user to obtain status) + } } - defer c.MIDs.Free(mid) - pb.PacketID = mid + c.config.PingHandler.PacketSent() - if _, err := pb.WriteTo(c.Conn); err != nil { - return nil, err + if o.Method == PublishMethod_AsyncSend { + return nil, nil // Async send, so we don't wait for the response (may add callbacks in the future to enable user to obtain status) } - var resp packets.ControlPacket + var resp packets.ControlPacket select { case <-pubCtx.Done(): - if ctxErr := pubCtx.Err(); ctxErr != nil { - c.debug.Println(fmt.Sprintf("terminated due to context: %v", ctxErr)) - return nil, ctxErr - } - case resp = <-cpCtx.Return: + ctxErr := pubCtx.Err() + c.debug.Println(fmt.Sprintf("terminated due to context waiting for Publish ack: %v", ctxErr)) + return nil, ctxErr + case resp = <-ret: + } + + if resp.Type == 0 { // default ControlPacket indicates we are shutting down + return nil, errors.New("PUBLISH transmitted but not fully acknowledged at time of shutdown") } switch pb.QoS { @@ -867,7 +950,7 @@ func (c *Client) publishQoS12(ctx context.Context, pb *packets.Publish) (*Publis } func (c *Client) expectConnack(packet chan<- *packets.Connack, errs chan<- error) { - recv, err := packets.ReadPacket(c.Conn) + recv, err := packets.ReadPacket(c.config.Conn) if err != nil { errs <- err return @@ -877,17 +960,17 @@ func (c *Client) expectConnack(packet chan<- *packets.Connack, errs chan<- error c.debug.Println("received CONNACK") if r.ReasonCode == packets.ConnackSuccess && r.Properties != nil && r.Properties.AuthMethod != "" { // Successful connack and AuthMethod is defined, must have successfully authed during connect - go c.AuthHandler.Authenticated() + go c.config.AuthHandler.Authenticated() } packet <- r case *packets.Auth: c.debug.Println("received AUTH") - if c.AuthHandler == nil { + if c.config.AuthHandler == nil { errs <- fmt.Errorf("enhanced authentication flow started but no AuthHandler configured") return } c.debug.Println("sending AUTH") - _, err := c.AuthHandler.Authenticate(AuthFromPacketAuth(r)).Packet().WriteTo(c.Conn) + _, err := c.config.AuthHandler.Authenticate(AuthFromPacketAuth(r)).Packet().WriteTo(c.config.Conn) if err != nil { errs <- fmt.Errorf("error sending authentication packet: %w", err) return @@ -905,23 +988,77 @@ func (c *Client) expectConnack(packet chan<- *packets.Connack, errs chan<- error // (and if it does this function returns any error) the network connection // is closed. func (c *Client) Disconnect(d *Disconnect) error { - c.debug.Println("disconnecting") - _, err := d.Packet().WriteTo(c.Conn) + c.debug.Println("disconnecting", d) + _, err := d.Packet().WriteTo(c.config.Conn) c.close() - c.workers.Wait() return err } +// AddOnPublishReceived adds a function that will be called when a PUBLISH is received +// The new function will be called after any functions already in the list +// Returns a function that can be called to remove the callback +func (c *Client) AddOnPublishReceived(f func(PublishReceived) (bool, error)) func() { + c.onPublishReceivedMu.Lock() + defer c.onPublishReceivedMu.Unlock() + + c.onPublishReceived = append(c.onPublishReceived, f) + + // We insert a unique ID into the same position in onPublishReceivedTracker; this enables us to + // remove the handler later (without complicating onPublishReceived which will be called frequently) + var id int +idLoop: + for id = 0; ; id++ { + for _, used := range c.onPublishReceivedTracker { + if used == id { + continue idLoop + } + } + break + } + c.onPublishReceivedTracker = append(c.onPublishReceivedTracker, id) + + return func() { + c.onPublishReceivedMu.Lock() + defer c.onPublishReceivedMu.Unlock() + for pos, storedID := range c.onPublishReceivedTracker { + if id == storedID { + c.onPublishReceivedTracker = append(c.onPublishReceivedTracker[:pos], c.onPublishReceivedTracker[pos+1:]...) + c.onPublishReceived = append(c.onPublishReceived[:pos], c.onPublishReceived[pos+1:]...) + } + } + } +} + +// ClientID retrieves the client ID from the config (sometimes used in handlers that require the ID) +func (c *Client) ClientID() string { + return c.config.ClientID +} + // SetDebugLogger takes an instance of the paho Logger interface // and sets it to be used by the debug log endpoint -func (c *Client) SetDebugLogger(l Logger) { +func (c *Client) SetDebugLogger(l log.Logger) { c.debug = l + if c.config.autoCloseSession { // If we created the session store then it should use the same logger + c.config.Session.SetDebugLogger(l) + } + if c.config.defaultPinger { // Debug logger is set after the client is created so need to copy it to pinger + c.config.PingHandler.SetDebug(c.debug) + } } // SetErrorLogger takes an instance of the paho Logger interface // and sets it to be used by the error log endpoint -func (c *Client) SetErrorLogger(l Logger) { +func (c *Client) SetErrorLogger(l log.Logger) { c.errors = l + if c.config.autoCloseSession { // If we created the session store then it should use the same logger + c.config.Session.SetErrorLogger(l) + } +} + +// TerminateConnectionForTest closes the active connection (if any). This function is intended for testing only, it +// simulates connection loss which supports testing QOS1 and 2 message delivery. +func (c *Client) TerminateConnectionForTest() { + _ = c.config.Conn.Close() } diff --git a/vendor/github.com/eclipse/paho.golang/paho/cp_auth.go b/vendor/github.com/eclipse/paho.golang/paho/cp_auth.go index 6ccef9b47..46b64f8e2 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/cp_auth.go +++ b/vendor/github.com/eclipse/paho.golang/paho/cp_auth.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho import "github.com/eclipse/paho.golang/packets" diff --git a/vendor/github.com/eclipse/paho.golang/paho/cp_connack.go b/vendor/github.com/eclipse/paho.golang/paho/cp_connack.go index 2a525eb48..ef8a58605 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/cp_connack.go +++ b/vendor/github.com/eclipse/paho.golang/paho/cp_connack.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho import ( diff --git a/vendor/github.com/eclipse/paho.golang/paho/cp_connect.go b/vendor/github.com/eclipse/paho.golang/paho/cp_connect.go index 8d731764d..76adb183a 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/cp_connect.go +++ b/vendor/github.com/eclipse/paho.golang/paho/cp_connect.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho import "github.com/eclipse/paho.golang/packets" @@ -26,7 +41,6 @@ type ( WillDelayInterval *uint32 ReceiveMaximum *uint16 TopicAliasMaximum *uint16 - MaximumQOS *byte MaximumPacketSize *uint32 User UserProperties RequestProblemInfo bool @@ -47,7 +61,6 @@ func (c *Connect) InitProperties(p *packets.Properties) { RequestProblemInfo: true, ReceiveMaximum: p.ReceiveMaximum, TopicAliasMaximum: p.TopicAliasMaximum, - MaximumQOS: p.MaximumQOS, MaximumPacketSize: p.MaximumPacketSize, User: UserPropertiesFromPacketUser(p.User), } @@ -122,7 +135,6 @@ func (c *Connect) Packet() *packets.Connect { WillDelayInterval: c.Properties.WillDelayInterval, ReceiveMaximum: c.Properties.ReceiveMaximum, TopicAliasMaximum: c.Properties.TopicAliasMaximum, - MaximumQOS: c.Properties.MaximumQOS, MaximumPacketSize: c.Properties.MaximumPacketSize, User: c.Properties.User.ToPacketProperties(), } diff --git a/vendor/github.com/eclipse/paho.golang/paho/cp_disconnect.go b/vendor/github.com/eclipse/paho.golang/paho/cp_disconnect.go index 5caa85b14..ac5931c40 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/cp_disconnect.go +++ b/vendor/github.com/eclipse/paho.golang/paho/cp_disconnect.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho import "github.com/eclipse/paho.golang/packets" diff --git a/vendor/github.com/eclipse/paho.golang/paho/cp_publish.go b/vendor/github.com/eclipse/paho.golang/paho/cp_publish.go index 1bb9654b3..7f845e3d8 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/cp_publish.go +++ b/vendor/github.com/eclipse/paho.golang/paho/cp_publish.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho import ( @@ -12,6 +27,7 @@ type ( Publish struct { PacketID uint16 QoS byte + duplicate bool // private because this should only ever be set in paho/session Retain bool Topic string Properties *PublishProperties @@ -52,26 +68,34 @@ func (p *Publish) InitProperties(prop *packets.Properties) { // returns a paho library Publish func PublishFromPacketPublish(p *packets.Publish) *Publish { v := &Publish{ - PacketID: p.PacketID, - QoS: p.QoS, - Retain: p.Retain, - Topic: p.Topic, - Payload: p.Payload, + PacketID: p.PacketID, + QoS: p.QoS, + duplicate: p.Duplicate, + Retain: p.Retain, + Topic: p.Topic, + Payload: p.Payload, } v.InitProperties(p.Properties) return v } +// Duplicate returns true if the duplicate flag is set (the server sets this if the message has +// been sent previously; this does not necessarily mean the client has previously processed the message). +func (p *Publish) Duplicate() bool { + return p.duplicate +} + // Packet returns a packets library Publish from the paho Publish // on which it is called func (p *Publish) Packet() *packets.Publish { v := &packets.Publish{ - PacketID: p.PacketID, - QoS: p.QoS, - Retain: p.Retain, - Topic: p.Topic, - Payload: p.Payload, + PacketID: p.PacketID, + QoS: p.QoS, + Duplicate: p.duplicate, + Retain: p.Retain, + Topic: p.Topic, + Payload: p.Payload, } if p.Properties != nil { v.Properties = &packets.Properties{ @@ -90,6 +114,9 @@ func (p *Publish) Packet() *packets.Publish { } func (p *Publish) String() string { + if p == nil { + return "Publish==nil" + } var b bytes.Buffer fmt.Fprintf(&b, "topic: %s qos: %d retain: %t\n", p.Topic, p.QoS, p.Retain) diff --git a/vendor/github.com/eclipse/paho.golang/paho/cp_pubresp.go b/vendor/github.com/eclipse/paho.golang/paho/cp_pubresp.go index 0c4e174aa..bf47fca56 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/cp_pubresp.go +++ b/vendor/github.com/eclipse/paho.golang/paho/cp_pubresp.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho import "github.com/eclipse/paho.golang/packets" diff --git a/vendor/github.com/eclipse/paho.golang/paho/cp_suback.go b/vendor/github.com/eclipse/paho.golang/paho/cp_suback.go index c1034c26c..f9bce0389 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/cp_suback.go +++ b/vendor/github.com/eclipse/paho.golang/paho/cp_suback.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho import "github.com/eclipse/paho.golang/packets" diff --git a/vendor/github.com/eclipse/paho.golang/paho/cp_subscribe.go b/vendor/github.com/eclipse/paho.golang/paho/cp_subscribe.go index 52dc54017..04298accf 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/cp_subscribe.go +++ b/vendor/github.com/eclipse/paho.golang/paho/cp_subscribe.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho import "github.com/eclipse/paho.golang/packets" diff --git a/vendor/github.com/eclipse/paho.golang/paho/cp_unsuback.go b/vendor/github.com/eclipse/paho.golang/paho/cp_unsuback.go index 15ca83885..d9f1ad2c1 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/cp_unsuback.go +++ b/vendor/github.com/eclipse/paho.golang/paho/cp_unsuback.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho import "github.com/eclipse/paho.golang/packets" diff --git a/vendor/github.com/eclipse/paho.golang/paho/cp_unsubscribe.go b/vendor/github.com/eclipse/paho.golang/paho/cp_unsubscribe.go index 375b917c8..ff20acf99 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/cp_unsubscribe.go +++ b/vendor/github.com/eclipse/paho.golang/paho/cp_unsubscribe.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho import "github.com/eclipse/paho.golang/packets" diff --git a/vendor/github.com/eclipse/paho.golang/paho/cp_utils.go b/vendor/github.com/eclipse/paho.golang/paho/cp_utils.go index 2d7995f5c..a1ca35ebe 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/cp_utils.go +++ b/vendor/github.com/eclipse/paho.golang/paho/cp_utils.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho import ( diff --git a/vendor/github.com/eclipse/paho.golang/paho/log/test.go b/vendor/github.com/eclipse/paho.golang/paho/log/test.go new file mode 100644 index 000000000..4e8ff4d7e --- /dev/null +++ b/vendor/github.com/eclipse/paho.golang/paho/log/test.go @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package log + +import ( + "sync" + "time" +) + +// test implements a logger than can be passed a testing.T (which will only output logs for failed tests) + +// testLogger contains the logging functions provided by testing.T +type testLogger interface { + Log(args ...interface{}) + Logf(format string, args ...interface{}) +} + +// The TestLog type is an adapter to allow the use of testing.T as a paho.Logger. +// With this implementation, log messages will only be output when a test fails (and will be associated with the test). +type TestLog struct { + sync.Mutex + l testLogger + prefix string +} + +// NewTestLogger accepts a testLogger (e.g. Testing.T) and a prefix (added to messages logged) and returns a Logger +func NewTestLogger(l testLogger, prefix string) *TestLog { + return &TestLog{ + l: l, + prefix: prefix, + } +} + +// Println prints a line to the log +// Println its arguments in the test log (only printed if the test files or appropriate arguments passed to go test). +func (t *TestLog) Println(v ...interface{}) { + t.Lock() + defer t.Unlock() + if t.l != nil { + t.l.Log(append([]interface{}{time.Now().Format(time.RFC3339Nano), t.prefix}, v...)...) + } +} + +// Printf formats its arguments according to the format, analogous to fmt.Printf, and +// records the text in the test log (only printed if the test files or appropriate arguments passed to go test). +func (t *TestLog) Printf(format string, v ...interface{}) { + t.Lock() + defer t.Unlock() + if t.l != nil { + t.l.Logf(time.Now().Format(time.RFC3339Nano)+" "+t.prefix+format, v...) + } +} + +// Stop prevents future logging +// func (t *TestLog) Stop() { +// t.Lock() +// defer t.Unlock() +// t.l = nil +// } diff --git a/vendor/github.com/eclipse/paho.golang/paho/trace.go b/vendor/github.com/eclipse/paho.golang/paho/log/trace.go similarity index 55% rename from vendor/github.com/eclipse/paho.golang/paho/trace.go rename to vendor/github.com/eclipse/paho.golang/paho/log/trace.go index 586c92398..8363f7254 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/trace.go +++ b/vendor/github.com/eclipse/paho.golang/paho/log/trace.go @@ -1,4 +1,19 @@ -package paho +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package log type ( // Logger interface allows implementations to provide to this package any diff --git a/vendor/github.com/eclipse/paho.golang/paho/message_ids.go b/vendor/github.com/eclipse/paho.golang/paho/message_ids.go deleted file mode 100644 index ad287b5c7..000000000 --- a/vendor/github.com/eclipse/paho.golang/paho/message_ids.go +++ /dev/null @@ -1,110 +0,0 @@ -package paho - -import ( - "context" - "errors" - "sync" - - "github.com/eclipse/paho.golang/packets" -) - -const ( - midMin uint16 = 1 - midMax uint16 = 65535 -) - -// ErrorMidsExhausted is returned from Request() when there are no -// free message ids to be used. -var ErrorMidsExhausted = errors.New("all message ids in use") - -// MIDService defines the interface for a struct that handles the -// relationship between message ids and CPContexts -// Request() takes a *CPContext and returns a uint16 that is the -// messageid that should be used by the code that called Request() -// Get() takes a uint16 that is a messageid and returns the matching -// *CPContext that the MIDService has associated with that messageid -// Free() takes a uint16 that is a messageid and instructs the MIDService -// to mark that messageid as available for reuse -// Clear() resets the internal state of the MIDService -type MIDService interface { - Request(*CPContext) (uint16, error) - Get(uint16) *CPContext - Free(uint16) - Clear() -} - -// CPContext is the struct that is used to return responses to -// ControlPackets that have them, eg: the suback to a subscribe. -// The response packet is send down the Return channel and the -// Context is used to track timeouts. -type CPContext struct { - Context context.Context - Return chan packets.ControlPacket -} - -// MIDs is the default MIDService provided by this library. -// It uses a slice of *CPContext to track responses -// to messages with a messageid tracking the last used message id -type MIDs struct { - sync.Mutex - lastMid uint16 - index []*CPContext // index of slice is (messageid - 1) -} - -// Request is the library provided MIDService's implementation of -// the required interface function() -func (m *MIDs) Request(c *CPContext) (uint16, error) { - m.Lock() - defer m.Unlock() - - // Scan from lastMid to end of range. - for i := m.lastMid; i < midMax; i++ { - if m.index[i] != nil { - continue - } - m.index[i] = c - m.lastMid = i + 1 - return i + 1, nil - } - // Scan from start of range to lastMid - for i := uint16(0); i < m.lastMid; i++ { - if m.index[i] != nil { - continue - } - m.index[i] = c - m.lastMid = i + 1 - return i + 1, nil - } - - return 0, ErrorMidsExhausted -} - -// Get is the library provided MIDService's implementation of -// the required interface function() -func (m *MIDs) Get(i uint16) *CPContext { - // 0 Packet Identifier is invalid but just in case handled with returning nil to avoid panic. - if i == 0 { - return nil - } - m.Lock() - defer m.Unlock() - return m.index[i-1] -} - -// Free is the library provided MIDService's implementation of -// the required interface function() -func (m *MIDs) Free(i uint16) { - // 0 Packet Identifier is invalid but just in case handled to avoid panic. - if i == 0 { - return - } - m.Lock() - m.index[i-1] = nil - m.Unlock() -} - -// Clear is the library provided MIDService's implementation of -// the required interface function() -func (m *MIDs) Clear() { - m.index = make([]*CPContext, int(midMax)) -} diff --git a/vendor/github.com/eclipse/paho.golang/paho/noop_persistence.go b/vendor/github.com/eclipse/paho.golang/paho/noop_persistence.go deleted file mode 100644 index d2d15704f..000000000 --- a/vendor/github.com/eclipse/paho.golang/paho/noop_persistence.go +++ /dev/null @@ -1,23 +0,0 @@ -package paho - -import "github.com/eclipse/paho.golang/packets" - -type noopPersistence struct{} - -func (n *noopPersistence) Open() {} - -func (n *noopPersistence) Put(id uint16, cp packets.ControlPacket) {} - -func (n *noopPersistence) Get(id uint16) packets.ControlPacket { - return packets.ControlPacket{} -} - -func (n *noopPersistence) All() []packets.ControlPacket { - return nil -} - -func (n *noopPersistence) Delete(id uint16) {} - -func (n *noopPersistence) Close() {} - -func (n *noopPersistence) Reset() {} diff --git a/vendor/github.com/eclipse/paho.golang/paho/persistence.go b/vendor/github.com/eclipse/paho.golang/paho/persistence.go deleted file mode 100644 index f02b846cc..000000000 --- a/vendor/github.com/eclipse/paho.golang/paho/persistence.go +++ /dev/null @@ -1,98 +0,0 @@ -package paho - -import ( - "sync" - - "github.com/eclipse/paho.golang/packets" -) - -// Persistence is an interface of the functions for a struct -// that is used to persist ControlPackets. -// Open() is an initialiser to prepare the Persistence for use -// Put() takes a uint16 which is a messageid and a ControlPacket -// to persist against that messageid -// Get() takes a uint16 which is a messageid and returns the -// persisted ControlPacket from the Persistence for that messageid -// All() returns a slice of all ControlPackets persisted -// Delete() takes a uint16 which is a messageid and deletes the -// associated stored ControlPacket from the Persistence -// Close() closes the Persistence -// Reset() clears the Persistence and prepares it to be reused -type Persistence interface { - Open() - Put(uint16, packets.ControlPacket) - Get(uint16) packets.ControlPacket - All() []packets.ControlPacket - Delete(uint16) - Close() - Reset() -} - -// MemoryPersistence is an implementation of a Persistence -// that stores the ControlPackets in memory using a map -type MemoryPersistence struct { - sync.RWMutex - packets map[uint16]packets.ControlPacket -} - -// Open is the library provided MemoryPersistence's implementation of -// the required interface function() -func (m *MemoryPersistence) Open() { - m.Lock() - m.packets = make(map[uint16]packets.ControlPacket) - m.Unlock() -} - -// Put is the library provided MemoryPersistence's implementation of -// the required interface function() -func (m *MemoryPersistence) Put(id uint16, cp packets.ControlPacket) { - m.Lock() - m.packets[id] = cp - m.Unlock() -} - -// Get is the library provided MemoryPersistence's implementation of -// the required interface function() -func (m *MemoryPersistence) Get(id uint16) packets.ControlPacket { - m.RLock() - defer m.RUnlock() - return m.packets[id] -} - -// All is the library provided MemoryPersistence's implementation of -// the required interface function() -func (m *MemoryPersistence) All() []packets.ControlPacket { - m.Lock() - defer m.RUnlock() - ret := make([]packets.ControlPacket, len(m.packets)) - - for _, cp := range m.packets { - ret = append(ret, cp) - } - - return ret -} - -// Delete is the library provided MemoryPersistence's implementation of -// the required interface function() -func (m *MemoryPersistence) Delete(id uint16) { - m.Lock() - delete(m.packets, id) - m.Unlock() -} - -// Close is the library provided MemoryPersistence's implementation of -// the required interface function() -func (m *MemoryPersistence) Close() { - m.Lock() - m.packets = nil - m.Unlock() -} - -// Reset is the library provided MemoryPersistence's implementation of -// the required interface function() -func (m *MemoryPersistence) Reset() { - m.Lock() - m.packets = make(map[uint16]packets.ControlPacket) - m.Unlock() -} diff --git a/vendor/github.com/eclipse/paho.golang/paho/pinger.go b/vendor/github.com/eclipse/paho.golang/paho/pinger.go index e135d25ac..09c952c7c 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/pinger.go +++ b/vendor/github.com/eclipse/paho.golang/paho/pinger.go @@ -1,122 +1,139 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho import ( + "context" "fmt" "net" "sync" - "sync/atomic" "time" "github.com/eclipse/paho.golang/packets" + "github.com/eclipse/paho.golang/paho/log" ) -// PingFailHandler is a type for the function that is invoked -// when we have sent a Pingreq to the server and not received -// a Pingresp within 1.5x our pingtimeout -type PingFailHandler func(error) - -// Pinger is an interface of the functions for a struct that is -// used to manage sending PingRequests and responding to -// PingResponses -// Start() takes a net.Conn which is a connection over which an -// MQTT session has already been established, and a time.Duration -// of the keepalive setting passed to the server when the MQTT -// session was established. -// Stop() is used to stop the Pinger -// PingResp() is the function that is called by the Client when -// a PingResponse is received -// SetDebug() is used to pass in a Logger to be used to log debug -// information, for example sharing a logger with the main client type Pinger interface { - Start(net.Conn, time.Duration) - Stop() + // Run starts the pinger. It blocks until the pinger is stopped. + // If the pinger stops due to an error, it returns the error. + // If the keepAlive is 0, it returns nil immediately. + // Run() may be called multiple times, but only after prior instances have terminated. + Run(ctx context.Context, conn net.Conn, keepAlive uint16) error + + // PacketSent is called when a packet is sent to the server. + PacketSent() + + // PingResp is called when a PINGRESP is received from the server. PingResp() - SetDebug(Logger) + + // SetDebug sets the logger for debugging. + // It is not thread-safe and must be called before Run() to avoid race conditions. + SetDebug(log.Logger) } -// PingHandler is the library provided default Pinger -type PingHandler struct { - mu sync.Mutex - lastPing time.Time - conn net.Conn - stop chan struct{} - pingFailHandler PingFailHandler - pingOutstanding int32 - debug Logger +// DefaultPinger is the default implementation of Pinger. +type DefaultPinger struct { + lastPacketSent time.Time + lastPingResponse time.Time + + debug log.Logger + + running bool // Used to prevent concurrent calls to Run + + mu sync.Mutex // Protects all of the above } -// DefaultPingerWithCustomFailHandler returns an instance of the -// default Pinger but with a custom PingFailHandler that is called -// when the client has not received a response to a PingRequest -// within the appropriate amount of time -func DefaultPingerWithCustomFailHandler(pfh PingFailHandler) *PingHandler { - return &PingHandler{ - pingFailHandler: pfh, - debug: NOOPLogger{}, +// NewDefaultPinger creates a DefaultPinger +func NewDefaultPinger() *DefaultPinger { + return &DefaultPinger{ + debug: log.NOOPLogger{}, } } -// Start is the library provided Pinger's implementation of -// the required interface function() -func (p *PingHandler) Start(c net.Conn, pt time.Duration) { +// Run starts the pinger; blocks until done (either context cancelled or error encountered) +func (p *DefaultPinger) Run(ctx context.Context, conn net.Conn, keepAlive uint16) error { + if keepAlive == 0 { + p.debug.Println("Run() returning immediately due to keepAlive == 0") + return nil + } + if conn == nil { + return fmt.Errorf("conn is nil") + } p.mu.Lock() - p.conn = c - p.stop = make(chan struct{}) + if p.running { + p.mu.Unlock() + return fmt.Errorf("Run() already in progress") + } + p.running = true p.mu.Unlock() - checkTicker := time.NewTicker(pt / 4) - defer checkTicker.Stop() + defer func() { + p.mu.Lock() + p.running = false + p.mu.Unlock() + }() + + interval := time.Duration(keepAlive) * time.Second + timer := time.NewTimer(0) // Immediately send first pingreq + var lastPingSent time.Time for { select { - case <-p.stop: - return - case <-checkTicker.C: - if atomic.LoadInt32(&p.pingOutstanding) > 0 && time.Since(p.lastPing) > (pt+pt>>1) { - p.pingFailHandler(fmt.Errorf("ping resp timed out")) - //ping outstanding and not reset in 1.5 times ping timer - return + case <-ctx.Done(): + timer.Stop() // We don't care if the timer has fired + return nil + case t := <-timer.C: + p.mu.Lock() + lastPingResponse := p.lastPingResponse + pingDue := p.lastPacketSent.Add(interval) + p.mu.Unlock() + + if !lastPingSent.IsZero() && lastPingSent.After(lastPingResponse) { + p.debug.Printf("DefaultPinger PINGRESP timeout") + return fmt.Errorf("PINGRESP timed out") } - if time.Since(p.lastPing) >= pt { - //time to send a ping - if _, err := packets.NewControlPacket(packets.PINGREQ).WriteTo(p.conn); err != nil { - if p.pingFailHandler != nil { - p.pingFailHandler(err) - } - return - } - atomic.AddInt32(&p.pingOutstanding, 1) - p.lastPing = time.Now() - p.debug.Println("pingHandler sending ping request") + + if t.Before(pingDue) { + // A Control Packet has been sent since we last checked, meaning the ping can be delayed + timer.Reset(pingDue.Sub(t)) + continue } + + lastPingSent = time.Now() // set before sending because WriteTo may return after PINGRESP is handled + if _, err := packets.NewControlPacket(packets.PINGREQ).WriteTo(conn); err != nil { + p.debug.Printf("DefaultPinger packet write error: %v", err) + return fmt.Errorf("failed to send PINGREQ: %w", err) + } + timer.Reset(interval) } } } -// Stop is the library provided Pinger's implementation of -// the required interface function() -func (p *PingHandler) Stop() { +func (p *DefaultPinger) PacketSent() { p.mu.Lock() defer p.mu.Unlock() - if p.stop == nil { - return - } - p.debug.Println("pingHandler stopping") - select { - case <-p.stop: - //Already stopped, do nothing - default: - close(p.stop) - } + p.lastPacketSent = time.Now() } -// PingResp is the library provided Pinger's implementation of -// the required interface function() -func (p *PingHandler) PingResp() { - p.debug.Println("pingHandler resetting pingOutstanding") - atomic.StoreInt32(&p.pingOutstanding, 0) +func (p *DefaultPinger) PingResp() { + p.mu.Lock() + defer p.mu.Unlock() + p.lastPingResponse = time.Now() } -// SetDebug sets the logger l to be used for printing debug -// information for the pinger -func (p *PingHandler) SetDebug(l Logger) { - p.debug = l +func (p *DefaultPinger) SetDebug(debug log.Logger) { + p.mu.Lock() + defer p.mu.Unlock() + p.debug = debug } diff --git a/vendor/github.com/eclipse/paho.golang/paho/router.go b/vendor/github.com/eclipse/paho.golang/paho/router.go index 05031596f..dc6fa7062 100644 --- a/vendor/github.com/eclipse/paho.golang/paho/router.go +++ b/vendor/github.com/eclipse/paho.golang/paho/router.go @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + package paho import ( @@ -5,10 +20,14 @@ import ( "sync" "github.com/eclipse/paho.golang/packets" + "github.com/eclipse/paho.golang/paho/log" ) // MessageHandler is a type for a function that is invoked // by a Router when it has received a Publish. +// MessageHandlers should complete quickly (start a go routine for +// long-running processes) and should not call functions within the +// paho instance that triggered them (due to potential deadlocks). type MessageHandler func(*Publish) // Router is an interface of the functions for a struct that is @@ -24,16 +43,17 @@ type Router interface { RegisterHandler(string, MessageHandler) UnregisterHandler(string) Route(*packets.Publish) - SetDebugLogger(Logger) + SetDebugLogger(log.Logger) } // StandardRouter is a library provided implementation of a Router that // allows for unique and multiple MessageHandlers per topic type StandardRouter struct { sync.RWMutex - subscriptions map[string][]MessageHandler - aliases map[uint16]string - debug Logger + defaultHandler MessageHandler + subscriptions map[string][]MessageHandler + aliases map[uint16]string + debug log.Logger } // NewStandardRouter instantiates and returns an instance of a StandardRouter @@ -41,10 +61,19 @@ func NewStandardRouter() *StandardRouter { return &StandardRouter{ subscriptions: make(map[string][]MessageHandler), aliases: make(map[uint16]string), - debug: NOOPLogger{}, + debug: log.NOOPLogger{}, } } +// NewStandardRouterWithDefault instantiates and returns an instance of a StandardRouter +// with the default handler set to the value passed in (for convenience when creating +// handler inline). +func NewStandardRouterWithDefault(h MessageHandler) *StandardRouter { + r := NewStandardRouter() + r.DefaultHandler(h) + return r +} + // RegisterHandler is the library provided StandardRouter's // implementation of the required interface function() func (r *StandardRouter) RegisterHandler(topic string, h MessageHandler) { @@ -78,7 +107,7 @@ func (r *StandardRouter) Route(pb *packets.Publish) { if pb.Properties.TopicAlias != nil { r.debug.Println("message is using topic aliasing") if pb.Topic != "" { - //Register new alias + // Register new alias r.debug.Printf("registering new topic alias '%d' for topic '%s'", *pb.Properties.TopicAlias, m.Topic) r.aliases[*pb.Properties.TopicAlias] = pb.Topic } @@ -90,22 +119,37 @@ func (r *StandardRouter) Route(pb *packets.Publish) { topic = m.Topic } + handlerCalled := false for route, handlers := range r.subscriptions { if match(route, topic) { r.debug.Println("found handler for:", route) for _, handler := range handlers { handler(m) + handlerCalled = true } } } + + if !handlerCalled && r.defaultHandler != nil { + r.defaultHandler(m) + } } // SetDebugLogger sets the logger l to be used for printing debug // information for the router -func (r *StandardRouter) SetDebugLogger(l Logger) { +func (r *StandardRouter) SetDebugLogger(l log.Logger) { r.debug = l } +// DefaultHandler sets handler to be called for messages that don't trigger another handler +// Pass nil to unset. +func (r *StandardRouter) DefaultHandler(h MessageHandler) { + r.debug.Println("registering default handler") + r.Lock() + defer r.Unlock() + r.defaultHandler = h +} + func match(route, topic string) bool { return route == topic || routeIncludesTopic(route, topic) } @@ -153,60 +197,12 @@ func topicSplit(topic string) []string { return strings.Split(topic, "/") } -// SingleHandlerRouter is a library provided implementation of a Router -// that stores only a single MessageHandler and invokes this MessageHandler -// for all received Publishes -type SingleHandlerRouter struct { - sync.Mutex - aliases map[uint16]string - handler MessageHandler - debug Logger -} - -// NewSingleHandlerRouter instantiates and returns an instance of a SingleHandlerRouter -func NewSingleHandlerRouter(h MessageHandler) *SingleHandlerRouter { - return &SingleHandlerRouter{ - aliases: make(map[uint16]string), - handler: h, - debug: NOOPLogger{}, - } -} - -// RegisterHandler is the library provided SingleHandlerRouter's -// implementation of the required interface function() -func (s *SingleHandlerRouter) RegisterHandler(topic string, h MessageHandler) { - s.debug.Println("registering handler for:", topic) - s.handler = h -} - -// UnregisterHandler is the library provided SingleHandlerRouter's -// implementation of the required interface function() -func (s *SingleHandlerRouter) UnregisterHandler(topic string) {} - -// Route is the library provided SingleHandlerRouter's -// implementation of the required interface function() -func (s *SingleHandlerRouter) Route(pb *packets.Publish) { - m := PublishFromPacketPublish(pb) - - s.debug.Println("routing message for:", m.Topic) - - if pb.Properties.TopicAlias != nil { - s.debug.Println("message is using topic aliasing") - if pb.Topic != "" { - //Register new alias - s.debug.Printf("registering new topic alias '%d' for topic '%s'", *pb.Properties.TopicAlias, m.Topic) - s.aliases[*pb.Properties.TopicAlias] = pb.Topic - } - if t, ok := s.aliases[*pb.Properties.TopicAlias]; ok { - s.debug.Printf("aliased topic '%d' translates to '%s'", *pb.Properties.TopicAlias, m.Topic) - m.Topic = t - } - } - s.handler(m) -} - -// SetDebugLogger sets the logger l to be used for printing debug -// information for the router -func (s *SingleHandlerRouter) SetDebugLogger(l Logger) { - s.debug = l +// NewSingleHandlerRouter instantiates a router that will call the passed in message handler for all +// inbound messages (assuming `RegisterHandler` is never called). +// +// Deprecated: SingleHandlerRouter has been removed because it did not meet the requirements set out +// in the `Router` interface documentation. This function is only included to maintain compatibility, +// but there are limits (this version does not ignore calls to `RegisterHandler`). +func NewSingleHandlerRouter(h MessageHandler) *StandardRouter { + return NewStandardRouterWithDefault(h) } diff --git a/vendor/github.com/eclipse/paho.golang/paho/session/session.go b/vendor/github.com/eclipse/paho.golang/paho/session/session.go new file mode 100644 index 000000000..a8d4a5349 --- /dev/null +++ b/vendor/github.com/eclipse/paho.golang/paho/session/session.go @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package session + +import ( + "context" + "errors" + "io" + + "github.com/eclipse/paho.golang/packets" + paholog "github.com/eclipse/paho.golang/paho/log" +) + +// The session state includes: +// * Inflight QOS1/2 Publish transactions (both sent and received) +// * All requests + +var ( + ErrNoConnection = errors.New("no connection available") // We are not in-between a call to ConAckReceived and ConnectionLost + ErrPacketIdentifiersExhausted = errors.New("all packet identifiers in use") // There are no available Packet IDs +) + +// Packet provides sufficient functionality to enable a packet to be transmitted with a packet identifier +type Packet interface { + SetIdentifier(uint16) // Sets the packet identifier + Type() byte // Gets the packet type + WriteTo(io.Writer) (int64, error) +} + +// SessionManager will manage the mqtt session state; note that the state may outlast a single `Client` instance +type SessionManager interface { + // ConAckReceived must be called when a CONNACK has been received (with no error). If an error is returned + // then the connection will be dropped. + ConAckReceived(io.Writer, *packets.Connect, *packets.Connack) error + + // ConnectionLost must be called whenever the connection is lost or a DISCONNECT packet is received. It can be + // called multiple times for the same event as long as ConAckReceived is not called in the interim. + ConnectionLost(dp *packets.Disconnect) error + + // AddToSession adds a packet to the session state (including allocation of a message identifier). + // This should only be used for packets that impact the session state (which does not include QOS0 publish). + // If this function returns a nil then: + // - A message Identifier has been added to the passed in packet + // - If a `PUBLISH` then a slot has been allocated (function will block if RECEIVE MAXIMUM messages are inflight) + // - Publish messages will have been written to the store (and will be automatically transmitted if a new connection + // is established before the message is fully acknowledged - subject to state rules in the MQTTv5 spec) + // - Something will be sent to `resp` when either the message is fully acknowledged or the packet is removed from + // the session (in which case nil will be sent). + // + // If the function returns an error, then any actions taken will be rewound prior to return. + AddToSession(ctx context.Context, packet Packet, resp chan<- packets.ControlPacket) error + + // PacketReceived must be called when any packet with a packet identifier is received. It will make any required + // response and pass any `PUBLISH` messages that need to be passed to the user via the channel. + PacketReceived(*packets.ControlPacket, chan<- *packets.Publish) error + + // Ack must be called when the client message handlers have completed (or, if manual acknowledgements are enabled, + // when`client.ACK()` has been called - note the potential issues discussed in issue #160. + Ack(pb *packets.Publish) error + + // Close shuts down the session store, this will release any blocked calls + // Note: `paho` will only call this if it created the session (i.e. it was not passed in the config) + Close() error + + // SetErrorLogger enables error logging via the passed logger (not thread safe) + SetErrorLogger(l paholog.Logger) + + // SetDebugLogger enables debug logging via the passed logger (not thread safe) + SetDebugLogger(l paholog.Logger) +} diff --git a/vendor/github.com/eclipse/paho.golang/paho/session/state/readme.md b/vendor/github.com/eclipse/paho.golang/paho/session/state/readme.md new file mode 100644 index 000000000..cbeec0f88 --- /dev/null +++ b/vendor/github.com/eclipse/paho.golang/paho/session/state/readme.md @@ -0,0 +1,5 @@ +paho.golang/paho/session/state +============================== + +This is an implementation of the `paho.golang/paho/session.SessionManager` interface and is the implementation used +by default (should be suitable for mose use cases but can be replaced if it does not meet your needs). \ No newline at end of file diff --git a/vendor/github.com/eclipse/paho.golang/paho/session/state/sendquota.go b/vendor/github.com/eclipse/paho.golang/paho/session/state/sendquota.go new file mode 100644 index 000000000..d48f05303 --- /dev/null +++ b/vendor/github.com/eclipse/paho.golang/paho/session/state/sendquota.go @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package state + +import ( + "context" + "errors" + "fmt" + "sync" +) + +// Implements send quota as described in section 4.9 of the spec. This is used to honour the receive-maximum +// received from the broker; each time a qos1/2 PUBLISH is to be sent `Acquire` must be called, and this will +// block until a slot is available (`Release` is called when the message is fully acknowledged). + +// This function was previously performed by `golang.org/x/sync/semaphore` but, as per the MQTT spec: +// +// > The send quota is not incremented if it is already equal to the initial send quota. The attempt to increment above +// > the initial send quota might be caused by the re-transmission of a PUBREL packet after a new Network Connection is +// > established. +// +// The result of this happening with `semaphore` is a `panic` which is not ideal. +// It is also possible (as per issue #179) that bugs, or unexpected circumstances, may result in the same situation. For +// example: if the local session state is lost but there is a session state on the server (meaning it sends an unexpected +// PUBACK). +// +// Note: If the broker does not correctly acknowledge messages, then the quota will be consumed over time. There +// should probably be a process to drop the connection if there are no slots available and no acknowledgements have been +// received recently. + +// ErrUnexpectedRelease is for logging only (to help identify if there are issues with state management) +var ErrUnexpectedRelease = errors.New("release called when quota at initial value") + +// newSendQuota creates a new tracker limited to quota concurrent messages +func newSendQuota(quota uint16) *sendQuota { + w := &sendQuota{initialQuota: quota, quota: quota} + return w +} + +// sendQuota provides a way to bound concurrent access to a resource. +// The callers can request access with a given weight. +type sendQuota struct { + mu sync.Mutex + initialQuota uint16 + quota uint16 + waiters []chan<- struct{} // using a slice because would generally expect this to be small +} + +// Retransmit takes a slot for a message that is being redelivered and will never block. +// This is not in compliance with the MQTT v5 spec and should be removed in the future. +func (s *sendQuota) Retransmit() error { + return s.acquire(context.Background(), true) +} + +// Acquire waits for a slot to become available so a message can be published +// If ctx is already done, Acquire may still succeed without blocking. +func (s *sendQuota) Acquire(ctx context.Context) error { + return s.acquire(ctx, false) +} + +// acquire attempts to allocate a slot for a message to be published +// If noWait is true quota will be ignored and the call will return immediately, otherwise acquire will block +// until a slot is available. +func (s *sendQuota) acquire(ctx context.Context, noWait bool) error { + s.mu.Lock() + if noWait || (s.quota > 0 && len(s.waiters) == 0) { + s.quota-- // Note: can go < 0 if noWait used + s.mu.Unlock() + return nil + } + + // We need to join the queue + ready := make(chan struct{}) + s.waiters = append(s.waiters, ready) + s.mu.Unlock() + + var err error + select { + case <-ctx.Done(): + err = ctx.Err() + s.mu.Lock() + select { + case <-ready: // If ready then already removed from s.waiters + // Acquired the semaphore after we were cancelled. Rather than trying to + // fix up the queue, just pretend we didn't notice the cancellation. + err = nil + fmt.Println("quota released entry but ready so nil error ", s.quota) + default: + // Remove ourselves from the list of waiters + for i, r := range s.waiters { + if ready == r { + s.waiters = append(s.waiters[:i], s.waiters[i+1:]...) + } + } + } + s.mu.Unlock() + case <-ready: // Note that quota already accounts for this item + } + return err +} + +// Release releases slot +func (s *sendQuota) Release() error { + s.mu.Lock() + defer s.mu.Unlock() + + if s.quota >= 0 && len(s.waiters) > 0 { // Possible quota could go negative when using noWait + close(s.waiters[0]) + s.waiters = append(s.waiters[:0], s.waiters[1:]...) + return nil + } + if s.quota < s.initialQuota { + s.quota++ + return nil + } + return ErrUnexpectedRelease +} diff --git a/vendor/github.com/eclipse/paho.golang/paho/session/state/state.go b/vendor/github.com/eclipse/paho.golang/paho/session/state/state.go new file mode 100644 index 000000000..bd197046e --- /dev/null +++ b/vendor/github.com/eclipse/paho.golang/paho/session/state/state.go @@ -0,0 +1,739 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package state + +import ( + "context" + "errors" + "fmt" + "io" + "sync" + "time" + + "github.com/eclipse/paho.golang/packets" + paholog "github.com/eclipse/paho.golang/paho/log" + "github.com/eclipse/paho.golang/paho/session" + "github.com/eclipse/paho.golang/paho/store/memory" +) + +// The Session State, as per the MQTT spec, contains: +// +// > QoS 1 and QoS 2 messages which have been sent to the Server, but have not been completely acknowledged. +// > QoS 2 messages which have been received from the Server, but have not been completely acknowledged. +// +// and, importantly, is used when resending as follows: +// +// > When a Client reconnects with Clean Start set to 0 and a session is present, both the Client and Server MUST resend +// > any unacknowledged PUBLISH packets (where QoS > 0) and PUBREL packets using their original Packet Identifiers. This +// > is the only circumstance where a Client or Server is REQUIRED to resend messages. Clients and Servers MUST NOT +// > resend messages at any other time +// +// There are a few other areas where the State is important: +// * When allocating a new packet identifier we need to know what Id's are already in the session state. +// * If a QOS2 Publish with `DUP=TRUE` is received then we should not pass it to the client if we have previously +// sent a `PUBREC` (indicating that the message has already been processed). +// * Some subscribers may need a transaction (i.e. commit transaction when `PUBREL` received), this is not implemented +// here, but the option is left open. +// +// This means that the following information may need to be retained after the connection is lost: +// * The IDs of any transactions initiated by the client (so that we don't reuse IDs and can notify the requester (if +// known) when a response is received or the request is removed from the state). +// * For client initiated publish: +// * Outgoing `PUBLISH` packets to allow resend. +// * Outgoing `PUBREL` packets to allow resend. +// * For server initiated publish: +// * Outgoing `PUBREL` packets. The fact that this has been sent indicates that the user app has acknowledged the +// message, and it should not be re-presented (doing so would breach the "exactly once" requirement). +// * In memory only - the fact that a QOS2 PUBLISH has been received and sent to the handler, but not acknowledged. +// This allows us to avoid presenting the message a second time (if the application is restarted, we have no option +// but to re-present it because we have no way of knowing if the application completed handling it). +// +// It is important to note that there are packets with identifiers that do not form part of the session state +// (SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK). Whilst these will never be stored to disk, it is important to track +// the packet IDs to ensure we don't reuse them. +// For packets relating to client-initiated transactions sent during a `session.State` lifetime we also want to link +// a channel to the message ID so that we can notify our user when the transaction is complete (allowing a call to, for +// instance `Publish()` to block until the message is fully acknowledged even if we disconnect/reconnect in the interim. + +const ( + midMin uint16 = 1 + midMax uint16 = 65535 +) + +type ( + // clientGenerated holds information on client-generated packets (e.g. an outgoing SUBSCRIBE request) + clientGenerated struct { + packetType byte // The type of the last packet sent (i.e. PUBLISH, SUBSCRIBE or UNSUBSCRIBE) - 0 means unknown until loaded from the store + + // When a message is fully acknowledged, we need to let the requester know by sending the final response to this + // channel. One and only one message will be sent (the channel will then be closed to ensure this!). + // We also guarantee to always send to the channel (assuming there is a clean shutdown) so that the end user knows + // the status of the request. + responseChan chan<- packets.ControlPacket + } +) + +// State manages the session state. The client will send messages that may impact the state via +// us, and we will maintain the session state +type State struct { + mu sync.Mutex // protects whole struct (operations should be quick, so the impact of multiple mutexes is likely to be low) + connectionLostAt time.Time // Time that the connection was lost + sessionExpiryInterval uint32 // The session expiry interval sent with the most recent CONNECT packet + + conn io.Writer // current connection or nil if we are not connected + connCtx context.Context // Context will be closed if the connection is lost (only valid when conn != nil) + connCtxCancel func() // Cancels the above context + + // client store - holds packets where the message ID was generated on the client (i.e. by paho.golang) + clientPackets map[uint16]clientGenerated // Store relating to messages sent TO the server + clientStore storer // Used to store session state that survives connection loss + lastMid uint16 // The message ID most recently issued + + // server store - holds packets where the message ID was generated on the server + serverPackets map[uint16]byte // The last packet received from the server with this ID (cleared when the transaction is complete) + serverStore storer // Used to store session state that survives connection loss + + // The number of messages in flight needs to be limited, as per receive maximum received from the server. + inflight *sendQuota + + debug paholog.Logger + errors paholog.Logger +} + +// New creates a new state which will persist information using the passed in storer's. +func New(client storer, server storer) *State { + return &State{ + clientStore: client, + serverStore: server, + debug: paholog.NOOPLogger{}, + errors: paholog.NOOPLogger{}, + } +} + +// NewInMemory returns a default State that stores all information in memory +func NewInMemory() *State { + return &State{ + clientStore: memory.New(), + serverStore: memory.New(), + debug: paholog.NOOPLogger{}, + errors: paholog.NOOPLogger{}, + } +} + +// Close closes the session state +func (s *State) Close() error { + s.mu.Lock() + defer s.mu.Unlock() + s.connectionLost(nil) // Connection may be up in which case we need to cleanup. + for packetID, cg := range s.clientPackets { + cg.responseChan <- packets.ControlPacket{} // Default control packet indicates that we are shutting down (TODO: better solution?) + delete(s.clientPackets, packetID) + } + return nil +} + +// ConAckReceived will be called when the client receives a CONACK that indicates the connection has been successfully +// established. This indicates that a new connection is live and the passed in connection should be used going forward. +// It is also the trigger to resend any queued messages. Note that this function should not be called concurrently with +// others (we should not begin sending/receiving packets until after the CONACK has been processed). +// TODO: Add errors() function so we can notify the client of errors whilst transmitting the session stuff? +func (s *State) ConAckReceived(conn io.Writer, cp *packets.Connect, ca *packets.Connack) error { + // We could use cp.Properties.SessionExpiryInterval / ca.Properties.SessionExpiryInterval to clear the session + // after the specified time period (if the Session Expiry Interval is absent the value in the CONNECT Packet used) + // however, this is not something the generic client can really accomplish (forks may wish to do this!). + s.mu.Lock() + defer s.mu.Unlock() + if s.conn != nil { + s.errors.Println("ConAckReceived called whilst connection active (you MUST call ConnectionLost before starting a new connection") + _ = s.connectionLost(nil) // assume the connection dropped + } + s.conn = conn + s.connCtx, s.connCtxCancel = context.WithCancel(context.Background()) + + // If the Server accepts a connection with Clean Start set to 1, the Server MUST set Session Present to 0 in the + // CONNACK packet in addition to setting a 0x00 (Success) Reason Code in the CONNACK packet [MQTT-3.2.2-2]. + // If the Server accepts a connection with Clean Start set to 0 and the Server has Session State for the ClientID, + // it MUST set Session Present to 1 in the CONNACK packet, otherwise it MUST set Session Present to 0 in the CONNACK + // packet. In both cases, it MUST set a 0x00 (Success) Reason Code in the CONNACK packet [MQTT-3.2.2-3]. + if !ca.SessionPresent { + s.debug.Println("no session present - cleaning session state") + s.clean() + } + inFlight := uint16(len(s.clientPackets)) + s.debug.Printf("%d inflight transactions upon connection", inFlight) + + // "If the Session Expiry Interval is absent, the Session Expiry Interval in the CONNECT packet is used." + // If the Session Expiry Interval is absent the value 0 is used. If it is set to 0, or is absent, the Session ends + // when the Network Connection is closed (3.1.2.11.2). + if ca.Properties != nil && ca.Properties.SessionExpiryInterval != nil { + s.sessionExpiryInterval = *ca.Properties.SessionExpiryInterval + } else if cp.Properties != nil && cp.Properties.SessionExpiryInterval != nil { + s.sessionExpiryInterval = *cp.Properties.SessionExpiryInterval + } else { + s.sessionExpiryInterval = 0 + } + + // If clientPackets already exists, we re-use it so that the responseChan survives the reconnection + // the map will be populated/repopulated when we retransmit the messages. + if s.clientPackets == nil { + s.clientPackets = make(map[uint16]clientGenerated) // This will be populated whilst packets are resent + } + + if s.serverPackets == nil { + if err := s.loadServerSession(ca); err != nil { + return fmt.Errorf("failed to server session: %w", err) + } + } + + // As per section 4.9 "The send quota and Receive Maximum value are not preserved across Network Connections" + recvMax := uint16(65535) // Default as per MQTT spec + if ca.Properties != nil && ca.Properties.ReceiveMaximum != nil { + recvMax = *ca.Properties.ReceiveMaximum + } + s.inflight = newSendQuota(recvMax) + + // Now we need to resend any packets in the store; this must happen in order, the simplest approach is to complete + // the sending them before returning. + toResend, err := s.clientStore.List() + if err != nil { + return fmt.Errorf("failed to load stored message ids: %w", err) + } + s.debug.Printf("retransmitting %d messages", len(toResend)) + for _, id := range toResend { + s.debug.Printf("resending message ID %d", id) + r, err := s.clientStore.Get(id) + if err != nil { + s.errors.Printf("failed to load packet %d from client store: %s", id, err) + continue + } + + // DUP needs to be set when resending PUBLISH + // Read/parse the full packet, so we can detect corruption (e.g. 0 byte file) + p, err := packets.ReadPacket(r) + if cErr := r.Close(); cErr != nil { + s.errors.Printf("failed to close stored client packet %d: %s", id, cErr) + } + if err != nil { // If the packet cannot be read, we quarantine it; otherwise we may retry infinitely. + if err := s.clientStore.Quarantine(id); err != nil { + s.errors.Printf("failed to quarantine packet %d from client store: %s", id, err) + } + s.errors.Printf("failed to retrieve/parse packet %d from client store: %s", id, err) + continue + } + + switch p.Type { + case packets.PUBLISH: + pub := p.Content.(*packets.Publish) + pub.Duplicate = true + case packets.PUBREL: + default: + if err := s.clientStore.Quarantine(id); err != nil { + s.errors.Printf("failed to quarantine packet %d from client store: %s", id, err) + } + s.errors.Printf("unexpected packet type %d (for packet identifier %d) in client store", p.Type, id) + continue + } + + // The messages being retransmitted form part of the "send quota"; however, as per the V5 spec, + // the limit does not apply to messages being resent (the quota can go under 0) + s.inflight.Retransmit() // This will never block (but is needed to block new messages) + + // Any failure from this point should result in loss of connection (so fatal) + if _, err := p.WriteTo(conn); err != nil { + s.debug.Printf("retransmitting of identifier %d failed: %s", id, err) + return fmt.Errorf("failed to retransmit message (%d): %w", id, err) + } + s.debug.Printf("retransmitted message with identifier %d", id) + // On initial connection, the packet needs to be added to our record of client-generated packets. + if _, ok := s.clientPackets[id]; !ok { + s.clientPackets[id] = clientGenerated{ + packetType: p.Type, + responseChan: make(chan packets.ControlPacket, 1), // Nothing will wait on this + } + } + } + return nil +} + +// loadServerSession should be called once, when the first connection is established. +// It loads the server session state from the store. +// The caller must hold a lock on s.mu +func (s *State) loadServerSession(ca *packets.Connack) error { + s.serverPackets = make(map[uint16]byte) + ids, err := s.serverStore.List() + if err != nil { + return fmt.Errorf("failed to load stored server message ids: %w", err) + } + for _, id := range ids { + r, err := s.serverStore.Get(id) + if err != nil { + s.errors.Printf("failed to load packet %d from server store: %s", id, err) + continue + } + // We only need to know the packet type so there is no need to process the entire packet + byte1 := make([]byte, 1) + _, err = r.Read(byte1) + _ = r.Close() + if err != nil { + if err := s.serverStore.Quarantine(id); err != nil { + s.errors.Printf("failed to quarantine packet %d from server store (failed to read): %s", id, err) + } + s.errors.Printf("packet %d from server store could not be read: %s", id, err) + continue // don't want to fail so quarantine and continue is the best we can do + } + packetType := byte1[0] >> 4 + switch packetType { + case packets.PUBLISH: + s.serverPackets[id] = packets.PUBLISH + case packets.PUBREC: + s.serverPackets[id] = packets.PUBREC + default: + if err := s.serverStore.Quarantine(id); err != nil { + s.errors.Printf("failed to quarantine packet %d from server store: %s", id, err) + } + s.errors.Printf("packet %d from server store had unexpected type %d", id, packetType) + continue // don't want to fail so quarantine and continue is the best we can do + } + } + return nil +} + +// ConnectionLost will be called when the connection is lost; either because we received a DISCONNECT packet or due +// to a network error (`nil` will be passed in) +func (s *State) ConnectionLost(dp *packets.Disconnect) error { + s.mu.Lock() + defer s.mu.Unlock() + return s.connectionLost(dp) +} + +// connectionLost process loss of connection +// Caller MUST have locked c.Mu +func (s *State) connectionLost(dp *packets.Disconnect) error { + if s.conn == nil { + return nil // ConnectionLost may be called multiple times (but call ref Disconnect packet should be first) + } + s.connCtxCancel() + s.conn, s.connCtx, s.connCtxCancel = nil, nil, nil + s.connectionLostAt = time.Now() + + if dp != nil && dp.Properties != nil && dp.Properties.SessionExpiryInterval != nil { + s.sessionExpiryInterval = *dp.Properties.SessionExpiryInterval + } + // The Client and Server MUST store the Session State after the Network Connection is closed if the Session Expiry + // Interval is greater than 0 [MQTT-3.1.2-23] + if s.sessionExpiryInterval == 0 { + s.debug.Println("sessionExpiryInterval is 0 and connection lost - cleaning session state") + s.clean() + } + return nil +} + +// AddToSession adds a packet to the session state (including allocation of a Message Identifier). +// If this function returns a nil then: +// - A slot has been allocated if the packet is a PUBLISH (function will block if RECEIVE MAXIMUM messages are inflight) +// - A message Identifier has been added to the passed in packet +// - Publish messages will have been written to the store (and will be automatically transmitted if a new connection +// is established before the message is fully acknowledged - subject to state rules in the MQTTv5 spec) +// - Something will be sent to `resp` when either the message is fully acknowledged or the packet is removed from +// the session (in which case nil will be sent). +// +// If the function returns an error, then any actions taken will be rewound prior to return. +func (s *State) AddToSession(ctx context.Context, packet session.Packet, resp chan<- packets.ControlPacket) error { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + s.mu.Lock() // There may be a delay waiting for semaphore so check for connection before and after + if s.conn == nil { + s.mu.Unlock() + return session.ErrNoConnection + } + // If the connection is lost whilst we are waiting, then in Acquire should terminate. + connCtx := s.connCtx + s.mu.Unlock() + + // If the connection drops while waiting we should abort + go func() { + select { + case <-connCtx.Done(): + cancel() + case <-ctx.Done(): + } + }() + + pt := packet.Type() + + // Ensure only "RECEIVE MAXIMUM" PUBLISH transactions are in flight at any time + if pt == packets.PUBLISH { + if err := s.inflight.Acquire(ctx); err != nil { + if connCtx.Err() != nil { + return session.ErrNoConnection + } + return err // Allow user to confirm if it was their context that led to termination + } + } + + // We have a slot, so acquire a Message ID + // Need to look at what to do if this fails. Should be infrequent as: + // its a lot of messages + // receive max often defaults to a fairly low value + // Maximum recieve max is 65535 which matches the number of slots (so would also need a SUB/UNSUB in flight). + packetID, err := s.allocateNextPacketId(pt, resp) + if err != nil { + if pt == packets.PUBLISH { + if qErr := s.inflight.Release(); qErr != nil { + s.errors.Printf("quota release due to packet id issue: %s", qErr) + } + } + return err + } + packet.SetIdentifier(packetID) + if pt == packets.PUBLISH { + if err = s.clientStore.Put(packetID, pt, packet); err != nil { + s.mu.Lock() + delete(s.clientPackets, packetID) + s.mu.Unlock() + if qErr := s.inflight.Release(); qErr != nil { + s.errors.Printf("quota release due to store issue: %s", qErr) + } + packet.SetIdentifier(0) // ensure the Message identifier is not used + return err + } + } + return nil +} + +// endClientGenerated should be called when a client-generated transaction has been fully acknowledged +// (or if, due to connection loss, it will never be acknowledged). +func (s *State) endClientGenerated(packetID uint16, recv *packets.ControlPacket) error { + s.mu.Lock() + defer s.mu.Unlock() + if cg, ok := s.clientPackets[packetID]; ok { + cg.responseChan <- *recv + delete(s.clientPackets, packetID) + // Outgoing publish messages will be in the store (replaced with PUBREL that is sent) + if cg.packetType == packets.PUBLISH || cg.packetType == packets.PUBREL { + if qErr := s.inflight.Release(); qErr != nil { + s.errors.Printf("quota release due to %s: %s", recv.PacketType(), qErr) + } + if err := s.clientStore.Delete(packetID); err != nil { + s.errors.Printf("failed to remove message %d from store: %s", packetID, err) + } + } + } else { + s.debug.Println("received a response for a message ID we don't know:", recv.PacketID()) + } + return nil // TODO: Should we return errors here (not much that could be done with them) +} + +// Ack is called when the client message handlers have completed (or, if manual acknowledgements are enabled, when +// `client.ACK()` has been called - this may happen some time after the message was received and it is conceivable that +// the connection may have been dropped and reestablished in the interim). +// See issue 160 re issues when the State is called after the connection is dropped. We assume that the +// user will ensure that all ACK's are completed before the State is applied to a new connection (not doing +// this may have unpredictable results). +func (s *State) Ack(pb *packets.Publish) error { + return s.ack(pb) +} + +// ack sends an acknowledgment of the `PUBLISH` (which will have been received from the server) +// `s.mu` must NOT be locked when this is called. +// Note: Adding properties to the response is not currently supported. If this functionality is added, then it is +// important to note that QOS2 PUBREC's may be resent if a duplicate `PUBLISH` is received. +// This function will only return comms related errors (so caller can assume connection has been lost). +func (s *State) ack(pb *packets.Publish) error { + s.mu.Lock() + defer s.mu.Unlock() + var err error + switch pb.QoS { + case 1: + pa := packets.Puback{ + Properties: &packets.Properties{}, + PacketID: pb.PacketID, + } + if s.conn != nil { + s.debug.Println("sending PUBACK") + _, err = pa.WriteTo(s.conn) + if err != nil { + s.errors.Printf("failed to send PUBACK for %d: %s", pb.PacketID, err) + } + } else { + s.debug.Println("PUBACK not send because connection down") + } + // We don't store outbound PUBACK. The server will retransmit the PUBLISH if the connection is reestablished + // before it receives the ACK. Unfortunately, there is no definitive way to determine if + // such messages are duplicates or not (so we are forced to treat them all as if they are new). + case 2: + pr := packets.Pubrec{ + Properties: &packets.Properties{}, + PacketID: pb.PacketID, + } + if s.conn != nil { + s.debug.Printf("sending PUBREC") + _, err = pr.WriteTo(s.conn) + if err != nil { + s.errors.Printf("failed to send PUBREC for %d: %s", pb.PacketID, err) + } + } else { + s.debug.Println("PUBREC not send because connection down") + } + + // We need to record the fact that a PUBREC has been sent so we can detect receipt of a duplicate `PUBLISH` + // (which should not be passed to the client app) + cp := pr.ToControlPacket() + s.serverStore.Put(pb.PacketID, packets.PUBREC, cp) + s.serverPackets[pb.PacketID] = cp.Type + default: + err = errors.New("ack called but publish not QOS 1 or 2") + } + return err +} + +// PacketReceived should be called whenever one of the following is received: +// `PUBLISH` (QOS1+ only), `PUBACK`, `PUBREC`, `PUBREL`, `PUBCOMP`, `SUBACK`, `UNSUBACK` +// It will handle sending any neccessary response or passing the message to the client. +// pubChan will be sent a `PUBLISH` if applicable (and a receiver must be active whilst this function runs) +func (s *State) PacketReceived(recv *packets.ControlPacket, pubChan chan<- *packets.Publish) error { + // Note: we do a type switch rather than using the packet type because it's safer and easier to understand + switch rp := recv.Content.(type) { + // + // Packets in response to client-generated transactions + // + case *packets.Suback: // Not in store, just need to advise client and free Message Identifier + s.debug.Println("received SUBACK packet with id ", rp.PacketID) + s.endClientGenerated(rp.PacketID, recv) + return nil + case *packets.Unsuback: // Not in store, just need to advise client and free Message Identifier + s.debug.Println("received UNSUBACK packet with id ", rp.PacketID) + s.endClientGenerated(rp.PacketID, recv) + return nil + case *packets.Puback: // QOS 1 initial (and final) response + s.debug.Println("received PUBACK packet with id ", rp.PacketID) + s.endClientGenerated(rp.PacketID, recv) + return nil + case *packets.Pubrec: // Initial response to a QOS2 Publish + s.debug.Println("received PUBREC packet with id ", rp.PacketID) + s.mu.Lock() + _, ok := s.clientPackets[rp.PacketID] + s.mu.Unlock() + if !ok { + pl := packets.Pubrel{ // Respond with "Packet Identifier not found" + PacketID: recv.Content.(*packets.Pubrec).PacketID, + ReasonCode: 0x92, + } + s.debug.Println("sending PUBREL (unknown ID) for ", pl.PacketID) + _, err := pl.WriteTo(s.conn) + if err != nil { + s.errors.Printf("failed to send PUBREL for %d: %s", pl.PacketID, err) + } + } else { + if rp.ReasonCode >= 0x80 { + s.endClientGenerated(rp.PacketID, recv) + } else { + pl := packets.Pubrel{ + PacketID: rp.PacketID, + } + s.debug.Println("sending PUBREL for", rp.PacketID) + // Update the store (we should never resend the PUBLISH after receiving a PUBREL) + if err := s.clientStore.Put(rp.PacketID, packets.PUBREL, &pl); err != nil { + s.errors.Printf("failed to write PUBREL to store for %d: %s", rp.PacketID, err) + } + if _, err := pl.WriteTo(s.conn); err != nil { + s.errors.Printf("failed to send PUBREL for %d: %s", rp.PacketID, err) + } + } + } + return nil + case *packets.Pubcomp: // QOS 2 final response + s.debug.Printf("received PUBCOMP packet with id %d", rp.PacketID) + s.endClientGenerated(rp.PacketID, recv) + return nil + // + // Packets relating to server generated PUBLISH + // + case *packets.Publish: + s.debug.Printf("received QoS%d PUBLISH", rp.QoS) + // There is no need to store the packet because it will be resent if not acknowledged before the connection is + // reestablished. + if rp.QoS > 0 { + if rp.PacketID == 0 { // Invalid + return fmt.Errorf("received QOS %d PUBLISH with 0 PacketID", rp.QoS) + } + if rp.QoS == 2 { + s.mu.Lock() + if lastSent, ok := s.serverPackets[rp.PacketID]; ok { + // If we have sent a PUBREC, that means that the client has already seen this message, so we can + // simply resend the acknowledgment. + if lastSent == packets.PUBREC { + // If the message is not flagged as a duplicate, then something is wrong; to avoid message loss, + // we will treat this as a new message (because it appears there is a session mismatch, and we + // have not actually seen this message). + if rp.Duplicate { + // The client has already seen this message meaning we do not want to resend it and, instead + // immediately acknowledge it. + s.mu.Unlock() // mu must be unlocked to call ack + return s.ack(rp) + } + s.errors.Printf("received duplicate PUBLISH (%d) but dup flag not set (will assume this overwrites old publish)", rp.PacketID) + } else { + s.errors.Printf("received PUBLISH (%d) but lastSent type is %d (unexpected!)", lastSent) + } + } + s.mu.Unlock() + } + } + pubChan <- rp // the message will be passed to router (and thus the end user app) + return nil + case *packets.Pubrel: + s.debug.Println("received PUBREL for", recv.PacketID()) + // Auto respond to pubrels unless failure code + pr := recv.Content.(*packets.Pubrel) + if pr.ReasonCode >= 0x80 { + // Received a failure code meaning the server does not know about the message (so all we can do is to remove + // it from our store). + s.errors.Printf("received PUBREL with reason code %d ", pr.ReasonCode) + return nil + } else { + pc := packets.Pubcomp{ + PacketID: pr.PacketID, + } + s.mu.Lock() + defer s.mu.Unlock() + s.debug.Println("sending PUBCOMP for", pr.PacketID) + var err error + if s.conn != nil { + _, err = pc.WriteTo(s.conn) + if err != nil { + s.errors.Printf("failed to send PUBCOMP for %d: %s", pc.PacketID, err) + } + // Note: If connection is down we do not clear store (because the server will resend PUBREL upon reconnect) + delete(s.serverPackets, pr.PacketID) + if sErr := s.serverStore.Delete(pr.PacketID); sErr != nil { + s.errors.Printf("failed to remove message %d from server store: %s", pr.PacketID, sErr) + } + } + return err + } + default: + s.errors.Printf("State.PacketReceived received unexpected packet: %#v ", rp) + return nil + } +} + +// allocateNextPacketId assigns the next available packet ID +// Callers must NOT hold lock on s.mu +func (s *State) allocateNextPacketId(forPacketType byte, resp chan<- packets.ControlPacket) (uint16, error) { + s.mu.Lock() // There may be a delay waiting for semaphore so check for connection before and after + defer s.mu.Unlock() + + cg := clientGenerated{ + packetType: forPacketType, + responseChan: resp, + } + + // Scan from lastMid to end of range. + for i := s.lastMid + 1; i != 0; i++ { + if _, ok := s.clientPackets[i]; ok { + continue + } + s.clientPackets[i] = cg + s.lastMid = i + return i, nil + } + + // Default struct will set s.lastMid=0 meaning we have already scanned all mids + if s.lastMid == 0 { + s.lastMid = 1 + return 0, session.ErrPacketIdentifiersExhausted + } + + // Scan from start of range to lastMid (use +1 to avoid rolling over when s.lastMid = 65535) + for i := uint16(0); i < s.lastMid; i++ { + if _, ok := s.clientPackets[i+1]; ok { + continue + } + s.clientPackets[i+1] = cg + s.lastMid = i + 1 + return i + 1, nil + } + return 0, session.ErrPacketIdentifiersExhausted +} + +// clean deletes any existing stored session information +// does not touch inflight because this is not part of the session state (so is reset separately) +// caller is responsible for locking s.mu +func (s *State) clean() { + s.debug.Println("State.clean() called") + s.serverPackets = make(map[uint16]byte) + s.clientPackets = make(map[uint16]clientGenerated) + + s.serverStore.Reset() + s.clientStore.Reset() +} + +// clean deletes any existing stored session information +// as per section 4.1 in the spec; The Session State in the Client consists of: +// > · QoS 1 and QoS 2 messages which have been sent to the Server, but have not been completely acknowledged. +// > · QoS 2 messages which have been received from the Server, but have not been completely acknowledged. +// This means that we keep PUBLISH, PUBREC and PUBREL packets. PUBACK and PUBCOMP will not be stored (the MID +// will be free once they have been sent). PUBREC is retained so we can check newly received PUBLISH messages (and +// confirm if they have already been processed). +// We only resend PUBLISH (where QoS > 0) and PUBREL packets (as per spec section 4.4) +// caller is responsible for locking s.mu +func (s *State) tidy(trigger *packets.ControlPacket) { + s.debug.Println("State.tidy() called") + for id, p := range s.serverPackets { + switch p { + case packets.PUBREC: + // For inbound messages, we only retain `PUBREC` messages so that we can determine if a PUBLISH received has + // already been processed (the `PUBREL` will be sent when the message has been processed by our user). + // The broker will resend any `PUBLISH` and `PUBREL` messages, so there is no need to retain those. + default: + delete(s.serverPackets, id) + s.serverStore.Delete(id) + } + } + + for id, p := range s.clientPackets { + // We only need to remember `PUBLISH` and `PUBREL` messages (both originating from a PUBLISH) + if p.packetType != packets.PUBLISH { + delete(s.clientPackets, id) + s.clientStore.Delete(id) + p.responseChan <- packets.ControlPacket{} + } + } +} + +// SetDebugLogger takes an instance of the paho Logger interface +// and sets it to be used by the debug log endpoint +func (s *State) SetDebugLogger(l paholog.Logger) { + s.debug = l +} + +// SetErrorLogger takes an instance of the paho Logger interface +// and sets it to be used by the error log endpoint +func (s *State) SetErrorLogger(l paholog.Logger) { + s.errors = l +} + +// AllocateClientPacketIDForTest is intended for use in tests only. It allocates a packet ID in the client session state +// This feels like a hack but makes it easier to test packet identifier exhaustion +func (s *State) AllocateClientPacketIDForTest(packetID uint16, forPacketType byte, resp chan<- packets.ControlPacket) { + s.mu.Lock() + defer s.mu.Unlock() + s.clientPackets[packetID] = clientGenerated{ + packetType: forPacketType, + responseChan: resp, + } +} diff --git a/vendor/github.com/eclipse/paho.golang/paho/session/state/store.go b/vendor/github.com/eclipse/paho.golang/paho/session/state/store.go new file mode 100644 index 000000000..b31a8e50e --- /dev/null +++ b/vendor/github.com/eclipse/paho.golang/paho/session/state/store.go @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package state + +import ( + "io" +) + +// storer must be implemented by session state stores +type storer interface { + Put(packetID uint16, packetType byte, w io.WriterTo) error // Store the packet + Get(packetID uint16) (io.ReadCloser, error) // Retrieve the packet with the specified in ID + Delete(id uint16) error // Removes the message with the specified store ID + + // Quarantine sets the message with the specified store ID into an error state; this may mean deleting it or storing + // it somewhere separate. This is intended for use when a corrupt packet is detected (as this may result in data + // loss, it's beneficial to have access to corrupt packets for analysis). + Quarantine(id uint16) error + + List() ([]uint16, error) // Returns packet IDs in the order they were Put + Reset() error // Clears the store (deleting all messages) +} diff --git a/vendor/github.com/eclipse/paho.golang/paho/store/memory/store.go b/vendor/github.com/eclipse/paho.golang/paho/store/memory/store.go new file mode 100644 index 000000000..03c52febb --- /dev/null +++ b/vendor/github.com/eclipse/paho.golang/paho/store/memory/store.go @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * https://www.eclipse.org/legal/epl-2.0/ + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +package memory + +import ( + "bytes" + "errors" + "fmt" + "io" + "sync" + + "github.com/eclipse/paho.golang/packets" +) + +var ( + ErrNotInStore = errors.New("the requested ID was not found in the store") // Returned when requested ID not found +) + +// memoryPacket is an element in the memory store +type memoryPacket struct { + c int // message count (used for ordering; as this is 32 bit min chance of rolling over seems remote) + p []byte // the packet we are storing +} + +// New creates a Store +func New() *Store { + return &Store{ + data: make(map[uint16]memoryPacket), + } + +} + +// Store is an implementation of a Store that stores the data in memory +type Store struct { + // server store - holds packets where the message ID was generated on the server + sync.Mutex + data map[uint16]memoryPacket // Holds messages initiated by the server (i.e. we will receive the PUBLISH) + c int // sequence counter used to maintain message order +} + +// Put stores the packet +func (m *Store) Put(packetID uint16, packetType byte, w io.WriterTo) error { + m.Lock() + defer m.Unlock() + var buff bytes.Buffer + + _, err := w.WriteTo(&buff) + if err != nil { + panic(err) + } + + m.data[packetID] = memoryPacket{ + c: m.c, + p: buff.Bytes(), + } + m.c++ + return nil +} + +func (m *Store) Get(packetID uint16) (io.ReadCloser, error) { + m.Lock() + defer m.Unlock() + d, ok := m.data[packetID] + if !ok { + return nil, ErrNotInStore + } + return io.NopCloser(bytes.NewReader(d.p)), nil +} + +// Delete removes the message with the specified store ID +func (m *Store) Delete(id uint16) error { + m.Lock() + defer m.Unlock() + if _, ok := m.data[id]; !ok { + // This could be ignored, but reporting it may help reveal other issues + return fmt.Errorf("request to delete packet %d; packet not found", id) + } + delete(m.data, id) + return nil +} + +// Quarantine is called if a corrupt packet is detected. +// There is little we can do other than deleting the packet. +func (m *Store) Quarantine(id uint16) error { + return m.Delete(id) +} + +// List returns packet IDs in the order they were Put +func (m *Store) List() ([]uint16, error) { + m.Lock() + defer m.Unlock() + + ids := make([]uint16, 0, len(m.data)) + seq := make([]int, 0, len(m.data)) + + // Basic insert sort from map ordered by time + // As the map is relatively small, this should be quick enough (data is retrieved infrequently) + itemNo := 0 + var pos int + for i, v := range m.data { + for pos = 0; pos < itemNo; pos++ { + if seq[pos] > v.c { + break + } + } + ids = append(ids[:pos], append([]uint16{i}, ids[pos:]...)...) + seq = append(seq[:pos], append([]int{v.c}, seq[pos:]...)...) + itemNo++ + } + return ids, nil +} + +// Reset clears the store (deleting all messages) +func (m *Store) Reset() error { + m.Lock() + defer m.Unlock() + m.data = make(map[uint16]memoryPacket) + return nil +} + +// String is for debugging purposes; it dumps the content of the store in a readable format +func (m *Store) String() string { + var b bytes.Buffer + for i, c := range m.data { + p, err := packets.ReadPacket(bytes.NewReader(c.p)) + if err != nil { + b.WriteString(fmt.Sprintf("packet %d could not be read: %s\n", i, err)) + continue + } + + b.WriteString(fmt.Sprintf("packet %d is %s\n", i, p)) + } + return b.String() +} diff --git a/vendor/golang.org/x/net/http2/frame.go b/vendor/golang.org/x/net/http2/frame.go index c1f6b90dc..e2b298d85 100644 --- a/vendor/golang.org/x/net/http2/frame.go +++ b/vendor/golang.org/x/net/http2/frame.go @@ -1510,13 +1510,12 @@ func (mh *MetaHeadersFrame) checkPseudos() error { } func (fr *Framer) maxHeaderStringLen() int { - v := fr.maxHeaderListSize() - if uint32(int(v)) == v { - return int(v) + v := int(fr.maxHeaderListSize()) + if v < 0 { + // If maxHeaderListSize overflows an int, use no limit (0). + return 0 } - // They had a crazy big number for MaxHeaderBytes anyway, - // so give them unlimited header lengths: - return 0 + return v } // readMetaFrame returns 0 or more CONTINUATION frames from fr and diff --git a/vendor/golang.org/x/sync/semaphore/semaphore.go b/vendor/golang.org/x/sync/semaphore/semaphore.go deleted file mode 100644 index 30f632c57..000000000 --- a/vendor/golang.org/x/sync/semaphore/semaphore.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package semaphore provides a weighted semaphore implementation. -package semaphore // import "golang.org/x/sync/semaphore" - -import ( - "container/list" - "context" - "sync" -) - -type waiter struct { - n int64 - ready chan<- struct{} // Closed when semaphore acquired. -} - -// NewWeighted creates a new weighted semaphore with the given -// maximum combined weight for concurrent access. -func NewWeighted(n int64) *Weighted { - w := &Weighted{size: n} - return w -} - -// Weighted provides a way to bound concurrent access to a resource. -// The callers can request access with a given weight. -type Weighted struct { - size int64 - cur int64 - mu sync.Mutex - waiters list.List -} - -// Acquire acquires the semaphore with a weight of n, blocking until resources -// are available or ctx is done. On success, returns nil. On failure, returns -// ctx.Err() and leaves the semaphore unchanged. -// -// If ctx is already done, Acquire may still succeed without blocking. -func (s *Weighted) Acquire(ctx context.Context, n int64) error { - s.mu.Lock() - if s.size-s.cur >= n && s.waiters.Len() == 0 { - s.cur += n - s.mu.Unlock() - return nil - } - - if n > s.size { - // Don't make other Acquire calls block on one that's doomed to fail. - s.mu.Unlock() - <-ctx.Done() - return ctx.Err() - } - - ready := make(chan struct{}) - w := waiter{n: n, ready: ready} - elem := s.waiters.PushBack(w) - s.mu.Unlock() - - select { - case <-ctx.Done(): - err := ctx.Err() - s.mu.Lock() - select { - case <-ready: - // Acquired the semaphore after we were canceled. Rather than trying to - // fix up the queue, just pretend we didn't notice the cancelation. - err = nil - default: - isFront := s.waiters.Front() == elem - s.waiters.Remove(elem) - // If we're at the front and there're extra tokens left, notify other waiters. - if isFront && s.size > s.cur { - s.notifyWaiters() - } - } - s.mu.Unlock() - return err - - case <-ready: - return nil - } -} - -// TryAcquire acquires the semaphore with a weight of n without blocking. -// On success, returns true. On failure, returns false and leaves the semaphore unchanged. -func (s *Weighted) TryAcquire(n int64) bool { - s.mu.Lock() - success := s.size-s.cur >= n && s.waiters.Len() == 0 - if success { - s.cur += n - } - s.mu.Unlock() - return success -} - -// Release releases the semaphore with a weight of n. -func (s *Weighted) Release(n int64) { - s.mu.Lock() - s.cur -= n - if s.cur < 0 { - s.mu.Unlock() - panic("semaphore: released more than held") - } - s.notifyWaiters() - s.mu.Unlock() -} - -func (s *Weighted) notifyWaiters() { - for { - next := s.waiters.Front() - if next == nil { - break // No more waiters blocked. - } - - w := next.Value.(waiter) - if s.size-s.cur < w.n { - // Not enough tokens for the next waiter. We could keep going (to try to - // find a waiter with a smaller request), but under load that could cause - // starvation for large requests; instead, we leave all remaining waiters - // blocked. - // - // Consider a semaphore used as a read-write lock, with N tokens, N - // readers, and one writer. Each reader can Acquire(1) to obtain a read - // lock. The writer can Acquire(N) to obtain a write lock, excluding all - // of the readers. If we allow the readers to jump ahead in the queue, - // the writer will starve — there is always one token available for every - // reader. - break - } - - s.cur += w.n - s.waiters.Remove(next) - close(w.ready) - } -} diff --git a/vendor/golang.org/x/sys/unix/mkerrors.sh b/vendor/golang.org/x/sys/unix/mkerrors.sh index 6202638ba..fdcaa974d 100644 --- a/vendor/golang.org/x/sys/unix/mkerrors.sh +++ b/vendor/golang.org/x/sys/unix/mkerrors.sh @@ -248,6 +248,7 @@ struct ltchars { #include #include #include +#include #include #include #include @@ -283,10 +284,6 @@ struct ltchars { #include #endif -#ifndef MSG_FASTOPEN -#define MSG_FASTOPEN 0x20000000 -#endif - #ifndef PTRACE_GETREGS #define PTRACE_GETREGS 0xc #endif @@ -295,14 +292,6 @@ struct ltchars { #define PTRACE_SETREGS 0xd #endif -#ifndef SOL_NETLINK -#define SOL_NETLINK 270 -#endif - -#ifndef SOL_SMC -#define SOL_SMC 286 -#endif - #ifdef SOL_BLUETOOTH // SPARC includes this in /usr/include/sparc64-linux-gnu/bits/socket.h // but it is already in bluetooth_linux.go @@ -319,10 +308,23 @@ struct ltchars { #undef TIPC_WAIT_FOREVER #define TIPC_WAIT_FOREVER 0xffffffff -// Copied from linux/l2tp.h -// Including linux/l2tp.h here causes conflicts between linux/in.h -// and netinet/in.h included via net/route.h above. -#define IPPROTO_L2TP 115 +// Copied from linux/netfilter/nf_nat.h +// Including linux/netfilter/nf_nat.h here causes conflicts between linux/in.h +// and netinet/in.h. +#define NF_NAT_RANGE_MAP_IPS (1 << 0) +#define NF_NAT_RANGE_PROTO_SPECIFIED (1 << 1) +#define NF_NAT_RANGE_PROTO_RANDOM (1 << 2) +#define NF_NAT_RANGE_PERSISTENT (1 << 3) +#define NF_NAT_RANGE_PROTO_RANDOM_FULLY (1 << 4) +#define NF_NAT_RANGE_PROTO_OFFSET (1 << 5) +#define NF_NAT_RANGE_NETMAP (1 << 6) +#define NF_NAT_RANGE_PROTO_RANDOM_ALL \ + (NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PROTO_RANDOM_FULLY) +#define NF_NAT_RANGE_MASK \ + (NF_NAT_RANGE_MAP_IPS | NF_NAT_RANGE_PROTO_SPECIFIED | \ + NF_NAT_RANGE_PROTO_RANDOM | NF_NAT_RANGE_PERSISTENT | \ + NF_NAT_RANGE_PROTO_RANDOM_FULLY | NF_NAT_RANGE_PROTO_OFFSET | \ + NF_NAT_RANGE_NETMAP) // Copied from linux/hid.h. // Keep in sync with the size of the referenced fields. @@ -582,7 +584,7 @@ ccflags="$@" $2 ~ /^KEY_(SPEC|REQKEY_DEFL)_/ || $2 ~ /^KEYCTL_/ || $2 ~ /^PERF_/ || - $2 ~ /^SECCOMP_MODE_/ || + $2 ~ /^SECCOMP_/ || $2 ~ /^SEEK_/ || $2 ~ /^SCHED_/ || $2 ~ /^SPLICE_/ || @@ -603,6 +605,9 @@ ccflags="$@" $2 ~ /^FSOPT_/ || $2 ~ /^WDIO[CFS]_/ || $2 ~ /^NFN/ || + $2 !~ /^NFT_META_IIFTYPE/ && + $2 ~ /^NFT_/ || + $2 ~ /^NF_NAT_/ || $2 ~ /^XDP_/ || $2 ~ /^RWF_/ || $2 ~ /^(HDIO|WIN|SMART)_/ || diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go index c73cfe2f1..36bf8399f 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go @@ -1785,6 +1785,8 @@ const ( LANDLOCK_ACCESS_FS_REMOVE_FILE = 0x20 LANDLOCK_ACCESS_FS_TRUNCATE = 0x4000 LANDLOCK_ACCESS_FS_WRITE_FILE = 0x2 + LANDLOCK_ACCESS_NET_BIND_TCP = 0x1 + LANDLOCK_ACCESS_NET_CONNECT_TCP = 0x2 LANDLOCK_CREATE_RULESET_VERSION = 0x1 LINUX_REBOOT_CMD_CAD_OFF = 0x0 LINUX_REBOOT_CMD_CAD_ON = 0x89abcdef @@ -2127,6 +2129,60 @@ const ( NFNL_SUBSYS_QUEUE = 0x3 NFNL_SUBSYS_ULOG = 0x4 NFS_SUPER_MAGIC = 0x6969 + NFT_CHAIN_FLAGS = 0x7 + NFT_CHAIN_MAXNAMELEN = 0x100 + NFT_CT_MAX = 0x17 + NFT_DATA_RESERVED_MASK = 0xffffff00 + NFT_DATA_VALUE_MAXLEN = 0x40 + NFT_EXTHDR_OP_MAX = 0x4 + NFT_FIB_RESULT_MAX = 0x3 + NFT_INNER_MASK = 0xf + NFT_LOGLEVEL_MAX = 0x8 + NFT_NAME_MAXLEN = 0x100 + NFT_NG_MAX = 0x1 + NFT_OBJECT_CONNLIMIT = 0x5 + NFT_OBJECT_COUNTER = 0x1 + NFT_OBJECT_CT_EXPECT = 0x9 + NFT_OBJECT_CT_HELPER = 0x3 + NFT_OBJECT_CT_TIMEOUT = 0x7 + NFT_OBJECT_LIMIT = 0x4 + NFT_OBJECT_MAX = 0xa + NFT_OBJECT_QUOTA = 0x2 + NFT_OBJECT_SECMARK = 0x8 + NFT_OBJECT_SYNPROXY = 0xa + NFT_OBJECT_TUNNEL = 0x6 + NFT_OBJECT_UNSPEC = 0x0 + NFT_OBJ_MAXNAMELEN = 0x100 + NFT_OSF_MAXGENRELEN = 0x10 + NFT_QUEUE_FLAG_BYPASS = 0x1 + NFT_QUEUE_FLAG_CPU_FANOUT = 0x2 + NFT_QUEUE_FLAG_MASK = 0x3 + NFT_REG32_COUNT = 0x10 + NFT_REG32_SIZE = 0x4 + NFT_REG_MAX = 0x4 + NFT_REG_SIZE = 0x10 + NFT_REJECT_ICMPX_MAX = 0x3 + NFT_RT_MAX = 0x4 + NFT_SECMARK_CTX_MAXLEN = 0x100 + NFT_SET_MAXNAMELEN = 0x100 + NFT_SOCKET_MAX = 0x3 + NFT_TABLE_F_MASK = 0x3 + NFT_TABLE_MAXNAMELEN = 0x100 + NFT_TRACETYPE_MAX = 0x3 + NFT_TUNNEL_F_MASK = 0x7 + NFT_TUNNEL_MAX = 0x1 + NFT_TUNNEL_MODE_MAX = 0x2 + NFT_USERDATA_MAXLEN = 0x100 + NFT_XFRM_KEY_MAX = 0x6 + NF_NAT_RANGE_MAP_IPS = 0x1 + NF_NAT_RANGE_MASK = 0x7f + NF_NAT_RANGE_NETMAP = 0x40 + NF_NAT_RANGE_PERSISTENT = 0x8 + NF_NAT_RANGE_PROTO_OFFSET = 0x20 + NF_NAT_RANGE_PROTO_RANDOM = 0x4 + NF_NAT_RANGE_PROTO_RANDOM_ALL = 0x14 + NF_NAT_RANGE_PROTO_RANDOM_FULLY = 0x10 + NF_NAT_RANGE_PROTO_SPECIFIED = 0x2 NILFS_SUPER_MAGIC = 0x3434 NL0 = 0x0 NL1 = 0x100 @@ -2411,6 +2467,7 @@ const ( PR_MCE_KILL_GET = 0x22 PR_MCE_KILL_LATE = 0x0 PR_MCE_KILL_SET = 0x1 + PR_MDWE_NO_INHERIT = 0x2 PR_MDWE_REFUSE_EXEC_GAIN = 0x1 PR_MPX_DISABLE_MANAGEMENT = 0x2c PR_MPX_ENABLE_MANAGEMENT = 0x2b @@ -2615,8 +2672,9 @@ const ( RTAX_FEATURES = 0xc RTAX_FEATURE_ALLFRAG = 0x8 RTAX_FEATURE_ECN = 0x1 - RTAX_FEATURE_MASK = 0xf + RTAX_FEATURE_MASK = 0x1f RTAX_FEATURE_SACK = 0x2 + RTAX_FEATURE_TCP_USEC_TS = 0x10 RTAX_FEATURE_TIMESTAMP = 0x4 RTAX_HOPLIMIT = 0xa RTAX_INITCWND = 0xb @@ -2859,9 +2917,38 @@ const ( SCM_RIGHTS = 0x1 SCM_TIMESTAMP = 0x1d SC_LOG_FLUSH = 0x100000 + SECCOMP_ADDFD_FLAG_SEND = 0x2 + SECCOMP_ADDFD_FLAG_SETFD = 0x1 + SECCOMP_FILTER_FLAG_LOG = 0x2 + SECCOMP_FILTER_FLAG_NEW_LISTENER = 0x8 + SECCOMP_FILTER_FLAG_SPEC_ALLOW = 0x4 + SECCOMP_FILTER_FLAG_TSYNC = 0x1 + SECCOMP_FILTER_FLAG_TSYNC_ESRCH = 0x10 + SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV = 0x20 + SECCOMP_GET_ACTION_AVAIL = 0x2 + SECCOMP_GET_NOTIF_SIZES = 0x3 + SECCOMP_IOCTL_NOTIF_RECV = 0xc0502100 + SECCOMP_IOCTL_NOTIF_SEND = 0xc0182101 + SECCOMP_IOC_MAGIC = '!' SECCOMP_MODE_DISABLED = 0x0 SECCOMP_MODE_FILTER = 0x2 SECCOMP_MODE_STRICT = 0x1 + SECCOMP_RET_ACTION = 0x7fff0000 + SECCOMP_RET_ACTION_FULL = 0xffff0000 + SECCOMP_RET_ALLOW = 0x7fff0000 + SECCOMP_RET_DATA = 0xffff + SECCOMP_RET_ERRNO = 0x50000 + SECCOMP_RET_KILL = 0x0 + SECCOMP_RET_KILL_PROCESS = 0x80000000 + SECCOMP_RET_KILL_THREAD = 0x0 + SECCOMP_RET_LOG = 0x7ffc0000 + SECCOMP_RET_TRACE = 0x7ff00000 + SECCOMP_RET_TRAP = 0x30000 + SECCOMP_RET_USER_NOTIF = 0x7fc00000 + SECCOMP_SET_MODE_FILTER = 0x1 + SECCOMP_SET_MODE_STRICT = 0x0 + SECCOMP_USER_NOTIF_FD_SYNC_WAKE_UP = 0x1 + SECCOMP_USER_NOTIF_FLAG_CONTINUE = 0x1 SECRETMEM_MAGIC = 0x5345434d SECURITYFS_MAGIC = 0x73636673 SEEK_CUR = 0x1 @@ -3021,6 +3108,7 @@ const ( SOL_TIPC = 0x10f SOL_TLS = 0x11a SOL_UDP = 0x11 + SOL_VSOCK = 0x11f SOL_X25 = 0x106 SOL_XDP = 0x11b SOMAXCONN = 0x1000 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go index 4920821cf..42ff8c3c1 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go @@ -281,6 +281,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x40082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x40082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go index a0c1e4112..dca436004 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go @@ -282,6 +282,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x40082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x40082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go index c63985560..5cca668ac 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go @@ -288,6 +288,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x40082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x40082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go index 47cc62e25..d8cae6d15 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go @@ -278,6 +278,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x40082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x40082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go index 27ac4a09e..28e39afdc 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go @@ -275,6 +275,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x40082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x40082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go index 54694642a..cd66e92cb 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go @@ -281,6 +281,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x80 SIOCATMARK = 0x40047307 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go index 3adb81d75..c1595eba7 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go @@ -281,6 +281,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x80 SIOCATMARK = 0x40047307 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go index 2dfe98f0d..ee9456b0d 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go @@ -281,6 +281,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x80 SIOCATMARK = 0x40047307 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go index f5398f84f..8cfca81e1 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go @@ -281,6 +281,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x80 SIOCATMARK = 0x40047307 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go index c54f152d6..60b0deb3a 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go @@ -336,6 +336,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go index 76057dc72..f90aa7281 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go @@ -340,6 +340,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go index e0c3725e2..ba9e01503 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go @@ -340,6 +340,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go index 18f2813ed..07cdfd6e9 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go @@ -272,6 +272,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x40082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x40082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go index 11619d4ec..2f1dd214a 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go @@ -344,6 +344,9 @@ const ( SCM_TIMESTAMPNS = 0x23 SCM_TXTIME = 0x3d SCM_WIFI_STATUS = 0x29 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x40082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x40082104 SFD_CLOEXEC = 0x80000 SFD_NONBLOCK = 0x800 SIOCATMARK = 0x8905 diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go index 396d994da..f40519d90 100644 --- a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go @@ -335,6 +335,9 @@ const ( SCM_TIMESTAMPNS = 0x21 SCM_TXTIME = 0x3f SCM_WIFI_STATUS = 0x25 + SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103 + SECCOMP_IOCTL_NOTIF_ID_VALID = 0x80082102 + SECCOMP_IOCTL_NOTIF_SET_FLAGS = 0x80082104 SFD_CLOEXEC = 0x400000 SFD_NONBLOCK = 0x4000 SF_FP = 0x38 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go index a1d061597..9dc42410b 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go @@ -2297,5 +2297,3 @@ func unveil(path *byte, flags *byte) (err error) { var libc_unveil_trampoline_addr uintptr //go:cgo_import_dynamic libc_unveil unveil "libc.so" - - diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go index 5b2a74097..0d3a0751c 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go @@ -2297,5 +2297,3 @@ func unveil(path *byte, flags *byte) (err error) { var libc_unveil_trampoline_addr uintptr //go:cgo_import_dynamic libc_unveil unveil "libc.so" - - diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go index f6eda1344..c39f7776d 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go @@ -2297,5 +2297,3 @@ func unveil(path *byte, flags *byte) (err error) { var libc_unveil_trampoline_addr uintptr //go:cgo_import_dynamic libc_unveil unveil "libc.so" - - diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go index 55df20ae9..57571d072 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go @@ -2297,5 +2297,3 @@ func unveil(path *byte, flags *byte) (err error) { var libc_unveil_trampoline_addr uintptr //go:cgo_import_dynamic libc_unveil unveil "libc.so" - - diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go index 8c1155cbc..e62963e67 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go @@ -2297,5 +2297,3 @@ func unveil(path *byte, flags *byte) (err error) { var libc_unveil_trampoline_addr uintptr //go:cgo_import_dynamic libc_unveil unveil "libc.so" - - diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go index 7cc80c58d..00831354c 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go @@ -2297,5 +2297,3 @@ func unveil(path *byte, flags *byte) (err error) { var libc_unveil_trampoline_addr uintptr //go:cgo_import_dynamic libc_unveil unveil "libc.so" - - diff --git a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go index 0688737f4..79029ed58 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go @@ -2297,5 +2297,3 @@ func unveil(path *byte, flags *byte) (err error) { var libc_unveil_trampoline_addr uintptr //go:cgo_import_dynamic libc_unveil unveil "libc.so" - - diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go index fcf3ecbdd..0cc3ce496 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go @@ -448,4 +448,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go index f56dc2504..856d92d69 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go @@ -371,4 +371,7 @@ const ( SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go index 974bf2467..8d467094c 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go @@ -412,4 +412,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go index 39a2739e2..edc173244 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go @@ -315,4 +315,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go index cf9c9d77e..445eba206 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go @@ -309,4 +309,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go index 10b7362ef..adba01bca 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go @@ -432,4 +432,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 4450 SYS_CACHESTAT = 4451 SYS_FCHMODAT2 = 4452 + SYS_MAP_SHADOW_STACK = 4453 + SYS_FUTEX_WAKE = 4454 + SYS_FUTEX_WAIT = 4455 + SYS_FUTEX_REQUEUE = 4456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go index cd4d8b4fd..014c4e9c7 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go @@ -362,4 +362,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 5450 SYS_CACHESTAT = 5451 SYS_FCHMODAT2 = 5452 + SYS_MAP_SHADOW_STACK = 5453 + SYS_FUTEX_WAKE = 5454 + SYS_FUTEX_WAIT = 5455 + SYS_FUTEX_REQUEUE = 5456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go index 2c0efca81..ccc97d74d 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go @@ -362,4 +362,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 5450 SYS_CACHESTAT = 5451 SYS_FCHMODAT2 = 5452 + SYS_MAP_SHADOW_STACK = 5453 + SYS_FUTEX_WAKE = 5454 + SYS_FUTEX_WAIT = 5455 + SYS_FUTEX_REQUEUE = 5456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go index a72e31d39..ec2b64a95 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go @@ -432,4 +432,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 4450 SYS_CACHESTAT = 4451 SYS_FCHMODAT2 = 4452 + SYS_MAP_SHADOW_STACK = 4453 + SYS_FUTEX_WAKE = 4454 + SYS_FUTEX_WAIT = 4455 + SYS_FUTEX_REQUEUE = 4456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go index c7d1e3747..21a839e33 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go @@ -439,4 +439,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go index f4d4838c8..c11121ec3 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go @@ -411,4 +411,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go index b64f0e591..909b631fc 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go @@ -411,4 +411,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go index 95711195a..e49bed16e 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go @@ -316,4 +316,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go index f94e943bc..66017d2d3 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go @@ -377,4 +377,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go index ba0c2bc51..47bab18dc 100644 --- a/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go +++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go @@ -390,4 +390,8 @@ const ( SYS_SET_MEMPOLICY_HOME_NODE = 450 SYS_CACHESTAT = 451 SYS_FCHMODAT2 = 452 + SYS_MAP_SHADOW_STACK = 453 + SYS_FUTEX_WAKE = 454 + SYS_FUTEX_WAIT = 455 + SYS_FUTEX_REQUEUE = 456 ) diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index bbf8399ff..dc0c955ee 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -174,7 +174,8 @@ type FscryptPolicyV2 struct { Contents_encryption_mode uint8 Filenames_encryption_mode uint8 Flags uint8 - _ [4]uint8 + Log2_data_unit_size uint8 + _ [3]uint8 Master_key_identifier [16]uint8 } @@ -455,60 +456,63 @@ type Ucred struct { } type TCPInfo struct { - State uint8 - Ca_state uint8 - Retransmits uint8 - Probes uint8 - Backoff uint8 - Options uint8 - Rto uint32 - Ato uint32 - Snd_mss uint32 - Rcv_mss uint32 - Unacked uint32 - Sacked uint32 - Lost uint32 - Retrans uint32 - Fackets uint32 - Last_data_sent uint32 - Last_ack_sent uint32 - Last_data_recv uint32 - Last_ack_recv uint32 - Pmtu uint32 - Rcv_ssthresh uint32 - Rtt uint32 - Rttvar uint32 - Snd_ssthresh uint32 - Snd_cwnd uint32 - Advmss uint32 - Reordering uint32 - Rcv_rtt uint32 - Rcv_space uint32 - Total_retrans uint32 - Pacing_rate uint64 - Max_pacing_rate uint64 - Bytes_acked uint64 - Bytes_received uint64 - Segs_out uint32 - Segs_in uint32 - Notsent_bytes uint32 - Min_rtt uint32 - Data_segs_in uint32 - Data_segs_out uint32 - Delivery_rate uint64 - Busy_time uint64 - Rwnd_limited uint64 - Sndbuf_limited uint64 - Delivered uint32 - Delivered_ce uint32 - Bytes_sent uint64 - Bytes_retrans uint64 - Dsack_dups uint32 - Reord_seen uint32 - Rcv_ooopack uint32 - Snd_wnd uint32 - Rcv_wnd uint32 - Rehash uint32 + State uint8 + Ca_state uint8 + Retransmits uint8 + Probes uint8 + Backoff uint8 + Options uint8 + Rto uint32 + Ato uint32 + Snd_mss uint32 + Rcv_mss uint32 + Unacked uint32 + Sacked uint32 + Lost uint32 + Retrans uint32 + Fackets uint32 + Last_data_sent uint32 + Last_ack_sent uint32 + Last_data_recv uint32 + Last_ack_recv uint32 + Pmtu uint32 + Rcv_ssthresh uint32 + Rtt uint32 + Rttvar uint32 + Snd_ssthresh uint32 + Snd_cwnd uint32 + Advmss uint32 + Reordering uint32 + Rcv_rtt uint32 + Rcv_space uint32 + Total_retrans uint32 + Pacing_rate uint64 + Max_pacing_rate uint64 + Bytes_acked uint64 + Bytes_received uint64 + Segs_out uint32 + Segs_in uint32 + Notsent_bytes uint32 + Min_rtt uint32 + Data_segs_in uint32 + Data_segs_out uint32 + Delivery_rate uint64 + Busy_time uint64 + Rwnd_limited uint64 + Sndbuf_limited uint64 + Delivered uint32 + Delivered_ce uint32 + Bytes_sent uint64 + Bytes_retrans uint64 + Dsack_dups uint32 + Reord_seen uint32 + Rcv_ooopack uint32 + Snd_wnd uint32 + Rcv_wnd uint32 + Rehash uint32 + Total_rto uint16 + Total_rto_recoveries uint16 + Total_rto_time uint32 } type CanFilter struct { @@ -551,7 +555,7 @@ const ( SizeofIPv6MTUInfo = 0x20 SizeofICMPv6Filter = 0x20 SizeofUcred = 0xc - SizeofTCPInfo = 0xf0 + SizeofTCPInfo = 0xf8 SizeofCanFilter = 0x8 SizeofTCPRepairOpt = 0x8 ) @@ -3399,7 +3403,7 @@ const ( DEVLINK_PORT_FN_ATTR_STATE = 0x2 DEVLINK_PORT_FN_ATTR_OPSTATE = 0x3 DEVLINK_PORT_FN_ATTR_CAPS = 0x4 - DEVLINK_PORT_FUNCTION_ATTR_MAX = 0x4 + DEVLINK_PORT_FUNCTION_ATTR_MAX = 0x5 ) type FsverityDigest struct { @@ -4183,7 +4187,8 @@ const ( ) type LandlockRulesetAttr struct { - Access_fs uint64 + Access_fs uint64 + Access_net uint64 } type LandlockPathBeneathAttr struct { @@ -5134,7 +5139,7 @@ const ( NL80211_FREQUENCY_ATTR_GO_CONCURRENT = 0xf NL80211_FREQUENCY_ATTR_INDOOR_ONLY = 0xe NL80211_FREQUENCY_ATTR_IR_CONCURRENT = 0xf - NL80211_FREQUENCY_ATTR_MAX = 0x1b + NL80211_FREQUENCY_ATTR_MAX = 0x1c NL80211_FREQUENCY_ATTR_MAX_TX_POWER = 0x6 NL80211_FREQUENCY_ATTR_NO_10MHZ = 0x11 NL80211_FREQUENCY_ATTR_NO_160MHZ = 0xc @@ -5547,7 +5552,7 @@ const ( NL80211_REGDOM_TYPE_CUSTOM_WORLD = 0x2 NL80211_REGDOM_TYPE_INTERSECTION = 0x3 NL80211_REGDOM_TYPE_WORLD = 0x1 - NL80211_REG_RULE_ATTR_MAX = 0x7 + NL80211_REG_RULE_ATTR_MAX = 0x8 NL80211_REKEY_DATA_AKM = 0x4 NL80211_REKEY_DATA_KCK = 0x2 NL80211_REKEY_DATA_KEK = 0x1 diff --git a/vendor/golang.org/x/sys/windows/env_windows.go b/vendor/golang.org/x/sys/windows/env_windows.go index b8ad19250..d4577a423 100644 --- a/vendor/golang.org/x/sys/windows/env_windows.go +++ b/vendor/golang.org/x/sys/windows/env_windows.go @@ -37,14 +37,17 @@ func (token Token) Environ(inheritExisting bool) (env []string, err error) { return nil, err } defer DestroyEnvironmentBlock(block) - blockp := unsafe.Pointer(block) - for { - entry := UTF16PtrToString((*uint16)(blockp)) - if len(entry) == 0 { - break + size := unsafe.Sizeof(*block) + for *block != 0 { + // find NUL terminator + end := unsafe.Pointer(block) + for *(*uint16)(end) != 0 { + end = unsafe.Add(end, size) } - env = append(env, entry) - blockp = unsafe.Add(blockp, 2*(len(entry)+1)) + + entry := unsafe.Slice(block, (uintptr(end)-uintptr(unsafe.Pointer(block)))/size) + env = append(env, UTF16ToString(entry)) + block = (*uint16)(unsafe.Add(end, size)) } return env, nil } diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go index 47dc57967..6395a031d 100644 --- a/vendor/golang.org/x/sys/windows/syscall_windows.go +++ b/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -125,8 +125,7 @@ func UTF16PtrToString(p *uint16) string { for ptr := unsafe.Pointer(p); *(*uint16)(ptr) != 0; n++ { ptr = unsafe.Pointer(uintptr(ptr) + unsafe.Sizeof(*p)) } - - return string(utf16.Decode(unsafe.Slice(p, n))) + return UTF16ToString(unsafe.Slice(p, n)) } func Getpagesize() int { return 4096 } @@ -194,6 +193,7 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys GetComputerName(buf *uint16, n *uint32) (err error) = GetComputerNameW //sys GetComputerNameEx(nametype uint32, buf *uint16, n *uint32) (err error) = GetComputerNameExW //sys SetEndOfFile(handle Handle) (err error) +//sys SetFileValidData(handle Handle, validDataLength int64) (err error) //sys GetSystemTimeAsFileTime(time *Filetime) //sys GetSystemTimePreciseAsFileTime(time *Filetime) //sys GetTimeZoneInformation(tzi *Timezoneinformation) (rc uint32, err error) [failretval==0xffffffff] diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go index 146a1f019..e8791c82c 100644 --- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go +++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go @@ -342,6 +342,7 @@ var ( procSetDefaultDllDirectories = modkernel32.NewProc("SetDefaultDllDirectories") procSetDllDirectoryW = modkernel32.NewProc("SetDllDirectoryW") procSetEndOfFile = modkernel32.NewProc("SetEndOfFile") + procSetFileValidData = modkernel32.NewProc("SetFileValidData") procSetEnvironmentVariableW = modkernel32.NewProc("SetEnvironmentVariableW") procSetErrorMode = modkernel32.NewProc("SetErrorMode") procSetEvent = modkernel32.NewProc("SetEvent") @@ -2988,6 +2989,14 @@ func SetEndOfFile(handle Handle) (err error) { return } +func SetFileValidData(handle Handle, validDataLength int64) (err error) { + r1, _, e1 := syscall.Syscall(procSetFileValidData.Addr(), 2, uintptr(handle), uintptr(validDataLength), 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + func SetEnvironmentVariable(name *uint16, value *uint16) (err error) { r1, _, e1 := syscall.Syscall(procSetEnvironmentVariableW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), 0) if r1 == 0 { diff --git a/vendor/modules.txt b/vendor/modules.txt index fab8adfed..42be530ff 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -127,11 +127,17 @@ github.com/eapache/go-xerial-snappy # github.com/eapache/queue v1.1.0 ## explicit github.com/eapache/queue -# github.com/eclipse/paho.golang v0.12.0 -## explicit; go 1.20 +# github.com/eclipse/paho.golang v0.21.0 +## explicit; go 1.21 github.com/eclipse/paho.golang/autopaho +github.com/eclipse/paho.golang/autopaho/queue +github.com/eclipse/paho.golang/autopaho/queue/memory github.com/eclipse/paho.golang/packets github.com/eclipse/paho.golang/paho +github.com/eclipse/paho.golang/paho/log +github.com/eclipse/paho.golang/paho/session +github.com/eclipse/paho.golang/paho/session/state +github.com/eclipse/paho.golang/paho/store/memory # github.com/elastic/elastic-transport-go/v8 v8.4.0 ## explicit; go 1.20 github.com/elastic/elastic-transport-go/v8/elastictransport @@ -1067,13 +1073,13 @@ go.opentelemetry.io/otel/trace/embedded # go.uber.org/atomic v1.11.0 ## explicit; go 1.18 go.uber.org/atomic -# golang.org/x/crypto v0.17.0 +# golang.org/x/crypto v0.19.0 ## explicit; go 1.18 golang.org/x/crypto/md4 golang.org/x/crypto/ocsp golang.org/x/crypto/pbkdf2 golang.org/x/crypto/scrypt -# golang.org/x/net v0.18.0 +# golang.org/x/net v0.21.0 ## explicit; go 1.18 golang.org/x/net/http/httpguts golang.org/x/net/http2 @@ -1086,9 +1092,8 @@ golang.org/x/net/trace # golang.org/x/sync v0.5.0 ## explicit; go 1.18 golang.org/x/sync/errgroup -golang.org/x/sync/semaphore golang.org/x/sync/singleflight -# golang.org/x/sys v0.15.0 +# golang.org/x/sys v0.17.0 ## explicit; go 1.18 golang.org/x/sys/unix golang.org/x/sys/windows