├── .gitignore ├── AUTHORS ├── service ├── doc.go ├── paho_test.go ├── examples_test.go ├── misc.go ├── buffer_test.go ├── helpers_test.go ├── sendrecv_test.go ├── client.go ├── sendrecv.go ├── process.go ├── service.go └── server.go ├── auth ├── mock.go ├── mock_test.go └── authenticator.go ├── sessions ├── memprovider_test.go ├── ackqueue_test.go ├── memprovider.go ├── sessions.go ├── session_test.go ├── session.go └── ackqueue.go ├── CONTRIBUTING.md ├── examples ├── surgemq │ ├── README.md │ ├── websocket.go │ └── surgemq.go └── pingmq │ ├── README.md │ └── pingmq.go ├── benchmark ├── client_test.go ├── mesh_test.go └── fan_test.go ├── topics ├── topics.go ├── memtopics.go └── memtopics_test.go ├── doc.go ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | .idea 25 | *.iml 26 | 27 | *.swp 28 | *.un~ 29 | 30 | .DS_Store 31 | 32 | *.pprof 33 | *.prof 34 | *.test 35 | *.out 36 | 37 | *.sublime-project 38 | *.sublime-workspace 39 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of SurgeMQ authors for copyright purposes. 2 | 3 | # If you are submitting a patch, please add your name or the name of the 4 | # organization which holds the copyright to this list in alphabetical order. 5 | 6 | # Names should be added to this file as 7 | # Name 8 | # The email address is not required for organizations. 9 | # Please keep the list sorted. 10 | 11 | 12 | # Individual Persons 13 | 14 | Jian Zhen 15 | KADOTA Kyohei 16 | Martin Rauscher 17 | 18 | # Organizations 19 | -------------------------------------------------------------------------------- /service/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package service provides the MQTT Server and Client services in a library form. 16 | // See Server and Client examples below for more detailed usage. 17 | package service 18 | -------------------------------------------------------------------------------- /auth/mock.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package auth 16 | 17 | type mockAuthenticator bool 18 | 19 | var _ Authenticator = (*mockAuthenticator)(nil) 20 | 21 | var ( 22 | mockSuccessAuthenticator mockAuthenticator = true 23 | mockFailureAuthenticator mockAuthenticator = false 24 | ) 25 | 26 | func init() { 27 | Register("mockSuccess", mockSuccessAuthenticator) 28 | Register("mockFailure", mockFailureAuthenticator) 29 | } 30 | 31 | func (this mockAuthenticator) Authenticate(id string, cred interface{}) error { 32 | if this == true { 33 | return nil 34 | } 35 | 36 | return ErrAuthFailure 37 | } 38 | -------------------------------------------------------------------------------- /sessions/memprovider_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sessions 16 | 17 | /* 18 | func TestMemStore(t *testing.T) { 19 | st := NewMemStore() 20 | 21 | for i := 0; i < 10; i++ { 22 | st.New(fmt.Sprintf("%d", i)) 23 | } 24 | 25 | require.Equal(t, 10, st.Len(), "Incorrect length.") 26 | 27 | for i := 0; i < 10; i++ { 28 | sess, err := st.Get(fmt.Sprintf("%d", i)) 29 | require.NoError(t, err, "Unable to Get() session #%d", i) 30 | 31 | require.Equal(t, fmt.Sprintf("%d", i), sess.ClientId, "Incorrect ID") 32 | } 33 | 34 | for i := 0; i < 5; i++ { 35 | st.Del(fmt.Sprintf("%d", i)) 36 | } 37 | 38 | require.Equal(t, 5, st.Len(), "Incorrect length.") 39 | } 40 | */ 41 | -------------------------------------------------------------------------------- /sessions/ackqueue_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sessions 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | "github.com/surgemq/message" 22 | ) 23 | 24 | func TestAckQueueOutOfOrder(t *testing.T) { 25 | q := newAckqueue(5) 26 | require.Equal(t, 8, q.cap()) 27 | 28 | for i := 0; i < 12; i++ { 29 | msg := newPublishMessage(uint16(i), 1) 30 | q.Wait(msg, nil) 31 | } 32 | 33 | require.Equal(t, 12, q.len()) 34 | 35 | ack1 := message.NewPubackMessage() 36 | ack1.SetPacketId(1) 37 | q.Ack(ack1) 38 | 39 | acked := q.Acked() 40 | 41 | require.Equal(t, 0, len(acked)) 42 | 43 | ack0 := message.NewPubackMessage() 44 | ack0.SetPacketId(0) 45 | q.Ack(ack0) 46 | 47 | acked = q.Acked() 48 | 49 | require.Equal(t, 2, len(acked)) 50 | } 51 | -------------------------------------------------------------------------------- /auth/mock_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package auth 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | ) 22 | 23 | func TestMockSuccessAuthenticator(t *testing.T) { 24 | require.NoError(t, mockSuccessAuthenticator.Authenticate("", "")) 25 | 26 | require.NoError(t, providers["mockSuccess"].Authenticate("", "")) 27 | 28 | mgr, err := NewManager("mockSuccess") 29 | require.NoError(t, err) 30 | require.NoError(t, mgr.Authenticate("", "")) 31 | } 32 | 33 | func TestMockFailureAuthenticator(t *testing.T) { 34 | require.Error(t, mockFailureAuthenticator.Authenticate("", "")) 35 | 36 | require.Error(t, providers["mockFailure"].Authenticate("", "")) 37 | 38 | mgr, err := NewManager("mockFailure") 39 | require.NoError(t, err) 40 | require.Error(t, mgr.Authenticate("", "")) 41 | } 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | ## Reporting Issues 4 | 5 | Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/surgemq/message/issues?state=open) or was [recently closed](https://github.com/surgemq/message/issues?direction=desc&page=1&sort=updated&state=closed). 6 | 7 | Please provide the following minimum information: 8 | * Your SurgeMQ version (or git SHA) 9 | * Your Go version (run `go version` in your console) 10 | * A detailed issue description 11 | * Error Log if present 12 | * If possible, a short example 13 | 14 | 15 | ## Contributing Code 16 | 17 | By contributing to this project, you share your code under the Apache License, Version 2.0, as specified in the LICENSE file. 18 | Don't forget to add yourself to the AUTHORS file. 19 | 20 | ### Pull Requests Checklist 21 | 22 | Please check the following points before submitting your pull request: 23 | - [x] Code compiles correctly 24 | - [x] Created tests, if possible 25 | - [x] All tests pass 26 | - [x] Extended the README / documentation, if necessary 27 | - [x] Added yourself to the AUTHORS file 28 | 29 | ### Code Review 30 | 31 | Everyone is invited to review and comment on pull requests. 32 | If it looks fine to you, comment with "LGTM" (Looks good to me). 33 | 34 | If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes. 35 | 36 | Before merging the Pull Request, at least one [team member](https://github.com/orgs/surgemq/people) must have commented with "LGTM". 37 | -------------------------------------------------------------------------------- /auth/authenticator.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package auth 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | ) 21 | 22 | var ( 23 | ErrAuthFailure = errors.New("auth: Authentication failure") 24 | ErrAuthProviderNotFound = errors.New("auth: Authentication provider not found") 25 | 26 | providers = make(map[string]Authenticator) 27 | ) 28 | 29 | type Authenticator interface { 30 | Authenticate(id string, cred interface{}) error 31 | } 32 | 33 | func Register(name string, provider Authenticator) { 34 | if provider == nil { 35 | panic("auth: Register provide is nil") 36 | } 37 | 38 | if _, dup := providers[name]; dup { 39 | panic("auth: Register called twice for provider " + name) 40 | } 41 | 42 | providers[name] = provider 43 | } 44 | 45 | func Unregister(name string) { 46 | delete(providers, name) 47 | } 48 | 49 | type Manager struct { 50 | p Authenticator 51 | } 52 | 53 | func NewManager(providerName string) (*Manager, error) { 54 | p, ok := providers[providerName] 55 | if !ok { 56 | return nil, fmt.Errorf("session: unknown provider %q", providerName) 57 | } 58 | 59 | return &Manager{p: p}, nil 60 | } 61 | 62 | func (this *Manager) Authenticate(id string, cred interface{}) error { 63 | return this.p.Authenticate(id, cred) 64 | } 65 | -------------------------------------------------------------------------------- /sessions/memprovider.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sessions 16 | 17 | import ( 18 | "fmt" 19 | "sync" 20 | ) 21 | 22 | var _ SessionsProvider = (*memProvider)(nil) 23 | 24 | func init() { 25 | Register("mem", NewMemProvider()) 26 | } 27 | 28 | type memProvider struct { 29 | st map[string]*Session 30 | mu sync.RWMutex 31 | } 32 | 33 | func NewMemProvider() *memProvider { 34 | return &memProvider{ 35 | st: make(map[string]*Session), 36 | } 37 | } 38 | 39 | func (this *memProvider) New(id string) (*Session, error) { 40 | this.mu.Lock() 41 | defer this.mu.Unlock() 42 | 43 | this.st[id] = &Session{id: id} 44 | return this.st[id], nil 45 | } 46 | 47 | func (this *memProvider) Get(id string) (*Session, error) { 48 | this.mu.RLock() 49 | defer this.mu.RUnlock() 50 | 51 | sess, ok := this.st[id] 52 | if !ok { 53 | return nil, fmt.Errorf("store/Get: No session found for key %s", id) 54 | } 55 | 56 | return sess, nil 57 | } 58 | 59 | func (this *memProvider) Del(id string) { 60 | this.mu.Lock() 61 | defer this.mu.Unlock() 62 | delete(this.st, id) 63 | } 64 | 65 | func (this *memProvider) Save(id string) error { 66 | return nil 67 | } 68 | 69 | func (this *memProvider) Count() int { 70 | return len(this.st) 71 | } 72 | 73 | func (this *memProvider) Close() error { 74 | this.st = make(map[string]*Session) 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /examples/surgemq/README.md: -------------------------------------------------------------------------------- 1 | # SurgeMQ Standalone Server 2 | 3 | Standalone SurgeMQ server, creates listeners for plaintext MQTT, Websocket and Secure Websocket. Without any options, surgemq listens on port 1883 for plaintext MQTT. 4 | 5 | ## Build 6 | 7 | * `go get github.com/surgemq/surgemq` 8 | * `cd $GOPATH/src/github.com/surgemq/surgemq/examples/surgemq/` 9 | * `go build` 10 | 11 | ## Usage 12 | 13 | ### Command line options 14 | 15 | - `-help` : Shows complete list of supported options 16 | - `-auth string`: Authenticator Type (default "mockSuccess") 17 | - `-keepalive int`: Keepalive (sec) (default 300) 18 | - `-sessions string`: Session Provider Type (default "mem") 19 | - `-topics string`: Topics Provider Type (default "mem") 20 | - `-wsaddr string`: HTTP websocket listener address, (eg. ":8080") (default none) 21 | - `-wssaddr string`: HTTPS websocket listener address, (eg. ":8443") (default none) 22 | - `-wsscertpath string`: HTTPS listener public key file, (eg. "certificate.pem") (default none) 23 | - `-wsskeypath string`: HTTPS listener private key file, (eg. "key.pem") (default none) 24 | 25 | ## Websocket listener 26 | 27 | 1. In addition to listening for MQTT traffic on port 1883, the standalone server can be configured to listen for websocket over HTTP or HTTPS. 28 | 2. `surgemq -wsaddr :8080` will start the server to listen for Websocket on port 8080 29 | 30 | ## Self-signed Websocket listener 31 | 32 | The following steps will setup the server to use a self-signed certificate. 33 | 34 | 1. Generate a self-signed TLS certificate: 35 | `openssl genrsa -out key.pem 2048; openssl req -new -key key.pem -out csr.pem; openssl req -x509 -days 365 -key key.pem -in csr.pem -out certificate.pem` 36 | 37 | 2. Start standalone server: `surgemq.exe -wssaddr :8443 -wsscertpath certificate.pem -wsskeypath key.pem` 38 | 39 | 3. For self-signed certificate, add a security exception to the browser (eg: http://www.poweradmin.com/help/sslhints/firefox.aspx) 40 | 41 | ## Testing 42 | 43 | Websocket support has been tested with the HiveMQ websocket client at http://www.hivemq.com/demos/websocket-client/ 44 | -------------------------------------------------------------------------------- /service/paho_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 IBM Corp. 3 | * 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * which accompanies this distribution, and is available at 7 | * http://www.eclipse.org/legal/epl-v10.html 8 | * 9 | * Contributors: 10 | * Seth Hoenig 11 | * Allan Stockdill-Mander 12 | * Mike Robertson 13 | */ 14 | 15 | // This is originally from 16 | // git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git/samples/simple.go 17 | // I turned it into a test that I can run from `go test` 18 | package service 19 | 20 | import ( 21 | "fmt" 22 | "net/url" 23 | "sync" 24 | "testing" 25 | "time" 26 | 27 | MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" 28 | "github.com/stretchr/testify/require" 29 | ) 30 | 31 | var f MQTT.MessageHandler = func(client *MQTT.Client, msg MQTT.Message) { 32 | fmt.Printf("TOPIC: %s\n", msg.Topic()) 33 | fmt.Printf("MSG: %s\n", msg.Payload()) 34 | } 35 | 36 | func TestPahoGoClient(t *testing.T) { 37 | var wg sync.WaitGroup 38 | 39 | ready1 := make(chan struct{}) 40 | ready2 := make(chan struct{}) 41 | 42 | uri := "tcp://127.0.0.1:1883" 43 | u, err := url.Parse(uri) 44 | require.NoError(t, err, "Error parsing URL") 45 | 46 | // Start listener 47 | wg.Add(1) 48 | go startServiceN(t, u, &wg, ready1, ready2, 1) 49 | 50 | <-ready1 51 | 52 | opts := MQTT.NewClientOptions().AddBroker("tcp://localhost:1883").SetClientID("trivial") 53 | opts.SetDefaultPublishHandler(f) 54 | 55 | c := MQTT.NewClient(opts) 56 | token := c.Connect() 57 | token.Wait() 58 | require.NoError(t, token.Error()) 59 | 60 | filters := map[string]byte{"/go-mqtt/sample": 0} 61 | token = c.SubscribeMultiple(filters, nil) 62 | token.WaitTimeout(time.Millisecond * 100) 63 | token.Wait() 64 | require.NoError(t, token.Error()) 65 | 66 | for i := 0; i < 100; i++ { 67 | text := fmt.Sprintf("this is msg #%d!", i) 68 | token = c.Publish("/go-mqtt/sample", 1, false, []byte(text)) 69 | token.WaitTimeout(time.Millisecond * 100) 70 | token.Wait() 71 | require.NoError(t, token.Error()) 72 | } 73 | 74 | time.Sleep(3 * time.Second) 75 | 76 | token = c.Unsubscribe("/go-mqtt/sample") 77 | token.Wait() 78 | token.WaitTimeout(time.Millisecond * 100) 79 | require.NoError(t, token.Error()) 80 | 81 | c.Disconnect(250) 82 | 83 | close(ready2) 84 | 85 | wg.Wait() 86 | } 87 | -------------------------------------------------------------------------------- /examples/pingmq/README.md: -------------------------------------------------------------------------------- 1 | pingmq 2 | ====== 3 | 4 | [pingmq](https://github.com/surge/surgemq/tree/master/cmd/pingmq) is developed to demonstrate the different use cases one can use SurgeMQ. In this simplified use case, a network administrator can setup server uptime monitoring system by periodically sending ICMP ECHO_REQUEST to all the IPs in their network, and send the results to SurgeMQ. 5 | 6 | Then multiple clients can subscribe to results based on their different needs. For example, a client maybe only interested in any failed ping attempts, as that would indicate a host might be down. After a certain number of failures the client may then raise some type of flag to indicate host down. 7 | 8 | There are three benefits of using SurgeMQ for this use case. 9 | 10 | * First, with all the different monitoring tools out there that wants to know if hosts are up or down, they can all now subscribe to a single source of information. They no longer need to write their own uptime tools. 11 | * Second, assuming there are 5 monitoring tools on the network that wants to ping each and every host, the small packets are going to congest the network. The company can save 80% on their uptime monitoring bandwidth by having a single tool that pings the hosts, and have the rest subscribe to the results. 12 | * Third/last, the company can enhance their security posture by placing tighter restrictions on their firewalls if there's only a single host that can send ICMP ECHO_REQUESTS to all other hosts. 13 | 14 | The following commands will run pingmq as a server, pinging the 8.8.8.0/28 CIDR block, and publishing the results to /ping/success/{ip} and /ping/failure/{ip} topics every 30 seconds. `sudo` is needed because we are using RAW sockets and that requires root privilege. 15 | 16 | ``` 17 | $ go build 18 | $ sudo ./pingmq server -p 8.8.8.0/28 -i 30 19 | ``` 20 | 21 | The following command will run pingmq as a client, subscribing to /ping/failure/+ topic and receiving any failed ping attempts. 22 | 23 | ``` 24 | $ ./pingmq client -t /ping/failure/+ 25 | 8.8.8.6: Request timed out for seq 1 26 | ``` 27 | 28 | The following command will run pingmq as a client, subscribing to /ping/failure/+ topic and receiving any failed ping attempts. 29 | 30 | ``` 31 | $ ./pingmq client -t /ping/success/+ 32 | 8 bytes from 8.8.8.8: seq=1 ttl=56 tos=32 time=21.753711ms 33 | ``` 34 | 35 | One can also subscribe to a specific IP by using the following command. 36 | 37 | ``` 38 | $ ./pingmq client -t /ping/+/8.8.8.8 39 | 8 bytes from 8.8.8.8: seq=1 ttl=56 tos=32 time=21.753711ms 40 | ``` -------------------------------------------------------------------------------- /examples/surgemq/websocket.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/surge/glog" 5 | "golang.org/x/net/websocket" 6 | "io" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | ) 11 | 12 | func DefaultListenAndServeWebsocket() error { 13 | if err := AddWebsocketHandler("/mqtt", "test.mosquitto.org:1883"); err != nil { 14 | return err 15 | } 16 | return ListenAndServeWebsocket(":1234") 17 | } 18 | 19 | func AddWebsocketHandler(urlPattern string, uri string) error { 20 | glog.Debugf("AddWebsocketHandler urlPattern=%s, uri=%s", urlPattern, uri) 21 | u, err := url.Parse(uri) 22 | if err != nil { 23 | glog.Errorf("surgemq/main: %v", err) 24 | return err 25 | } 26 | 27 | h := func(ws *websocket.Conn) { 28 | WebsocketTcpProxy(ws, u.Scheme, u.Host) 29 | } 30 | http.Handle(urlPattern, websocket.Handler(h)) 31 | return nil 32 | } 33 | 34 | /* start a listener that proxies websocket <-> tcp */ 35 | func ListenAndServeWebsocket(addr string) error { 36 | return http.ListenAndServe(addr, nil) 37 | } 38 | 39 | /* starts an HTTPS listener */ 40 | func ListenAndServeWebsocketSecure(addr string, cert string, key string) error { 41 | return http.ListenAndServeTLS(addr, cert, key, nil) 42 | } 43 | 44 | /* copy from websocket to writer, this copies the binary frames as is */ 45 | func io_copy_ws(src *websocket.Conn, dst io.Writer) (int, error) { 46 | var buffer []byte 47 | count := 0 48 | for { 49 | err := websocket.Message.Receive(src, &buffer) 50 | if err != nil { 51 | return count, err 52 | } 53 | n := len(buffer) 54 | count += n 55 | i, err := dst.Write(buffer) 56 | if err != nil || i < 1 { 57 | return count, err 58 | } 59 | } 60 | return count, nil 61 | } 62 | 63 | /* copy from reader to websocket, this copies the binary frames as is */ 64 | func io_ws_copy(src io.Reader, dst *websocket.Conn) (int, error) { 65 | buffer := make([]byte, 2048) 66 | count := 0 67 | for { 68 | n, err := src.Read(buffer) 69 | if err != nil || n < 1 { 70 | return count, err 71 | } 72 | count += n 73 | err = websocket.Message.Send(dst, buffer[0:n]) 74 | if err != nil { 75 | return count, err 76 | } 77 | } 78 | return count, nil 79 | } 80 | 81 | /* handler that proxies websocket <-> unix domain socket */ 82 | func WebsocketTcpProxy(ws *websocket.Conn, nettype string, host string) error { 83 | client, err := net.Dial(nettype, host) 84 | if err != nil { 85 | return err 86 | } 87 | defer client.Close() 88 | defer ws.Close() 89 | chDone := make(chan bool) 90 | 91 | go func() { 92 | io_ws_copy(client, ws) 93 | chDone <- true 94 | }() 95 | go func() { 96 | io_copy_ws(ws, client) 97 | chDone <- true 98 | }() 99 | <-chDone 100 | return nil 101 | } 102 | -------------------------------------------------------------------------------- /service/examples_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import "github.com/surgemq/message" 18 | 19 | func ExampleServer() { 20 | // Create a new server 21 | svr := &Server{ 22 | KeepAlive: 300, // seconds 23 | ConnectTimeout: 2, // seconds 24 | SessionsProvider: "mem", // keeps sessions in memory 25 | Authenticator: "mockSuccess", // always succeed 26 | TopicsProvider: "mem", // keeps topic subscriptions in memory 27 | } 28 | 29 | // Listen and serve connections at localhost:1883 30 | svr.ListenAndServe("tcp://:1883") 31 | } 32 | 33 | func ExampleClient() { 34 | // Instantiates a new Client 35 | c := &Client{} 36 | 37 | // Creates a new MQTT CONNECT message and sets the proper parameters 38 | msg := message.NewConnectMessage() 39 | msg.SetWillQos(1) 40 | msg.SetVersion(4) 41 | msg.SetCleanSession(true) 42 | msg.SetClientId([]byte("surgemq")) 43 | msg.SetKeepAlive(10) 44 | msg.SetWillTopic([]byte("will")) 45 | msg.SetWillMessage([]byte("send me home")) 46 | msg.SetUsername([]byte("surgemq")) 47 | msg.SetPassword([]byte("verysecret")) 48 | 49 | // Connects to the remote server at 127.0.0.1 port 1883 50 | c.Connect("tcp://127.0.0.1:1883", msg) 51 | 52 | // Creates a new SUBSCRIBE message to subscribe to topic "abc" 53 | submsg := message.NewSubscribeMessage() 54 | submsg.AddTopic([]byte("abc"), 0) 55 | 56 | // Subscribes to the topic by sending the message. The first nil in the function 57 | // call is a OnCompleteFunc that should handle the SUBACK message from the server. 58 | // Nil means we are ignoring the SUBACK messages. The second nil should be a 59 | // OnPublishFunc that handles any messages send to the client because of this 60 | // subscription. Nil means we are ignoring any PUBLISH messages for this topic. 61 | c.Subscribe(submsg, nil, nil) 62 | 63 | // Creates a new PUBLISH message with the appropriate contents for publishing 64 | pubmsg := message.NewPublishMessage() 65 | pubmsg.SetTopic([]byte("abc")) 66 | pubmsg.SetPayload(make([]byte, 1024)) 67 | pubmsg.SetQoS(0) 68 | 69 | // Publishes to the server by sending the message 70 | c.Publish(pubmsg, nil) 71 | 72 | // Disconnects from the server 73 | c.Disconnect() 74 | } 75 | -------------------------------------------------------------------------------- /sessions/sessions.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sessions 16 | 17 | import ( 18 | "crypto/rand" 19 | "encoding/base64" 20 | "errors" 21 | "fmt" 22 | "io" 23 | ) 24 | 25 | var ( 26 | ErrSessionsProviderNotFound = errors.New("Session: Session provider not found") 27 | ErrKeyNotAvailable = errors.New("Session: not item found for key.") 28 | 29 | providers = make(map[string]SessionsProvider) 30 | ) 31 | 32 | type SessionsProvider interface { 33 | New(id string) (*Session, error) 34 | Get(id string) (*Session, error) 35 | Del(id string) 36 | Save(id string) error 37 | Count() int 38 | Close() error 39 | } 40 | 41 | // Register makes a session provider available by the provided name. 42 | // If a Register is called twice with the same name or if the driver is nil, 43 | // it panics. 44 | func Register(name string, provider SessionsProvider) { 45 | if provider == nil { 46 | panic("session: Register provide is nil") 47 | } 48 | 49 | if _, dup := providers[name]; dup { 50 | panic("session: Register called twice for provider " + name) 51 | } 52 | 53 | providers[name] = provider 54 | } 55 | 56 | func Unregister(name string) { 57 | delete(providers, name) 58 | } 59 | 60 | type Manager struct { 61 | p SessionsProvider 62 | } 63 | 64 | func NewManager(providerName string) (*Manager, error) { 65 | p, ok := providers[providerName] 66 | if !ok { 67 | return nil, fmt.Errorf("session: unknown provider %q", providerName) 68 | } 69 | 70 | return &Manager{p: p}, nil 71 | } 72 | 73 | func (this *Manager) New(id string) (*Session, error) { 74 | if id == "" { 75 | id = this.sessionId() 76 | } 77 | return this.p.New(id) 78 | } 79 | 80 | func (this *Manager) Get(id string) (*Session, error) { 81 | return this.p.Get(id) 82 | } 83 | 84 | func (this *Manager) Del(id string) { 85 | this.p.Del(id) 86 | } 87 | 88 | func (this *Manager) Save(id string) error { 89 | return this.p.Save(id) 90 | } 91 | 92 | func (this *Manager) Count() int { 93 | return this.p.Count() 94 | } 95 | 96 | func (this *Manager) Close() error { 97 | return this.p.Close() 98 | } 99 | 100 | func (manager *Manager) sessionId() string { 101 | b := make([]byte, 15) 102 | if _, err := io.ReadFull(rand.Reader, b); err != nil { 103 | return "" 104 | } 105 | return base64.URLEncoding.EncodeToString(b) 106 | } 107 | -------------------------------------------------------------------------------- /sessions/session_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sessions 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | "github.com/surgemq/message" 22 | ) 23 | 24 | func TestSessionInit(t *testing.T) { 25 | sess := &Session{} 26 | cmsg := newConnectMessage() 27 | 28 | err := sess.Init(cmsg) 29 | require.NoError(t, err) 30 | require.Equal(t, len(sess.cbuf), cmsg.Len()) 31 | require.Equal(t, cmsg.WillQos(), sess.Cmsg.WillQos()) 32 | require.Equal(t, cmsg.Version(), sess.Cmsg.Version()) 33 | require.Equal(t, cmsg.CleanSession(), sess.Cmsg.CleanSession()) 34 | require.Equal(t, cmsg.ClientId(), sess.Cmsg.ClientId()) 35 | require.Equal(t, cmsg.KeepAlive(), sess.Cmsg.KeepAlive()) 36 | require.Equal(t, cmsg.WillTopic(), sess.Cmsg.WillTopic()) 37 | require.Equal(t, cmsg.WillMessage(), sess.Cmsg.WillMessage()) 38 | require.Equal(t, cmsg.Username(), sess.Cmsg.Username()) 39 | require.Equal(t, cmsg.Password(), sess.Cmsg.Password()) 40 | require.Equal(t, []byte("will"), sess.Will.Topic()) 41 | require.Equal(t, cmsg.WillQos(), sess.Will.QoS()) 42 | 43 | sess.AddTopic("test", 1) 44 | require.Equal(t, 1, len(sess.topics)) 45 | 46 | topics, qoss, err := sess.Topics() 47 | require.NoError(t, err) 48 | require.Equal(t, 1, len(topics)) 49 | require.Equal(t, 1, len(qoss)) 50 | require.Equal(t, "test", topics[0]) 51 | require.Equal(t, 1, int(qoss[0])) 52 | 53 | sess.RemoveTopic("test") 54 | require.Equal(t, 0, len(sess.topics)) 55 | } 56 | 57 | func TestSessionPublishAckqueue(t *testing.T) { 58 | sess := &Session{} 59 | cmsg := newConnectMessage() 60 | err := sess.Init(cmsg) 61 | require.NoError(t, err) 62 | 63 | for i := 0; i < 12; i++ { 64 | msg := newPublishMessage(uint16(i), 1) 65 | sess.Pub1ack.Wait(msg, nil) 66 | } 67 | 68 | require.Equal(t, 12, sess.Pub1ack.len()) 69 | 70 | ack1 := message.NewPubackMessage() 71 | ack1.SetPacketId(1) 72 | sess.Pub1ack.Ack(ack1) 73 | 74 | acked := sess.Pub1ack.Acked() 75 | require.Equal(t, 0, len(acked)) 76 | 77 | ack0 := message.NewPubackMessage() 78 | ack0.SetPacketId(0) 79 | sess.Pub1ack.Ack(ack0) 80 | 81 | acked = sess.Pub1ack.Acked() 82 | require.Equal(t, 2, len(acked)) 83 | } 84 | 85 | func newConnectMessage() *message.ConnectMessage { 86 | msg := message.NewConnectMessage() 87 | msg.SetWillQos(1) 88 | msg.SetVersion(4) 89 | msg.SetCleanSession(true) 90 | msg.SetClientId([]byte("surgemq")) 91 | msg.SetKeepAlive(10) 92 | msg.SetWillTopic([]byte("will")) 93 | msg.SetWillMessage([]byte("send me home")) 94 | msg.SetUsername([]byte("surgemq")) 95 | msg.SetPassword([]byte("verysecret")) 96 | 97 | return msg 98 | } 99 | 100 | func newPublishMessage(pktid uint16, qos byte) *message.PublishMessage { 101 | msg := message.NewPublishMessage() 102 | msg.SetPacketId(pktid) 103 | msg.SetTopic([]byte("abc")) 104 | msg.SetPayload([]byte("abc")) 105 | msg.SetQoS(qos) 106 | 107 | return msg 108 | } 109 | -------------------------------------------------------------------------------- /benchmark/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package benchmark 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "strconv" 21 | "sync" 22 | "testing" 23 | 24 | "github.com/stretchr/testify/require" 25 | "github.com/surgemq/message" 26 | "github.com/surgemq/surgemq/service" 27 | ) 28 | 29 | var ( 30 | messages int = 100000 31 | publishers int = 1 32 | subscribers int = 1 33 | size int = 1024 34 | topic []byte = []byte("test") 35 | qos byte = 0 36 | nap int = 10 37 | host string = "127.0.0.1" 38 | port int = 1883 39 | user string = "surgemq" 40 | pass string = "surgemq" 41 | version int = 4 42 | 43 | subdone, rcvdone, sentdone int64 44 | 45 | done, done2 chan struct{} 46 | 47 | totalSent, 48 | totalSentTime, 49 | totalRcvd, 50 | totalRcvdTime, 51 | sentSince, 52 | rcvdSince int64 53 | 54 | statMu sync.Mutex 55 | ) 56 | 57 | func init() { 58 | flag.StringVar(&host, "host", host, "host to server") 59 | flag.IntVar(&port, "port", port, "port to server") 60 | flag.StringVar(&user, "user", user, "pass to server") 61 | flag.StringVar(&pass, "pass", pass, "user to server") 62 | flag.IntVar(&messages, "messages", messages, "number of messages to send") 63 | flag.IntVar(&publishers, "publishers", publishers, "number of publishers to start (in FullMesh, only this is used)") 64 | flag.IntVar(&subscribers, "subscribers", subscribers, "number of subscribers to start (in FullMesh, this is NOT used") 65 | flag.IntVar(&size, "size", size, "size of message payload to send, minimum 10 bytes") 66 | flag.IntVar(&version, "version", version, "mqtt version (4 is 3.1.1)") 67 | flag.Parse() 68 | } 69 | 70 | func runClientTest(t testing.TB, cid int, wg *sync.WaitGroup, f func(*service.Client)) { 71 | defer wg.Done() 72 | 73 | if size < 10 { 74 | size = 10 75 | } 76 | 77 | uri := "tcp://" + host + ":" + strconv.Itoa(port) 78 | 79 | c := connectToServer(t, uri, cid) 80 | if c == nil { 81 | return 82 | } 83 | 84 | if f != nil { 85 | f(c) 86 | } 87 | 88 | c.Disconnect() 89 | } 90 | 91 | func connectToServer(t testing.TB, uri string, cid int) *service.Client { 92 | c := &service.Client{} 93 | 94 | msg := newConnectMessage(cid) 95 | 96 | err := c.Connect(uri, msg) 97 | require.NoError(t, err) 98 | 99 | return c 100 | } 101 | 102 | func newSubscribeMessage(topic string, qos byte) *message.SubscribeMessage { 103 | msg := message.NewSubscribeMessage() 104 | msg.SetPacketId(1) 105 | msg.AddTopic([]byte(topic), qos) 106 | 107 | return msg 108 | } 109 | 110 | func newPublishMessageLarge(qos byte) *message.PublishMessage { 111 | msg := message.NewPublishMessage() 112 | msg.SetTopic([]byte("test")) 113 | msg.SetPayload(make([]byte, 1024)) 114 | msg.SetQoS(qos) 115 | 116 | return msg 117 | } 118 | 119 | func newConnectMessage(cid int) *message.ConnectMessage { 120 | msg := message.NewConnectMessage() 121 | msg.SetWillQos(1) 122 | msg.SetVersion(byte(version)) 123 | msg.SetCleanSession(true) 124 | msg.SetClientId([]byte(fmt.Sprintf("surgemq%d", cid))) 125 | msg.SetKeepAlive(10) 126 | msg.SetWillTopic([]byte("will")) 127 | msg.SetWillMessage([]byte("send me home")) 128 | msg.SetUsername([]byte(user)) 129 | msg.SetPassword([]byte(pass)) 130 | 131 | return msg 132 | } 133 | -------------------------------------------------------------------------------- /service/misc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "encoding/binary" 19 | "fmt" 20 | "io" 21 | "net" 22 | 23 | "github.com/surgemq/message" 24 | ) 25 | 26 | func getConnectMessage(conn io.Closer) (*message.ConnectMessage, error) { 27 | buf, err := getMessageBuffer(conn) 28 | if err != nil { 29 | //glog.Debugf("Receive error: %v", err) 30 | return nil, err 31 | } 32 | 33 | msg := message.NewConnectMessage() 34 | 35 | _, err = msg.Decode(buf) 36 | //glog.Debugf("Received: %s", msg) 37 | return msg, err 38 | } 39 | 40 | func getConnackMessage(conn io.Closer) (*message.ConnackMessage, error) { 41 | buf, err := getMessageBuffer(conn) 42 | if err != nil { 43 | //glog.Debugf("Receive error: %v", err) 44 | return nil, err 45 | } 46 | 47 | msg := message.NewConnackMessage() 48 | 49 | _, err = msg.Decode(buf) 50 | //glog.Debugf("Received: %s", msg) 51 | return msg, err 52 | } 53 | 54 | func writeMessage(conn io.Closer, msg message.Message) error { 55 | buf := make([]byte, msg.Len()) 56 | _, err := msg.Encode(buf) 57 | if err != nil { 58 | //glog.Debugf("Write error: %v", err) 59 | return err 60 | } 61 | //glog.Debugf("Writing: %s", msg) 62 | 63 | return writeMessageBuffer(conn, buf) 64 | } 65 | 66 | func getMessageBuffer(c io.Closer) ([]byte, error) { 67 | if c == nil { 68 | return nil, ErrInvalidConnectionType 69 | } 70 | 71 | conn, ok := c.(net.Conn) 72 | if !ok { 73 | return nil, ErrInvalidConnectionType 74 | } 75 | 76 | var ( 77 | // the message buffer 78 | buf []byte 79 | 80 | // tmp buffer to read a single byte 81 | b []byte = make([]byte, 1) 82 | 83 | // total bytes read 84 | l int = 0 85 | ) 86 | 87 | // Let's read enough bytes to get the message header (msg type, remaining length) 88 | for { 89 | // If we have read 5 bytes and still not done, then there's a problem. 90 | if l > 5 { 91 | return nil, fmt.Errorf("connect/getMessage: 4th byte of remaining length has continuation bit set") 92 | } 93 | 94 | n, err := conn.Read(b[0:]) 95 | if err != nil { 96 | //glog.Debugf("Read error: %v", err) 97 | return nil, err 98 | } 99 | 100 | // Technically i don't think we will ever get here 101 | if n == 0 { 102 | continue 103 | } 104 | 105 | buf = append(buf, b...) 106 | l += n 107 | 108 | // Check the remlen byte (1+) to see if the continuation bit is set. If so, 109 | // increment cnt and continue reading. Otherwise break. 110 | if l > 1 && b[0] < 0x80 { 111 | break 112 | } 113 | } 114 | 115 | // Get the remaining length of the message 116 | remlen, _ := binary.Uvarint(buf[1:]) 117 | buf = append(buf, make([]byte, remlen)...) 118 | 119 | for l < len(buf) { 120 | n, err := conn.Read(buf[l:]) 121 | if err != nil { 122 | return nil, err 123 | } 124 | l += n 125 | } 126 | 127 | return buf, nil 128 | } 129 | 130 | func writeMessageBuffer(c io.Closer, b []byte) error { 131 | if c == nil { 132 | return ErrInvalidConnectionType 133 | } 134 | 135 | conn, ok := c.(net.Conn) 136 | if !ok { 137 | return ErrInvalidConnectionType 138 | } 139 | 140 | _, err := conn.Write(b) 141 | return err 142 | } 143 | 144 | // Copied from http://golang.org/src/pkg/net/timeout_test.go 145 | func isTimeout(err error) bool { 146 | e, ok := err.(net.Error) 147 | return ok && e.Timeout() 148 | } 149 | -------------------------------------------------------------------------------- /examples/surgemq/surgemq.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "log" 20 | "os" 21 | "os/signal" 22 | "runtime/pprof" 23 | 24 | "github.com/surge/glog" 25 | "github.com/surgemq/surgemq/service" 26 | ) 27 | 28 | var ( 29 | keepAlive int 30 | connectTimeout int 31 | ackTimeout int 32 | timeoutRetries int 33 | authenticator string 34 | sessionsProvider string 35 | topicsProvider string 36 | cpuprofile string 37 | wsAddr string // HTTPS websocket address eg. :8080 38 | wssAddr string // HTTPS websocket address, eg. :8081 39 | wssCertPath string // path to HTTPS public key 40 | wssKeyPath string // path to HTTPS private key 41 | ) 42 | 43 | func init() { 44 | flag.IntVar(&keepAlive, "keepalive", service.DefaultKeepAlive, "Keepalive (sec)") 45 | flag.IntVar(&connectTimeout, "connecttimeout", service.DefaultConnectTimeout, "Connect Timeout (sec)") 46 | flag.IntVar(&ackTimeout, "acktimeout", service.DefaultAckTimeout, "Ack Timeout (sec)") 47 | flag.IntVar(&timeoutRetries, "retries", service.DefaultTimeoutRetries, "Timeout Retries") 48 | flag.StringVar(&authenticator, "auth", service.DefaultAuthenticator, "Authenticator Type") 49 | flag.StringVar(&sessionsProvider, "sessions", service.DefaultSessionsProvider, "Session Provider Type") 50 | flag.StringVar(&topicsProvider, "topics", service.DefaultTopicsProvider, "Topics Provider Type") 51 | flag.StringVar(&cpuprofile, "cpuprofile", "", "CPU Profile Filename") 52 | flag.StringVar(&wsAddr, "wsaddr", "", "HTTP websocket address, eg. ':8080'") 53 | flag.StringVar(&wssAddr, "wssaddr", "", "HTTPS websocket address, eg. ':8081'") 54 | flag.StringVar(&wssCertPath, "wsscertpath", "", "HTTPS server public key file") 55 | flag.StringVar(&wssKeyPath, "wsskeypath", "", "HTTPS server private key file") 56 | flag.Parse() 57 | } 58 | 59 | func main() { 60 | svr := &service.Server{ 61 | KeepAlive: keepAlive, 62 | ConnectTimeout: connectTimeout, 63 | AckTimeout: ackTimeout, 64 | TimeoutRetries: timeoutRetries, 65 | SessionsProvider: sessionsProvider, 66 | TopicsProvider: topicsProvider, 67 | } 68 | 69 | var f *os.File 70 | var err error 71 | 72 | if cpuprofile != "" { 73 | f, err = os.Create(cpuprofile) 74 | if err != nil { 75 | log.Fatal(err) 76 | } 77 | 78 | pprof.StartCPUProfile(f) 79 | } 80 | 81 | sigchan := make(chan os.Signal, 1) 82 | signal.Notify(sigchan, os.Interrupt, os.Kill) 83 | go func() { 84 | sig := <-sigchan 85 | glog.Errorf("Existing due to trapped signal; %v", sig) 86 | 87 | if f != nil { 88 | glog.Errorf("Stopping profile") 89 | pprof.StopCPUProfile() 90 | f.Close() 91 | } 92 | 93 | svr.Close() 94 | 95 | os.Exit(0) 96 | }() 97 | 98 | mqttaddr := "tcp://:1883" 99 | 100 | if len(wsAddr) > 0 || len(wssAddr) > 0 { 101 | addr := "tcp://127.0.0.1:1883" 102 | AddWebsocketHandler("/mqtt", addr) 103 | /* start a plain websocket listener */ 104 | if len(wsAddr) > 0 { 105 | go ListenAndServeWebsocket(wsAddr) 106 | } 107 | /* start a secure websocket listener */ 108 | if len(wssAddr) > 0 && len(wssCertPath) > 0 && len(wssKeyPath) > 0 { 109 | go ListenAndServeWebsocketSecure(wssAddr, wssCertPath, wssKeyPath) 110 | } 111 | } 112 | 113 | /* create plain MQTT listener */ 114 | err = svr.ListenAndServe(mqttaddr) 115 | if err != nil { 116 | glog.Errorf("surgemq/main: %v", err) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /topics/topics.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package topics deals with MQTT topic names, topic filters and subscriptions. 16 | // - "Topic name" is a / separated string that could contain #, * and $ 17 | // - / in topic name separates the string into "topic levels" 18 | // - # is a multi-level wildcard, and it must be the last character in the 19 | // topic name. It represents the parent and all children levels. 20 | // - + is a single level wildwcard. It must be the only character in the 21 | // topic level. It represents all names in the current level. 22 | // - $ is a special character that says the topic is a system level topic 23 | package topics 24 | 25 | import ( 26 | "errors" 27 | "fmt" 28 | 29 | "github.com/surgemq/message" 30 | ) 31 | 32 | const ( 33 | // MWC is the multi-level wildcard 34 | MWC = "#" 35 | 36 | // SWC is the single level wildcard 37 | SWC = "+" 38 | 39 | // SEP is the topic level separator 40 | SEP = "/" 41 | 42 | // SYS is the starting character of the system level topics 43 | SYS = "$" 44 | 45 | // Both wildcards 46 | _WC = "#+" 47 | ) 48 | 49 | var ( 50 | // ErrAuthFailure is returned when the user/pass supplied are invalid 51 | ErrAuthFailure = errors.New("auth: Authentication failure") 52 | 53 | // ErrAuthProviderNotFound is returned when the requested provider does not exist. 54 | // It probably hasn't been registered yet. 55 | ErrAuthProviderNotFound = errors.New("auth: Authentication provider not found") 56 | 57 | providers = make(map[string]TopicsProvider) 58 | ) 59 | 60 | // TopicsProvider 61 | type TopicsProvider interface { 62 | Subscribe(topic []byte, qos byte, subscriber interface{}) (byte, error) 63 | Unsubscribe(topic []byte, subscriber interface{}) error 64 | Subscribers(topic []byte, qos byte, subs *[]interface{}, qoss *[]byte) error 65 | Retain(msg *message.PublishMessage) error 66 | Retained(topic []byte, msgs *[]*message.PublishMessage) error 67 | Close() error 68 | } 69 | 70 | func Register(name string, provider TopicsProvider) { 71 | if provider == nil { 72 | panic("topics: Register provide is nil") 73 | } 74 | 75 | if _, dup := providers[name]; dup { 76 | panic("topics: Register called twice for provider " + name) 77 | } 78 | 79 | providers[name] = provider 80 | } 81 | 82 | func Unregister(name string) { 83 | delete(providers, name) 84 | } 85 | 86 | type Manager struct { 87 | p TopicsProvider 88 | } 89 | 90 | func NewManager(providerName string) (*Manager, error) { 91 | p, ok := providers[providerName] 92 | if !ok { 93 | return nil, fmt.Errorf("session: unknown provider %q", providerName) 94 | } 95 | 96 | return &Manager{p: p}, nil 97 | } 98 | 99 | func (this *Manager) Subscribe(topic []byte, qos byte, subscriber interface{}) (byte, error) { 100 | return this.p.Subscribe(topic, qos, subscriber) 101 | } 102 | 103 | func (this *Manager) Unsubscribe(topic []byte, subscriber interface{}) error { 104 | return this.p.Unsubscribe(topic, subscriber) 105 | } 106 | 107 | func (this *Manager) Subscribers(topic []byte, qos byte, subs *[]interface{}, qoss *[]byte) error { 108 | return this.p.Subscribers(topic, qos, subs, qoss) 109 | } 110 | 111 | func (this *Manager) Retain(msg *message.PublishMessage) error { 112 | return this.p.Retain(msg) 113 | } 114 | 115 | func (this *Manager) Retained(topic []byte, msgs *[]*message.PublishMessage) error { 116 | return this.p.Retained(topic, msgs) 117 | } 118 | 119 | func (this *Manager) Close() error { 120 | return this.p.Close() 121 | } 122 | -------------------------------------------------------------------------------- /benchmark/mesh_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package benchmark 16 | 17 | import ( 18 | "encoding/binary" 19 | "sync" 20 | "sync/atomic" 21 | "testing" 22 | "time" 23 | 24 | "github.com/surge/glog" 25 | "github.com/surgemq/message" 26 | "github.com/surgemq/surgemq/service" 27 | ) 28 | 29 | // Usage: go test -run=Mesh 30 | func TestMesh(t *testing.T) { 31 | var wg sync.WaitGroup 32 | 33 | totalSent = 0 34 | totalRcvd = 0 35 | totalSentTime = 0 36 | totalRcvdTime = 0 37 | sentSince = 0 38 | rcvdSince = 0 39 | 40 | subdone = 0 41 | rcvdone = 0 42 | 43 | done = make(chan struct{}) 44 | 45 | for i := 1; i < publishers+1; i++ { 46 | time.Sleep(time.Millisecond * 20) 47 | wg.Add(1) 48 | go startMeshClient(t, i, &wg) 49 | } 50 | 51 | wg.Wait() 52 | 53 | glog.Infof("Total Sent %d messages in %d ns, %d ns/msg, %d msgs/sec", totalSent, sentSince, int(float64(sentSince)/float64(totalSent)), int(float64(totalSent)/(float64(sentSince)/float64(time.Second)))) 54 | glog.Infof("Total Received %d messages in %d ns, %d ns/msg, %d msgs/sec", totalRcvd, rcvdSince, int(float64(rcvdSince)/float64(totalRcvd)), int(float64(totalRcvd)/(float64(rcvdSince)/float64(time.Second)))) 55 | } 56 | 57 | func startMeshClient(t testing.TB, cid int, wg *sync.WaitGroup) { 58 | runClientTest(t, cid, wg, func(svc *service.Client) { 59 | done2 := make(chan struct{}) 60 | 61 | cnt := messages 62 | expected := publishers * cnt 63 | 64 | received := 0 65 | sent := 0 66 | 67 | now := time.Now() 68 | since := time.Since(now).Nanoseconds() 69 | 70 | sub := newSubscribeMessage("test", 0) 71 | svc.Subscribe(sub, 72 | func(msg, ack message.Message, err error) error { 73 | subs := atomic.AddInt64(&subdone, 1) 74 | if subs == int64(publishers) { 75 | close(done) 76 | } 77 | return nil 78 | }, 79 | func(msg *message.PublishMessage) error { 80 | if received == 0 { 81 | now = time.Now() 82 | } 83 | 84 | received++ 85 | //glog.Debugf("(surgemq%d) messages received=%d", cid, received) 86 | since = time.Since(now).Nanoseconds() 87 | 88 | if received == expected { 89 | close(done2) 90 | } 91 | 92 | return nil 93 | }) 94 | 95 | select { 96 | case <-done: 97 | case <-time.After(time.Second * time.Duration(publishers)): 98 | glog.Infof("(surgemq%d) Timed out waiting for subscribe response", cid) 99 | return 100 | } 101 | 102 | payload := make([]byte, size) 103 | msg := message.NewPublishMessage() 104 | msg.SetTopic(topic) 105 | msg.SetQoS(qos) 106 | 107 | go func() { 108 | now := time.Now() 109 | 110 | for i := 0; i < cnt; i++ { 111 | binary.BigEndian.PutUint32(payload, uint32(cid*cnt+i)) 112 | msg.SetPayload(payload) 113 | 114 | err := svc.Publish(msg, nil) 115 | if err != nil { 116 | break 117 | } 118 | sent++ 119 | } 120 | 121 | since := time.Since(now).Nanoseconds() 122 | 123 | statMu.Lock() 124 | totalSent += int64(sent) 125 | totalSentTime += int64(since) 126 | if since > sentSince { 127 | sentSince = since 128 | } 129 | statMu.Unlock() 130 | 131 | glog.Debugf("(surgemq%d) Sent %d messages in %d ns, %d ns/msg, %d msgs/sec", cid, sent, since, int(float64(since)/float64(cnt)), int(float64(sent)/(float64(since)/float64(time.Second)))) 132 | }() 133 | 134 | select { 135 | case <-done2: 136 | case <-time.Tick(time.Second * time.Duration(nap*publishers)): 137 | glog.Errorf("Timed out waiting for messages to be received.") 138 | } 139 | 140 | statMu.Lock() 141 | totalRcvd += int64(received) 142 | totalRcvdTime += int64(since) 143 | if since > rcvdSince { 144 | rcvdSince = since 145 | } 146 | statMu.Unlock() 147 | 148 | glog.Debugf("(surgemq%d) Received %d messages in %d ns, %d ns/msg, %d msgs/sec", cid, received, since, int(float64(since)/float64(cnt)), int(float64(received)/(float64(since)/float64(time.Second)))) 149 | }) 150 | } 151 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // SurgeMQ is a high performance MQTT broker and client library that aims to be 16 | // fully compliant with MQTT 3.1 and 3.1.1 specs. 17 | // 18 | // The primary package that's of interest is package service. It provides the 19 | // MQTT Server and Client services in a library form. 20 | // 21 | // MQTT is a Client Server publish/subscribe messaging transport protocol. It is 22 | // light weight, open, simple, and designed so as to be easy to implement. These 23 | // characteristics make it ideal for use in many situations, including constrained 24 | // environments such as for communication in Machine to Machine (M2M) and Internet 25 | // of Things (IoT) contexts where a small code footprint is required and/or network 26 | // bandwidth is at a premium. 27 | // 28 | // The protocol runs over TCP/IP, or over other network protocols that provide 29 | // ordered, lossless, bi-directional connections. Its features include: 30 | // 31 | // - Use of the publish/subscribe message pattern which provides one-to-many 32 | // message distribution and decoupling of applications. 33 | // - A messaging transport that is agnostic to the content of the payload. 34 | // - Three qualities of service for message delivery: 35 | // - "At most once", where messages are delivered according to the best efforts 36 | // of the operating environment. Message loss can occur. This level could be 37 | // used, for example, with ambient sensor data where it does not matter if an 38 | // individual reading is lost as the next one will be published soon after. 39 | // - "At least once", where messages are assured to arrive but duplicates can occur. 40 | // - "Exactly once", where message are assured to arrive exactly once. This 41 | // level could be used, for example, with billing systems where duplicate or 42 | // lost messages could lead to incorrect charges being applied. 43 | // - A small transport overhead and protocol exchanges minimized to reduce 44 | // network traffic. 45 | // - A mechanism to notify interested parties when an abnormal disconnection occurs. 46 | // 47 | // Current performance benchmark of SurgeMQ, running all publishers, subscribers 48 | // and broker on a single 4-core (2.8Ghz i7) MacBook Pro, is able to achieve: 49 | // - over 400,000 MPS in a 1:1 single publisher and single producer configuration 50 | // - over 450,000 MPS in a 20:1 fan-in configuration 51 | // - over 750,000 MPS in a 1:20 fan-out configuration 52 | // - over 700,000 MPS in a full mesh configuration with 20 clients 53 | // 54 | // In addition, SurgeMQ has been tested with the following client libraries and 55 | // it _seems_ to work: 56 | // - libmosquitto 1.3.5 (C) 57 | // - Tested with the bundled test programs msgsps_pub and msgsps_sub 58 | // - Paho MQTT Conformance/Interoperability Testing Suite (Python) 59 | // - Tested with all 10 test cases, 3 did not pass. They are 60 | // 1) offline_message_queueing_test which is not supported by SurgeMQ, 61 | // 2) redelivery_on_reconnect_test which is not yet implemented by SurgeMQ, 62 | // 3) run_subscribe_failure_test which is not a valid test. 63 | // - Paho Go Client Library (Go) 64 | // - Tested with one of the tests in the library, in fact, that tests is now 65 | // part of the tests for SurgeMQ 66 | // - Paho C Client library (C) 67 | // - Tested with most of the test cases and failed the same ones as the 68 | // conformance test because the features are not yet implemented. 69 | // - Actually I think there's a bug in the test suite as it calls the PUBLISH 70 | // handler function for non-PUBLISH messages. 71 | // 72 | // A quick example of how to use SurgeMQ: 73 | // func main() { 74 | // // Create a new server 75 | // svr := &service.Server{ 76 | // KeepAlive: 300, // seconds 77 | // ConnectTimeout: 2, // seconds 78 | // SessionsProvider: "mem", // keeps sessions in memory 79 | // Authenticator: "mockSuccess", // always succeed 80 | // TopicsProvider: "mem", // keeps topic subscriptions in memory 81 | // } 82 | // 83 | // // Listen and serve connections at localhost:1883 84 | // err := svr.ListenAndServe("tcp://:1883") 85 | // fmt.Printf("%v", err) 86 | // } 87 | package surgemq 88 | -------------------------------------------------------------------------------- /service/buffer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "bytes" 19 | "io" 20 | "testing" 21 | "time" 22 | 23 | "github.com/stretchr/testify/require" 24 | ) 25 | 26 | func TestBufferSequence(t *testing.T) { 27 | seq := newSequence() 28 | 29 | seq.set(100) 30 | require.Equal(t, int64(100), seq.get()) 31 | 32 | seq.set(20000) 33 | require.Equal(t, int64(20000), seq.get()) 34 | } 35 | 36 | func TestBufferReadFrom(t *testing.T) { 37 | testFillBuffer(t, 144, 16384) 38 | testFillBuffer(t, 2048, 16384) 39 | testFillBuffer(t, 3072, 16384) 40 | } 41 | 42 | func TestBufferReadBytes(t *testing.T) { 43 | buf := testFillBuffer(t, 2048, 16384) 44 | 45 | testReadBytes(t, buf) 46 | } 47 | 48 | func TestBufferCommitBytes(t *testing.T) { 49 | buf := testFillBuffer(t, 2048, 16384) 50 | 51 | testCommit(t, buf) 52 | } 53 | 54 | func TestBufferConsumerProducerRead(t *testing.T) { 55 | buf, err := newBuffer(16384) 56 | 57 | require.NoError(t, err) 58 | 59 | testRead(t, buf) 60 | } 61 | 62 | func TestBufferConsumerProducerWriteTo(t *testing.T) { 63 | buf, err := newBuffer(16384) 64 | 65 | require.NoError(t, err) 66 | 67 | testWriteTo(t, buf) 68 | } 69 | 70 | func TestBufferConsumerProducerPeekCommit(t *testing.T) { 71 | buf, err := newBuffer(16384) 72 | 73 | require.NoError(t, err) 74 | 75 | testPeekCommit(t, buf) 76 | } 77 | 78 | func TestBufferPeek(t *testing.T) { 79 | buf := testFillBuffer(t, 2048, 16384) 80 | 81 | peekBuffer(t, buf, 100) 82 | peekBuffer(t, buf, 1000) 83 | } 84 | 85 | func BenchmarkBufferConsumerProducerRead(b *testing.B) { 86 | buf, _ := newBuffer(0) 87 | benchmarkRead(b, buf) 88 | } 89 | 90 | func testFillBuffer(t *testing.T, bufsize, ringsize int64) *buffer { 91 | buf, err := newBuffer(ringsize) 92 | 93 | require.NoError(t, err) 94 | 95 | fillBuffer(t, buf, bufsize) 96 | 97 | require.Equal(t, int(bufsize), buf.Len()) 98 | 99 | return buf 100 | } 101 | 102 | func fillBuffer(t *testing.T, buf *buffer, bufsize int64) { 103 | p := make([]byte, bufsize) 104 | for i := range p { 105 | p[i] = 'a' 106 | } 107 | 108 | n, err := buf.ReadFrom(bytes.NewBuffer(p)) 109 | 110 | require.Equal(t, bufsize, n) 111 | require.Equal(t, err, io.EOF) 112 | } 113 | 114 | func peekBuffer(t *testing.T, buf *buffer, n int) { 115 | pkbuf, err := buf.ReadPeek(n) 116 | 117 | require.NoError(t, err) 118 | require.Equal(t, n, len(pkbuf)) 119 | 120 | for _, b := range pkbuf { 121 | require.Equal(t, byte('a'), b) 122 | } 123 | } 124 | 125 | func testPeekCommit(t *testing.T, buf *buffer) { 126 | n := 20000 127 | 128 | go func(n int64) { 129 | fillBuffer(t, buf, n) 130 | }(int64(n)) 131 | 132 | i := 0 133 | 134 | for n > 0 { 135 | pkbuf, _ := buf.ReadPeek(1024) 136 | l, err := buf.ReadCommit(len(pkbuf)) 137 | 138 | require.NoError(t, err) 139 | 140 | n -= l 141 | i += l 142 | } 143 | } 144 | 145 | func testWriteTo(t *testing.T, buf *buffer) { 146 | n := int64(20000) 147 | 148 | go func(n int64) { 149 | fillBuffer(t, buf, n) 150 | time.Sleep(time.Millisecond * 100) 151 | buf.Close() 152 | }(n) 153 | 154 | m, err := buf.WriteTo(bytes.NewBuffer(make([]byte, n))) 155 | 156 | require.Equal(t, io.EOF, err) 157 | require.Equal(t, int64(20000), m) 158 | } 159 | 160 | func testRead(t *testing.T, buf *buffer) { 161 | n := int64(20000) 162 | 163 | go func(n int64) { 164 | fillBuffer(t, buf, n) 165 | }(n) 166 | 167 | p := make([]byte, n) 168 | i := 0 169 | 170 | for n > 0 { 171 | l, err := buf.Read(p[i:]) 172 | 173 | require.NoError(t, err) 174 | 175 | n -= int64(l) 176 | i += l 177 | } 178 | } 179 | 180 | func testCommit(t *testing.T, buf *buffer) { 181 | n, err := buf.ReadCommit(256) 182 | 183 | require.NoError(t, err) 184 | require.Equal(t, 256, n) 185 | 186 | _, err = buf.ReadCommit(2048) 187 | 188 | require.Equal(t, ErrBufferInsufficientData, err) 189 | } 190 | 191 | func testReadBytes(t *testing.T, buf *buffer) { 192 | p := make([]byte, 256) 193 | n, err := buf.Read(p) 194 | 195 | require.NoError(t, err) 196 | require.Equal(t, 256, n) 197 | 198 | p2 := make([]byte, 4096) 199 | n, err = buf.Read(p2) 200 | 201 | require.NoError(t, err) 202 | require.Equal(t, 2048-256, n) 203 | } 204 | 205 | func benchmarkRead(b *testing.B, buf *buffer) { 206 | n := int64(b.N) 207 | 208 | go func(n int64) { 209 | p := make([]byte, n) 210 | buf.ReadFrom(bytes.NewBuffer(p)) 211 | }(n) 212 | 213 | p := make([]byte, n) 214 | i := 0 215 | 216 | for n > 0 { 217 | l, _ := buf.Read(p[i:]) 218 | 219 | n -= int64(l) 220 | i += l 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /sessions/session.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sessions 16 | 17 | import ( 18 | "fmt" 19 | "sync" 20 | 21 | "github.com/surgemq/message" 22 | ) 23 | 24 | const ( 25 | // Queue size for the ack queue 26 | defaultQueueSize = 16 27 | ) 28 | 29 | type Session struct { 30 | // Ack queue for outgoing PUBLISH QoS 1 messages 31 | Pub1ack *Ackqueue 32 | 33 | // Ack queue for incoming PUBLISH QoS 2 messages 34 | Pub2in *Ackqueue 35 | 36 | // Ack queue for outgoing PUBLISH QoS 2 messages 37 | Pub2out *Ackqueue 38 | 39 | // Ack queue for outgoing SUBSCRIBE messages 40 | Suback *Ackqueue 41 | 42 | // Ack queue for outgoing UNSUBSCRIBE messages 43 | Unsuback *Ackqueue 44 | 45 | // Ack queue for outgoing PINGREQ messages 46 | Pingack *Ackqueue 47 | 48 | // cmsg is the CONNECT message 49 | Cmsg *message.ConnectMessage 50 | 51 | // Will message to publish if connect is closed unexpectedly 52 | Will *message.PublishMessage 53 | 54 | // Retained publish message 55 | Retained *message.PublishMessage 56 | 57 | // cbuf is the CONNECT message buffer, this is for storing all the will stuff 58 | cbuf []byte 59 | 60 | // rbuf is the retained PUBLISH message buffer 61 | rbuf []byte 62 | 63 | // topics stores all the topis for this session/client 64 | topics map[string]byte 65 | 66 | // Initialized? 67 | initted bool 68 | 69 | // Serialize access to this session 70 | mu sync.Mutex 71 | 72 | id string 73 | } 74 | 75 | func (this *Session) Init(msg *message.ConnectMessage) error { 76 | this.mu.Lock() 77 | defer this.mu.Unlock() 78 | 79 | if this.initted { 80 | return fmt.Errorf("Session already initialized") 81 | } 82 | 83 | this.cbuf = make([]byte, msg.Len()) 84 | this.Cmsg = message.NewConnectMessage() 85 | 86 | if _, err := msg.Encode(this.cbuf); err != nil { 87 | return err 88 | } 89 | 90 | if _, err := this.Cmsg.Decode(this.cbuf); err != nil { 91 | return err 92 | } 93 | 94 | if this.Cmsg.WillFlag() { 95 | this.Will = message.NewPublishMessage() 96 | this.Will.SetQoS(this.Cmsg.WillQos()) 97 | this.Will.SetTopic(this.Cmsg.WillTopic()) 98 | this.Will.SetPayload(this.Cmsg.WillMessage()) 99 | this.Will.SetRetain(this.Cmsg.WillRetain()) 100 | } 101 | 102 | this.topics = make(map[string]byte, 1) 103 | 104 | this.id = string(msg.ClientId()) 105 | 106 | this.Pub1ack = newAckqueue(defaultQueueSize) 107 | this.Pub2in = newAckqueue(defaultQueueSize) 108 | this.Pub2out = newAckqueue(defaultQueueSize) 109 | this.Suback = newAckqueue(defaultQueueSize) 110 | this.Unsuback = newAckqueue(defaultQueueSize) 111 | this.Pingack = newAckqueue(defaultQueueSize) 112 | 113 | this.initted = true 114 | 115 | return nil 116 | } 117 | 118 | func (this *Session) Update(msg *message.ConnectMessage) error { 119 | this.mu.Lock() 120 | defer this.mu.Unlock() 121 | 122 | this.cbuf = make([]byte, msg.Len()) 123 | this.Cmsg = message.NewConnectMessage() 124 | 125 | if _, err := msg.Encode(this.cbuf); err != nil { 126 | return err 127 | } 128 | 129 | if _, err := this.Cmsg.Decode(this.cbuf); err != nil { 130 | return err 131 | } 132 | 133 | return nil 134 | } 135 | 136 | func (this *Session) RetainMessage(msg *message.PublishMessage) error { 137 | this.mu.Lock() 138 | defer this.mu.Unlock() 139 | 140 | this.rbuf = make([]byte, msg.Len()) 141 | this.Retained = message.NewPublishMessage() 142 | 143 | if _, err := msg.Encode(this.rbuf); err != nil { 144 | return err 145 | } 146 | 147 | if _, err := this.Retained.Decode(this.rbuf); err != nil { 148 | return err 149 | } 150 | 151 | return nil 152 | } 153 | 154 | func (this *Session) AddTopic(topic string, qos byte) error { 155 | this.mu.Lock() 156 | defer this.mu.Unlock() 157 | 158 | if !this.initted { 159 | return fmt.Errorf("Session not yet initialized") 160 | } 161 | 162 | this.topics[topic] = qos 163 | 164 | return nil 165 | } 166 | 167 | func (this *Session) RemoveTopic(topic string) error { 168 | this.mu.Lock() 169 | defer this.mu.Unlock() 170 | 171 | if !this.initted { 172 | return fmt.Errorf("Session not yet initialized") 173 | } 174 | 175 | delete(this.topics, topic) 176 | 177 | return nil 178 | } 179 | 180 | func (this *Session) Topics() ([]string, []byte, error) { 181 | this.mu.Lock() 182 | defer this.mu.Unlock() 183 | 184 | if !this.initted { 185 | return nil, nil, fmt.Errorf("Session not yet initialized") 186 | } 187 | 188 | var ( 189 | topics []string 190 | qoss []byte 191 | ) 192 | 193 | for k, v := range this.topics { 194 | topics = append(topics, k) 195 | qoss = append(qoss, v) 196 | } 197 | 198 | return topics, qoss, nil 199 | } 200 | 201 | func (this *Session) ID() string { 202 | return string(this.Cmsg.ClientId()) 203 | } 204 | -------------------------------------------------------------------------------- /benchmark/fan_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package benchmark 16 | 17 | import ( 18 | "encoding/binary" 19 | "sync" 20 | "sync/atomic" 21 | "testing" 22 | "time" 23 | 24 | "github.com/surge/glog" 25 | "github.com/surgemq/message" 26 | "github.com/surgemq/surgemq/service" 27 | ) 28 | 29 | // Usage: go test -run=FullMesh 30 | func TestFan(t *testing.T) { 31 | var wg sync.WaitGroup 32 | 33 | totalSent = 0 34 | totalRcvd = 0 35 | totalSentTime = 0 36 | totalRcvdTime = 0 37 | sentSince = 0 38 | rcvdSince = 0 39 | 40 | subdone = 0 41 | rcvdone = 0 42 | 43 | done = make(chan struct{}) 44 | done2 = make(chan struct{}) 45 | 46 | for i := 1; i < subscribers+1; i++ { 47 | time.Sleep(time.Millisecond * 20) 48 | wg.Add(1) 49 | go startFanSubscribers(t, i, &wg) 50 | } 51 | 52 | for i := subscribers + 1; i < publishers+subscribers+1; i++ { 53 | time.Sleep(time.Millisecond * 20) 54 | wg.Add(1) 55 | go startFanPublisher(t, i, &wg) 56 | } 57 | 58 | wg.Wait() 59 | 60 | glog.Infof("Total Sent %d messages in %d ns, %d ns/msg, %d msgs/sec", totalSent, sentSince, int(float64(sentSince)/float64(totalSent)), int(float64(totalSent)/(float64(sentSince)/float64(time.Second)))) 61 | glog.Infof("Total Received %d messages in %d ns, %d ns/msg, %d msgs/sec", totalRcvd, sentSince, int(float64(sentSince)/float64(totalRcvd)), int(float64(totalRcvd)/(float64(sentSince)/float64(time.Second)))) 62 | } 63 | 64 | func startFanSubscribers(t testing.TB, cid int, wg *sync.WaitGroup) { 65 | now := time.Now() 66 | 67 | runClientTest(t, cid, wg, func(svc *service.Client) { 68 | cnt := messages * publishers 69 | received := 0 70 | since := time.Since(now).Nanoseconds() 71 | 72 | sub := newSubscribeMessage("test", 0) 73 | svc.Subscribe(sub, 74 | func(msg, ack message.Message, err error) error { 75 | subs := atomic.AddInt64(&subdone, 1) 76 | if subs == int64(subscribers) { 77 | now = time.Now() 78 | close(done) 79 | } 80 | return nil 81 | }, 82 | func(msg *message.PublishMessage) error { 83 | if received == 0 { 84 | now = time.Now() 85 | } 86 | 87 | received++ 88 | //glog.Debugf("(surgemq%d) messages received=%d", cid, received) 89 | since = time.Since(now).Nanoseconds() 90 | 91 | if received == cnt { 92 | rcvd := atomic.AddInt64(&rcvdone, 1) 93 | if rcvd == int64(subscribers) { 94 | close(done2) 95 | } 96 | } 97 | 98 | return nil 99 | }) 100 | 101 | select { 102 | case <-done: 103 | case <-time.After(time.Second * time.Duration(subscribers)): 104 | glog.Infof("(surgemq%d) Timed out waiting for subscribe response", cid) 105 | return 106 | } 107 | 108 | select { 109 | case <-done2: 110 | case <-time.Tick(time.Second * time.Duration(nap*publishers)): 111 | glog.Errorf("Timed out waiting for messages to be received.") 112 | } 113 | 114 | statMu.Lock() 115 | totalRcvd += int64(received) 116 | totalRcvdTime += int64(since) 117 | if since > rcvdSince { 118 | rcvdSince = since 119 | } 120 | statMu.Unlock() 121 | 122 | glog.Debugf("(surgemq%d) Received %d messages in %d ns, %d ns/msg, %d msgs/sec", cid, received, since, int(float64(since)/float64(cnt)), int(float64(received)/(float64(since)/float64(time.Second)))) 123 | }) 124 | } 125 | 126 | func startFanPublisher(t testing.TB, cid int, wg *sync.WaitGroup) { 127 | now := time.Now() 128 | 129 | runClientTest(t, cid, wg, func(svc *service.Client) { 130 | select { 131 | case <-done: 132 | case <-time.After(time.Second * time.Duration(subscribers)): 133 | glog.Infof("(surgemq%d) Timed out waiting for subscribe response", cid) 134 | return 135 | } 136 | 137 | cnt := messages 138 | sent := 0 139 | 140 | payload := make([]byte, size) 141 | msg := message.NewPublishMessage() 142 | msg.SetTopic(topic) 143 | msg.SetQoS(qos) 144 | 145 | for i := 0; i < cnt; i++ { 146 | binary.BigEndian.PutUint32(payload, uint32(cid*cnt+i)) 147 | msg.SetPayload(payload) 148 | 149 | err := svc.Publish(msg, nil) 150 | if err != nil { 151 | break 152 | } 153 | sent++ 154 | } 155 | 156 | since := time.Since(now).Nanoseconds() 157 | 158 | statMu.Lock() 159 | totalSent += int64(sent) 160 | totalSentTime += int64(since) 161 | if since > sentSince { 162 | sentSince = since 163 | } 164 | statMu.Unlock() 165 | 166 | glog.Debugf("(surgemq%d) Sent %d messages in %d ns, %d ns/msg, %d msgs/sec", cid, sent, since, int(float64(since)/float64(cnt)), int(float64(sent)/(float64(since)/float64(time.Second)))) 167 | 168 | select { 169 | case <-done2: 170 | case <-time.Tick(time.Second * time.Duration(nap*publishers)): 171 | glog.Errorf("Timed out waiting for messages to be received.") 172 | } 173 | }) 174 | } 175 | -------------------------------------------------------------------------------- /service/helpers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "bufio" 19 | "bytes" 20 | "fmt" 21 | "net" 22 | "net/url" 23 | "sync" 24 | "sync/atomic" 25 | "testing" 26 | 27 | "github.com/stretchr/testify/require" 28 | "github.com/surge/glog" 29 | "github.com/surgemq/message" 30 | "github.com/surgemq/surgemq/sessions" 31 | "github.com/surgemq/surgemq/topics" 32 | ) 33 | 34 | var ( 35 | gTestClientId uint64 = 0 36 | ) 37 | 38 | func runClientServerTests(t testing.TB, f func(*Client)) { 39 | var wg sync.WaitGroup 40 | 41 | ready1 := make(chan struct{}) 42 | ready2 := make(chan struct{}) 43 | 44 | uri := "tcp://127.0.0.1:1883" 45 | u, err := url.Parse(uri) 46 | require.NoError(t, err, "Error parsing URL") 47 | 48 | // Start listener 49 | wg.Add(1) 50 | go startService(t, u, &wg, ready1, ready2) 51 | 52 | <-ready1 53 | 54 | c := connectToServer(t, uri) 55 | if c == nil { 56 | return 57 | } 58 | 59 | defer topics.Unregister(c.svc.sess.ID()) 60 | 61 | if f != nil { 62 | f(c) 63 | } 64 | 65 | c.Disconnect() 66 | 67 | close(ready2) 68 | 69 | wg.Wait() 70 | } 71 | 72 | func startServiceN(t testing.TB, u *url.URL, wg *sync.WaitGroup, ready1, ready2 chan struct{}, cnt int) { 73 | defer wg.Done() 74 | 75 | topics.Unregister("mem") 76 | tp := topics.NewMemProvider() 77 | topics.Register("mem", tp) 78 | 79 | sessions.Unregister("mem") 80 | sp := sessions.NewMemProvider() 81 | sessions.Register("mem", sp) 82 | 83 | ln, err := net.Listen(u.Scheme, u.Host) 84 | require.NoError(t, err) 85 | defer ln.Close() 86 | 87 | close(ready1) 88 | 89 | svr := &Server{ 90 | Authenticator: authenticator, 91 | } 92 | 93 | for i := 0; i < cnt; i++ { 94 | conn, err := ln.Accept() 95 | require.NoError(t, err) 96 | 97 | _, err = svr.handleConnection(conn) 98 | if authenticator == "mockFailure" { 99 | require.Error(t, err) 100 | return 101 | } else { 102 | require.NoError(t, err) 103 | } 104 | } 105 | 106 | <-ready2 107 | 108 | for _, svc := range svr.svcs { 109 | glog.Infof("Stopping service %d", svc.id) 110 | svc.stop() 111 | } 112 | 113 | } 114 | 115 | func startService(t testing.TB, u *url.URL, wg *sync.WaitGroup, ready1, ready2 chan struct{}) { 116 | startServiceN(t, u, wg, ready1, ready2, 1) 117 | } 118 | 119 | func connectToServer(t testing.TB, uri string) *Client { 120 | c := &Client{} 121 | 122 | msg := newConnectMessage() 123 | 124 | err := c.Connect(uri, msg) 125 | if authenticator == "mockFailure" { 126 | require.Error(t, err) 127 | return nil 128 | } else { 129 | require.NoError(t, err) 130 | } 131 | 132 | return c 133 | } 134 | 135 | func newPubrelMessage(pktid uint16) *message.PubrelMessage { 136 | msg := message.NewPubrelMessage() 137 | msg.SetPacketId(pktid) 138 | 139 | return msg 140 | } 141 | 142 | func newPublishMessage(pktid uint16, qos byte) *message.PublishMessage { 143 | msg := message.NewPublishMessage() 144 | msg.SetPacketId(pktid) 145 | msg.SetTopic([]byte("abc")) 146 | msg.SetPayload([]byte("abc")) 147 | msg.SetQoS(qos) 148 | 149 | return msg 150 | } 151 | 152 | func newPublishMessageLarge(pktid uint16, qos byte) *message.PublishMessage { 153 | msg := message.NewPublishMessage() 154 | msg.SetPacketId(pktid) 155 | msg.SetTopic([]byte("abc")) 156 | msg.SetPayload(make([]byte, 1024)) 157 | msg.SetQoS(qos) 158 | 159 | return msg 160 | } 161 | 162 | func newSubscribeMessage(qos byte) *message.SubscribeMessage { 163 | msg := message.NewSubscribeMessage() 164 | msg.AddTopic([]byte("abc"), qos) 165 | 166 | return msg 167 | } 168 | 169 | func newUnsubscribeMessage() *message.UnsubscribeMessage { 170 | msg := message.NewUnsubscribeMessage() 171 | msg.AddTopic([]byte("abc")) 172 | 173 | return msg 174 | } 175 | 176 | func newConnectMessage() *message.ConnectMessage { 177 | msg := message.NewConnectMessage() 178 | msg.SetWillQos(1) 179 | msg.SetVersion(4) 180 | msg.SetCleanSession(true) 181 | msg.SetClientId([]byte(fmt.Sprintf("surgemq%d", atomic.AddUint64(&gTestClientId, 1)))) 182 | msg.SetKeepAlive(10) 183 | msg.SetWillTopic([]byte("will")) 184 | msg.SetWillMessage([]byte("send me home")) 185 | msg.SetUsername([]byte("surgemq")) 186 | msg.SetPassword([]byte("verysecret")) 187 | 188 | return msg 189 | } 190 | 191 | func newConnectMessageBuffer() *bufio.Reader { 192 | msgBytes := []byte{ 193 | byte(message.CONNECT << 4), 194 | 60, 195 | 0, // Length MSB (0) 196 | 4, // Length LSB (4) 197 | 'M', 'Q', 'T', 'T', 198 | 4, // Protocol level 4 199 | 206, // connect flags 11001110, will QoS = 01 200 | 0, // Keep Alive MSB (0) 201 | 10, // Keep Alive LSB (10) 202 | 0, // Client ID MSB (0) 203 | 7, // Client ID LSB (7) 204 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 205 | 0, // Will Topic MSB (0) 206 | 4, // Will Topic LSB (4) 207 | 'w', 'i', 'l', 'l', 208 | 0, // Will Message MSB (0) 209 | 12, // Will Message LSB (12) 210 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 211 | 0, // Username ID MSB (0) 212 | 7, // Username ID LSB (7) 213 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 214 | 0, // Password ID MSB (0) 215 | 10, // Password ID LSB (10) 216 | 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', 217 | } 218 | 219 | return bufio.NewReader(bytes.NewBuffer(msgBytes)) 220 | } 221 | -------------------------------------------------------------------------------- /service/sendrecv_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "bytes" 19 | "io" 20 | "testing" 21 | 22 | "github.com/stretchr/testify/require" 23 | "github.com/surgemq/message" 24 | ) 25 | 26 | func TestReadMessageSuccess(t *testing.T) { 27 | msgBytes := []byte{ 28 | byte(message.CONNECT << 4), 29 | 60, 30 | 0, // Length MSB (0) 31 | 4, // Length LSB (4) 32 | 'M', 'Q', 'T', 'T', 33 | 4, // Protocol level 4 34 | 206, // connect flags 11001110, will QoS = 01 35 | 0, // Keep Alive MSB (0) 36 | 10, // Keep Alive LSB (10) 37 | 0, // Client ID MSB (0) 38 | 7, // Client ID LSB (7) 39 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 40 | 0, // Will Topic MSB (0) 41 | 4, // Will Topic LSB (4) 42 | 'w', 'i', 'l', 'l', 43 | 0, // Will Message MSB (0) 44 | 12, // Will Message LSB (12) 45 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 46 | 0, // Username ID MSB (0) 47 | 7, // Username ID LSB (7) 48 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 49 | 0, // Password ID MSB (0) 50 | 10, // Password ID LSB (10) 51 | 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', 52 | } 53 | 54 | svc := newTestBuffer(t, msgBytes) 55 | 56 | m, n, err := svc.readMessage(message.CONNECT, 62) 57 | 58 | require.NoError(t, err, "Error decoding message.") 59 | 60 | require.Equal(t, len(msgBytes), n, "Error decoding message.") 61 | 62 | msg := m.(*message.ConnectMessage) 63 | 64 | require.Equal(t, message.QosAtLeastOnce, msg.WillQos(), "Incorrect Will QoS") 65 | 66 | require.Equal(t, 10, int(msg.KeepAlive()), "Incorrect KeepAlive value.") 67 | 68 | require.Equal(t, "surgemq", string(msg.ClientId()), "Incorrect client ID value.") 69 | 70 | require.Equal(t, "will", string(msg.WillTopic()), "Incorrect will topic value.") 71 | 72 | require.Equal(t, "send me home", string(msg.WillMessage()), "Incorrect will message value.") 73 | 74 | require.Equal(t, "surgemq", string(msg.Username()), "Incorrect username value.") 75 | 76 | require.Equal(t, "verysecret", string(msg.Password()), "Incorrect password value.") 77 | } 78 | 79 | // Wrong messag type 80 | func TestReadMessageError(t *testing.T) { 81 | msgBytes := []byte{ 82 | byte(message.RESERVED << 4), // <--- WRONG 83 | 60, 84 | 0, // Length MSB (0) 85 | 4, // Length LSB (4) 86 | 'M', 'Q', 'T', 'T', 87 | 4, // Protocol level 4 88 | 206, // connect flags 11001110, will QoS = 01 89 | 0, // Keep Alive MSB (0) 90 | 10, // Keep Alive LSB (10) 91 | 0, // Client ID MSB (0) 92 | 7, // Client ID LSB (7) 93 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 94 | 0, // Will Topic MSB (0) 95 | 4, // Will Topic LSB (4) 96 | 'w', 'i', 'l', 'l', 97 | 0, // Will Message MSB (0) 98 | 12, // Will Message LSB (12) 99 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 100 | 0, // Username ID MSB (0) 101 | 7, // Username ID LSB (7) 102 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 103 | 0, // Password ID MSB (0) 104 | 10, // Password ID LSB (10) 105 | 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', 106 | } 107 | 108 | svc := newTestBuffer(t, msgBytes) 109 | 110 | _, _, err := svc.readMessage(message.CONNECT, 62) 111 | 112 | require.Error(t, err) 113 | } 114 | 115 | // Wrong messag size 116 | func TestReadMessageError2(t *testing.T) { 117 | msgBytes := []byte{ 118 | byte(message.CONNECT << 4), 119 | 62, // <--- WRONG 120 | 0, // Length MSB (0) 121 | 4, // Length LSB (4) 122 | 'M', 'Q', 'T', 'T', 123 | 4, // Protocol level 4 124 | 206, // connect flags 11001110, will QoS = 01 125 | 0, // Keep Alive MSB (0) 126 | 10, // Keep Alive LSB (10) 127 | 0, // Client ID MSB (0) 128 | 7, // Client ID LSB (7) 129 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 130 | 0, // Will Topic MSB (0) 131 | 4, // Will Topic LSB (4) 132 | 'w', 'i', 'l', 'l', 133 | 0, // Will Message MSB (0) 134 | 12, // Will Message LSB (12) 135 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 136 | 0, // Username ID MSB (0) 137 | 7, // Username ID LSB (7) 138 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 139 | 0, // Password ID MSB (0) 140 | 10, // Password ID LSB (10) 141 | 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', 142 | } 143 | 144 | svc := newTestBuffer(t, msgBytes) 145 | 146 | _, _, err := svc.readMessage(message.CONNECT, 64) 147 | 148 | require.Equal(t, io.EOF, err) 149 | } 150 | 151 | func TestWriteMessage(t *testing.T) { 152 | msgBytes := []byte{ 153 | byte(message.CONNECT << 4), 154 | 60, 155 | 0, // Length MSB (0) 156 | 4, // Length LSB (4) 157 | 'M', 'Q', 'T', 'T', 158 | 4, // Protocol level 4 159 | 206, // connect flags 11001110, will QoS = 01 160 | 0, // Keep Alive MSB (0) 161 | 10, // Keep Alive LSB (10) 162 | 0, // Client ID MSB (0) 163 | 7, // Client ID LSB (7) 164 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 165 | 0, // Will Topic MSB (0) 166 | 4, // Will Topic LSB (4) 167 | 'w', 'i', 'l', 'l', 168 | 0, // Will Message MSB (0) 169 | 12, // Will Message LSB (12) 170 | 's', 'e', 'n', 'd', ' ', 'm', 'e', ' ', 'h', 'o', 'm', 'e', 171 | 0, // Username ID MSB (0) 172 | 7, // Username ID LSB (7) 173 | 's', 'u', 'r', 'g', 'e', 'm', 'q', 174 | 0, // Password ID MSB (0) 175 | 10, // Password ID LSB (10) 176 | 'v', 'e', 'r', 'y', 's', 'e', 'c', 'r', 'e', 't', 177 | } 178 | 179 | msg := newConnectMessage() 180 | msg.SetClientId([]byte("surgemq")) 181 | var err error 182 | 183 | svc := &service{} 184 | svc.out, err = newBuffer(16384) 185 | 186 | require.NoError(t, err) 187 | 188 | n, err := svc.writeMessage(msg) 189 | 190 | require.NoError(t, err, "error decoding message.") 191 | 192 | require.Equal(t, len(msgBytes), n, "error decoding message.") 193 | 194 | dst, err := svc.out.ReadPeek(len(msgBytes)) 195 | 196 | require.NoError(t, err) 197 | 198 | require.Equal(t, msgBytes, dst, "error decoding message.") 199 | } 200 | 201 | func newTestBuffer(t *testing.T, msgBytes []byte) *service { 202 | buf := bytes.NewBuffer(msgBytes) 203 | svc := &service{} 204 | var err error 205 | 206 | svc.in, err = newBuffer(16384) 207 | 208 | require.NoError(t, err) 209 | 210 | l, err := svc.in.ReadFrom(buf) 211 | 212 | require.Equal(t, io.EOF, err) 213 | require.Equal(t, int64(len(msgBytes)), l) 214 | 215 | return svc 216 | } 217 | -------------------------------------------------------------------------------- /service/client.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "net/url" 21 | "sync/atomic" 22 | "time" 23 | 24 | "github.com/surgemq/message" 25 | "github.com/surgemq/surgemq/sessions" 26 | "github.com/surgemq/surgemq/topics" 27 | ) 28 | 29 | const ( 30 | minKeepAlive = 30 31 | ) 32 | 33 | // Client is a library implementation of the MQTT client that, as best it can, complies 34 | // with the MQTT 3.1 and 3.1.1 specs. 35 | type Client struct { 36 | // The number of seconds to keep the connection live if there's no data. 37 | // If not set then default to 5 mins. 38 | KeepAlive int 39 | 40 | // The number of seconds to wait for the CONNACK message before disconnecting. 41 | // If not set then default to 2 seconds. 42 | ConnectTimeout int 43 | 44 | // The number of seconds to wait for any ACK messages before failing. 45 | // If not set then default to 20 seconds. 46 | AckTimeout int 47 | 48 | // The number of times to retry sending a packet if ACK is not received. 49 | // If no set then default to 3 retries. 50 | TimeoutRetries int 51 | 52 | svc *service 53 | } 54 | 55 | // Connect is for MQTT clients to open a connection to a remote server. It needs to 56 | // know the URI, e.g., "tcp://127.0.0.1:1883", so it knows where to connect to. It also 57 | // needs to be supplied with the MQTT CONNECT message. 58 | func (this *Client) Connect(uri string, msg *message.ConnectMessage) (err error) { 59 | this.checkConfiguration() 60 | 61 | if msg == nil { 62 | return fmt.Errorf("msg is nil") 63 | } 64 | 65 | u, err := url.Parse(uri) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | if u.Scheme != "tcp" { 71 | return ErrInvalidConnectionType 72 | } 73 | 74 | conn, err := net.Dial(u.Scheme, u.Host) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | defer func() { 80 | if err != nil { 81 | conn.Close() 82 | } 83 | }() 84 | 85 | if msg.KeepAlive() < minKeepAlive { 86 | msg.SetKeepAlive(minKeepAlive) 87 | } 88 | 89 | if err = writeMessage(conn, msg); err != nil { 90 | return err 91 | } 92 | 93 | conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(this.ConnectTimeout))) 94 | 95 | resp, err := getConnackMessage(conn) 96 | if err != nil { 97 | return err 98 | } 99 | 100 | if resp.ReturnCode() != message.ConnectionAccepted { 101 | return resp.ReturnCode() 102 | } 103 | 104 | this.svc = &service{ 105 | id: atomic.AddUint64(&gsvcid, 1), 106 | client: true, 107 | conn: conn, 108 | 109 | keepAlive: int(msg.KeepAlive()), 110 | connectTimeout: this.ConnectTimeout, 111 | ackTimeout: this.AckTimeout, 112 | timeoutRetries: this.TimeoutRetries, 113 | } 114 | 115 | err = this.getSession(this.svc, msg, resp) 116 | if err != nil { 117 | return err 118 | } 119 | 120 | p := topics.NewMemProvider() 121 | topics.Register(this.svc.sess.ID(), p) 122 | 123 | this.svc.topicsMgr, err = topics.NewManager(this.svc.sess.ID()) 124 | if err != nil { 125 | return err 126 | } 127 | 128 | if err := this.svc.start(); err != nil { 129 | this.svc.stop() 130 | return err 131 | } 132 | 133 | this.svc.inStat.increment(int64(msg.Len())) 134 | this.svc.outStat.increment(int64(resp.Len())) 135 | 136 | return nil 137 | } 138 | 139 | // Publish sends a single MQTT PUBLISH message to the server. On completion, the 140 | // supplied OnCompleteFunc is called. For QOS 0 messages, onComplete is called 141 | // immediately after the message is sent to the outgoing buffer. For QOS 1 messages, 142 | // onComplete is called when PUBACK is received. For QOS 2 messages, onComplete is 143 | // called after the PUBCOMP message is received. 144 | func (this *Client) Publish(msg *message.PublishMessage, onComplete OnCompleteFunc) error { 145 | return this.svc.publish(msg, onComplete) 146 | } 147 | 148 | // Subscribe sends a single SUBSCRIBE message to the server. The SUBSCRIBE message 149 | // can contain multiple topics that the client wants to subscribe to. On completion, 150 | // which is when the client receives a SUBACK messsage back from the server, the 151 | // supplied onComplete funciton is called. 152 | // 153 | // When messages are sent to the client from the server that matches the topics the 154 | // client subscribed to, the onPublish function is called to handle those messages. 155 | // So in effect, the client can supply different onPublish functions for different 156 | // topics. 157 | func (this *Client) Subscribe(msg *message.SubscribeMessage, onComplete OnCompleteFunc, onPublish OnPublishFunc) error { 158 | return this.svc.subscribe(msg, onComplete, onPublish) 159 | } 160 | 161 | // Unsubscribe sends a single UNSUBSCRIBE message to the server. The UNSUBSCRIBE 162 | // message can contain multiple topics that the client wants to unsubscribe. On 163 | // completion, which is when the client receives a UNSUBACK message from the server, 164 | // the supplied onComplete function is called. The client will no longer handle 165 | // messages from the server for those unsubscribed topics. 166 | func (this *Client) Unsubscribe(msg *message.UnsubscribeMessage, onComplete OnCompleteFunc) error { 167 | return this.svc.unsubscribe(msg, onComplete) 168 | } 169 | 170 | // Ping sends a single PINGREQ message to the server. PINGREQ/PINGRESP messages are 171 | // mainly used by the client to keep a heartbeat to the server so the connection won't 172 | // be dropped. 173 | func (this *Client) Ping(onComplete OnCompleteFunc) error { 174 | return this.svc.ping(onComplete) 175 | } 176 | 177 | // Disconnect sends a single DISCONNECT message to the server. The client immediately 178 | // terminates after the sending of the DISCONNECT message. 179 | func (this *Client) Disconnect() { 180 | //msg := message.NewDisconnectMessage() 181 | this.svc.stop() 182 | } 183 | 184 | func (this *Client) getSession(svc *service, req *message.ConnectMessage, resp *message.ConnackMessage) error { 185 | //id := string(req.ClientId()) 186 | svc.sess = &sessions.Session{} 187 | return svc.sess.Init(req) 188 | } 189 | 190 | func (this *Client) checkConfiguration() { 191 | if this.KeepAlive == 0 { 192 | this.KeepAlive = DefaultKeepAlive 193 | } 194 | 195 | if this.ConnectTimeout == 0 { 196 | this.ConnectTimeout = DefaultConnectTimeout 197 | } 198 | 199 | if this.AckTimeout == 0 { 200 | this.AckTimeout = DefaultAckTimeout 201 | } 202 | 203 | if this.TimeoutRetries == 0 { 204 | this.TimeoutRetries = DefaultTimeoutRetries 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /examples/pingmq/pingmq.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Dataence, LLC. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // pingmq is developed to demonstrate the different use cases one can use SurgeMQ. 16 | // In this simplified use case, a network administrator can setup server uptime 17 | // monitoring system by periodically sending ICMP ECHO_REQUEST to all the IPs 18 | // in their network, and send the results to SurgeMQ. 19 | // 20 | // Then multiple clients can subscribe to results based on their different needs. 21 | // For example, a client maybe only interested in any failed ping attempts, as that 22 | // would indicate a host might be down. After a certain number of failures the 23 | // client may then raise some type of flag to indicate host down. 24 | // 25 | // There are three benefits of using SurgeMQ for this use case. First, with all the 26 | // different monitoring tools out there that wants to know if hosts are up or down, 27 | // they can all now subscribe to a single source of information. They no longer 28 | // need to write their own uptime tools. Second, assuming there are 5 monitoring 29 | // tools on the network that wants to ping each and every host, the small packets 30 | // are going to congest the network. The company can save 80% on their uptime 31 | // monitoring bandwidth by having a single tool that pings the hosts, and have the 32 | // rest subscribe to the results. Third/last, the company can enhance their security 33 | // posture by placing tighter restrictions on their firewalls if there's only a 34 | // single host that can send ICMP ECHO_REQUESTS to all other hosts. 35 | // 36 | // The following commands will run pingmq as a server, pinging the 8.8.8.0/28 CIDR 37 | // block, and publishing the results to /ping/success/{ip} and /ping/failure/{ip} 38 | // topics every 30 seconds. `sudo` is needed because we are using RAW sockets and 39 | // that requires root privilege. 40 | // 41 | // $ go build 42 | // $ sudo ./pingmq server -p 8.8.8.0/28 -i 30 43 | // 44 | // The following command will run pingmq as a client, subscribing to /ping/failure/+ 45 | // topic and receiving any failed ping attempts. 46 | // 47 | // $ ./pingmq client -t /ping/failure/+ 48 | // 8.8.8.6: Request timed out for seq 1 49 | // 50 | // The following command will run pingmq as a client, subscribing to /ping/failure/+ 51 | // topic and receiving any failed ping attempts. 52 | // 53 | // $ ./pingmq client -t /ping/success/+ 54 | // 8 bytes from 8.8.8.8: seq=1 ttl=56 tos=32 time=21.753711ms 55 | // 56 | // One can also subscribe to a specific IP by using the following command. 57 | // 58 | // $ ./pingmq client -t /ping/+/8.8.8.8 59 | // 8 bytes from 8.8.8.8: seq=1 ttl=56 tos=32 time=21.753711ms 60 | // 61 | package main 62 | 63 | import ( 64 | "fmt" 65 | "log" 66 | "os" 67 | "strings" 68 | "sync" 69 | "time" 70 | 71 | "github.com/spf13/cobra" 72 | "github.com/surge/netx" 73 | "github.com/surgemq/message" 74 | "github.com/surgemq/surgemq/service" 75 | ) 76 | 77 | type strlist []string 78 | 79 | func (this *strlist) String() string { 80 | return fmt.Sprint(*this) 81 | } 82 | 83 | func (this *strlist) Type() string { 84 | return "strlist" 85 | } 86 | 87 | func (this *strlist) Set(value string) error { 88 | for _, ip := range strings.Split(value, ",") { 89 | *this = append(*this, ip) 90 | } 91 | 92 | return nil 93 | } 94 | 95 | var ( 96 | pingmqCmd = &cobra.Command{ 97 | Use: "pingmq", 98 | Short: "Pingmq is a program designed to demonstrate the SurgeMQ usage.", 99 | Long: `Pingmq demonstrates the use of SurgeMQ by pinging a list of hosts, 100 | publishing the result to any clients subscribed to two topics: 101 | /ping/success/{ip} and /ping/failure/{ip}.`, 102 | } 103 | 104 | serverCmd = &cobra.Command{ 105 | Use: "server", 106 | Short: "server starts a SurgeMQ server and publishes to it all the ping results", 107 | } 108 | 109 | clientCmd = &cobra.Command{ 110 | Use: "client", 111 | Short: "client subscribes to the pingmq server and prints out the ping results", 112 | } 113 | 114 | serverURI string 115 | serverQuiet bool 116 | serverIPs strlist 117 | 118 | pingInterval int 119 | 120 | clientURI string 121 | clientTopics strlist 122 | 123 | s *service.Server 124 | c *service.Client 125 | p *netx.Pinger 126 | 127 | wg sync.WaitGroup 128 | 129 | done chan struct{} 130 | ) 131 | 132 | func init() { 133 | serverCmd.Flags().StringVarP(&serverURI, "uri", "u", "tcp://:5836", "URI to run the server on") 134 | serverCmd.Flags().BoolVarP(&serverQuiet, "quiet", "q", false, "print out ping results") 135 | serverCmd.Flags().VarP(&serverIPs, "ping", "p", "Comma separated list of IPv4 addresses to ping") 136 | serverCmd.Flags().IntVarP(&pingInterval, "interval", "i", 60, "ping interval in seconds") 137 | serverCmd.Run = server 138 | 139 | clientCmd.Flags().StringVarP(&clientURI, "server", "s", "tcp://127.0.0.1:5836", "PingMQ server to connect to") 140 | clientCmd.Flags().VarP(&clientTopics, "topic", "t", "Comma separated list of topics to subscribe to") 141 | clientCmd.Run = client 142 | 143 | pingmqCmd.AddCommand(serverCmd) 144 | pingmqCmd.AddCommand(clientCmd) 145 | 146 | done = make(chan struct{}) 147 | } 148 | 149 | func pinger() { 150 | p = &netx.Pinger{} 151 | if err := p.AddIPs(serverIPs); err != nil { 152 | log.Fatal(err) 153 | } 154 | 155 | cnt := 0 156 | tick := time.NewTicker(time.Duration(pingInterval) * time.Second) 157 | 158 | for { 159 | if cnt != 0 { 160 | <-tick.C 161 | } 162 | 163 | res, err := p.Start() 164 | if err != nil { 165 | log.Fatal(err) 166 | } 167 | 168 | for pr := range res { 169 | if !serverQuiet { 170 | log.Println(pr) 171 | } 172 | 173 | // Creates a new PUBLISH message with the appropriate contents for publishing 174 | pubmsg := message.NewPublishMessage() 175 | if pr.Err != nil { 176 | pubmsg.SetTopic([]byte(fmt.Sprintf("/ping/failure/%s", pr.Src))) 177 | } else { 178 | pubmsg.SetTopic([]byte(fmt.Sprintf("/ping/success/%s", pr.Src))) 179 | } 180 | pubmsg.SetQoS(0) 181 | 182 | b, err := pr.GobEncode() 183 | if err != nil { 184 | log.Printf("pinger: Error from GobEncode: %v\n", err) 185 | continue 186 | } 187 | 188 | pubmsg.SetPayload(b) 189 | 190 | // Publishes to the server 191 | s.Publish(pubmsg, nil) 192 | } 193 | 194 | p.Stop() 195 | cnt++ 196 | } 197 | } 198 | 199 | func server(cmd *cobra.Command, args []string) { 200 | // Create a new server 201 | s = &service.Server{ 202 | KeepAlive: 300, // seconds 203 | ConnectTimeout: 2, // seconds 204 | SessionsProvider: "mem", // keeps sessions in memory 205 | Authenticator: "mockSuccess", // always succeed 206 | TopicsProvider: "mem", // keeps topic subscriptions in memory 207 | } 208 | 209 | log.Printf("Starting server...") 210 | go func() { 211 | // Listen and serve connections at serverURI 212 | if err := s.ListenAndServe(serverURI); err != nil { 213 | log.Fatal(err) 214 | } 215 | }() 216 | time.Sleep(300 * time.Millisecond) 217 | 218 | log.Printf("Starting pinger...") 219 | pinger() 220 | } 221 | 222 | func client(cmd *cobra.Command, args []string) { 223 | // Instantiates a new Client 224 | c = &service.Client{} 225 | 226 | // Creates a new MQTT CONNECT message and sets the proper parameters 227 | msg := message.NewConnectMessage() 228 | msg.SetVersion(4) 229 | msg.SetCleanSession(true) 230 | msg.SetClientId([]byte(fmt.Sprintf("pingmqclient%d%d", os.Getpid(), time.Now().Unix()))) 231 | msg.SetKeepAlive(300) 232 | 233 | // Connects to the remote server at 127.0.0.1 port 1883 234 | if err := c.Connect(clientURI, msg); err != nil { 235 | log.Fatal(err) 236 | } 237 | 238 | // Creates a new SUBSCRIBE message to subscribe to topic "abc" 239 | submsg := message.NewSubscribeMessage() 240 | 241 | for _, t := range clientTopics { 242 | submsg.AddTopic([]byte(t), 0) 243 | } 244 | 245 | c.Subscribe(submsg, nil, onPublish) 246 | 247 | <-done 248 | } 249 | 250 | func onPublish(msg *message.PublishMessage) error { 251 | pr := &netx.PingResult{} 252 | if err := pr.GobDecode(msg.Payload()); err != nil { 253 | log.Printf("Error decoding ping result: %v\n", err) 254 | return err 255 | } 256 | 257 | log.Println(pr) 258 | return nil 259 | } 260 | 261 | func main() { 262 | pingmqCmd.Execute() 263 | } 264 | -------------------------------------------------------------------------------- /service/sendrecv.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "encoding/binary" 19 | "fmt" 20 | "io" 21 | "net" 22 | "time" 23 | 24 | "github.com/surge/glog" 25 | "github.com/surgemq/message" 26 | ) 27 | 28 | type netReader interface { 29 | io.Reader 30 | SetReadDeadline(t time.Time) error 31 | } 32 | 33 | type timeoutReader struct { 34 | d time.Duration 35 | conn netReader 36 | } 37 | 38 | func (r timeoutReader) Read(b []byte) (int, error) { 39 | if err := r.conn.SetReadDeadline(time.Now().Add(r.d)); err != nil { 40 | return 0, err 41 | } 42 | return r.conn.Read(b) 43 | } 44 | 45 | // receiver() reads data from the network, and writes the data into the incoming buffer 46 | func (this *service) receiver() { 47 | defer func() { 48 | // Let's recover from panic 49 | if r := recover(); r != nil { 50 | glog.Errorf("(%s) Recovering from panic: %v", this.cid(), r) 51 | } 52 | 53 | this.wgStopped.Done() 54 | 55 | glog.Debugf("(%s) Stopping receiver", this.cid()) 56 | }() 57 | 58 | glog.Debugf("(%s) Starting receiver", this.cid()) 59 | 60 | this.wgStarted.Done() 61 | 62 | switch conn := this.conn.(type) { 63 | case net.Conn: 64 | //glog.Debugf("server/handleConnection: Setting read deadline to %d", time.Second*time.Duration(this.keepAlive)) 65 | keepAlive := time.Second * time.Duration(this.keepAlive) 66 | r := timeoutReader{ 67 | d: keepAlive + (keepAlive / 2), 68 | conn: conn, 69 | } 70 | 71 | for { 72 | _, err := this.in.ReadFrom(r) 73 | 74 | if err != nil { 75 | if err != io.EOF { 76 | glog.Errorf("(%s) error reading from connection: %v", this.cid(), err) 77 | } 78 | return 79 | } 80 | } 81 | 82 | //case *websocket.Conn: 83 | // glog.Errorf("(%s) Websocket: %v", this.cid(), ErrInvalidConnectionType) 84 | 85 | default: 86 | glog.Errorf("(%s) %v", this.cid(), ErrInvalidConnectionType) 87 | } 88 | } 89 | 90 | // sender() writes data from the outgoing buffer to the network 91 | func (this *service) sender() { 92 | defer func() { 93 | // Let's recover from panic 94 | if r := recover(); r != nil { 95 | glog.Errorf("(%s) Recovering from panic: %v", this.cid(), r) 96 | } 97 | 98 | this.wgStopped.Done() 99 | 100 | glog.Debugf("(%s) Stopping sender", this.cid()) 101 | }() 102 | 103 | glog.Debugf("(%s) Starting sender", this.cid()) 104 | 105 | this.wgStarted.Done() 106 | 107 | switch conn := this.conn.(type) { 108 | case net.Conn: 109 | for { 110 | _, err := this.out.WriteTo(conn) 111 | 112 | if err != nil { 113 | if err != io.EOF { 114 | glog.Errorf("(%s) error writing data: %v", this.cid(), err) 115 | } 116 | return 117 | } 118 | } 119 | 120 | //case *websocket.Conn: 121 | // glog.Errorf("(%s) Websocket not supported", this.cid()) 122 | 123 | default: 124 | glog.Errorf("(%s) Invalid connection type", this.cid()) 125 | } 126 | } 127 | 128 | // peekMessageSize() reads, but not commits, enough bytes to determine the size of 129 | // the next message and returns the type and size. 130 | func (this *service) peekMessageSize() (message.MessageType, int, error) { 131 | var ( 132 | b []byte 133 | err error 134 | cnt int = 2 135 | ) 136 | 137 | if this.in == nil { 138 | err = ErrBufferNotReady 139 | return 0, 0, err 140 | } 141 | 142 | // Let's read enough bytes to get the message header (msg type, remaining length) 143 | for { 144 | // If we have read 5 bytes and still not done, then there's a problem. 145 | if cnt > 5 { 146 | return 0, 0, fmt.Errorf("sendrecv/peekMessageSize: 4th byte of remaining length has continuation bit set") 147 | } 148 | 149 | // Peek cnt bytes from the input buffer. 150 | b, err = this.in.ReadWait(cnt) 151 | if err != nil { 152 | return 0, 0, err 153 | } 154 | 155 | // If not enough bytes are returned, then continue until there's enough. 156 | if len(b) < cnt { 157 | continue 158 | } 159 | 160 | // If we got enough bytes, then check the last byte to see if the continuation 161 | // bit is set. If so, increment cnt and continue peeking 162 | if b[cnt-1] >= 0x80 { 163 | cnt++ 164 | } else { 165 | break 166 | } 167 | } 168 | 169 | // Get the remaining length of the message 170 | remlen, m := binary.Uvarint(b[1:]) 171 | 172 | // Total message length is remlen + 1 (msg type) + m (remlen bytes) 173 | total := int(remlen) + 1 + m 174 | 175 | mtype := message.MessageType(b[0] >> 4) 176 | 177 | return mtype, total, err 178 | } 179 | 180 | // peekMessage() reads a message from the buffer, but the bytes are NOT committed. 181 | // This means the buffer still thinks the bytes are not read yet. 182 | func (this *service) peekMessage(mtype message.MessageType, total int) (message.Message, int, error) { 183 | var ( 184 | b []byte 185 | err error 186 | i, n int 187 | msg message.Message 188 | ) 189 | 190 | if this.in == nil { 191 | return nil, 0, ErrBufferNotReady 192 | } 193 | 194 | // Peek until we get total bytes 195 | for i = 0; ; i++ { 196 | // Peek remlen bytes from the input buffer. 197 | b, err = this.in.ReadWait(total) 198 | if err != nil && err != ErrBufferInsufficientData { 199 | return nil, 0, err 200 | } 201 | 202 | // If not enough bytes are returned, then continue until there's enough. 203 | if len(b) >= total { 204 | break 205 | } 206 | } 207 | 208 | msg, err = mtype.New() 209 | if err != nil { 210 | return nil, 0, err 211 | } 212 | 213 | n, err = msg.Decode(b) 214 | return msg, n, err 215 | } 216 | 217 | // readMessage() reads and copies a message from the buffer. The buffer bytes are 218 | // committed as a result of the read. 219 | func (this *service) readMessage(mtype message.MessageType, total int) (message.Message, int, error) { 220 | var ( 221 | b []byte 222 | err error 223 | n int 224 | msg message.Message 225 | ) 226 | 227 | if this.in == nil { 228 | err = ErrBufferNotReady 229 | return nil, 0, err 230 | } 231 | 232 | if len(this.intmp) < total { 233 | this.intmp = make([]byte, total) 234 | } 235 | 236 | // Read until we get total bytes 237 | l := 0 238 | for l < total { 239 | n, err = this.in.Read(this.intmp[l:]) 240 | l += n 241 | glog.Debugf("read %d bytes, total %d", n, l) 242 | if err != nil { 243 | return nil, 0, err 244 | } 245 | } 246 | 247 | b = this.intmp[:total] 248 | 249 | msg, err = mtype.New() 250 | if err != nil { 251 | return msg, 0, err 252 | } 253 | 254 | n, err = msg.Decode(b) 255 | return msg, n, err 256 | } 257 | 258 | // writeMessage() writes a message to the outgoing buffer 259 | func (this *service) writeMessage(msg message.Message) (int, error) { 260 | var ( 261 | l int = msg.Len() 262 | m, n int 263 | err error 264 | buf []byte 265 | wrap bool 266 | ) 267 | 268 | if this.out == nil { 269 | return 0, ErrBufferNotReady 270 | } 271 | 272 | // This is to serialize writes to the underlying buffer. Multiple goroutines could 273 | // potentially get here because of calling Publish() or Subscribe() or other 274 | // functions that will send messages. For example, if a message is received in 275 | // another connetion, and the message needs to be published to this client, then 276 | // the Publish() function is called, and at the same time, another client could 277 | // do exactly the same thing. 278 | // 279 | // Not an ideal fix though. If possible we should remove mutex and be lockfree. 280 | // Mainly because when there's a large number of goroutines that want to publish 281 | // to this client, then they will all block. However, this will do for now. 282 | // 283 | // FIXME: Try to find a better way than a mutex...if possible. 284 | this.wmu.Lock() 285 | defer this.wmu.Unlock() 286 | 287 | buf, wrap, err = this.out.WriteWait(l) 288 | if err != nil { 289 | return 0, err 290 | } 291 | 292 | if wrap { 293 | if len(this.outtmp) < l { 294 | this.outtmp = make([]byte, l) 295 | } 296 | 297 | n, err = msg.Encode(this.outtmp[0:]) 298 | if err != nil { 299 | return 0, err 300 | } 301 | 302 | m, err = this.out.Write(this.outtmp[0:n]) 303 | if err != nil { 304 | return m, err 305 | } 306 | } else { 307 | n, err = msg.Encode(buf[0:]) 308 | if err != nil { 309 | return 0, err 310 | } 311 | 312 | m, err = this.out.WriteCommit(n) 313 | if err != nil { 314 | return 0, err 315 | } 316 | } 317 | 318 | this.outStat.increment(int64(m)) 319 | 320 | return m, nil 321 | } 322 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SurgeMQ 2 | ======= 3 | 4 | [![GoDoc](http://godoc.org/github.com/surgemq/surgemq?status.svg)](http://godoc.org/github.com/surgemq/surgemq) 5 | 6 | SurgeMQ is a high performance MQTT broker and client library that aims to be fully compliant with MQTT 3.1 and 3.1.1 specs. The primary package that's of interest is package [service](http://godoc.org/github.com/surgemq/surgemq/service). It provides the MQTT Server and Client services in a library form. 7 | 8 | SurgeMQ development is currently **on hold and unmaintained**. 9 | 10 | **This project should be considered unstable until further notice.** 11 | 12 | **Please check out [VolantMQ](https://github.com/VolantMQ/volantmq) as an alternative.** 13 | 14 | ### MQTT 15 | 16 | According to the [MQTT spec](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/mqtt-v3.1.1.html): 17 | 18 | > MQTT is a Client Server publish/subscribe messaging transport protocol. It is light weight, open, simple, and designed so as to be easy to implement. These characteristics make it ideal for use in many situations, including constrained environments such as for communication in Machine to Machine (M2M) and Internet of Things (IoT) contexts where a small code footprint is required and/or network bandwidth is at a premium. 19 | > 20 | > The protocol runs over TCP/IP, or over other network protocols that provide ordered, lossless, bi-directional connections. Its features include: 21 | > 22 | > * Use of the publish/subscribe message pattern which provides one-to-many message distribution and decoupling of applications. 23 | > * A messaging transport that is agnostic to the content of the payload. 24 | > * Three qualities of service for message delivery: 25 | > * "At most once", where messages are delivered according to the best efforts of the operating environment. Message loss can occur. This level could be used, for example, with ambient sensor data where it does not matter if an individual reading is lost as the next one will be published soon after. 26 | > * "At least once", where messages are assured to arrive but duplicates can occur. 27 | > * "Exactly once", where message are assured to arrive exactly once. This level could be used, for example, with billing systems where duplicate or lost messages could lead to incorrect charges being applied. 28 | > * A small transport overhead and protocol exchanges minimized to reduce network traffic. 29 | > * A mechanism to notify interested parties when an abnormal disconnection occurs. 30 | 31 | There's some very large implementation of MQTT such as [Facebook Messenger](https://www.facebook.com/notes/facebook-engineering/building-facebook-messenger/10150259350998920). There's also an active Eclipse project, [Paho](https://eclipse.org/paho/), that provides scalable open-source client implementations for many different languages, including C/C++, Java, Python, JavaScript, C# .Net and Go. 32 | 33 | ### Features, Limitations, and Future 34 | 35 | **Features** 36 | 37 | * Supports QOS 0, 1 and 2 messages 38 | * Supports will messages 39 | * Supports retained messages (add/remove) 40 | * Pretty much everything in the spec except for the list below 41 | 42 | **Limitations** 43 | 44 | * All features supported are in memory only. Once the server restarts everything is cleared. 45 | * However, all the components are written to be pluggable so one can write plugins based on the Go interfaces defined. 46 | * Message redelivery on reconnect is not currently supported. 47 | * Message offline queueing on disconnect is not supported. Though this is also not a specific requirement for MQTT. 48 | 49 | **Future** 50 | 51 | * Message re-delivery (DUP) 52 | * $SYS topics 53 | * Server bridge 54 | * Ack timeout/retry 55 | * Session persistence 56 | * Better authentication modules 57 | 58 | ### Performance 59 | 60 | Current performance benchmark of SurgeMQ, running all publishers, subscribers and broker on a single 4-core (2.8Ghz i7) MacBook Pro,is able to achieve: 61 | 62 | * over **400,000 MPS** in a 1:1 single publisher and single producer configuration 63 | * over **450,000 MPS** in a 20:1 fan-in configuration 64 | * over **750,000 MPS** in a 1:20 fan-out configuration 65 | * over **700,000 MPS** in a full mesh configuration with 20 clients 66 | 67 | ### Compatibility 68 | 69 | In addition, SurgeMQ has been tested with the following client libraries and it _seems_ to work: 70 | 71 | * libmosquitto 1.3.5 (in C) 72 | * Tested with the bundled test programs msgsps_pub and msgsps_sub 73 | * Paho MQTT Conformance/Interoperability Testing Suite (in Python). Tested with all 10 test cases, 3 did not pass. They are 74 | 1. "offline messages queueing test" which is not supported by SurgeMQ 75 | 2. "redelivery on reconnect test" which is not yet implemented by SurgeMQ 76 | 3. "run subscribe failure test" which is not a valid test 77 | * Paho Go Client Library (in Go) 78 | * Tested with one of the tests in the library, in fact, that tests is now part of the tests for SurgeMQ 79 | * Paho C Client library (in C) 80 | * Tested with most of the test cases and failed the same ones as the conformance test because the features are not yet implemented. 81 | * Actually I think there's a bug in the test suite as it calls the PUBLISH handler function for non-PUBLISH messages. 82 | 83 | ### Documentation 84 | 85 | Documentation is available at [godoc](http://godoc.org/github.com/surgemq/surgemq). 86 | 87 | More information regarding the design of the SurgeMQ is available at [zen 3.1](http://surgemq.com). 88 | 89 | ### License 90 | 91 | Copyright (c) 2014 Dataence, LLC. All rights reserved. 92 | 93 | Licensed under the Apache License, Version 2.0 (the "License"); 94 | you may not use this file except in compliance with the License. 95 | You may obtain a copy of the License at 96 | 97 | http://www.apache.org/licenses/LICENSE-2.0 98 | 99 | Unless required by applicable law or agreed to in writing, software 100 | distributed under the License is distributed on an "AS IS" BASIS, 101 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 102 | See the License for the specific language governing permissions and 103 | limitations under the License. 104 | 105 | 106 | ### Examples 107 | 108 | #### PingMQ 109 | 110 | `pingmq` is developed to demonstrate the different use cases one can use SurgeMQ. In this simplified use case, a network administrator can setup server uptime monitoring system by periodically sending ICMP ECHO_REQUEST to all the IPs in their network, and send the results to SurgeMQ. 111 | 112 | Then multiple clients can subscribe to results based on their different needs. For example, a client maybe only interested in any failed ping attempts, as that would indicate a host might be down. After a certain number of failures the client may then raise some type of flag to indicate host down. 113 | 114 | `pingmq` is available [here](https://github.com/surgemq/surgemq/tree/master/cmd/pingmq) and documentation is available at [godoc](http://godoc.org/github.com/surgemq/surgemq/cmd/pingmq). It utilizes [surge/ping](https://github.com/surge/ping) to perform the pings. 115 | 116 | #### Server Example 117 | 118 | ``` 119 | // Create a new server 120 | svr := &service.Server{ 121 | KeepAlive: 300, // seconds 122 | ConnectTimeout: 2, // seconds 123 | SessionsProvider: "mem", // keeps sessions in memory 124 | Authenticator: "mockSuccess", // always succeed 125 | TopicsProvider: "mem", // keeps topic subscriptions in memory 126 | } 127 | 128 | // Listen and serve connections at localhost:1883 129 | svr.ListenAndServe("tcp://:1883") 130 | ``` 131 | #### Client Example 132 | 133 | ``` 134 | // Instantiates a new Client 135 | c := &Client{} 136 | 137 | // Creates a new MQTT CONNECT message and sets the proper parameters 138 | msg := message.NewConnectMessage() 139 | msg.SetWillQos(1) 140 | msg.SetVersion(4) 141 | msg.SetCleanSession(true) 142 | msg.SetClientId([]byte("surgemq")) 143 | msg.SetKeepAlive(10) 144 | msg.SetWillTopic([]byte("will")) 145 | msg.SetWillMessage([]byte("send me home")) 146 | msg.SetUsername([]byte("surgemq")) 147 | msg.SetPassword([]byte("verysecret")) 148 | 149 | // Connects to the remote server at 127.0.0.1 port 1883 150 | c.Connect("tcp://127.0.0.1:1883", msg) 151 | 152 | // Creates a new SUBSCRIBE message to subscribe to topic "abc" 153 | submsg := message.NewSubscribeMessage() 154 | submsg.AddTopic([]byte("abc"), 0) 155 | 156 | // Subscribes to the topic by sending the message. The first nil in the function 157 | // call is a OnCompleteFunc that should handle the SUBACK message from the server. 158 | // Nil means we are ignoring the SUBACK messages. The second nil should be a 159 | // OnPublishFunc that handles any messages send to the client because of this 160 | // subscription. Nil means we are ignoring any PUBLISH messages for this topic. 161 | c.Subscribe(submsg, nil, nil) 162 | 163 | // Creates a new PUBLISH message with the appropriate contents for publishing 164 | pubmsg := message.NewPublishMessage() 165 | pubmsg.SetPacketId(pktid) 166 | pubmsg.SetTopic([]byte("abc")) 167 | pubmsg.SetPayload(make([]byte, 1024)) 168 | pubmsg.SetQoS(qos) 169 | 170 | // Publishes to the server by sending the message 171 | c.Publish(pubmsg, nil) 172 | 173 | // Disconnects from the server 174 | c.Disconnect() 175 | ``` 176 | 177 | -------------------------------------------------------------------------------- /sessions/ackqueue.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sessions 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "math" 21 | "sync" 22 | 23 | "github.com/surgemq/message" 24 | ) 25 | 26 | var ( 27 | errQueueFull error = errors.New("queue full") 28 | errQueueEmpty error = errors.New("queue empty") 29 | errWaitMessage error = errors.New("Invalid message to wait for ack") 30 | errAckMessage error = errors.New("Invalid message for acking") 31 | ) 32 | 33 | type ackmsg struct { 34 | // Message type of the message waiting for ack 35 | Mtype message.MessageType 36 | 37 | // Current state of the ack-waiting message 38 | State message.MessageType 39 | 40 | // Packet ID of the message. Every message that require ack'ing must have a valid 41 | // packet ID. Messages that have message I 42 | Pktid uint16 43 | 44 | // Slice containing the message bytes 45 | Msgbuf []byte 46 | 47 | // Slice containing the ack message bytes 48 | Ackbuf []byte 49 | 50 | // When ack cycle completes, call this function 51 | OnComplete interface{} 52 | } 53 | 54 | // Ackqueue is a growing queue implemented based on a ring buffer. As the buffer 55 | // gets full, it will auto-grow. 56 | // 57 | // Ackqueue is used to store messages that are waiting for acks to come back. There 58 | // are a few scenarios in which acks are required. 59 | // 1. Client sends SUBSCRIBE message to server, waits for SUBACK. 60 | // 2. Client sends UNSUBSCRIBE message to server, waits for UNSUBACK. 61 | // 3. Client sends PUBLISH QoS 1 message to server, waits for PUBACK. 62 | // 4. Server sends PUBLISH QoS 1 message to client, waits for PUBACK. 63 | // 5. Client sends PUBLISH QoS 2 message to server, waits for PUBREC. 64 | // 6. Server sends PUBREC message to client, waits for PUBREL. 65 | // 7. Client sends PUBREL message to server, waits for PUBCOMP. 66 | // 8. Server sends PUBLISH QoS 2 message to client, waits for PUBREC. 67 | // 9. Client sends PUBREC message to server, waits for PUBREL. 68 | // 10. Server sends PUBREL message to client, waits for PUBCOMP. 69 | // 11. Client sends PINGREQ message to server, waits for PINGRESP. 70 | type Ackqueue struct { 71 | size int64 72 | mask int64 73 | count int64 74 | head int64 75 | tail int64 76 | 77 | ping ackmsg 78 | ring []ackmsg 79 | emap map[uint16]int64 80 | 81 | ackdone []ackmsg 82 | 83 | mu sync.Mutex 84 | } 85 | 86 | func newAckqueue(n int) *Ackqueue { 87 | m := int64(n) 88 | if !powerOfTwo64(m) { 89 | m = roundUpPowerOfTwo64(m) 90 | } 91 | 92 | return &Ackqueue{ 93 | size: m, 94 | mask: m - 1, 95 | count: 0, 96 | head: 0, 97 | tail: 0, 98 | ring: make([]ackmsg, m), 99 | emap: make(map[uint16]int64, m), 100 | ackdone: make([]ackmsg, 0), 101 | } 102 | } 103 | 104 | // Wait() copies the message into a waiting queue, and waits for the corresponding 105 | // ack message to be received. 106 | func (this *Ackqueue) Wait(msg message.Message, onComplete interface{}) error { 107 | this.mu.Lock() 108 | defer this.mu.Unlock() 109 | 110 | switch msg := msg.(type) { 111 | case *message.PublishMessage: 112 | if msg.QoS() == message.QosAtMostOnce { 113 | //return fmt.Errorf("QoS 0 messages don't require ack") 114 | return errWaitMessage 115 | } 116 | 117 | this.insert(msg.PacketId(), msg, onComplete) 118 | 119 | case *message.SubscribeMessage: 120 | this.insert(msg.PacketId(), msg, onComplete) 121 | 122 | case *message.UnsubscribeMessage: 123 | this.insert(msg.PacketId(), msg, onComplete) 124 | 125 | case *message.PingreqMessage: 126 | this.ping = ackmsg{ 127 | Mtype: message.PINGREQ, 128 | State: message.RESERVED, 129 | OnComplete: onComplete, 130 | } 131 | 132 | default: 133 | return errWaitMessage 134 | } 135 | 136 | return nil 137 | } 138 | 139 | // Ack() takes the ack message supplied and updates the status of messages waiting. 140 | func (this *Ackqueue) Ack(msg message.Message) error { 141 | this.mu.Lock() 142 | defer this.mu.Unlock() 143 | 144 | switch msg.Type() { 145 | case message.PUBACK, message.PUBREC, message.PUBREL, message.PUBCOMP, message.SUBACK, message.UNSUBACK: 146 | // Check to see if the message w/ the same packet ID is in the queue 147 | i, ok := this.emap[msg.PacketId()] 148 | if ok { 149 | // If message w/ the packet ID exists, update the message state and copy 150 | // the ack message 151 | this.ring[i].State = msg.Type() 152 | 153 | ml := msg.Len() 154 | this.ring[i].Ackbuf = make([]byte, ml) 155 | 156 | _, err := msg.Encode(this.ring[i].Ackbuf) 157 | if err != nil { 158 | return err 159 | } 160 | //glog.Debugf("Acked: %v", msg) 161 | //} else { 162 | //glog.Debugf("Cannot ack %s message with packet ID %d", msg.Type(), msg.PacketId()) 163 | } 164 | 165 | case message.PINGRESP: 166 | if this.ping.Mtype == message.PINGREQ { 167 | this.ping.State = message.PINGRESP 168 | } 169 | 170 | default: 171 | return errAckMessage 172 | } 173 | 174 | return nil 175 | } 176 | 177 | // Acked() returns the list of messages that have completed the ack cycle. 178 | func (this *Ackqueue) Acked() []ackmsg { 179 | this.mu.Lock() 180 | defer this.mu.Unlock() 181 | 182 | this.ackdone = this.ackdone[0:0] 183 | 184 | if this.ping.State == message.PINGRESP { 185 | this.ackdone = append(this.ackdone, this.ping) 186 | this.ping = ackmsg{} 187 | } 188 | 189 | FORNOTEMPTY: 190 | for !this.empty() { 191 | switch this.ring[this.head].State { 192 | case message.PUBACK, message.PUBREL, message.PUBCOMP, message.SUBACK, message.UNSUBACK: 193 | this.ackdone = append(this.ackdone, this.ring[this.head]) 194 | this.removeHead() 195 | 196 | default: 197 | break FORNOTEMPTY 198 | } 199 | } 200 | 201 | return this.ackdone 202 | } 203 | 204 | func (this *Ackqueue) insert(pktid uint16, msg message.Message, onComplete interface{}) error { 205 | if this.full() { 206 | this.grow() 207 | } 208 | 209 | if _, ok := this.emap[pktid]; !ok { 210 | // message length 211 | ml := msg.Len() 212 | 213 | // ackmsg 214 | am := ackmsg{ 215 | Mtype: msg.Type(), 216 | State: message.RESERVED, 217 | Pktid: msg.PacketId(), 218 | Msgbuf: make([]byte, ml), 219 | OnComplete: onComplete, 220 | } 221 | 222 | if _, err := msg.Encode(am.Msgbuf); err != nil { 223 | return err 224 | } 225 | 226 | this.ring[this.tail] = am 227 | this.emap[pktid] = this.tail 228 | this.tail = this.increment(this.tail) 229 | this.count++ 230 | } else { 231 | // If packet w/ pktid already exist, then this must be a PUBLISH message 232 | // Other message types should never send with the same packet ID 233 | pm, ok := msg.(*message.PublishMessage) 234 | if !ok { 235 | return fmt.Errorf("ack/insert: duplicate packet ID for %s message", msg.Name()) 236 | } 237 | 238 | // If this is a publish message, then the DUP flag must be set. This is the 239 | // only scenario in which we will receive duplicate messages. 240 | if pm.Dup() { 241 | return fmt.Errorf("ack/insert: duplicate packet ID for PUBLISH message, but DUP flag is not set") 242 | } 243 | 244 | // Since it's a dup, there's really nothing we need to do. Moving on... 245 | } 246 | 247 | return nil 248 | } 249 | 250 | func (this *Ackqueue) removeHead() error { 251 | if this.empty() { 252 | return errQueueEmpty 253 | } 254 | 255 | it := this.ring[this.head] 256 | // set this to empty ackmsg{} to ensure GC will collect the buffer 257 | this.ring[this.head] = ackmsg{} 258 | this.head = this.increment(this.head) 259 | this.count-- 260 | delete(this.emap, it.Pktid) 261 | 262 | return nil 263 | } 264 | 265 | func (this *Ackqueue) grow() { 266 | if math.MaxInt64/2 < this.size { 267 | panic("new size will overflow int64") 268 | } 269 | 270 | newsize := this.size << 1 271 | newmask := newsize - 1 272 | newring := make([]ackmsg, newsize) 273 | 274 | if this.tail > this.head { 275 | copy(newring, this.ring[this.head:this.tail]) 276 | } else { 277 | copy(newring, this.ring[this.head:]) 278 | copy(newring[this.size-this.head:], this.ring[:this.tail]) 279 | } 280 | 281 | this.size = newsize 282 | this.mask = newmask 283 | this.ring = newring 284 | this.head = 0 285 | this.tail = this.count 286 | 287 | this.emap = make(map[uint16]int64, this.size) 288 | 289 | for i := int64(0); i < this.tail; i++ { 290 | this.emap[this.ring[i].Pktid] = i 291 | } 292 | } 293 | 294 | func (this *Ackqueue) len() int { 295 | return int(this.count) 296 | } 297 | 298 | func (this *Ackqueue) cap() int { 299 | return int(this.size) 300 | } 301 | 302 | func (this *Ackqueue) index(n int64) int64 { 303 | return n & this.mask 304 | } 305 | 306 | func (this *Ackqueue) full() bool { 307 | return this.count == this.size 308 | } 309 | 310 | func (this *Ackqueue) empty() bool { 311 | return this.count == 0 312 | } 313 | 314 | func (this *Ackqueue) increment(n int64) int64 { 315 | return this.index(n + 1) 316 | } 317 | 318 | func powerOfTwo64(n int64) bool { 319 | return n != 0 && (n&(n-1)) == 0 320 | } 321 | 322 | func roundUpPowerOfTwo64(n int64) int64 { 323 | n-- 324 | n |= n >> 1 325 | n |= n >> 2 326 | n |= n >> 4 327 | n |= n >> 8 328 | n |= n >> 16 329 | n |= n >> 32 330 | n++ 331 | 332 | return n 333 | } 334 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /service/process.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | "reflect" 22 | 23 | "github.com/surge/glog" 24 | "github.com/surgemq/message" 25 | "github.com/surgemq/surgemq/sessions" 26 | ) 27 | 28 | var ( 29 | errDisconnect = errors.New("Disconnect") 30 | ) 31 | 32 | // processor() reads messages from the incoming buffer and processes them 33 | func (this *service) processor() { 34 | defer func() { 35 | // Let's recover from panic 36 | if r := recover(); r != nil { 37 | //glog.Errorf("(%s) Recovering from panic: %v", this.cid(), r) 38 | } 39 | 40 | this.wgStopped.Done() 41 | this.stop() 42 | 43 | //glog.Debugf("(%s) Stopping processor", this.cid()) 44 | }() 45 | 46 | glog.Debugf("(%s) Starting processor", this.cid()) 47 | 48 | this.wgStarted.Done() 49 | 50 | for { 51 | // 1. Find out what message is next and the size of the message 52 | mtype, total, err := this.peekMessageSize() 53 | if err != nil { 54 | //if err != io.EOF { 55 | glog.Errorf("(%s) Error peeking next message size: %v", this.cid(), err) 56 | //} 57 | return 58 | } 59 | 60 | msg, n, err := this.peekMessage(mtype, total) 61 | if err != nil { 62 | //if err != io.EOF { 63 | glog.Errorf("(%s) Error peeking next message: %v", this.cid(), err) 64 | //} 65 | return 66 | } 67 | 68 | //glog.Debugf("(%s) Received: %s", this.cid(), msg) 69 | 70 | this.inStat.increment(int64(n)) 71 | 72 | // 5. Process the read message 73 | err = this.processIncoming(msg) 74 | if err != nil { 75 | if err != errDisconnect { 76 | glog.Errorf("(%s) Error processing %s: %v", this.cid(), msg.Name(), err) 77 | } else { 78 | return 79 | } 80 | } 81 | 82 | // 7. We should commit the bytes in the buffer so we can move on 83 | _, err = this.in.ReadCommit(total) 84 | if err != nil { 85 | if err != io.EOF { 86 | glog.Errorf("(%s) Error committing %d read bytes: %v", this.cid(), total, err) 87 | } 88 | return 89 | } 90 | 91 | // 7. Check to see if done is closed, if so, exit 92 | if this.isDone() && this.in.Len() == 0 { 93 | return 94 | } 95 | 96 | //if this.inStat.msgs%1000 == 0 { 97 | // glog.Debugf("(%s) Going to process message %d", this.cid(), this.inStat.msgs) 98 | //} 99 | } 100 | } 101 | 102 | func (this *service) processIncoming(msg message.Message) error { 103 | var err error = nil 104 | 105 | switch msg := msg.(type) { 106 | case *message.PublishMessage: 107 | // For PUBLISH message, we should figure out what QoS it is and process accordingly 108 | // If QoS == 0, we should just take the next step, no ack required 109 | // If QoS == 1, we should send back PUBACK, then take the next step 110 | // If QoS == 2, we need to put it in the ack queue, send back PUBREC 111 | err = this.processPublish(msg) 112 | 113 | case *message.PubackMessage: 114 | // For PUBACK message, it means QoS 1, we should send to ack queue 115 | this.sess.Pub1ack.Ack(msg) 116 | this.processAcked(this.sess.Pub1ack) 117 | 118 | case *message.PubrecMessage: 119 | // For PUBREC message, it means QoS 2, we should send to ack queue, and send back PUBREL 120 | if err = this.sess.Pub2out.Ack(msg); err != nil { 121 | break 122 | } 123 | 124 | resp := message.NewPubrelMessage() 125 | resp.SetPacketId(msg.PacketId()) 126 | _, err = this.writeMessage(resp) 127 | 128 | case *message.PubrelMessage: 129 | // For PUBREL message, it means QoS 2, we should send to ack queue, and send back PUBCOMP 130 | if err = this.sess.Pub2in.Ack(msg); err != nil { 131 | break 132 | } 133 | 134 | this.processAcked(this.sess.Pub2in) 135 | 136 | resp := message.NewPubcompMessage() 137 | resp.SetPacketId(msg.PacketId()) 138 | _, err = this.writeMessage(resp) 139 | 140 | case *message.PubcompMessage: 141 | // For PUBCOMP message, it means QoS 2, we should send to ack queue 142 | if err = this.sess.Pub2out.Ack(msg); err != nil { 143 | break 144 | } 145 | 146 | this.processAcked(this.sess.Pub2out) 147 | 148 | case *message.SubscribeMessage: 149 | // For SUBSCRIBE message, we should add subscriber, then send back SUBACK 150 | return this.processSubscribe(msg) 151 | 152 | case *message.SubackMessage: 153 | // For SUBACK message, we should send to ack queue 154 | this.sess.Suback.Ack(msg) 155 | this.processAcked(this.sess.Suback) 156 | 157 | case *message.UnsubscribeMessage: 158 | // For UNSUBSCRIBE message, we should remove subscriber, then send back UNSUBACK 159 | return this.processUnsubscribe(msg) 160 | 161 | case *message.UnsubackMessage: 162 | // For UNSUBACK message, we should send to ack queue 163 | this.sess.Unsuback.Ack(msg) 164 | this.processAcked(this.sess.Unsuback) 165 | 166 | case *message.PingreqMessage: 167 | // For PINGREQ message, we should send back PINGRESP 168 | resp := message.NewPingrespMessage() 169 | _, err = this.writeMessage(resp) 170 | 171 | case *message.PingrespMessage: 172 | this.sess.Pingack.Ack(msg) 173 | this.processAcked(this.sess.Pingack) 174 | 175 | case *message.DisconnectMessage: 176 | // For DISCONNECT message, we should quit 177 | this.sess.Cmsg.SetWillFlag(false) 178 | return errDisconnect 179 | 180 | default: 181 | return fmt.Errorf("(%s) invalid message type %s.", this.cid(), msg.Name()) 182 | } 183 | 184 | if err != nil { 185 | glog.Debugf("(%s) Error processing acked message: %v", this.cid(), err) 186 | } 187 | 188 | return err 189 | } 190 | 191 | func (this *service) processAcked(ackq *sessions.Ackqueue) { 192 | for _, ackmsg := range ackq.Acked() { 193 | // Let's get the messages from the saved message byte slices. 194 | msg, err := ackmsg.Mtype.New() 195 | if err != nil { 196 | glog.Errorf("process/processAcked: Unable to creating new %s message: %v", ackmsg.Mtype, err) 197 | continue 198 | } 199 | 200 | if _, err := msg.Decode(ackmsg.Msgbuf); err != nil { 201 | glog.Errorf("process/processAcked: Unable to decode %s message: %v", ackmsg.Mtype, err) 202 | continue 203 | } 204 | 205 | ack, err := ackmsg.State.New() 206 | if err != nil { 207 | glog.Errorf("process/processAcked: Unable to creating new %s message: %v", ackmsg.State, err) 208 | continue 209 | } 210 | 211 | if _, err := ack.Decode(ackmsg.Ackbuf); err != nil { 212 | glog.Errorf("process/processAcked: Unable to decode %s message: %v", ackmsg.State, err) 213 | continue 214 | } 215 | 216 | //glog.Debugf("(%s) Processing acked message: %v", this.cid(), ack) 217 | 218 | // - PUBACK if it's QoS 1 message. This is on the client side. 219 | // - PUBREL if it's QoS 2 message. This is on the server side. 220 | // - PUBCOMP if it's QoS 2 message. This is on the client side. 221 | // - SUBACK if it's a subscribe message. This is on the client side. 222 | // - UNSUBACK if it's a unsubscribe message. This is on the client side. 223 | switch ackmsg.State { 224 | case message.PUBREL: 225 | // If ack is PUBREL, that means the QoS 2 message sent by a remote client is 226 | // releassed, so let's publish it to other subscribers. 227 | if err = this.onPublish(msg.(*message.PublishMessage)); err != nil { 228 | glog.Errorf("(%s) Error processing ack'ed %s message: %v", this.cid(), ackmsg.Mtype, err) 229 | } 230 | 231 | case message.PUBACK, message.PUBCOMP, message.SUBACK, message.UNSUBACK, message.PINGRESP: 232 | glog.Debugf("process/processAcked: %s", ack) 233 | // If ack is PUBACK, that means the QoS 1 message sent by this service got 234 | // ack'ed. There's nothing to do other than calling onComplete() below. 235 | 236 | // If ack is PUBCOMP, that means the QoS 2 message sent by this service got 237 | // ack'ed. There's nothing to do other than calling onComplete() below. 238 | 239 | // If ack is SUBACK, that means the SUBSCRIBE message sent by this service 240 | // got ack'ed. There's nothing to do other than calling onComplete() below. 241 | 242 | // If ack is UNSUBACK, that means the SUBSCRIBE message sent by this service 243 | // got ack'ed. There's nothing to do other than calling onComplete() below. 244 | 245 | // If ack is PINGRESP, that means the PINGREQ message sent by this service 246 | // got ack'ed. There's nothing to do other than calling onComplete() below. 247 | 248 | err = nil 249 | 250 | default: 251 | glog.Errorf("(%s) Invalid ack message type %s.", this.cid(), ackmsg.State) 252 | continue 253 | } 254 | 255 | // Call the registered onComplete function 256 | if ackmsg.OnComplete != nil { 257 | onComplete, ok := ackmsg.OnComplete.(OnCompleteFunc) 258 | if !ok { 259 | glog.Errorf("process/processAcked: Error type asserting onComplete function: %v", reflect.TypeOf(ackmsg.OnComplete)) 260 | } else if onComplete != nil { 261 | if err := onComplete(msg, ack, nil); err != nil { 262 | glog.Errorf("process/processAcked: Error running onComplete(): %v", err) 263 | } 264 | } 265 | } 266 | } 267 | } 268 | 269 | // For PUBLISH message, we should figure out what QoS it is and process accordingly 270 | // If QoS == 0, we should just take the next step, no ack required 271 | // If QoS == 1, we should send back PUBACK, then take the next step 272 | // If QoS == 2, we need to put it in the ack queue, send back PUBREC 273 | func (this *service) processPublish(msg *message.PublishMessage) error { 274 | switch msg.QoS() { 275 | case message.QosExactlyOnce: 276 | this.sess.Pub2in.Wait(msg, nil) 277 | 278 | resp := message.NewPubrecMessage() 279 | resp.SetPacketId(msg.PacketId()) 280 | 281 | _, err := this.writeMessage(resp) 282 | return err 283 | 284 | case message.QosAtLeastOnce: 285 | resp := message.NewPubackMessage() 286 | resp.SetPacketId(msg.PacketId()) 287 | 288 | if _, err := this.writeMessage(resp); err != nil { 289 | return err 290 | } 291 | 292 | return this.onPublish(msg) 293 | 294 | case message.QosAtMostOnce: 295 | return this.onPublish(msg) 296 | } 297 | 298 | return fmt.Errorf("(%s) invalid message QoS %d.", this.cid(), msg.QoS()) 299 | } 300 | 301 | // For SUBSCRIBE message, we should add subscriber, then send back SUBACK 302 | func (this *service) processSubscribe(msg *message.SubscribeMessage) error { 303 | resp := message.NewSubackMessage() 304 | resp.SetPacketId(msg.PacketId()) 305 | 306 | // Subscribe to the different topics 307 | var retcodes []byte 308 | 309 | topics := msg.Topics() 310 | qos := msg.Qos() 311 | 312 | this.rmsgs = this.rmsgs[0:0] 313 | 314 | for i, t := range topics { 315 | rqos, err := this.topicsMgr.Subscribe(t, qos[i], &this.onpub) 316 | if err != nil { 317 | return err 318 | } 319 | this.sess.AddTopic(string(t), qos[i]) 320 | 321 | retcodes = append(retcodes, rqos) 322 | 323 | // yeah I am not checking errors here. If there's an error we don't want the 324 | // subscription to stop, just let it go. 325 | this.topicsMgr.Retained(t, &this.rmsgs) 326 | glog.Debugf("(%s) topic = %s, retained count = %d", this.cid(), string(t), len(this.rmsgs)) 327 | } 328 | 329 | if err := resp.AddReturnCodes(retcodes); err != nil { 330 | return err 331 | } 332 | 333 | if _, err := this.writeMessage(resp); err != nil { 334 | return err 335 | } 336 | 337 | for _, rm := range this.rmsgs { 338 | if err := this.publish(rm, nil); err != nil { 339 | glog.Errorf("service/processSubscribe: Error publishing retained message: %v", err) 340 | return err 341 | } 342 | } 343 | 344 | return nil 345 | } 346 | 347 | // For UNSUBSCRIBE message, we should remove the subscriber, and send back UNSUBACK 348 | func (this *service) processUnsubscribe(msg *message.UnsubscribeMessage) error { 349 | topics := msg.Topics() 350 | 351 | for _, t := range topics { 352 | this.topicsMgr.Unsubscribe(t, &this.onpub) 353 | this.sess.RemoveTopic(string(t)) 354 | } 355 | 356 | resp := message.NewUnsubackMessage() 357 | resp.SetPacketId(msg.PacketId()) 358 | 359 | _, err := this.writeMessage(resp) 360 | return err 361 | } 362 | 363 | // onPublish() is called when the server receives a PUBLISH message AND have completed 364 | // the ack cycle. This method will get the list of subscribers based on the publish 365 | // topic, and publishes the message to the list of subscribers. 366 | func (this *service) onPublish(msg *message.PublishMessage) error { 367 | if msg.Retain() { 368 | if err := this.topicsMgr.Retain(msg); err != nil { 369 | glog.Errorf("(%s) Error retaining message: %v", this.cid(), err) 370 | } 371 | } 372 | 373 | err := this.topicsMgr.Subscribers(msg.Topic(), msg.QoS(), &this.subs, &this.qoss) 374 | if err != nil { 375 | glog.Errorf("(%s) Error retrieving subscribers list: %v", this.cid(), err) 376 | return err 377 | } 378 | 379 | msg.SetRetain(false) 380 | 381 | //glog.Debugf("(%s) Publishing to topic %q and %d subscribers", this.cid(), string(msg.Topic()), len(this.subs)) 382 | for _, s := range this.subs { 383 | if s != nil { 384 | fn, ok := s.(*OnPublishFunc) 385 | if !ok { 386 | glog.Errorf("Invalid onPublish Function") 387 | return fmt.Errorf("Invalid onPublish Function") 388 | } else { 389 | (*fn)(msg) 390 | } 391 | } 392 | } 393 | 394 | return nil 395 | } 396 | -------------------------------------------------------------------------------- /service/service.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "fmt" 19 | "io" 20 | "sync" 21 | "sync/atomic" 22 | 23 | "github.com/surge/glog" 24 | "github.com/surgemq/message" 25 | "github.com/surgemq/surgemq/sessions" 26 | "github.com/surgemq/surgemq/topics" 27 | ) 28 | 29 | type ( 30 | OnCompleteFunc func(msg, ack message.Message, err error) error 31 | OnPublishFunc func(msg *message.PublishMessage) error 32 | ) 33 | 34 | type stat struct { 35 | bytes int64 36 | msgs int64 37 | } 38 | 39 | func (this *stat) increment(n int64) { 40 | atomic.AddInt64(&this.bytes, n) 41 | atomic.AddInt64(&this.msgs, 1) 42 | } 43 | 44 | var ( 45 | gsvcid uint64 = 0 46 | ) 47 | 48 | type service struct { 49 | // The ID of this service, it's not related to the Client ID, just a number that's 50 | // incremented for every new service. 51 | id uint64 52 | 53 | // Is this a client or server. It's set by either Connect (client) or 54 | // HandleConnection (server). 55 | client bool 56 | 57 | // The number of seconds to keep the connection live if there's no data. 58 | // If not set then default to 5 mins. 59 | keepAlive int 60 | 61 | // The number of seconds to wait for the CONNACK message before disconnecting. 62 | // If not set then default to 2 seconds. 63 | connectTimeout int 64 | 65 | // The number of seconds to wait for any ACK messages before failing. 66 | // If not set then default to 20 seconds. 67 | ackTimeout int 68 | 69 | // The number of times to retry sending a packet if ACK is not received. 70 | // If no set then default to 3 retries. 71 | timeoutRetries int 72 | 73 | // Network connection for this service 74 | conn io.Closer 75 | 76 | // Session manager for tracking all the clients 77 | sessMgr *sessions.Manager 78 | 79 | // Topics manager for all the client subscriptions 80 | topicsMgr *topics.Manager 81 | 82 | // sess is the session object for this MQTT session. It keeps track session variables 83 | // such as ClientId, KeepAlive, Username, etc 84 | sess *sessions.Session 85 | 86 | // Wait for the various goroutines to finish starting and stopping 87 | wgStarted sync.WaitGroup 88 | wgStopped sync.WaitGroup 89 | 90 | // writeMessage mutex - serializes writes to the outgoing buffer. 91 | wmu sync.Mutex 92 | 93 | // Whether this is service is closed or not. 94 | closed int64 95 | 96 | // Quit signal for determining when this service should end. If channel is closed, 97 | // then exit. 98 | done chan struct{} 99 | 100 | // Incoming data buffer. Bytes are read from the connection and put in here. 101 | in *buffer 102 | 103 | // Outgoing data buffer. Bytes written here are in turn written out to the connection. 104 | out *buffer 105 | 106 | // onpub is the method that gets added to the topic subscribers list by the 107 | // processSubscribe() method. When the server finishes the ack cycle for a 108 | // PUBLISH message, it will call the subscriber, which is this method. 109 | // 110 | // For the server, when this method is called, it means there's a message that 111 | // should be published to the client on the other end of this connection. So we 112 | // will call publish() to send the message. 113 | onpub OnPublishFunc 114 | 115 | inStat stat 116 | outStat stat 117 | 118 | intmp []byte 119 | outtmp []byte 120 | 121 | subs []interface{} 122 | qoss []byte 123 | rmsgs []*message.PublishMessage 124 | } 125 | 126 | func (this *service) start() error { 127 | var err error 128 | 129 | // Create the incoming ring buffer 130 | this.in, err = newBuffer(defaultBufferSize) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | // Create the outgoing ring buffer 136 | this.out, err = newBuffer(defaultBufferSize) 137 | if err != nil { 138 | return err 139 | } 140 | 141 | // If this is a server 142 | if !this.client { 143 | // Creat the onPublishFunc so it can be used for published messages 144 | this.onpub = func(msg *message.PublishMessage) error { 145 | if err := this.publish(msg, nil); err != nil { 146 | glog.Errorf("service/onPublish: Error publishing message: %v", err) 147 | return err 148 | } 149 | 150 | return nil 151 | } 152 | 153 | // If this is a recovered session, then add any topics it subscribed before 154 | topics, qoss, err := this.sess.Topics() 155 | if err != nil { 156 | return err 157 | } else { 158 | for i, t := range topics { 159 | this.topicsMgr.Subscribe([]byte(t), qoss[i], &this.onpub) 160 | } 161 | } 162 | } 163 | 164 | // Processor is responsible for reading messages out of the buffer and processing 165 | // them accordingly. 166 | this.wgStarted.Add(1) 167 | this.wgStopped.Add(1) 168 | go this.processor() 169 | 170 | // Receiver is responsible for reading from the connection and putting data into 171 | // a buffer. 172 | this.wgStarted.Add(1) 173 | this.wgStopped.Add(1) 174 | go this.receiver() 175 | 176 | // Sender is responsible for writing data in the buffer into the connection. 177 | this.wgStarted.Add(1) 178 | this.wgStopped.Add(1) 179 | go this.sender() 180 | 181 | // Wait for all the goroutines to start before returning 182 | this.wgStarted.Wait() 183 | 184 | return nil 185 | } 186 | 187 | // FIXME: The order of closing here causes panic sometimes. For example, if receiver 188 | // calls this, and closes the buffers, somehow it causes buffer.go:476 to panid. 189 | func (this *service) stop() { 190 | defer func() { 191 | // Let's recover from panic 192 | if r := recover(); r != nil { 193 | glog.Errorf("(%s) Recovering from panic: %v", this.cid(), r) 194 | } 195 | }() 196 | 197 | doit := atomic.CompareAndSwapInt64(&this.closed, 0, 1) 198 | if !doit { 199 | return 200 | } 201 | 202 | // Close quit channel, effectively telling all the goroutines it's time to quit 203 | if this.done != nil { 204 | glog.Debugf("(%s) closing this.done", this.cid()) 205 | close(this.done) 206 | } 207 | 208 | // Close the network connection 209 | if this.conn != nil { 210 | glog.Debugf("(%s) closing this.conn", this.cid()) 211 | this.conn.Close() 212 | } 213 | 214 | this.in.Close() 215 | this.out.Close() 216 | 217 | // Wait for all the goroutines to stop. 218 | this.wgStopped.Wait() 219 | 220 | glog.Debugf("(%s) Received %d bytes in %d messages.", this.cid(), this.inStat.bytes, this.inStat.msgs) 221 | glog.Debugf("(%s) Sent %d bytes in %d messages.", this.cid(), this.outStat.bytes, this.outStat.msgs) 222 | 223 | // Unsubscribe from all the topics for this client, only for the server side though 224 | if !this.client && this.sess != nil { 225 | topics, _, err := this.sess.Topics() 226 | if err != nil { 227 | glog.Errorf("(%s/%d): %v", this.cid(), this.id, err) 228 | } else { 229 | for _, t := range topics { 230 | if err := this.topicsMgr.Unsubscribe([]byte(t), &this.onpub); err != nil { 231 | glog.Errorf("(%s): Error unsubscribing topic %q: %v", this.cid(), t, err) 232 | } 233 | } 234 | } 235 | } 236 | 237 | // Publish will message if WillFlag is set. Server side only. 238 | if !this.client && this.sess.Cmsg.WillFlag() { 239 | glog.Infof("(%s) service/stop: connection unexpectedly closed. Sending Will.", this.cid()) 240 | this.onPublish(this.sess.Will) 241 | } 242 | 243 | // Remove the client topics manager 244 | if this.client { 245 | topics.Unregister(this.sess.ID()) 246 | } 247 | 248 | // Remove the session from session store if it's suppose to be clean session 249 | if this.sess.Cmsg.CleanSession() && this.sessMgr != nil { 250 | this.sessMgr.Del(this.sess.ID()) 251 | } 252 | 253 | this.conn = nil 254 | this.in = nil 255 | this.out = nil 256 | } 257 | 258 | func (this *service) publish(msg *message.PublishMessage, onComplete OnCompleteFunc) error { 259 | //glog.Debugf("service/publish: Publishing %s", msg) 260 | _, err := this.writeMessage(msg) 261 | if err != nil { 262 | return fmt.Errorf("(%s) Error sending %s message: %v", this.cid(), msg.Name(), err) 263 | } 264 | 265 | switch msg.QoS() { 266 | case message.QosAtMostOnce: 267 | if onComplete != nil { 268 | return onComplete(msg, nil, nil) 269 | } 270 | 271 | return nil 272 | 273 | case message.QosAtLeastOnce: 274 | return this.sess.Pub1ack.Wait(msg, onComplete) 275 | 276 | case message.QosExactlyOnce: 277 | return this.sess.Pub2out.Wait(msg, onComplete) 278 | } 279 | 280 | return nil 281 | } 282 | 283 | func (this *service) subscribe(msg *message.SubscribeMessage, onComplete OnCompleteFunc, onPublish OnPublishFunc) error { 284 | if onPublish == nil { 285 | return fmt.Errorf("onPublish function is nil. No need to subscribe.") 286 | } 287 | 288 | _, err := this.writeMessage(msg) 289 | if err != nil { 290 | return fmt.Errorf("(%s) Error sending %s message: %v", this.cid(), msg.Name(), err) 291 | } 292 | 293 | var onc OnCompleteFunc = func(msg, ack message.Message, err error) error { 294 | onComplete := onComplete 295 | onPublish := onPublish 296 | 297 | if err != nil { 298 | if onComplete != nil { 299 | return onComplete(msg, ack, err) 300 | } 301 | return err 302 | } 303 | 304 | sub, ok := msg.(*message.SubscribeMessage) 305 | if !ok { 306 | if onComplete != nil { 307 | return onComplete(msg, ack, fmt.Errorf("Invalid SubscribeMessage received")) 308 | } 309 | return nil 310 | } 311 | 312 | suback, ok := ack.(*message.SubackMessage) 313 | if !ok { 314 | if onComplete != nil { 315 | return onComplete(msg, ack, fmt.Errorf("Invalid SubackMessage received")) 316 | } 317 | return nil 318 | } 319 | 320 | if sub.PacketId() != suback.PacketId() { 321 | if onComplete != nil { 322 | return onComplete(msg, ack, fmt.Errorf("Sub and Suback packet ID not the same. %d != %d.", sub.PacketId(), suback.PacketId())) 323 | } 324 | return nil 325 | } 326 | 327 | retcodes := suback.ReturnCodes() 328 | topics := sub.Topics() 329 | 330 | if len(topics) != len(retcodes) { 331 | if onComplete != nil { 332 | return onComplete(msg, ack, fmt.Errorf("Incorrect number of return codes received. Expecting %d, got %d.", len(topics), len(retcodes))) 333 | } 334 | return nil 335 | } 336 | 337 | var err2 error = nil 338 | 339 | for i, t := range topics { 340 | c := retcodes[i] 341 | 342 | if c == message.QosFailure { 343 | err2 = fmt.Errorf("Failed to subscribe to '%s'\n%v", string(t), err2) 344 | } else { 345 | this.sess.AddTopic(string(t), c) 346 | _, err := this.topicsMgr.Subscribe(t, c, &onPublish) 347 | if err != nil { 348 | err2 = fmt.Errorf("Failed to subscribe to '%s' (%v)\n%v", string(t), err, err2) 349 | } 350 | } 351 | } 352 | 353 | if onComplete != nil { 354 | return onComplete(msg, ack, err2) 355 | } 356 | 357 | return err2 358 | } 359 | 360 | return this.sess.Suback.Wait(msg, onc) 361 | } 362 | 363 | func (this *service) unsubscribe(msg *message.UnsubscribeMessage, onComplete OnCompleteFunc) error { 364 | _, err := this.writeMessage(msg) 365 | if err != nil { 366 | return fmt.Errorf("(%s) Error sending %s message: %v", this.cid(), msg.Name(), err) 367 | } 368 | 369 | var onc OnCompleteFunc = func(msg, ack message.Message, err error) error { 370 | onComplete := onComplete 371 | 372 | if err != nil { 373 | if onComplete != nil { 374 | return onComplete(msg, ack, err) 375 | } 376 | return err 377 | } 378 | 379 | unsub, ok := msg.(*message.UnsubscribeMessage) 380 | if !ok { 381 | if onComplete != nil { 382 | return onComplete(msg, ack, fmt.Errorf("Invalid UnsubscribeMessage received")) 383 | } 384 | return nil 385 | } 386 | 387 | unsuback, ok := ack.(*message.UnsubackMessage) 388 | if !ok { 389 | if onComplete != nil { 390 | return onComplete(msg, ack, fmt.Errorf("Invalid UnsubackMessage received")) 391 | } 392 | return nil 393 | } 394 | 395 | if unsub.PacketId() != unsuback.PacketId() { 396 | if onComplete != nil { 397 | return onComplete(msg, ack, fmt.Errorf("Unsub and Unsuback packet ID not the same. %d != %d.", unsub.PacketId(), unsuback.PacketId())) 398 | } 399 | return nil 400 | } 401 | 402 | var err2 error = nil 403 | 404 | for _, tb := range unsub.Topics() { 405 | // Remove all subscribers, which basically it's just this client, since 406 | // each client has it's own topic tree. 407 | err := this.topicsMgr.Unsubscribe(tb, nil) 408 | if err != nil { 409 | err2 = fmt.Errorf("%v\n%v", err2, err) 410 | } 411 | 412 | this.sess.RemoveTopic(string(tb)) 413 | } 414 | 415 | if onComplete != nil { 416 | return onComplete(msg, ack, err2) 417 | } 418 | 419 | return err2 420 | } 421 | 422 | return this.sess.Unsuback.Wait(msg, onc) 423 | } 424 | 425 | func (this *service) ping(onComplete OnCompleteFunc) error { 426 | msg := message.NewPingreqMessage() 427 | 428 | _, err := this.writeMessage(msg) 429 | if err != nil { 430 | return fmt.Errorf("(%s) Error sending %s message: %v", this.cid(), msg.Name(), err) 431 | } 432 | 433 | return this.sess.Pingack.Wait(msg, onComplete) 434 | } 435 | 436 | func (this *service) isDone() bool { 437 | select { 438 | case <-this.done: 439 | return true 440 | 441 | default: 442 | } 443 | 444 | return false 445 | } 446 | 447 | func (this *service) cid() string { 448 | return fmt.Sprintf("%d/%s", this.id, this.sess.ID()) 449 | } 450 | -------------------------------------------------------------------------------- /service/server.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "io" 21 | "net" 22 | "net/url" 23 | "sync" 24 | "sync/atomic" 25 | "time" 26 | 27 | "github.com/surge/glog" 28 | "github.com/surgemq/message" 29 | "github.com/surgemq/surgemq/auth" 30 | "github.com/surgemq/surgemq/sessions" 31 | "github.com/surgemq/surgemq/topics" 32 | ) 33 | 34 | var ( 35 | ErrInvalidConnectionType error = errors.New("service: Invalid connection type") 36 | ErrInvalidSubscriber error = errors.New("service: Invalid subscriber") 37 | ErrBufferNotReady error = errors.New("service: buffer is not ready") 38 | ErrBufferInsufficientData error = errors.New("service: buffer has insufficient data.") 39 | ) 40 | 41 | const ( 42 | DefaultKeepAlive = 300 43 | DefaultConnectTimeout = 2 44 | DefaultAckTimeout = 20 45 | DefaultTimeoutRetries = 3 46 | DefaultSessionsProvider = "mem" 47 | DefaultAuthenticator = "mockSuccess" 48 | DefaultTopicsProvider = "mem" 49 | ) 50 | 51 | // Server is a library implementation of the MQTT server that, as best it can, complies 52 | // with the MQTT 3.1 and 3.1.1 specs. 53 | type Server struct { 54 | // The number of seconds to keep the connection live if there's no data. 55 | // If not set then default to 5 mins. 56 | KeepAlive int 57 | 58 | // The number of seconds to wait for the CONNECT message before disconnecting. 59 | // If not set then default to 2 seconds. 60 | ConnectTimeout int 61 | 62 | // The number of seconds to wait for any ACK messages before failing. 63 | // If not set then default to 20 seconds. 64 | AckTimeout int 65 | 66 | // The number of times to retry sending a packet if ACK is not received. 67 | // If no set then default to 3 retries. 68 | TimeoutRetries int 69 | 70 | // Authenticator is the authenticator used to check username and password sent 71 | // in the CONNECT message. If not set then default to "mockSuccess". 72 | Authenticator string 73 | 74 | // SessionsProvider is the session store that keeps all the Session objects. 75 | // This is the store to check if CleanSession is set to 0 in the CONNECT message. 76 | // If not set then default to "mem". 77 | SessionsProvider string 78 | 79 | // TopicsProvider is the topic store that keeps all the subscription topics. 80 | // If not set then default to "mem". 81 | TopicsProvider string 82 | 83 | // authMgr is the authentication manager that we are going to use for authenticating 84 | // incoming connections 85 | authMgr *auth.Manager 86 | 87 | // sessMgr is the sessions manager for keeping track of the sessions 88 | sessMgr *sessions.Manager 89 | 90 | // topicsMgr is the topics manager for keeping track of subscriptions 91 | topicsMgr *topics.Manager 92 | 93 | // The quit channel for the server. If the server detects that this channel 94 | // is closed, then it's a signal for it to shutdown as well. 95 | quit chan struct{} 96 | 97 | ln net.Listener 98 | 99 | // A list of services created by the server. We keep track of them so we can 100 | // gracefully shut them down if they are still alive when the server goes down. 101 | svcs []*service 102 | 103 | // Mutex for updating svcs 104 | mu sync.Mutex 105 | 106 | // A indicator on whether this server is running 107 | running int32 108 | 109 | // A indicator on whether this server has already checked configuration 110 | configOnce sync.Once 111 | 112 | subs []interface{} 113 | qoss []byte 114 | } 115 | 116 | // ListenAndServe listents to connections on the URI requested, and handles any 117 | // incoming MQTT client sessions. It should not return until Close() is called 118 | // or if there's some critical error that stops the server from running. The URI 119 | // supplied should be of the form "protocol://host:port" that can be parsed by 120 | // url.Parse(). For example, an URI could be "tcp://0.0.0.0:1883". 121 | func (this *Server) ListenAndServe(uri string) error { 122 | defer atomic.CompareAndSwapInt32(&this.running, 1, 0) 123 | 124 | if !atomic.CompareAndSwapInt32(&this.running, 0, 1) { 125 | return fmt.Errorf("server/ListenAndServe: Server is already running") 126 | } 127 | 128 | this.quit = make(chan struct{}) 129 | 130 | u, err := url.Parse(uri) 131 | if err != nil { 132 | return err 133 | } 134 | 135 | this.ln, err = net.Listen(u.Scheme, u.Host) 136 | if err != nil { 137 | return err 138 | } 139 | defer this.ln.Close() 140 | 141 | glog.Infof("server/ListenAndServe: server is ready...") 142 | 143 | var tempDelay time.Duration // how long to sleep on accept failure 144 | 145 | for { 146 | conn, err := this.ln.Accept() 147 | 148 | if err != nil { 149 | // http://zhen.org/blog/graceful-shutdown-of-go-net-dot-listeners/ 150 | select { 151 | case <-this.quit: 152 | return nil 153 | 154 | default: 155 | } 156 | 157 | // Borrowed from go1.3.3/src/pkg/net/http/server.go:1699 158 | if ne, ok := err.(net.Error); ok && ne.Temporary() { 159 | if tempDelay == 0 { 160 | tempDelay = 5 * time.Millisecond 161 | } else { 162 | tempDelay *= 2 163 | } 164 | if max := 1 * time.Second; tempDelay > max { 165 | tempDelay = max 166 | } 167 | glog.Errorf("server/ListenAndServe: Accept error: %v; retrying in %v", err, tempDelay) 168 | time.Sleep(tempDelay) 169 | continue 170 | } 171 | return err 172 | } 173 | 174 | go this.handleConnection(conn) 175 | } 176 | } 177 | 178 | // Publish sends a single MQTT PUBLISH message to the server. On completion, the 179 | // supplied OnCompleteFunc is called. For QOS 0 messages, onComplete is called 180 | // immediately after the message is sent to the outgoing buffer. For QOS 1 messages, 181 | // onComplete is called when PUBACK is received. For QOS 2 messages, onComplete is 182 | // called after the PUBCOMP message is received. 183 | func (this *Server) Publish(msg *message.PublishMessage, onComplete OnCompleteFunc) error { 184 | if err := this.checkConfiguration(); err != nil { 185 | return err 186 | } 187 | 188 | if msg.Retain() { 189 | if err := this.topicsMgr.Retain(msg); err != nil { 190 | glog.Errorf("Error retaining message: %v", err) 191 | } 192 | } 193 | 194 | if err := this.topicsMgr.Subscribers(msg.Topic(), msg.QoS(), &this.subs, &this.qoss); err != nil { 195 | return err 196 | } 197 | 198 | msg.SetRetain(false) 199 | 200 | //glog.Debugf("(server) Publishing to topic %q and %d subscribers", string(msg.Topic()), len(this.subs)) 201 | for _, s := range this.subs { 202 | if s != nil { 203 | fn, ok := s.(*OnPublishFunc) 204 | if !ok { 205 | glog.Errorf("Invalid onPublish Function") 206 | } else { 207 | (*fn)(msg) 208 | } 209 | } 210 | } 211 | 212 | return nil 213 | } 214 | 215 | // Close terminates the server by shutting down all the client connections and closing 216 | // the listener. It will, as best it can, clean up after itself. 217 | func (this *Server) Close() error { 218 | // By closing the quit channel, we are telling the server to stop accepting new 219 | // connection. 220 | close(this.quit) 221 | 222 | // We then close the net.Listener, which will force Accept() to return if it's 223 | // blocked waiting for new connections. 224 | this.ln.Close() 225 | 226 | for _, svc := range this.svcs { 227 | glog.Infof("Stopping service %d", svc.id) 228 | svc.stop() 229 | } 230 | 231 | if this.sessMgr != nil { 232 | this.sessMgr.Close() 233 | } 234 | 235 | if this.topicsMgr != nil { 236 | this.topicsMgr.Close() 237 | } 238 | 239 | return nil 240 | } 241 | 242 | // HandleConnection is for the broker to handle an incoming connection from a client 243 | func (this *Server) handleConnection(c io.Closer) (svc *service, err error) { 244 | if c == nil { 245 | return nil, ErrInvalidConnectionType 246 | } 247 | 248 | defer func() { 249 | if err != nil { 250 | c.Close() 251 | } 252 | }() 253 | 254 | err = this.checkConfiguration() 255 | if err != nil { 256 | return nil, err 257 | } 258 | 259 | conn, ok := c.(net.Conn) 260 | if !ok { 261 | return nil, ErrInvalidConnectionType 262 | } 263 | 264 | // To establish a connection, we must 265 | // 1. Read and decode the message.ConnectMessage from the wire 266 | // 2. If no decoding errors, then authenticate using username and password. 267 | // Otherwise, write out to the wire message.ConnackMessage with 268 | // appropriate error. 269 | // 3. If authentication is successful, then either create a new session or 270 | // retrieve existing session 271 | // 4. Write out to the wire a successful message.ConnackMessage message 272 | 273 | // Read the CONNECT message from the wire, if error, then check to see if it's 274 | // a CONNACK error. If it's CONNACK error, send the proper CONNACK error back 275 | // to client. Exit regardless of error type. 276 | 277 | conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(this.ConnectTimeout))) 278 | 279 | resp := message.NewConnackMessage() 280 | 281 | req, err := getConnectMessage(conn) 282 | if err != nil { 283 | if cerr, ok := err.(message.ConnackCode); ok { 284 | //glog.Debugf("request message: %s\nresponse message: %s\nerror : %v", mreq, resp, err) 285 | resp.SetReturnCode(cerr) 286 | resp.SetSessionPresent(false) 287 | writeMessage(conn, resp) 288 | } 289 | return nil, err 290 | } 291 | 292 | // Authenticate the user, if error, return error and exit 293 | if err = this.authMgr.Authenticate(string(req.Username()), string(req.Password())); err != nil { 294 | resp.SetReturnCode(message.ErrBadUsernameOrPassword) 295 | resp.SetSessionPresent(false) 296 | writeMessage(conn, resp) 297 | return nil, err 298 | } 299 | 300 | if req.KeepAlive() == 0 { 301 | req.SetKeepAlive(minKeepAlive) 302 | } 303 | 304 | svc = &service{ 305 | id: atomic.AddUint64(&gsvcid, 1), 306 | client: false, 307 | 308 | keepAlive: int(req.KeepAlive()), 309 | connectTimeout: this.ConnectTimeout, 310 | ackTimeout: this.AckTimeout, 311 | timeoutRetries: this.TimeoutRetries, 312 | 313 | conn: conn, 314 | sessMgr: this.sessMgr, 315 | topicsMgr: this.topicsMgr, 316 | } 317 | 318 | err = this.getSession(svc, req, resp) 319 | if err != nil { 320 | return nil, err 321 | } 322 | 323 | resp.SetReturnCode(message.ConnectionAccepted) 324 | 325 | if err = writeMessage(c, resp); err != nil { 326 | return nil, err 327 | } 328 | 329 | svc.inStat.increment(int64(req.Len())) 330 | svc.outStat.increment(int64(resp.Len())) 331 | 332 | if err := svc.start(); err != nil { 333 | svc.stop() 334 | return nil, err 335 | } 336 | 337 | //this.mu.Lock() 338 | //this.svcs = append(this.svcs, svc) 339 | //this.mu.Unlock() 340 | 341 | glog.Infof("(%s) server/handleConnection: Connection established.", svc.cid()) 342 | 343 | return svc, nil 344 | } 345 | 346 | func (this *Server) checkConfiguration() error { 347 | var err error 348 | 349 | this.configOnce.Do(func() { 350 | if this.KeepAlive == 0 { 351 | this.KeepAlive = DefaultKeepAlive 352 | } 353 | 354 | if this.ConnectTimeout == 0 { 355 | this.ConnectTimeout = DefaultConnectTimeout 356 | } 357 | 358 | if this.AckTimeout == 0 { 359 | this.AckTimeout = DefaultAckTimeout 360 | } 361 | 362 | if this.TimeoutRetries == 0 { 363 | this.TimeoutRetries = DefaultTimeoutRetries 364 | } 365 | 366 | if this.Authenticator == "" { 367 | this.Authenticator = "mockSuccess" 368 | } 369 | 370 | this.authMgr, err = auth.NewManager(this.Authenticator) 371 | if err != nil { 372 | return 373 | } 374 | 375 | if this.SessionsProvider == "" { 376 | this.SessionsProvider = "mem" 377 | } 378 | 379 | this.sessMgr, err = sessions.NewManager(this.SessionsProvider) 380 | if err != nil { 381 | return 382 | } 383 | 384 | if this.TopicsProvider == "" { 385 | this.TopicsProvider = "mem" 386 | } 387 | 388 | this.topicsMgr, err = topics.NewManager(this.TopicsProvider) 389 | 390 | return 391 | }) 392 | 393 | return err 394 | } 395 | 396 | func (this *Server) getSession(svc *service, req *message.ConnectMessage, resp *message.ConnackMessage) error { 397 | // If CleanSession is set to 0, the server MUST resume communications with the 398 | // client based on state from the current session, as identified by the client 399 | // identifier. If there is no session associated with the client identifier the 400 | // server must create a new session. 401 | // 402 | // If CleanSession is set to 1, the client and server must discard any previous 403 | // session and start a new one. This session lasts as long as the network c 404 | // onnection. State data associated with this session must not be reused in any 405 | // subsequent session. 406 | 407 | var err error 408 | 409 | // Check to see if the client supplied an ID, if not, generate one and set 410 | // clean session. 411 | if len(req.ClientId()) == 0 { 412 | req.SetClientId([]byte(fmt.Sprintf("internalclient%d", svc.id))) 413 | req.SetCleanSession(true) 414 | } 415 | 416 | cid := string(req.ClientId()) 417 | 418 | // If CleanSession is NOT set, check the session store for existing session. 419 | // If found, return it. 420 | if !req.CleanSession() { 421 | if svc.sess, err = this.sessMgr.Get(cid); err == nil { 422 | resp.SetSessionPresent(true) 423 | 424 | if err := svc.sess.Update(req); err != nil { 425 | return err 426 | } 427 | } 428 | } 429 | 430 | // If CleanSession, or no existing session found, then create a new one 431 | if svc.sess == nil { 432 | if svc.sess, err = this.sessMgr.New(cid); err != nil { 433 | return err 434 | } 435 | 436 | resp.SetSessionPresent(false) 437 | 438 | if err := svc.sess.Init(req); err != nil { 439 | return err 440 | } 441 | } 442 | 443 | return nil 444 | } 445 | -------------------------------------------------------------------------------- /topics/memtopics.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package topics 16 | 17 | import ( 18 | "fmt" 19 | "reflect" 20 | "sync" 21 | 22 | "github.com/surgemq/message" 23 | ) 24 | 25 | var ( 26 | // MaxQosAllowed is the maximum QOS supported by this server 27 | MaxQosAllowed = message.QosExactlyOnce 28 | ) 29 | 30 | var _ TopicsProvider = (*memTopics)(nil) 31 | 32 | type memTopics struct { 33 | // Sub/unsub mutex 34 | smu sync.RWMutex 35 | // Subscription tree 36 | sroot *snode 37 | 38 | // Retained message mutex 39 | rmu sync.RWMutex 40 | // Retained messages topic tree 41 | rroot *rnode 42 | } 43 | 44 | func init() { 45 | Register("mem", NewMemProvider()) 46 | } 47 | 48 | var _ TopicsProvider = (*memTopics)(nil) 49 | 50 | // NewMemProvider returns an new instance of the memTopics, which is implements the 51 | // TopicsProvider interface. memProvider is a hidden struct that stores the topic 52 | // subscriptions and retained messages in memory. The content is not persistend so 53 | // when the server goes, everything will be gone. Use with care. 54 | func NewMemProvider() *memTopics { 55 | return &memTopics{ 56 | sroot: newSNode(), 57 | rroot: newRNode(), 58 | } 59 | } 60 | 61 | func (this *memTopics) Subscribe(topic []byte, qos byte, sub interface{}) (byte, error) { 62 | if !message.ValidQos(qos) { 63 | return message.QosFailure, fmt.Errorf("Invalid QoS %d", qos) 64 | } 65 | 66 | if sub == nil { 67 | return message.QosFailure, fmt.Errorf("Subscriber cannot be nil") 68 | } 69 | 70 | this.smu.Lock() 71 | defer this.smu.Unlock() 72 | 73 | if qos > MaxQosAllowed { 74 | qos = MaxQosAllowed 75 | } 76 | 77 | if err := this.sroot.sinsert(topic, qos, sub); err != nil { 78 | return message.QosFailure, err 79 | } 80 | 81 | return qos, nil 82 | } 83 | 84 | func (this *memTopics) Unsubscribe(topic []byte, sub interface{}) error { 85 | this.smu.Lock() 86 | defer this.smu.Unlock() 87 | 88 | return this.sroot.sremove(topic, sub) 89 | } 90 | 91 | // Returned values will be invalidated by the next Subscribers call 92 | func (this *memTopics) Subscribers(topic []byte, qos byte, subs *[]interface{}, qoss *[]byte) error { 93 | if !message.ValidQos(qos) { 94 | return fmt.Errorf("Invalid QoS %d", qos) 95 | } 96 | 97 | this.smu.RLock() 98 | defer this.smu.RUnlock() 99 | 100 | *subs = (*subs)[0:0] 101 | *qoss = (*qoss)[0:0] 102 | 103 | return this.sroot.smatch(topic, qos, subs, qoss) 104 | } 105 | 106 | func (this *memTopics) Retain(msg *message.PublishMessage) error { 107 | this.rmu.Lock() 108 | defer this.rmu.Unlock() 109 | 110 | // So apparently, at least according to the MQTT Conformance/Interoperability 111 | // Testing, that a payload of 0 means delete the retain message. 112 | // https://eclipse.org/paho/clients/testing/ 113 | if len(msg.Payload()) == 0 { 114 | return this.rroot.rremove(msg.Topic()) 115 | } 116 | 117 | return this.rroot.rinsert(msg.Topic(), msg) 118 | } 119 | 120 | func (this *memTopics) Retained(topic []byte, msgs *[]*message.PublishMessage) error { 121 | this.rmu.RLock() 122 | defer this.rmu.RUnlock() 123 | 124 | return this.rroot.rmatch(topic, msgs) 125 | } 126 | 127 | func (this *memTopics) Close() error { 128 | this.sroot = nil 129 | this.rroot = nil 130 | return nil 131 | } 132 | 133 | // subscrition nodes 134 | type snode struct { 135 | // If this is the end of the topic string, then add subscribers here 136 | subs []interface{} 137 | qos []byte 138 | 139 | // Otherwise add the next topic level here 140 | snodes map[string]*snode 141 | } 142 | 143 | func newSNode() *snode { 144 | return &snode{ 145 | snodes: make(map[string]*snode), 146 | } 147 | } 148 | 149 | func (this *snode) sinsert(topic []byte, qos byte, sub interface{}) error { 150 | // If there's no more topic levels, that means we are at the matching snode 151 | // to insert the subscriber. So let's see if there's such subscriber, 152 | // if so, update it. Otherwise insert it. 153 | if len(topic) == 0 { 154 | // Let's see if the subscriber is already on the list. If yes, update 155 | // QoS and then return. 156 | for i := range this.subs { 157 | if equal(this.subs[i], sub) { 158 | this.qos[i] = qos 159 | return nil 160 | } 161 | } 162 | 163 | // Otherwise add. 164 | this.subs = append(this.subs, sub) 165 | this.qos = append(this.qos, qos) 166 | 167 | return nil 168 | } 169 | 170 | // Not the last level, so let's find or create the next level snode, and 171 | // recursively call it's insert(). 172 | 173 | // ntl = next topic level 174 | ntl, rem, err := nextTopicLevel(topic) 175 | if err != nil { 176 | return err 177 | } 178 | 179 | level := string(ntl) 180 | 181 | // Add snode if it doesn't already exist 182 | n, ok := this.snodes[level] 183 | if !ok { 184 | n = newSNode() 185 | this.snodes[level] = n 186 | } 187 | 188 | return n.sinsert(rem, qos, sub) 189 | } 190 | 191 | // This remove implementation ignores the QoS, as long as the subscriber 192 | // matches then it's removed 193 | func (this *snode) sremove(topic []byte, sub interface{}) error { 194 | // If the topic is empty, it means we are at the final matching snode. If so, 195 | // let's find the matching subscribers and remove them. 196 | if len(topic) == 0 { 197 | // If subscriber == nil, then it's signal to remove ALL subscribers 198 | if sub == nil { 199 | this.subs = this.subs[0:0] 200 | this.qos = this.qos[0:0] 201 | return nil 202 | } 203 | 204 | // If we find the subscriber then remove it from the list. Technically 205 | // we just overwrite the slot by shifting all other items up by one. 206 | for i := range this.subs { 207 | if equal(this.subs[i], sub) { 208 | this.subs = append(this.subs[:i], this.subs[i+1:]...) 209 | this.qos = append(this.qos[:i], this.qos[i+1:]...) 210 | return nil 211 | } 212 | } 213 | 214 | return fmt.Errorf("memtopics/remove: No topic found for subscriber") 215 | } 216 | 217 | // Not the last level, so let's find the next level snode, and recursively 218 | // call it's remove(). 219 | 220 | // ntl = next topic level 221 | ntl, rem, err := nextTopicLevel(topic) 222 | if err != nil { 223 | return err 224 | } 225 | 226 | level := string(ntl) 227 | 228 | // Find the snode that matches the topic level 229 | n, ok := this.snodes[level] 230 | if !ok { 231 | return fmt.Errorf("memtopics/remove: No topic found") 232 | } 233 | 234 | // Remove the subscriber from the next level snode 235 | if err := n.sremove(rem, sub); err != nil { 236 | return err 237 | } 238 | 239 | // If there are no more subscribers and snodes to the next level we just visited 240 | // let's remove it 241 | if len(n.subs) == 0 && len(n.snodes) == 0 { 242 | delete(this.snodes, level) 243 | } 244 | 245 | return nil 246 | } 247 | 248 | // smatch() returns all the subscribers that are subscribed to the topic. Given a topic 249 | // with no wildcards (publish topic), it returns a list of subscribers that subscribes 250 | // to the topic. For each of the level names, it's a match 251 | // - if there are subscribers to '#', then all the subscribers are added to result set 252 | func (this *snode) smatch(topic []byte, qos byte, subs *[]interface{}, qoss *[]byte) error { 253 | // If the topic is empty, it means we are at the final matching snode. If so, 254 | // let's find the subscribers that match the qos and append them to the list. 255 | if len(topic) == 0 { 256 | this.matchQos(qos, subs, qoss) 257 | return nil 258 | } 259 | 260 | // ntl = next topic level 261 | ntl, rem, err := nextTopicLevel(topic) 262 | if err != nil { 263 | return err 264 | } 265 | 266 | level := string(ntl) 267 | 268 | for k, n := range this.snodes { 269 | // If the key is "#", then these subscribers are added to the result set 270 | if k == MWC { 271 | n.matchQos(qos, subs, qoss) 272 | } else if k == SWC || k == level { 273 | if err := n.smatch(rem, qos, subs, qoss); err != nil { 274 | return err 275 | } 276 | } 277 | } 278 | 279 | return nil 280 | } 281 | 282 | // retained message nodes 283 | type rnode struct { 284 | // If this is the end of the topic string, then add retained messages here 285 | msg *message.PublishMessage 286 | buf []byte 287 | 288 | // Otherwise add the next topic level here 289 | rnodes map[string]*rnode 290 | } 291 | 292 | func newRNode() *rnode { 293 | return &rnode{ 294 | rnodes: make(map[string]*rnode), 295 | } 296 | } 297 | 298 | func (this *rnode) rinsert(topic []byte, msg *message.PublishMessage) error { 299 | // If there's no more topic levels, that means we are at the matching rnode. 300 | if len(topic) == 0 { 301 | l := msg.Len() 302 | 303 | // Let's reuse the buffer if there's enough space 304 | if l > cap(this.buf) { 305 | this.buf = make([]byte, l) 306 | } else { 307 | this.buf = this.buf[0:l] 308 | } 309 | 310 | if _, err := msg.Encode(this.buf); err != nil { 311 | return err 312 | } 313 | 314 | // Reuse the message if possible 315 | if this.msg == nil { 316 | this.msg = message.NewPublishMessage() 317 | } 318 | 319 | if _, err := this.msg.Decode(this.buf); err != nil { 320 | return err 321 | } 322 | 323 | return nil 324 | } 325 | 326 | // Not the last level, so let's find or create the next level snode, and 327 | // recursively call it's insert(). 328 | 329 | // ntl = next topic level 330 | ntl, rem, err := nextTopicLevel(topic) 331 | if err != nil { 332 | return err 333 | } 334 | 335 | level := string(ntl) 336 | 337 | // Add snode if it doesn't already exist 338 | n, ok := this.rnodes[level] 339 | if !ok { 340 | n = newRNode() 341 | this.rnodes[level] = n 342 | } 343 | 344 | return n.rinsert(rem, msg) 345 | } 346 | 347 | // Remove the retained message for the supplied topic 348 | func (this *rnode) rremove(topic []byte) error { 349 | // If the topic is empty, it means we are at the final matching rnode. If so, 350 | // let's remove the buffer and message. 351 | if len(topic) == 0 { 352 | this.buf = nil 353 | this.msg = nil 354 | return nil 355 | } 356 | 357 | // Not the last level, so let's find the next level rnode, and recursively 358 | // call it's remove(). 359 | 360 | // ntl = next topic level 361 | ntl, rem, err := nextTopicLevel(topic) 362 | if err != nil { 363 | return err 364 | } 365 | 366 | level := string(ntl) 367 | 368 | // Find the rnode that matches the topic level 369 | n, ok := this.rnodes[level] 370 | if !ok { 371 | return fmt.Errorf("memtopics/rremove: No topic found") 372 | } 373 | 374 | // Remove the subscriber from the next level rnode 375 | if err := n.rremove(rem); err != nil { 376 | return err 377 | } 378 | 379 | // If there are no more rnodes to the next level we just visited let's remove it 380 | if len(n.rnodes) == 0 { 381 | delete(this.rnodes, level) 382 | } 383 | 384 | return nil 385 | } 386 | 387 | // rmatch() finds the retained messages for the topic and qos provided. It's somewhat 388 | // of a reverse match compare to match() since the supplied topic can contain 389 | // wildcards, whereas the retained message topic is a full (no wildcard) topic. 390 | func (this *rnode) rmatch(topic []byte, msgs *[]*message.PublishMessage) error { 391 | // If the topic is empty, it means we are at the final matching rnode. If so, 392 | // add the retained msg to the list. 393 | if len(topic) == 0 { 394 | if this.msg != nil { 395 | *msgs = append(*msgs, this.msg) 396 | } 397 | return nil 398 | } 399 | 400 | // ntl = next topic level 401 | ntl, rem, err := nextTopicLevel(topic) 402 | if err != nil { 403 | return err 404 | } 405 | 406 | level := string(ntl) 407 | 408 | if level == MWC { 409 | // If '#', add all retained messages starting this node 410 | this.allRetained(msgs) 411 | } else if level == SWC { 412 | // If '+', check all nodes at this level. Next levels must be matched. 413 | for _, n := range this.rnodes { 414 | if err := n.rmatch(rem, msgs); err != nil { 415 | return err 416 | } 417 | } 418 | } else { 419 | // Otherwise, find the matching node, go to the next level 420 | if n, ok := this.rnodes[level]; ok { 421 | if err := n.rmatch(rem, msgs); err != nil { 422 | return err 423 | } 424 | } 425 | } 426 | 427 | return nil 428 | } 429 | 430 | func (this *rnode) allRetained(msgs *[]*message.PublishMessage) { 431 | if this.msg != nil { 432 | *msgs = append(*msgs, this.msg) 433 | } 434 | 435 | for _, n := range this.rnodes { 436 | n.allRetained(msgs) 437 | } 438 | } 439 | 440 | const ( 441 | stateCHR byte = iota // Regular character 442 | stateMWC // Multi-level wildcard 443 | stateSWC // Single-level wildcard 444 | stateSEP // Topic level separator 445 | stateSYS // System level topic ($) 446 | ) 447 | 448 | // Returns topic level, remaining topic levels and any errors 449 | func nextTopicLevel(topic []byte) ([]byte, []byte, error) { 450 | s := stateCHR 451 | 452 | for i, c := range topic { 453 | switch c { 454 | case '/': 455 | if s == stateMWC { 456 | return nil, nil, fmt.Errorf("memtopics/nextTopicLevel: Multi-level wildcard found in topic and it's not at the last level") 457 | } 458 | 459 | if i == 0 { 460 | return []byte(SWC), topic[i+1:], nil 461 | } 462 | 463 | return topic[:i], topic[i+1:], nil 464 | 465 | case '#': 466 | if i != 0 { 467 | return nil, nil, fmt.Errorf("memtopics/nextTopicLevel: Wildcard character '#' must occupy entire topic level") 468 | } 469 | 470 | s = stateMWC 471 | 472 | case '+': 473 | if i != 0 { 474 | return nil, nil, fmt.Errorf("memtopics/nextTopicLevel: Wildcard character '+' must occupy entire topic level") 475 | } 476 | 477 | s = stateSWC 478 | 479 | case '$': 480 | if i == 0 { 481 | return nil, nil, fmt.Errorf("memtopics/nextTopicLevel: Cannot publish to $ topics") 482 | } 483 | 484 | s = stateSYS 485 | 486 | default: 487 | if s == stateMWC || s == stateSWC { 488 | return nil, nil, fmt.Errorf("memtopics/nextTopicLevel: Wildcard characters '#' and '+' must occupy entire topic level") 489 | } 490 | 491 | s = stateCHR 492 | } 493 | } 494 | 495 | // If we got here that means we didn't hit the separator along the way, so the 496 | // topic is either empty, or does not contain a separator. Either way, we return 497 | // the full topic 498 | return topic, nil, nil 499 | } 500 | 501 | // The QoS of the payload messages sent in response to a subscription must be the 502 | // minimum of the QoS of the originally published message (in this case, it's the 503 | // qos parameter) and the maximum QoS granted by the server (in this case, it's 504 | // the QoS in the topic tree). 505 | // 506 | // It's also possible that even if the topic matches, the subscriber is not included 507 | // due to the QoS granted is lower than the published message QoS. For example, 508 | // if the client is granted only QoS 0, and the publish message is QoS 1, then this 509 | // client is not to be send the published message. 510 | func (this *snode) matchQos(qos byte, subs *[]interface{}, qoss *[]byte) { 511 | for i, sub := range this.subs { 512 | // If the published QoS is higher than the subscriber QoS, then we skip the 513 | // subscriber. Otherwise, add to the list. 514 | if qos <= this.qos[i] { 515 | *subs = append(*subs, sub) 516 | *qoss = append(*qoss, qos) 517 | } 518 | } 519 | } 520 | 521 | func equal(k1, k2 interface{}) bool { 522 | if reflect.TypeOf(k1) != reflect.TypeOf(k2) { 523 | return false 524 | } 525 | 526 | if reflect.ValueOf(k1).Kind() == reflect.Func { 527 | return &k1 == &k2 528 | } 529 | 530 | if k1 == k2 { 531 | return true 532 | } 533 | 534 | switch k1 := k1.(type) { 535 | case string: 536 | return k1 == k2.(string) 537 | 538 | case int64: 539 | return k1 == k2.(int64) 540 | 541 | case int32: 542 | return k1 == k2.(int32) 543 | 544 | case int16: 545 | return k1 == k2.(int16) 546 | 547 | case int8: 548 | return k1 == k2.(int8) 549 | 550 | case int: 551 | return k1 == k2.(int) 552 | 553 | case float32: 554 | return k1 == k2.(float32) 555 | 556 | case float64: 557 | return k1 == k2.(float64) 558 | 559 | case uint: 560 | return k1 == k2.(uint) 561 | 562 | case uint8: 563 | return k1 == k2.(uint8) 564 | 565 | case uint16: 566 | return k1 == k2.(uint16) 567 | 568 | case uint32: 569 | return k1 == k2.(uint32) 570 | 571 | case uint64: 572 | return k1 == k2.(uint64) 573 | 574 | case uintptr: 575 | return k1 == k2.(uintptr) 576 | } 577 | 578 | return false 579 | } 580 | -------------------------------------------------------------------------------- /topics/memtopics_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The SurgeMQ Authors. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package topics 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | "github.com/surgemq/message" 22 | ) 23 | 24 | func TestNextTopicLevelSuccess(t *testing.T) { 25 | topics := [][]byte{ 26 | []byte("sport/tennis/player1/#"), 27 | []byte("sport/tennis/player1/ranking"), 28 | []byte("sport/#"), 29 | []byte("#"), 30 | []byte("sport/tennis/#"), 31 | []byte("+"), 32 | []byte("+/tennis/#"), 33 | []byte("sport/+/player1"), 34 | []byte("/finance"), 35 | } 36 | 37 | levels := [][][]byte{ 38 | [][]byte{[]byte("sport"), []byte("tennis"), []byte("player1"), []byte("#")}, 39 | [][]byte{[]byte("sport"), []byte("tennis"), []byte("player1"), []byte("ranking")}, 40 | [][]byte{[]byte("sport"), []byte("#")}, 41 | [][]byte{[]byte("#")}, 42 | [][]byte{[]byte("sport"), []byte("tennis"), []byte("#")}, 43 | [][]byte{[]byte("+")}, 44 | [][]byte{[]byte("+"), []byte("tennis"), []byte("#")}, 45 | [][]byte{[]byte("sport"), []byte("+"), []byte("player1")}, 46 | [][]byte{[]byte("+"), []byte("finance")}, 47 | } 48 | 49 | for i, topic := range topics { 50 | var ( 51 | tl []byte 52 | rem = topic 53 | err error 54 | ) 55 | 56 | for _, level := range levels[i] { 57 | tl, rem, err = nextTopicLevel(rem) 58 | require.NoError(t, err) 59 | require.Equal(t, level, tl) 60 | } 61 | } 62 | } 63 | 64 | func TestNextTopicLevelFailure(t *testing.T) { 65 | topics := [][]byte{ 66 | []byte("sport/tennis#"), 67 | []byte("sport/tennis/#/ranking"), 68 | []byte("sport+"), 69 | } 70 | 71 | var ( 72 | rem []byte 73 | err error 74 | ) 75 | 76 | _, rem, err = nextTopicLevel(topics[0]) 77 | require.NoError(t, err) 78 | 79 | _, rem, err = nextTopicLevel(rem) 80 | require.Error(t, err) 81 | 82 | _, rem, err = nextTopicLevel(topics[1]) 83 | require.NoError(t, err) 84 | 85 | _, rem, err = nextTopicLevel(rem) 86 | require.NoError(t, err) 87 | 88 | _, rem, err = nextTopicLevel(rem) 89 | require.Error(t, err) 90 | 91 | _, rem, err = nextTopicLevel(topics[2]) 92 | require.Error(t, err) 93 | } 94 | 95 | func TestSNodeInsert1(t *testing.T) { 96 | n := newSNode() 97 | topic := []byte("sport/tennis/player1/#") 98 | 99 | err := n.sinsert(topic, 1, "sub1") 100 | 101 | require.NoError(t, err) 102 | require.Equal(t, 1, len(n.snodes)) 103 | require.Equal(t, 0, len(n.subs)) 104 | 105 | n2, ok := n.snodes["sport"] 106 | 107 | require.True(t, ok) 108 | require.Equal(t, 1, len(n2.snodes)) 109 | require.Equal(t, 0, len(n2.subs)) 110 | 111 | n3, ok := n2.snodes["tennis"] 112 | 113 | require.True(t, ok) 114 | require.Equal(t, 1, len(n3.snodes)) 115 | require.Equal(t, 0, len(n3.subs)) 116 | 117 | n4, ok := n3.snodes["player1"] 118 | 119 | require.True(t, ok) 120 | require.Equal(t, 1, len(n4.snodes)) 121 | require.Equal(t, 0, len(n4.subs)) 122 | 123 | n5, ok := n4.snodes["#"] 124 | 125 | require.True(t, ok) 126 | require.Equal(t, 0, len(n5.snodes)) 127 | require.Equal(t, 1, len(n5.subs)) 128 | require.Equal(t, "sub1", n5.subs[0].(string)) 129 | } 130 | 131 | func TestSNodeInsert2(t *testing.T) { 132 | n := newSNode() 133 | topic := []byte("#") 134 | 135 | err := n.sinsert(topic, 1, "sub1") 136 | 137 | require.NoError(t, err) 138 | require.Equal(t, 1, len(n.snodes)) 139 | require.Equal(t, 0, len(n.subs)) 140 | 141 | n2, ok := n.snodes["#"] 142 | 143 | require.True(t, ok) 144 | require.Equal(t, 0, len(n2.snodes)) 145 | require.Equal(t, 1, len(n2.subs)) 146 | require.Equal(t, "sub1", n2.subs[0].(string)) 147 | } 148 | 149 | func TestSNodeInsert3(t *testing.T) { 150 | n := newSNode() 151 | topic := []byte("+/tennis/#") 152 | 153 | err := n.sinsert(topic, 1, "sub1") 154 | 155 | require.NoError(t, err) 156 | require.Equal(t, 1, len(n.snodes)) 157 | require.Equal(t, 0, len(n.subs)) 158 | 159 | n2, ok := n.snodes["+"] 160 | 161 | require.True(t, ok) 162 | require.Equal(t, 1, len(n2.snodes)) 163 | require.Equal(t, 0, len(n2.subs)) 164 | 165 | n3, ok := n2.snodes["tennis"] 166 | 167 | require.True(t, ok) 168 | require.Equal(t, 1, len(n3.snodes)) 169 | require.Equal(t, 0, len(n3.subs)) 170 | 171 | n4, ok := n3.snodes["#"] 172 | 173 | require.True(t, ok) 174 | require.Equal(t, 0, len(n4.snodes)) 175 | require.Equal(t, 1, len(n4.subs)) 176 | require.Equal(t, "sub1", n4.subs[0].(string)) 177 | } 178 | 179 | func TestSNodeInsert4(t *testing.T) { 180 | n := newSNode() 181 | topic := []byte("/finance") 182 | 183 | err := n.sinsert(topic, 1, "sub1") 184 | 185 | require.NoError(t, err) 186 | require.Equal(t, 1, len(n.snodes)) 187 | require.Equal(t, 0, len(n.subs)) 188 | 189 | n2, ok := n.snodes["+"] 190 | 191 | require.True(t, ok) 192 | require.Equal(t, 1, len(n2.snodes)) 193 | require.Equal(t, 0, len(n2.subs)) 194 | 195 | n3, ok := n2.snodes["finance"] 196 | 197 | require.True(t, ok) 198 | require.Equal(t, 0, len(n3.snodes)) 199 | require.Equal(t, 1, len(n3.subs)) 200 | require.Equal(t, "sub1", n3.subs[0].(string)) 201 | } 202 | 203 | func TestSNodeInsertDup(t *testing.T) { 204 | n := newSNode() 205 | topic := []byte("/finance") 206 | 207 | err := n.sinsert(topic, 1, "sub1") 208 | err = n.sinsert(topic, 1, "sub1") 209 | 210 | require.NoError(t, err) 211 | require.Equal(t, 1, len(n.snodes)) 212 | require.Equal(t, 0, len(n.subs)) 213 | 214 | n2, ok := n.snodes["+"] 215 | 216 | require.True(t, ok) 217 | require.Equal(t, 1, len(n2.snodes)) 218 | require.Equal(t, 0, len(n2.subs)) 219 | 220 | n3, ok := n2.snodes["finance"] 221 | 222 | require.True(t, ok) 223 | require.Equal(t, 0, len(n3.snodes)) 224 | require.Equal(t, 1, len(n3.subs)) 225 | require.Equal(t, "sub1", n3.subs[0].(string)) 226 | } 227 | 228 | func TestSNodeRemove1(t *testing.T) { 229 | n := newSNode() 230 | topic := []byte("sport/tennis/player1/#") 231 | 232 | n.sinsert(topic, 1, "sub1") 233 | err := n.sremove([]byte("sport/tennis/player1/#"), "sub1") 234 | 235 | require.NoError(t, err) 236 | require.Equal(t, 0, len(n.snodes)) 237 | require.Equal(t, 0, len(n.subs)) 238 | } 239 | 240 | func TestSNodeRemove2(t *testing.T) { 241 | n := newSNode() 242 | topic := []byte("sport/tennis/player1/#") 243 | 244 | n.sinsert(topic, 1, "sub1") 245 | err := n.sremove([]byte("sport/tennis/player1"), "sub1") 246 | 247 | require.Error(t, err) 248 | } 249 | 250 | func TestSNodeRemove3(t *testing.T) { 251 | n := newSNode() 252 | topic := []byte("sport/tennis/player1/#") 253 | 254 | n.sinsert(topic, 1, "sub1") 255 | n.sinsert(topic, 1, "sub2") 256 | err := n.sremove([]byte("sport/tennis/player1/#"), nil) 257 | 258 | require.NoError(t, err) 259 | require.Equal(t, 0, len(n.snodes)) 260 | require.Equal(t, 0, len(n.subs)) 261 | } 262 | 263 | func TestSNodeMatch1(t *testing.T) { 264 | n := newSNode() 265 | topic := []byte("sport/tennis/player1/#") 266 | n.sinsert(topic, 1, "sub1") 267 | 268 | subs := make([]interface{}, 0, 5) 269 | qoss := make([]byte, 0, 5) 270 | 271 | err := n.smatch([]byte("sport/tennis/player1/anzel"), 1, &subs, &qoss) 272 | 273 | require.NoError(t, err) 274 | require.Equal(t, 1, len(subs)) 275 | require.Equal(t, 1, int(qoss[0])) 276 | } 277 | 278 | func TestSNodeMatch2(t *testing.T) { 279 | n := newSNode() 280 | topic := []byte("sport/tennis/player1/#") 281 | n.sinsert(topic, 1, "sub1") 282 | 283 | subs := make([]interface{}, 0, 5) 284 | qoss := make([]byte, 0, 5) 285 | 286 | err := n.smatch([]byte("sport/tennis/player1/anzel"), 1, &subs, &qoss) 287 | 288 | require.NoError(t, err) 289 | require.Equal(t, 1, len(subs)) 290 | require.Equal(t, 1, int(qoss[0])) 291 | } 292 | 293 | func TestSNodeMatch3(t *testing.T) { 294 | n := newSNode() 295 | topic := []byte("sport/tennis/player1/#") 296 | n.sinsert(topic, 2, "sub1") 297 | 298 | subs := make([]interface{}, 0, 5) 299 | qoss := make([]byte, 0, 5) 300 | 301 | err := n.smatch([]byte("sport/tennis/player1/anzel"), 2, &subs, &qoss) 302 | 303 | require.NoError(t, err) 304 | require.Equal(t, 1, len(subs)) 305 | require.Equal(t, 2, int(qoss[0])) 306 | } 307 | 308 | func TestSNodeMatch4(t *testing.T) { 309 | n := newSNode() 310 | n.sinsert([]byte("sport/tennis/#"), 2, "sub1") 311 | 312 | subs := make([]interface{}, 0, 5) 313 | qoss := make([]byte, 0, 5) 314 | 315 | err := n.smatch([]byte("sport/tennis/player1/anzel"), 2, &subs, &qoss) 316 | 317 | require.NoError(t, err) 318 | require.Equal(t, 1, len(subs)) 319 | require.Equal(t, 2, int(qoss[0])) 320 | } 321 | 322 | func TestSNodeMatch5(t *testing.T) { 323 | n := newSNode() 324 | n.sinsert([]byte("sport/tennis/+/anzel"), 1, "sub1") 325 | n.sinsert([]byte("sport/tennis/player1/anzel"), 1, "sub2") 326 | 327 | subs := make([]interface{}, 0, 5) 328 | qoss := make([]byte, 0, 5) 329 | 330 | err := n.smatch([]byte("sport/tennis/player1/anzel"), 1, &subs, &qoss) 331 | 332 | require.NoError(t, err) 333 | require.Equal(t, 2, len(subs)) 334 | } 335 | 336 | func TestSNodeMatch6(t *testing.T) { 337 | n := newSNode() 338 | n.sinsert([]byte("sport/tennis/#"), 2, "sub1") 339 | n.sinsert([]byte("sport/tennis"), 1, "sub2") 340 | 341 | subs := make([]interface{}, 0, 5) 342 | qoss := make([]byte, 0, 5) 343 | 344 | err := n.smatch([]byte("sport/tennis/player1/anzel"), 2, &subs, &qoss) 345 | 346 | require.NoError(t, err) 347 | require.Equal(t, 1, len(subs)) 348 | require.Equal(t, "sub1", subs[0]) 349 | } 350 | 351 | func TestSNodeMatch7(t *testing.T) { 352 | n := newSNode() 353 | n.sinsert([]byte("+/+"), 2, "sub1") 354 | 355 | subs := make([]interface{}, 0, 5) 356 | qoss := make([]byte, 0, 5) 357 | 358 | err := n.smatch([]byte("/finance"), 1, &subs, &qoss) 359 | 360 | require.NoError(t, err) 361 | require.Equal(t, 1, len(subs)) 362 | } 363 | 364 | func TestSNodeMatch8(t *testing.T) { 365 | n := newSNode() 366 | n.sinsert([]byte("/+"), 2, "sub1") 367 | 368 | subs := make([]interface{}, 0, 5) 369 | qoss := make([]byte, 0, 5) 370 | 371 | err := n.smatch([]byte("/finance"), 1, &subs, &qoss) 372 | 373 | require.NoError(t, err) 374 | require.Equal(t, 1, len(subs)) 375 | } 376 | 377 | func TestSNodeMatch9(t *testing.T) { 378 | n := newSNode() 379 | n.sinsert([]byte("+"), 2, "sub1") 380 | 381 | subs := make([]interface{}, 0, 5) 382 | qoss := make([]byte, 0, 5) 383 | 384 | err := n.smatch([]byte("/finance"), 1, &subs, &qoss) 385 | 386 | require.NoError(t, err) 387 | require.Equal(t, 0, len(subs)) 388 | } 389 | 390 | func TestRNodeInsertRemove(t *testing.T) { 391 | n := newRNode() 392 | 393 | // --- Insert msg1 394 | 395 | msg := newPublishMessageLarge([]byte("sport/tennis/player1/ricardo"), 1) 396 | 397 | err := n.rinsert(msg.Topic(), msg) 398 | 399 | require.NoError(t, err) 400 | require.Equal(t, 1, len(n.rnodes)) 401 | require.Nil(t, n.msg) 402 | 403 | n2, ok := n.rnodes["sport"] 404 | 405 | require.True(t, ok) 406 | require.Equal(t, 1, len(n2.rnodes)) 407 | require.Nil(t, n2.msg) 408 | 409 | n3, ok := n2.rnodes["tennis"] 410 | 411 | require.True(t, ok) 412 | require.Equal(t, 1, len(n3.rnodes)) 413 | require.Nil(t, n3.msg) 414 | 415 | n4, ok := n3.rnodes["player1"] 416 | 417 | require.True(t, ok) 418 | require.Equal(t, 1, len(n4.rnodes)) 419 | require.Nil(t, n4.msg) 420 | 421 | n5, ok := n4.rnodes["ricardo"] 422 | 423 | require.True(t, ok) 424 | require.Equal(t, 0, len(n5.rnodes)) 425 | require.NotNil(t, n5.msg) 426 | require.Equal(t, msg.QoS(), n5.msg.QoS()) 427 | require.Equal(t, msg.Topic(), n5.msg.Topic()) 428 | require.Equal(t, msg.Payload(), n5.msg.Payload()) 429 | 430 | // --- Insert msg2 431 | 432 | msg2 := newPublishMessageLarge([]byte("sport/tennis/player1/andre"), 1) 433 | 434 | err = n.rinsert(msg2.Topic(), msg2) 435 | 436 | require.NoError(t, err) 437 | require.Equal(t, 2, len(n4.rnodes)) 438 | 439 | n6, ok := n4.rnodes["andre"] 440 | 441 | require.True(t, ok) 442 | require.Equal(t, 0, len(n6.rnodes)) 443 | require.NotNil(t, n6.msg) 444 | require.Equal(t, msg2.QoS(), n6.msg.QoS()) 445 | require.Equal(t, msg2.Topic(), n6.msg.Topic()) 446 | 447 | // --- Remove 448 | 449 | err = n.rremove([]byte("sport/tennis/player1/andre")) 450 | require.NoError(t, err) 451 | require.Equal(t, 1, len(n4.rnodes)) 452 | } 453 | 454 | func TestRNodeMatch(t *testing.T) { 455 | n := newRNode() 456 | 457 | msg1 := newPublishMessageLarge([]byte("sport/tennis/ricardo/stats"), 1) 458 | err := n.rinsert(msg1.Topic(), msg1) 459 | require.NoError(t, err) 460 | 461 | msg2 := newPublishMessageLarge([]byte("sport/tennis/andre/stats"), 1) 462 | err = n.rinsert(msg2.Topic(), msg2) 463 | require.NoError(t, err) 464 | 465 | msg3 := newPublishMessageLarge([]byte("sport/tennis/andre/bio"), 1) 466 | err = n.rinsert(msg3.Topic(), msg3) 467 | require.NoError(t, err) 468 | 469 | var msglist []*message.PublishMessage 470 | 471 | // --- 472 | 473 | err = n.rmatch(msg1.Topic(), &msglist) 474 | 475 | require.NoError(t, err) 476 | require.Equal(t, 1, len(msglist)) 477 | 478 | // --- 479 | 480 | msglist = msglist[0:0] 481 | err = n.rmatch(msg2.Topic(), &msglist) 482 | 483 | require.NoError(t, err) 484 | require.Equal(t, 1, len(msglist)) 485 | 486 | // --- 487 | 488 | msglist = msglist[0:0] 489 | err = n.rmatch(msg3.Topic(), &msglist) 490 | 491 | require.NoError(t, err) 492 | require.Equal(t, 1, len(msglist)) 493 | 494 | // --- 495 | 496 | msglist = msglist[0:0] 497 | err = n.rmatch([]byte("sport/tennis/andre/+"), &msglist) 498 | 499 | require.NoError(t, err) 500 | require.Equal(t, 2, len(msglist)) 501 | 502 | // --- 503 | 504 | msglist = msglist[0:0] 505 | err = n.rmatch([]byte("sport/tennis/andre/#"), &msglist) 506 | 507 | require.NoError(t, err) 508 | require.Equal(t, 2, len(msglist)) 509 | 510 | // --- 511 | 512 | msglist = msglist[0:0] 513 | err = n.rmatch([]byte("sport/tennis/+/stats"), &msglist) 514 | 515 | require.NoError(t, err) 516 | require.Equal(t, 2, len(msglist)) 517 | 518 | // --- 519 | 520 | msglist = msglist[0:0] 521 | err = n.rmatch([]byte("sport/tennis/#"), &msglist) 522 | 523 | require.NoError(t, err) 524 | require.Equal(t, 3, len(msglist)) 525 | } 526 | 527 | func TestMemTopicsSubscription(t *testing.T) { 528 | Unregister("mem") 529 | p := NewMemProvider() 530 | Register("mem", p) 531 | 532 | mgr, err := NewManager("mem") 533 | 534 | MaxQosAllowed = 1 535 | qos, err := mgr.Subscribe([]byte("sports/tennis/+/stats"), 2, "sub1") 536 | 537 | require.NoError(t, err) 538 | require.Equal(t, 1, int(qos)) 539 | 540 | err = mgr.Unsubscribe([]byte("sports/tennis"), "sub1") 541 | 542 | require.Error(t, err) 543 | 544 | subs := make([]interface{}, 5) 545 | qoss := make([]byte, 5) 546 | 547 | err = mgr.Subscribers([]byte("sports/tennis/anzel/stats"), 2, &subs, &qoss) 548 | 549 | require.NoError(t, err) 550 | require.Equal(t, 0, len(subs)) 551 | 552 | err = mgr.Subscribers([]byte("sports/tennis/anzel/stats"), 1, &subs, &qoss) 553 | 554 | require.NoError(t, err) 555 | require.Equal(t, 1, len(subs)) 556 | require.Equal(t, 1, int(qoss[0])) 557 | 558 | err = mgr.Unsubscribe([]byte("sports/tennis/+/stats"), "sub1") 559 | 560 | require.NoError(t, err) 561 | } 562 | 563 | func TestMemTopicsRetained(t *testing.T) { 564 | Unregister("mem") 565 | p := NewMemProvider() 566 | Register("mem", p) 567 | 568 | mgr, err := NewManager("mem") 569 | require.NoError(t, err) 570 | require.NotNil(t, mgr) 571 | 572 | msg1 := newPublishMessageLarge([]byte("sport/tennis/ricardo/stats"), 1) 573 | err = mgr.Retain(msg1) 574 | require.NoError(t, err) 575 | 576 | msg2 := newPublishMessageLarge([]byte("sport/tennis/andre/stats"), 1) 577 | err = mgr.Retain(msg2) 578 | require.NoError(t, err) 579 | 580 | msg3 := newPublishMessageLarge([]byte("sport/tennis/andre/bio"), 1) 581 | err = mgr.Retain(msg3) 582 | require.NoError(t, err) 583 | 584 | var msglist []*message.PublishMessage 585 | 586 | // --- 587 | 588 | err = mgr.Retained(msg1.Topic(), &msglist) 589 | 590 | require.NoError(t, err) 591 | require.Equal(t, 1, len(msglist)) 592 | 593 | // --- 594 | 595 | msglist = msglist[0:0] 596 | err = mgr.Retained(msg2.Topic(), &msglist) 597 | 598 | require.NoError(t, err) 599 | require.Equal(t, 1, len(msglist)) 600 | 601 | // --- 602 | 603 | msglist = msglist[0:0] 604 | err = mgr.Retained(msg3.Topic(), &msglist) 605 | 606 | require.NoError(t, err) 607 | require.Equal(t, 1, len(msglist)) 608 | 609 | // --- 610 | 611 | msglist = msglist[0:0] 612 | err = mgr.Retained([]byte("sport/tennis/andre/+"), &msglist) 613 | 614 | require.NoError(t, err) 615 | require.Equal(t, 2, len(msglist)) 616 | 617 | // --- 618 | 619 | msglist = msglist[0:0] 620 | err = mgr.Retained([]byte("sport/tennis/andre/#"), &msglist) 621 | 622 | require.NoError(t, err) 623 | require.Equal(t, 2, len(msglist)) 624 | 625 | // --- 626 | 627 | msglist = msglist[0:0] 628 | err = mgr.Retained([]byte("sport/tennis/+/stats"), &msglist) 629 | 630 | require.NoError(t, err) 631 | require.Equal(t, 2, len(msglist)) 632 | 633 | // --- 634 | 635 | msglist = msglist[0:0] 636 | err = mgr.Retained([]byte("sport/tennis/#"), &msglist) 637 | 638 | require.NoError(t, err) 639 | require.Equal(t, 3, len(msglist)) 640 | } 641 | 642 | func newPublishMessageLarge(topic []byte, qos byte) *message.PublishMessage { 643 | msg := message.NewPublishMessage() 644 | msg.SetTopic(topic) 645 | msg.SetPayload(make([]byte, 1024)) 646 | msg.SetQoS(qos) 647 | 648 | return msg 649 | } 650 | --------------------------------------------------------------------------------