├── .github ├── FUNDING.yml └── workflows │ ├── edge.yml │ ├── test.yml │ ├── latest.yml │ └── release.yml ├── deploy └── k8s │ ├── aws │ ├── storage_ssd.yaml │ ├── service_dns.yaml │ ├── service_loadbalancer.yaml │ ├── README.md │ └── broker.yaml │ ├── azure │ ├── service_dns.yaml │ ├── service_loadbalancer.yaml │ ├── README.md │ └── broker.yaml │ ├── gcloud │ ├── service_dns.yaml │ ├── service_loadbalancer.yaml │ ├── storage_ssd.yaml │ ├── README.md │ └── broker.yaml │ ├── digitalocean │ ├── service_dns.yaml │ ├── service_loadbalancer.yaml │ ├── README.md │ └── broker.yaml │ ├── minikube │ ├── storage_ssd.yaml │ └── broker_test.yaml │ └── README.md ├── emitter.conf ├── .vscode ├── launch.json └── settings.json ├── .gitignore ├── Dockerfile ├── internal ├── service │ ├── pubsub │ │ ├── service_test.go │ │ ├── service.go │ │ ├── lastwill.go │ │ ├── unsubscribe.go │ │ └── unsubscribe_test.go │ ├── me │ │ ├── requests_test.go │ │ ├── me_test.go │ │ ├── requests.go │ │ └── me.go │ ├── link │ │ ├── requests_test.go │ │ ├── requests.go │ │ ├── link_test.go │ │ └── link.go │ ├── presence │ │ ├── requests_test.go │ │ └── service_test.go │ ├── keyban │ │ ├── requests_test.go │ │ ├── requests.go │ │ ├── keyban.go │ │ └── keyban_test.go │ ├── keygen │ │ ├── requests_test.go │ │ ├── requests.go │ │ └── assets │ │ │ └── keygen.html │ ├── cluster │ │ ├── memberlist_test.go │ │ ├── peer_test.go │ │ └── memberlist.go │ ├── history │ │ ├── service.go │ │ └── history_test.go │ └── interfaces.go ├── network │ ├── mock │ │ ├── addr_test.go │ │ ├── addr.go │ │ ├── conn_test.go │ │ ├── end_test.go │ │ ├── noop_test.go │ │ ├── conn.go │ │ ├── noop.go │ │ └── end.go │ ├── http │ │ ├── mock_test.go │ │ └── mock.go │ ├── listener │ │ ├── matcher_test.go │ │ └── conn_test.go │ └── mqtt │ │ └── buffer.go ├── errors │ ├── error_test.go │ └── error.go ├── command │ ├── version │ │ ├── version.go │ │ └── version_test.go │ ├── license │ │ ├── license_test.go │ │ └── license.go │ └── load │ │ └── load_test.go ├── provider │ ├── monitor │ │ ├── stats_test.go │ │ ├── self_test.go │ │ ├── stats.go │ │ ├── http_test.go │ │ ├── statsd_test.go │ │ ├── prometheus_test.go │ │ └── self.go │ ├── contract │ │ └── mock │ │ │ ├── mock_test.go │ │ │ └── mock.go │ ├── logging │ │ ├── logging_test.go │ │ └── logging.go │ ├── storage │ │ └── memory.go │ └── usage │ │ └── usage_test.go ├── broker │ ├── status_test.go │ ├── conn_test.go │ └── status.go ├── security │ ├── id_test.go │ ├── license │ │ ├── v1_test.go │ │ ├── v2_test.go │ │ ├── v3_test.go │ │ └── license.go │ ├── id.go │ ├── hash │ │ ├── murmur.go │ │ └── murmur_test.go │ └── cipher │ │ ├── base64_test.go │ │ ├── base64.go │ │ ├── shuffle.go │ │ └── salsa_test.go ├── config │ └── config_test.go ├── async │ ├── timer_test.go │ └── timer.go ├── message │ ├── codec_test.go │ ├── codec.go │ └── id_test.go └── event │ └── crdt │ └── volatile_test.go ├── appveyor.yml ├── go.mod └── main.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [kelindar] 2 | -------------------------------------------------------------------------------- /deploy/k8s/aws/storage_ssd.yaml: -------------------------------------------------------------------------------- 1 | kind: StorageClass 2 | apiVersion: storage.k8s.io/v1beta1 3 | metadata: 4 | name: fast 5 | provisioner: kubernetes.io/aws-ebs 6 | parameters: 7 | type: gp2 -------------------------------------------------------------------------------- /emitter.conf: -------------------------------------------------------------------------------- 1 | { 2 | "listen": ":8080", 3 | "tls": { 4 | "listen": ":443" 5 | }, 6 | "cluster": { 7 | "listen": ":4000", 8 | "advertise": "external:4000" 9 | }, 10 | "storage": { 11 | "provider": "inmemory" 12 | } 13 | } -------------------------------------------------------------------------------- /deploy/k8s/aws/service_dns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: broker 5 | labels: 6 | app: broker 7 | spec: 8 | clusterIP: None # Headless would give us a DNS name 9 | selector: 10 | app: broker 11 | ports: # No port is actually required for this 12 | - port: 4000 13 | targetPort: 4000 -------------------------------------------------------------------------------- /deploy/k8s/azure/service_dns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: broker 5 | labels: 6 | app: broker 7 | spec: 8 | clusterIP: None # Headless would give us a DNS name 9 | selector: 10 | app: broker 11 | ports: # No port is actually required for this 12 | - port: 4000 13 | targetPort: 4000 -------------------------------------------------------------------------------- /deploy/k8s/gcloud/service_dns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: broker 5 | labels: 6 | app: broker 7 | spec: 8 | clusterIP: None # Headless would give us a DNS name 9 | selector: 10 | app: broker 11 | ports: # No port is actually required for this 12 | - port: 4000 13 | targetPort: 4000 -------------------------------------------------------------------------------- /deploy/k8s/aws/service_loadbalancer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: broker-loadbalancer 5 | spec: 6 | type: LoadBalancer 7 | selector: 8 | app: broker 9 | ports: 10 | - port: 80 11 | targetPort: 8080 12 | name: http 13 | - port: 443 14 | targetPort: 443 15 | name: https -------------------------------------------------------------------------------- /deploy/k8s/digitalocean/service_dns.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: broker 5 | labels: 6 | app: broker 7 | spec: 8 | clusterIP: None # Headless would give us a DNS name 9 | selector: 10 | app: broker 11 | ports: # No port is actually required for this 12 | - port: 4000 13 | targetPort: 4000 -------------------------------------------------------------------------------- /deploy/k8s/azure/service_loadbalancer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: broker-loadbalancer 5 | spec: 6 | type: LoadBalancer 7 | selector: 8 | app: broker 9 | ports: 10 | - port: 80 11 | targetPort: 8080 12 | name: http 13 | - port: 443 14 | targetPort: 443 15 | name: https -------------------------------------------------------------------------------- /deploy/k8s/gcloud/service_loadbalancer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: broker-loadbalancer 5 | spec: 6 | type: LoadBalancer 7 | selector: 8 | app: broker 9 | ports: 10 | - port: 80 11 | targetPort: 8080 12 | name: http 13 | - port: 443 14 | targetPort: 443 15 | name: https -------------------------------------------------------------------------------- /deploy/k8s/digitalocean/service_loadbalancer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: broker-loadbalancer 5 | spec: 6 | type: LoadBalancer 7 | selector: 8 | app: broker 9 | ports: 10 | - port: 80 11 | targetPort: 8080 12 | name: http 13 | - port: 443 14 | targetPort: 443 15 | name: https -------------------------------------------------------------------------------- /deploy/k8s/minikube/storage_ssd.yaml: -------------------------------------------------------------------------------- 1 | kind: StorageClass 2 | apiVersion: storage.k8s.io/v1 3 | metadata: 4 | namespace: kube-system 5 | name: fast 6 | annotations: 7 | storageclass.beta.kubernetes.io/is-default-class: "true" 8 | labels: 9 | addonmanager.kubernetes.io/mode: Reconcile 10 | 11 | provisioner: k8s.io/minikube-hostpath 12 | 13 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch", 6 | "type": "go", 7 | "request": "launch", 8 | "mode": "debug", 9 | "remotePath": "", 10 | "port": 2345, 11 | "host": "127.0.0.1", 12 | "program": "${fileDirname}", 13 | "env": {}, 14 | "args": [], 15 | "showLog": true 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | *.coverprofile 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 15 | .glide/ 16 | debug 17 | *.log 18 | *.sst 19 | build/* 20 | *.log 21 | *.bat 22 | *.conf 23 | *.lnk 24 | creds.json 25 | *.crt 26 | *.key 27 | data 28 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.git": true, 4 | "**/.DS_Store": true, 5 | ".gitignore": true, 6 | "vendor/": true, 7 | ".github/": true, 8 | ".vscode": true, 9 | "build*": true, 10 | "LICENSE": true, 11 | "appveyor.yml": true, 12 | "Dockerfile": true 13 | }, 14 | "editor.fontLigatures": true, 15 | "go.formatTool": "gofmt", 16 | "go.formatFlags": [ 17 | "-s" 18 | ] 19 | } -------------------------------------------------------------------------------- /deploy/k8s/azure/README.md: -------------------------------------------------------------------------------- 1 | # Deploying Emitter with Kubernetes on Azure 2 | 3 | This directory contains Kubernetes configuration files which can be used to deploy an [production-grade cluster of emitter](https://emitter.io) on Microsoft Azure (AKS). 4 | 5 | In order to get emitter running, you'll need to have `kubectl` [tool installed](https://kubernetes.io/docs/tasks/tools/install-kubectl/) and an [Microsoft Azure](https://azure.microsoft.com) account. 6 | 7 | ``` 8 | kubectl apply -f service_dns.yaml 9 | kubectl apply -f service_loadbalancer.yaml 10 | kubectl apply -f broker.yaml 11 | ``` 12 | -------------------------------------------------------------------------------- /deploy/k8s/aws/README.md: -------------------------------------------------------------------------------- 1 | # Deploying Emitter with Kubernetes on AWS 2 | 3 | This directory contains Kubernetes configuration files which can be used to deploy an [production-grade cluster of emitter](https://emitter.io) on Amazon Web Services (EKS). 4 | 5 | In order to get emitter running, you'll need to have `kubectl` [tool installed](https://kubernetes.io/docs/tasks/tools/install-kubectl/) and an [Amazon Web Services](https://aws.amazon.com) account. 6 | 7 | ``` 8 | kubectl apply -f storage_ssd.yaml 9 | kubectl apply -f service_dns.yaml 10 | kubectl apply -f service_loadbalancer.yaml 11 | kubectl apply -f broker.yaml 12 | ``` 13 | -------------------------------------------------------------------------------- /deploy/k8s/README.md: -------------------------------------------------------------------------------- 1 | # Deploying with Kubernetes 2 | 3 | This directory contains various configuration files which help you setup Emitter cluster on Kubernetes. You can find different setups for different public clous as they leverage different storage classes. Typically, you'd want to use SSD-backed message storage for higher performance, given that Emitter's `ssd` message storage provider is optimised for it. 4 | 5 | * [Amazon Web Services (EKS)](aws) 6 | * [Microsoft Azure (AKS)](azure) 7 | * [Google Cloud (GKE)](gcloud) 8 | * [Digital Ocean](digitalocean) 9 | 10 | Keep in mind that you'd need to edit the license file in `broker.yaml`. 11 | 12 | ## Please Contribute 13 | 14 | These templates are provided for reference only, contributions are welcome. -------------------------------------------------------------------------------- /deploy/k8s/gcloud/storage_ssd.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017, Google, Inc. 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http:#www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | kind: StorageClass 14 | apiVersion: storage.k8s.io/v1beta1 15 | metadata: 16 | name: fast 17 | provisioner: kubernetes.io/gce-pd 18 | parameters: 19 | type: pd-ssd 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS builder 2 | LABEL MAINTAINER="roman@misakai.com" 3 | 4 | # Copy the directory into the container outside of the gopath 5 | RUN mkdir -p /go-build/src/github.com/emitter-io/emitter/ 6 | WORKDIR /go-build/src/github.com/emitter-io/emitter/ 7 | ADD . /go-build/src/github.com/emitter-io/emitter/ 8 | 9 | # Download and install any required third party dependencies into the container. 10 | RUN apk add --no-cache git g++ \ 11 | && go install \ 12 | && apk del g++ 13 | 14 | # Base image for runtime 15 | FROM alpine:latest 16 | RUN apk --no-cache add ca-certificates 17 | 18 | WORKDIR /root/ 19 | # Get the executable binary from build-img declared previously 20 | COPY --from=builder /go/bin/emitter . 21 | 22 | # Expose emitter ports 23 | EXPOSE 4000 24 | EXPOSE 8080 25 | EXPOSE 8443 26 | 27 | # Start the broker 28 | CMD ["./emitter"] 29 | -------------------------------------------------------------------------------- /.github/workflows/edge.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: [pull_request] 3 | env: 4 | PROJECT: "github.com/emitter-io/emitter" 5 | GO111MODULE: "on" 6 | 7 | jobs: 8 | publish: 9 | name: Docker 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Install Go 13 | uses: actions/setup-go@v1 14 | with: 15 | go-version: 1.24 16 | id: go 17 | - name: Login to DockerHub Registry 18 | run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin 19 | - name: Check out code into the Go module directory 20 | uses: actions/checkout@v1 21 | - name: Build the Docker image 22 | run: | 23 | go build . 24 | docker build . --file Dockerfile --tag emitter/server:edge 25 | - name: Push the Docker image 26 | run: docker push emitter/server:edge 27 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | env: 4 | GITHUB_TOKEN: ${{ secrets.COVERALLS_TOKEN }} 5 | GO111MODULE: "on" 6 | jobs: 7 | test: 8 | name: Test with Coverage 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | go: ["1.24"] 13 | steps: 14 | - name: Set up Go ${{ matrix.go }} 15 | uses: actions/setup-go@v3 16 | with: 17 | go-version: ${{ matrix.go }} 18 | - name: Check out code 19 | uses: actions/checkout@v3 20 | - name: Install dependencies 21 | run: | 22 | go mod download 23 | - name: Run Unit Tests 24 | run: | 25 | go test -race -covermode atomic -coverprofile=profile.cov ./... 26 | - name: Upload Coverage 27 | uses: shogo82148/actions-goveralls@v1 28 | with: 29 | path-to-profile: profile.cov 30 | -------------------------------------------------------------------------------- /.github/workflows/latest.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | env: 7 | PROJECT: "github.com/emitter-io/emitter" 8 | GO111MODULE: "on" 9 | 10 | jobs: 11 | publish: 12 | name: Docker 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Install Go 16 | uses: actions/setup-go@v1 17 | with: 18 | go-version: 1.24 19 | id: go 20 | - name: Login to DockerHub Registry 21 | run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v1 24 | - name: Build the Docker image 25 | run: | 26 | go build . 27 | docker build . --file Dockerfile --tag emitter/server:latest 28 | - name: Push the Docker image 29 | run: docker push emitter/server:latest 30 | -------------------------------------------------------------------------------- /internal/service/pubsub/service_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package pubsub 16 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 2.{build} 2 | platform: x64 3 | branches: 4 | only: 5 | - master 6 | 7 | clone_folder: c:\gopath\src\github.com\emitter-io\emitter 8 | 9 | environment: 10 | GOPATH: c:\gopath 11 | GO111MODULE: on 12 | 13 | install: 14 | - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% 15 | - rmdir c:\go /s /q 16 | - appveyor DownloadFile https://storage.googleapis.com/golang/go1.20.windows-amd64.zip 17 | - 7z x go1.20.windows-amd64.zip -y -oC:\ > NUL 18 | - go version 19 | 20 | build_script: 21 | - cmd: .\build.bat 22 | 23 | artifacts: 24 | - path: build/* 25 | name: binary 26 | 27 | deploy: 28 | - provider: GitHub 29 | auth_token: 30 | secure: ImwOgsH/e1F+reDfqNIvoQ773FZHsjQt/4znrFdxUVrs1VNpFK9IUaW4hIL/yl4c 31 | release: "edge" 32 | description: "This is v$(appveyor_build_version) pre-release which is automatically built on every commit to master." 33 | draft: false 34 | prerelease: true 35 | force_update: true 36 | artifact: binary 37 | on: 38 | branch: master 39 | -------------------------------------------------------------------------------- /deploy/k8s/gcloud/README.md: -------------------------------------------------------------------------------- 1 | # Deploying Emitter with Kubernetes on Google Cloud 2 | 3 | This directory contains Kubernetes configuration files which can be used to deploy an [production-grade cluster of emitter](https://emitter.io) on Google Cloud's Kubernetes (GKE). 4 | 5 | In order to get emitter running, you'll need to have `kubectl` [tool installed](https://kubernetes.io/docs/tasks/tools/install-kubectl/) and a [Google Cloud](https://cloud.google.com) account. 6 | 7 | ``` 8 | kubectl apply -f storage_ssd.yaml 9 | kubectl apply -f service_dns.yaml 10 | kubectl apply -f service_loadbalancer.yaml 11 | kubectl apply -f broker.yaml 12 | ``` 13 | 14 | 15 | The video tutorials below demonstrate how to create an emitter cluster with K8s and Google Cloud. 16 | 17 | [![Creating Kubernetes Cluster](https://s3.amazonaws.com/cdn.misakai.com/www-emitter/thumb/k8s-gcloud.png)](https://www.youtube.com/watch?v=l1uDWG3Suzw) 18 | [![Setting up Emitter Cluster](http://s3.amazonaws.com/cdn.misakai.com/www-emitter/thumb/emitter-gcloud.png)](https://www.youtube.com/watch?v=IL7WEH_2IOo) 19 | 20 | -------------------------------------------------------------------------------- /deploy/k8s/digitalocean/README.md: -------------------------------------------------------------------------------- 1 | # Deploying Emitter with Kubernetes on DigitalOcean 2 | 3 | This directory contains Kubernetes configuration files which can be used to deploy a production-grade cluster on DigitalOcean's Kubernetes. 4 | 5 | In order to get emitter running, you'll need to have `kubectl` installed (https://kubernetes.io/docs/tasks/tools/install-kubectl/) and a DigitalOcean account (100$ free credit here: https://m.do.co/c/5bf0e26da5f2). 6 | 7 | ``` 8 | kubectl --kubeconfig="" apply -f broker.yaml 9 | kubectl --kubeconfig="" apply -f service_dns.yaml 10 | kubectl --kubeconfig="" apply -f service_loadbalancer.yaml 11 | ``` 12 | 13 | The video tutorials below demonstrate how to create an emitter cluster with K8s and DigitalOcean. 14 | 15 | [![Creating Kubernetes Cluster](https://s3.amazonaws.com/cdn.misakai.com/www-emitter/thumb/k8s-digitalocean.png)](https://www.youtube.com/watch?v=lgSJCDTubqo) 16 | [![Setting up Emitter Cluster](http://s3.amazonaws.com/cdn.misakai.com/www-emitter/thumb/emitter-k8s.png)](https://www.youtube.com/watch?v=CsrKiNjZ2Ew) 17 | 18 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | release: 4 | types: [published] 5 | env: 6 | PROJECT: "github.com/emitter-io/emitter" 7 | GO111MODULE: "on" 8 | 9 | jobs: 10 | publish: 11 | name: Docker 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Install Go 15 | uses: actions/setup-go@v1 16 | with: 17 | go-version: 1.24 18 | id: go 19 | - name: Login to DockerHub Registry 20 | run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin 21 | - name: Check out code into the Go module directory 22 | uses: actions/checkout@v1 23 | - name: Get the version 24 | id: vars 25 | run: echo ::set-output name=tag::$(echo ${GITHUB_REF:10}) 26 | - name: Build the tagged Docker image 27 | run: | 28 | go build . 29 | docker build . --file Dockerfile --build-arg GO_BINARY=emitter --tag emitter/server:${{steps.vars.outputs.tag}} 30 | - name: Push the tagged Docker image 31 | run: docker push emitter/server:${{steps.vars.outputs.tag}} 32 | -------------------------------------------------------------------------------- /internal/service/me/requests_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package me 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func Test_Response(t *testing.T) { 24 | res := new(Response) 25 | res.ForRequest(1) 26 | assert.Equal(t, 1, int(res.Request)) 27 | } 28 | -------------------------------------------------------------------------------- /internal/service/link/requests_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package link 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func Test_Response(t *testing.T) { 24 | 25 | res := new(Response) 26 | res.ForRequest(1) 27 | assert.Equal(t, 1, int(res.Request)) 28 | } 29 | -------------------------------------------------------------------------------- /internal/service/presence/requests_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package presence 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func Test_Response(t *testing.T) { 24 | res := new(Response) 25 | res.ForRequest(1) 26 | assert.Equal(t, 1, int(res.Request)) 27 | } 28 | -------------------------------------------------------------------------------- /internal/service/keyban/requests_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package keyban 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func Test_Response(t *testing.T) { 24 | 25 | res := new(Response) 26 | res.ForRequest(1) 27 | assert.Equal(t, 1, int(res.Request)) 28 | } 29 | -------------------------------------------------------------------------------- /internal/network/mock/addr_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package mock 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestAddr(t *testing.T) { 24 | addr := Addr{ 25 | NetworkString: "1", 26 | AddrString: "2", 27 | } 28 | 29 | assert.Equal(t, "1", addr.Network()) 30 | assert.Equal(t, "2", addr.String()) 31 | } 32 | -------------------------------------------------------------------------------- /internal/errors/error_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package errors 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestErrors(t *testing.T) { 24 | assert.Equal(t, 500, New("test").Status) 25 | assert.Equal(t, ErrBadRequest.Message, ErrBadRequest.Error()) 26 | 27 | cpy := ErrBadRequest.Copy() 28 | cpy.ForRequest(15) 29 | assert.Equal(t, uint16(15), cpy.Request) 30 | } 31 | -------------------------------------------------------------------------------- /internal/network/mock/addr.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package mock 16 | 17 | // Addr is a fake network interface which implements the net.Addr interface 18 | type Addr struct { 19 | NetworkString string 20 | AddrString string 21 | } 22 | 23 | // Network gets the network string. 24 | func (a Addr) Network() string { 25 | return a.NetworkString 26 | } 27 | 28 | // Network gets the addr string. 29 | func (a Addr) String() string { 30 | return a.AddrString 31 | } 32 | -------------------------------------------------------------------------------- /internal/network/mock/conn_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package mock 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestConn(t *testing.T) { 24 | conn := NewConn() 25 | 26 | buffer := make([]byte, 10) 27 | go func() { 28 | conn.Server.Read(buffer) 29 | }() 30 | 31 | n, err := conn.Client.Write([]byte{1}) 32 | assert.Equal(t, 1, n) 33 | assert.NoError(t, err) 34 | assert.NoError(t, conn.Close()) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /internal/command/version/version.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package version 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/emitter-io/emitter/internal/provider/logging" 21 | cli "github.com/jawher/mow.cli" 22 | ) 23 | 24 | var ( 25 | version = "0" 26 | commit = "untracked" 27 | ) 28 | 29 | // Print prints the version 30 | func Print(cmd *cli.Cmd) { 31 | cmd.Spec = "" 32 | cmd.Action = func() { 33 | logging.LogAction("version", fmt.Sprintf("emitter version %s, commit %s", version, commit)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /internal/service/me/me_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package me 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/emitter-io/emitter/internal/service/fake" 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestMe(t *testing.T) { 25 | s := New() 26 | c := &fake.Conn{ 27 | Shortcuts: map[string]string{ 28 | "a": "test", 29 | }, 30 | } 31 | 32 | r, ok := s.OnRequest(c, nil) 33 | assert.Equal(t, true, ok) 34 | 35 | resp := r.(*Response) 36 | assert.Contains(t, resp.Links, "a") 37 | 38 | } 39 | -------------------------------------------------------------------------------- /internal/command/license/license_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package license 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/jawher/mow.cli" 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestNew(t *testing.T) { 25 | assert.NotPanics(t, func() { 26 | runCommand(New) 27 | }) 28 | } 29 | 30 | func runCommand(f func(cmd *cli.Cmd), args ...string) { 31 | app := cli.App("emitter", "") 32 | app.Command("test", "", f) 33 | v := []string{"emitter", "test"} 34 | v = append(v, args...) 35 | app.Run(v) 36 | } 37 | -------------------------------------------------------------------------------- /internal/provider/monitor/stats_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package monitor 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestNoop_Configure(t *testing.T) { 24 | s := NewNoop() 25 | err := s.Configure(nil) 26 | assert.NoError(t, err) 27 | } 28 | 29 | func TestNoop_Name(t *testing.T) { 30 | s := new(Noop) 31 | assert.Equal(t, "noop", s.Name()) 32 | } 33 | 34 | func TestNoop_Close(t *testing.T) { 35 | s := new(Noop) 36 | err := s.Close() 37 | assert.NoError(t, err) 38 | } 39 | -------------------------------------------------------------------------------- /internal/command/version/version_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package version 16 | 17 | import ( 18 | "testing" 19 | 20 | cli "github.com/jawher/mow.cli" 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestNew(t *testing.T) { 25 | assert.NotPanics(t, func() { 26 | runCommand(Print) 27 | }) 28 | } 29 | 30 | func runCommand(f func(cmd *cli.Cmd), args ...string) { 31 | app := cli.App("emitter", "") 32 | app.Command("version", "", f) 33 | v := []string{"emitter", "version"} 34 | v = append(v, args...) 35 | app.Run(v) 36 | } 37 | -------------------------------------------------------------------------------- /internal/service/keygen/requests_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package keygen 16 | 17 | import ( 18 | "testing" 19 | "time" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func Test_access(t *testing.T) { 25 | r := &Request{ 26 | Type: "rwslpex", 27 | } 28 | 29 | assert.Zero(t, r.expires().Unix()) 30 | assert.Equal(t, 254, int(r.access())) 31 | } 32 | 33 | func Test_expires(t *testing.T) { 34 | req := &Request{ 35 | TTL: 20, 36 | } 37 | 38 | assert.Less(t, time.Now().Unix(), req.expires().Unix()) 39 | 40 | res := new(Response) 41 | res.ForRequest(1) 42 | assert.Equal(t, 1, int(res.Request)) 43 | } 44 | -------------------------------------------------------------------------------- /internal/command/license/license.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package license 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/emitter-io/emitter/internal/provider/logging" 21 | "github.com/emitter-io/emitter/internal/security/license" 22 | "github.com/jawher/mow.cli" 23 | ) 24 | 25 | // New generates a new license and secret key pair. 26 | func New(cmd *cli.Cmd) { 27 | cmd.Spec = "" 28 | cmd.Action = func() { 29 | license, secret := license.New() 30 | logging.LogAction("license", fmt.Sprintf("generated new license: %v", license)) 31 | logging.LogAction("license", fmt.Sprintf("generated new secret key: %v", secret)) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /internal/network/mock/end_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package mock 16 | 17 | import ( 18 | "io" 19 | "testing" 20 | "time" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestEnd(t *testing.T) { 26 | r, w := io.Pipe() 27 | end := End{ 28 | Reader: r, 29 | Writer: w, 30 | } 31 | 32 | assert.Equal(t, "127.0.0.1", end.LocalAddr().String()) 33 | assert.Equal(t, "127.0.0.1", end.RemoteAddr().String()) 34 | assert.NoError(t, end.Close()) 35 | assert.NoError(t, end.SetDeadline(time.Now())) 36 | assert.NoError(t, end.SetReadDeadline(time.Now())) 37 | assert.NoError(t, end.SetWriteDeadline(time.Now())) 38 | 39 | } 40 | -------------------------------------------------------------------------------- /internal/service/me/requests.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package me 16 | 17 | // Response represents a response for the 'me' request. 18 | type Response struct { 19 | Request uint16 `json:"req,omitempty"` // The corresponding request ID. 20 | Status int `json:"status"` // The status of the response 21 | ID string `json:"id"` // The private ID of the connection. 22 | Links map[string]string `json:"links,omitempty"` // The set of pre-defined channels. 23 | } 24 | 25 | // ForRequest sets the request ID in the response for matching 26 | func (r *Response) ForRequest(id uint16) { 27 | r.Request = id 28 | } 29 | -------------------------------------------------------------------------------- /internal/network/mock/noop_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package mock 16 | 17 | import ( 18 | "testing" 19 | "time" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestNoop(t *testing.T) { 25 | end := NewNoop() 26 | 27 | assert.Equal(t, "127.0.0.1", end.LocalAddr().String()) 28 | assert.Equal(t, "127.0.0.1", end.RemoteAddr().String()) 29 | assert.NoError(t, end.Close()) 30 | assert.NoError(t, end.SetDeadline(time.Now())) 31 | assert.NoError(t, end.SetReadDeadline(time.Now())) 32 | assert.NoError(t, end.SetWriteDeadline(time.Now())) 33 | 34 | n, err := end.Read(nil) 35 | assert.Equal(t, 0, n) 36 | assert.NoError(t, err) 37 | 38 | n, err = end.Write([]byte{1, 2, 3}) 39 | assert.Equal(t, 3, n) 40 | assert.NoError(t, err) 41 | } 42 | -------------------------------------------------------------------------------- /internal/provider/monitor/self_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package monitor 16 | 17 | import ( 18 | "strings" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | type snapshot string 25 | 26 | func (s snapshot) Snapshot() []byte { 27 | return []byte(s) 28 | } 29 | 30 | func TestSelf(t *testing.T) { 31 | r := snapshot("test") 32 | cfg := map[string]interface{}{ 33 | "interval": float64(100), 34 | "channel": "chan", 35 | } 36 | 37 | s := NewSelf(r, func(c string, v []byte) { 38 | assert.True(t, strings.HasPrefix(c, "chan/")) 39 | assert.Equal(t, "test", string(v)) 40 | }) 41 | 42 | err := s.Configure(cfg) 43 | assert.NoError(t, err) 44 | assert.Equal(t, "self", s.Name()) 45 | 46 | errClose := s.Close() 47 | assert.NoError(t, errClose) 48 | } 49 | -------------------------------------------------------------------------------- /internal/network/http/mock_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package http 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | "github.com/stretchr/testify/mock" 22 | ) 23 | 24 | func TestMock(t *testing.T) { 25 | 26 | c := NewMockClient() 27 | 28 | url := "testurl.com" 29 | buf := []byte("test") 30 | out := new(testObject) 31 | 32 | // Get(url string, output interface{}, headers ...HeaderValue) error 33 | c.On("Get", url, mock.Anything, mock.Anything).Return([]byte{}, nil).Once() 34 | _, e1 := c.Get(url, out) 35 | assert.Nil(t, e1) 36 | 37 | // Post(url string, body []byte, output interface{}, headers ...HeaderValue) error 38 | c.On("Post", url, mock.Anything, mock.Anything, mock.Anything).Return([]byte{}, nil).Once() 39 | _, e2 := c.Post(url, buf, out) 40 | assert.Nil(t, e2) 41 | 42 | } 43 | -------------------------------------------------------------------------------- /internal/service/cluster/memberlist_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package cluster 16 | 17 | import ( 18 | "testing" 19 | "time" 20 | 21 | "github.com/emitter-io/emitter/internal/message" 22 | "github.com/stretchr/testify/assert" 23 | "github.com/weaveworks/mesh" 24 | ) 25 | 26 | func TestFallback(t *testing.T) { 27 | m := newMemberlist(newPeer) 28 | _, ok := m.Fallback(2000) 29 | assert.False(t, ok) 30 | 31 | m.GetOrAdd(1900) 32 | m.GetOrAdd(2000) 33 | m.GetOrAdd(2500) 34 | m.GetOrAdd(2200) 35 | 36 | f, ok := m.Fallback(2000) 37 | assert.True(t, ok) 38 | assert.Equal(t, 2200, int(f.name)) 39 | } 40 | 41 | func newPeer(name mesh.PeerName) *Peer { 42 | return &Peer{ 43 | name: name, 44 | frame: message.NewFrame(defaultFrameSize), 45 | subs: message.NewCounters(), 46 | activity: time.Now().Unix(), 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /internal/broker/status_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package broker 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/emitter-io/emitter/internal/config" 21 | "github.com/emitter-io/emitter/internal/message" 22 | "github.com/emitter-io/emitter/internal/security/license" 23 | "github.com/emitter-io/stats" 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | func Test_sendStats(t *testing.T) { 28 | license, _ := license.Parse(testLicense) 29 | 30 | assert.NotPanics(t, func() { 31 | s := &Service{ 32 | subscriptions: message.NewTrie(), 33 | measurer: stats.New(), 34 | License: license, 35 | Config: &config.Config{ 36 | ListenAddr: ":1234", 37 | }, 38 | } 39 | defer s.Close() 40 | 41 | sampler := newSampler(s, s.measurer) 42 | b := sampler.Snapshot() 43 | assert.NotZero(t, len(b)) 44 | 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /internal/service/keyban/requests.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package keyban 16 | 17 | // Request represents a key ban request. 18 | type Request struct { 19 | Secret string `json:"secret"` // The master key to use. 20 | Target string `json:"target"` // The target key to ban. 21 | Banned bool `json:"banned"` // Whether the target should be banned or not. 22 | } 23 | 24 | // ------------------------------------------------------------------------------------ 25 | 26 | // Response represents a key ban response. 27 | type Response struct { 28 | Request uint16 `json:"req,omitempty"` 29 | Status int `json:"status"` // The status of the response 30 | Banned bool `json:"banned"` // Whether the target should be banned or not. 31 | } 32 | 33 | // ForRequest sets the request ID in the response for matching 34 | func (r *Response) ForRequest(id uint16) { 35 | r.Request = id 36 | } 37 | -------------------------------------------------------------------------------- /deploy/k8s/digitalocean/broker.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: broker 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: broker # has to match .spec.template.metadata.labels 9 | serviceName: "broker" 10 | replicas: 3 11 | template: 12 | metadata: 13 | labels: 14 | app: broker 15 | role: broker 16 | spec: 17 | terminationGracePeriodSeconds: 30 18 | affinity: 19 | podAntiAffinity: 20 | requiredDuringSchedulingIgnoredDuringExecution: 21 | - labelSelector: 22 | matchExpressions: 23 | - key: app 24 | operator: In 25 | values: 26 | - broker 27 | topologyKey: kubernetes.io/hostname 28 | containers: 29 | - env: 30 | - name: EMITTER_LICENSE 31 | value: "..." # <- Provide license 32 | - name: EMITTER_CLUSTER_SEED 33 | value: "broker" # or "broker-0.broker.default.svc.cluster.local" 34 | - name: EMITTER_CLUSTER_ADVERTISE 35 | value: "private:4000" 36 | - name: EMITTER_STORAGE_PROVIDER 37 | value: "ssd" 38 | name: broker 39 | image: emitter/server:latest 40 | ports: 41 | - containerPort: 8080 42 | - containerPort: 443 43 | - containerPort: 4000 44 | volumeMounts: 45 | - name: broker-volume 46 | mountPath: /data 47 | restartPolicy: Always 48 | updateStrategy: 49 | type: RollingUpdate 50 | volumeClaimTemplates: 51 | - metadata: 52 | name: broker-volume 53 | spec: 54 | accessModes: [ "ReadWriteOnce" ] 55 | storageClassName: do-block-storage 56 | resources: 57 | requests: 58 | storage: 50Gi 59 | -------------------------------------------------------------------------------- /deploy/k8s/aws/broker.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: broker 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: broker # has to match .spec.template.metadata.labels 9 | serviceName: "broker" 10 | replicas: 3 11 | template: 12 | metadata: 13 | labels: 14 | app: broker 15 | role: broker 16 | spec: 17 | terminationGracePeriodSeconds: 30 18 | affinity: 19 | podAntiAffinity: 20 | requiredDuringSchedulingIgnoredDuringExecution: 21 | - labelSelector: 22 | matchExpressions: 23 | - key: app 24 | operator: In 25 | values: 26 | - broker 27 | topologyKey: kubernetes.io/hostname 28 | containers: 29 | - env: 30 | - name: EMITTER_LICENSE 31 | value: "..." # <- Provide license 32 | - name: EMITTER_CLUSTER_SEED 33 | value: "broker" # or "broker-0.broker.default.svc.cluster.local" 34 | - name: EMITTER_CLUSTER_ADVERTISE 35 | value: "private:4000" 36 | - name: EMITTER_STORAGE_PROVIDER 37 | value: "ssd" 38 | name: broker 39 | image: emitter/server:latest 40 | ports: 41 | - containerPort: 8080 42 | - containerPort: 443 43 | - containerPort: 4000 44 | volumeMounts: 45 | - name: broker-volume 46 | mountPath: /data 47 | restartPolicy: Always 48 | updateStrategy: 49 | type: RollingUpdate 50 | volumeClaimTemplates: 51 | - metadata: 52 | name: broker-volume 53 | annotations: 54 | volume.beta.kubernetes.io/storage-class: "fast" 55 | spec: 56 | accessModes: [ "ReadWriteOnce" ] 57 | resources: 58 | requests: 59 | storage: 50Gi -------------------------------------------------------------------------------- /internal/security/id_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package security 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestNewID(t *testing.T) { 24 | defer func(n uint64) { next = n }(next) 25 | 26 | next = 0 27 | i1 := NewID() 28 | i2 := NewID() 29 | 30 | assert.Equal(t, ID(1), i1) 31 | assert.Equal(t, ID(2), i2) 32 | } 33 | 34 | func TestIDToString(t *testing.T) { 35 | defer func(n uint64) { next = n }(next) 36 | 37 | next = 0 38 | i1 := NewID() 39 | i2 := NewID() 40 | 41 | assert.Equal(t, "01", i1.String()) 42 | assert.Equal(t, "02", i2.String()) 43 | } 44 | 45 | func TestIDToUnique(t *testing.T) { 46 | defer func(n uint64) { next = n }(next) 47 | 48 | next = 0 49 | i1 := NewID() 50 | i2 := NewID() 51 | 52 | assert.Equal(t, "F45JPXDSXVRWBUKTDNCCM4PGQI", i1.Unique(123, "hello")) 53 | assert.Equal(t, "XCFU2OA7OO2COPZOJ5VA6GS6BM", i2.Unique(123, "hello")) 54 | } 55 | -------------------------------------------------------------------------------- /deploy/k8s/gcloud/broker.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: broker 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: broker # has to match .spec.template.metadata.labels 9 | serviceName: "broker" 10 | replicas: 3 11 | template: 12 | metadata: 13 | labels: 14 | app: broker 15 | role: broker 16 | spec: 17 | terminationGracePeriodSeconds: 30 18 | affinity: 19 | podAntiAffinity: 20 | requiredDuringSchedulingIgnoredDuringExecution: 21 | - labelSelector: 22 | matchExpressions: 23 | - key: app 24 | operator: In 25 | values: 26 | - broker 27 | topologyKey: kubernetes.io/hostname 28 | containers: 29 | - env: 30 | - name: EMITTER_LICENSE 31 | value: "..." # <- Provide license 32 | - name: EMITTER_CLUSTER_SEED 33 | value: "broker" # or "broker-0.broker.default.svc.cluster.local" 34 | - name: EMITTER_CLUSTER_ADVERTISE 35 | value: "private:4000" 36 | - name: EMITTER_STORAGE_PROVIDER 37 | value: "ssd" 38 | name: broker 39 | image: emitter/server:latest 40 | ports: 41 | - containerPort: 8080 42 | - containerPort: 443 43 | - containerPort: 4000 44 | volumeMounts: 45 | - name: broker-volume 46 | mountPath: /data 47 | restartPolicy: Always 48 | updateStrategy: 49 | type: RollingUpdate 50 | volumeClaimTemplates: 51 | - metadata: 52 | name: broker-volume 53 | annotations: 54 | volume.beta.kubernetes.io/storage-class: "fast" 55 | spec: 56 | accessModes: [ "ReadWriteOnce" ] 57 | resources: 58 | requests: 59 | storage: 50Gi -------------------------------------------------------------------------------- /deploy/k8s/azure/broker.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: broker 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: broker # has to match .spec.template.metadata.labels 9 | serviceName: "broker" 10 | replicas: 3 11 | template: 12 | metadata: 13 | labels: 14 | app: broker 15 | role: broker 16 | spec: 17 | terminationGracePeriodSeconds: 30 18 | affinity: 19 | podAntiAffinity: 20 | requiredDuringSchedulingIgnoredDuringExecution: 21 | - labelSelector: 22 | matchExpressions: 23 | - key: app 24 | operator: In 25 | values: 26 | - broker 27 | topologyKey: kubernetes.io/hostname 28 | containers: 29 | - env: 30 | - name: EMITTER_LICENSE 31 | value: "..." # <- Provide license 32 | - name: EMITTER_CLUSTER_SEED 33 | value: "broker" # or "broker-0.broker.default.svc.cluster.local" 34 | - name: EMITTER_CLUSTER_ADVERTISE 35 | value: "private:4000" 36 | - name: EMITTER_STORAGE_PROVIDER 37 | value: "ssd" 38 | name: broker 39 | image: emitter/server:latest 40 | ports: 41 | - containerPort: 8080 42 | - containerPort: 443 43 | - containerPort: 4000 44 | volumeMounts: 45 | - name: broker-volume 46 | mountPath: /data 47 | restartPolicy: Always 48 | updateStrategy: 49 | type: RollingUpdate 50 | volumeClaimTemplates: 51 | - metadata: 52 | name: broker-volume 53 | annotations: 54 | volume.beta.kubernetes.io/storage-class: "managed-premium" 55 | spec: 56 | accessModes: [ "ReadWriteOnce" ] 57 | resources: 58 | requests: 59 | storage: 50Gi -------------------------------------------------------------------------------- /internal/service/me/me.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package me 16 | 17 | import ( 18 | "regexp" 19 | 20 | "github.com/emitter-io/emitter/internal/security" 21 | "github.com/emitter-io/emitter/internal/service" 22 | ) 23 | 24 | var ( 25 | shortcut = regexp.MustCompile("^[a-zA-Z0-9]{1,2}$") 26 | ) 27 | 28 | type keygen interface { 29 | DecryptKey(string) (security.Key, error) 30 | } 31 | 32 | // Service represents a self-introspection service. 33 | type Service struct{} 34 | 35 | // New creates a new service. 36 | func New() *Service { 37 | return new(Service) 38 | } 39 | 40 | // OnRequest handles a request to create a link. 41 | func (s *Service) OnRequest(c service.Conn, payload []byte) (service.Response, bool) { 42 | links := make(map[string]string) 43 | for k, v := range c.Links() { 44 | links[k] = security.ParseChannel([]byte(v)).SafeString() 45 | } 46 | 47 | return &Response{ 48 | Status: 200, 49 | ID: c.ID(), 50 | Links: links, 51 | }, true 52 | } 53 | -------------------------------------------------------------------------------- /internal/config/config_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package config 16 | 17 | import ( 18 | "os" 19 | "strings" 20 | "testing" 21 | 22 | "github.com/emitter-io/config/dynamo" 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func Test_NewDefaut(t *testing.T) { 27 | c := NewDefault().(*Config) 28 | assert.Equal(t, ":8080", c.ListenAddr) 29 | //assert.Nil(t, c.Vault()) 30 | 31 | tls, _, ok := c.Certificate() 32 | assert.Nil(t, tls) 33 | assert.False(t, ok) 34 | } 35 | 36 | func Test_Addr(t *testing.T) { 37 | c := &Config{ 38 | ListenAddr: "private", 39 | } 40 | 41 | addr := c.Addr() 42 | assert.True(t, strings.HasSuffix(addr.String(), ":8080")) 43 | } 44 | 45 | func Test_AddrInvalid(t *testing.T) { 46 | assert.Panics(t, func() { 47 | c := &Config{ListenAddr: "g3ew235wgs"} 48 | c.Addr() 49 | }) 50 | } 51 | 52 | func Test_New(t *testing.T) { 53 | c := New("test.conf", dynamo.NewProvider()) 54 | defer os.Remove("test.conf") 55 | 56 | assert.NotNil(t, c) 57 | } 58 | -------------------------------------------------------------------------------- /internal/network/mock/conn.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package mock 16 | 17 | import "io" 18 | 19 | // Conn facilitates testing by providing two connected ReadWriteClosers 20 | // each of which can be used in place of a net.Conn 21 | type Conn struct { 22 | Server *End 23 | Client *End 24 | } 25 | 26 | // NewConn creates a new mock connection. 27 | func NewConn() *Conn { 28 | // A connection consists of two pipes: 29 | // Client | Server 30 | // writes ===> reads 31 | // reads <=== writes 32 | 33 | serverRead, clientWrite := io.Pipe() 34 | clientRead, serverWrite := io.Pipe() 35 | 36 | return &Conn{ 37 | Server: &End{ 38 | Reader: serverRead, 39 | Writer: serverWrite, 40 | }, 41 | Client: &End{ 42 | Reader: clientRead, 43 | Writer: clientWrite, 44 | }, 45 | } 46 | } 47 | 48 | // Close closes the mock connection. 49 | func (c *Conn) Close() (err error) { 50 | if err = c.Server.Close(); err == nil { 51 | err = c.Client.Close() 52 | } 53 | return 54 | } 55 | -------------------------------------------------------------------------------- /internal/service/history/service.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package history 16 | 17 | import ( 18 | "github.com/emitter-io/emitter/internal/provider/storage" 19 | "github.com/emitter-io/emitter/internal/security/hash" 20 | "github.com/emitter-io/emitter/internal/service" 21 | ) 22 | 23 | // Service represents a history service. 24 | type Service struct { 25 | auth service.Authorizer // The authorizer to use. 26 | store storage.Storage // The storage provider to use. 27 | handlers map[uint32]service.Handler // The emitter request handlers. 28 | } 29 | 30 | // New creates a new publisher service. 31 | func New(auth service.Authorizer, store storage.Storage) *Service { 32 | return &Service{ 33 | auth: auth, 34 | store: store, 35 | handlers: make(map[uint32]service.Handler), 36 | } 37 | } 38 | 39 | // Handle adds a handler for an "emitter/..." request 40 | func (s *Service) Handle(request string, handler service.Handler) { 41 | s.handlers[hash.OfString(request)] = handler 42 | } 43 | -------------------------------------------------------------------------------- /internal/security/license/v1_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package license 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestNewV1(t *testing.T) { 24 | l := NewV1() 25 | assert.NotEqual(t, "", l.EncryptionKey) 26 | assert.Len(t, l.EncryptionKey, 22) 27 | 28 | c, err := l.Cipher() 29 | assert.NotNil(t, c) 30 | assert.NoError(t, err) 31 | 32 | text := l.String() 33 | assert.NotEqual(t, "", text) 34 | assert.Equal(t, ":1", text[len(text)-2:]) 35 | 36 | out, err := parseV1(text[:len(text)-2]) 37 | assert.NoError(t, err) 38 | assert.Equal(t, l, out) 39 | 40 | master, err := l.NewMasterKey(9) 41 | assert.NoError(t, err) 42 | assert.Equal(t, 9, int(master.Master())) 43 | } 44 | 45 | func TestParseV1(t *testing.T) { 46 | l, err := Parse("zT83oDV0DWY5_JysbSTPTDr8KB0AAAAAFCDVAAAAAAI:1") 47 | assert.NoError(t, err) 48 | assert.Equal(t, uint32(0x3afc281d), l.Contract()) 49 | assert.Equal(t, uint32(0x0), l.Signature()) 50 | assert.Equal(t, uint32(0x1), l.Master()) 51 | } -------------------------------------------------------------------------------- /internal/provider/contract/mock/mock_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package mock 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/emitter-io/emitter/internal/provider/usage" 21 | "github.com/stretchr/testify/assert" 22 | "github.com/stretchr/testify/mock" 23 | ) 24 | 25 | func TestMock(t *testing.T) { 26 | 27 | id := uint32(1) 28 | 29 | c := new(Contract) 30 | c.On("Validate", mock.Anything).Return(true) 31 | c.On("Stats").Return(usage.NewMeter(id)) 32 | 33 | m := NewContractProvider() 34 | cfg := make(map[string]interface{}) 35 | 36 | assert.Equal(t, "mock", m.Name()) 37 | 38 | m.On("Configure", cfg).Return(nil) 39 | assert.NoError(t, m.Configure(cfg)) 40 | 41 | m.On("Get", id).Return(c, true) 42 | c1, o1 := m.Get(id) 43 | assert.True(t, o1) 44 | assert.Equal(t, c, c1) 45 | assert.True(t, c1.Validate(nil), true) 46 | assert.Equal(t, usage.NewMeter(id), c1.Stats()) 47 | 48 | m.On("Create").Return(c, nil) 49 | contract, err := m.Create() 50 | assert.Equal(t, c, contract) 51 | assert.NoError(t, err) 52 | } 53 | -------------------------------------------------------------------------------- /internal/network/http/mock.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package http 16 | 17 | import ( 18 | "github.com/stretchr/testify/mock" 19 | ) 20 | 21 | // MockClient is a mock implementation of Client 22 | type MockClient struct { 23 | mock.Mock 24 | } 25 | 26 | // NewMockClient returns a mock implementation of Client 27 | func NewMockClient() *MockClient { 28 | return &MockClient{ 29 | Mock: mock.Mock{}, 30 | } 31 | } 32 | 33 | // Get issues an HTTP Get on a specified URL and decodes the payload as JSON. 34 | func (mock *MockClient) Get(url string, output interface{}, headers ...HeaderValue) ([]byte, error) { 35 | mockArgs := mock.Called(url, output, headers) 36 | return mockArgs.Get(0).([]byte), mockArgs.Error(1) 37 | } 38 | 39 | // Post is a utility function which marshals and issues an HTTP post on a specified URL. 40 | func (mock *MockClient) Post(url string, body []byte, output interface{}, headers ...HeaderValue) ([]byte, error) { 41 | mockArgs := mock.Called(url, body, output, headers) 42 | return mockArgs.Get(0).([]byte), mockArgs.Error(1) 43 | } 44 | -------------------------------------------------------------------------------- /internal/async/timer_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package async 16 | 17 | import ( 18 | "context" 19 | "sync/atomic" 20 | "testing" 21 | "time" 22 | 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func TestRepeat(t *testing.T) { 27 | assert.NotPanics(t, func() { 28 | out := make(chan bool, 1) 29 | cancel := Repeat(context.TODO(), time.Nanosecond*10, func() { 30 | out <- true 31 | }) 32 | 33 | <-out 34 | v := <-out 35 | assert.True(t, v) 36 | cancel() 37 | }) 38 | } 39 | 40 | func TestRepeatFirstActionPanic(t *testing.T) { 41 | assert.NotPanics(t, func() { 42 | cancel := Repeat(context.TODO(), time.Nanosecond*10, func() { 43 | panic("test") 44 | }) 45 | 46 | cancel() 47 | }) 48 | } 49 | 50 | func TestRepeatPanic(t *testing.T) { 51 | assert.NotPanics(t, func() { 52 | var counter int32 53 | 54 | cancel := Repeat(context.TODO(), time.Nanosecond*10, func() { 55 | atomic.AddInt32(&counter, 1) 56 | panic("test") 57 | }) 58 | 59 | for atomic.LoadInt32(&counter) <= 10 { 60 | } 61 | 62 | cancel() 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /internal/broker/conn_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package broker 16 | 17 | import ( 18 | "io" 19 | "testing" 20 | 21 | "github.com/emitter-io/emitter/internal/errors" 22 | "github.com/emitter-io/emitter/internal/message" 23 | netmock "github.com/emitter-io/emitter/internal/network/mock" 24 | "github.com/emitter-io/emitter/internal/security/license" 25 | "github.com/emitter-io/stats" 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func newTestConn() (pipe *netmock.Conn, conn *Conn) { 30 | license, _ := license.Parse(testLicense) 31 | s := &Service{ 32 | subscriptions: message.NewTrie(), 33 | License: license, 34 | measurer: stats.NewNoop(), 35 | } 36 | 37 | pipe = netmock.NewConn() 38 | conn = s.newConn(pipe.Client, 0) 39 | return 40 | } 41 | 42 | func TestNotifyError(t *testing.T) { 43 | pipe, conn := newTestConn() 44 | assert.NotNil(t, pipe) 45 | 46 | go func() { 47 | conn.notifyError(errors.ErrUnauthorized, 1) 48 | conn.Close() 49 | }() 50 | 51 | b, err := io.ReadAll(pipe.Server) 52 | assert.Contains(t, string(b), errors.ErrUnauthorized.Message) 53 | assert.NoError(t, err) 54 | } 55 | -------------------------------------------------------------------------------- /internal/async/timer.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package async 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "runtime/debug" 21 | "time" 22 | 23 | "github.com/emitter-io/emitter/internal/provider/logging" 24 | ) 25 | 26 | // Repeat performs an action asynchronously on a predetermined interval. 27 | func Repeat(ctx context.Context, interval time.Duration, action func()) context.CancelFunc { 28 | 29 | // Create cancellation context first 30 | ctx, cancel := context.WithCancel(ctx) 31 | safeAction := func() { 32 | defer handlePanic() 33 | action() 34 | } 35 | 36 | // Perform the action for the first time, syncrhonously 37 | safeAction() 38 | timer := time.NewTicker(interval) 39 | go func() { 40 | 41 | for { 42 | select { 43 | case <-ctx.Done(): 44 | timer.Stop() 45 | return 46 | case <-timer.C: 47 | safeAction() 48 | } 49 | } 50 | }() 51 | 52 | return cancel 53 | } 54 | 55 | // handlePanic handles the panic and logs it out. 56 | func handlePanic() { 57 | if r := recover(); r != nil { 58 | logging.LogAction("async", fmt.Sprintf("panic recovered: %ss \n %s", r, debug.Stack())) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /internal/service/link/requests.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package link 16 | 17 | // Request represents a link generation request. 18 | type Request struct { 19 | Name string `json:"name"` // The name of the shortcut, max 2 characters. 20 | Key string `json:"key"` // The key for the channel. 21 | Channel string `json:"channel"` // The channel name for the shortcut. 22 | Subscribe bool `json:"subscribe"` // Specifies whether the broker should auto-subscribe. 23 | } 24 | 25 | // ------------------------------------------------------------------------------------ 26 | 27 | // Response represents a link generation response. 28 | type Response struct { 29 | Request uint16 `json:"req,omitempty"` // The corresponding request ID. 30 | Status int `json:"status"` // The status of the response. 31 | Name string `json:"name,omitempty"` // The name of the shortcut, max 2 characters. 32 | Channel string `json:"channel,omitempty"` // The channel which was registered. 33 | } 34 | 35 | // ForRequest sets the request ID in the response for matching 36 | func (r *Response) ForRequest(id uint16) { 37 | r.Request = id 38 | } 39 | -------------------------------------------------------------------------------- /internal/security/license/v2_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package license 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestNewV2(t *testing.T) { 24 | l := NewV2() 25 | assert.NotEqual(t, "", l.EncryptionKey) 26 | assert.Len(t, l.EncryptionKey, 32) 27 | 28 | c, err := l.Cipher() 29 | assert.NotNil(t, c) 30 | assert.NoError(t, err) 31 | 32 | text := l.String() 33 | assert.NotEqual(t, "", text) 34 | assert.Equal(t, ":2", text[len(text)-2:]) 35 | 36 | out, err := parseV2(text[:len(text)-2]) 37 | assert.NoError(t, err) 38 | assert.Equal(t, l, out) 39 | 40 | master, err := l.NewMasterKey(9) 41 | assert.NoError(t, err) 42 | assert.Equal(t, 9, int(master.Master())) 43 | } 44 | 45 | func TestParseV2(t *testing.T) { 46 | l, err := Parse("RfBEIO9PA3cczC6bZQGnEeX8zbgKhm5Gw4ZlJSFJsaChuGStGCKNZ-8LTxKwiD7wK8EOAhrmleUY7PbLHrmCkokB0NaYgAEB:2") 47 | assert.NoError(t, err) 48 | assert.Equal(t, uint32(0x11248139), l.Contract()) 49 | assert.Equal(t, uint32(0x10062b50), l.Signature()) 50 | assert.Equal(t, uint32(0x1), l.Master()) 51 | } 52 | 53 | func TestParseV2_Invalid(t *testing.T) { 54 | _, err := Parse("``````````:2") 55 | assert.Error(t, err) 56 | 57 | _, err = Parse("xxxxxx:2") 58 | assert.Error(t, err) 59 | } 60 | -------------------------------------------------------------------------------- /internal/service/pubsub/service.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package pubsub 16 | 17 | import ( 18 | "github.com/emitter-io/emitter/internal/message" 19 | "github.com/emitter-io/emitter/internal/provider/storage" 20 | "github.com/emitter-io/emitter/internal/security/hash" 21 | "github.com/emitter-io/emitter/internal/service" 22 | ) 23 | 24 | // Service represents a publish service. 25 | type Service struct { 26 | auth service.Authorizer // The authorizer to use. 27 | store storage.Storage // The storage provider to use. 28 | notifier service.Notifier // The notifier to use. 29 | trie *message.Trie // The subscription matching trie. 30 | handlers map[uint32]service.Handler // The emitter request handlers. 31 | } 32 | 33 | // New creates a new publisher service. 34 | func New(auth service.Authorizer, store storage.Storage, notifier service.Notifier, trie *message.Trie) *Service { 35 | return &Service{ 36 | auth: auth, 37 | store: store, 38 | notifier: notifier, 39 | trie: trie, 40 | handlers: make(map[uint32]service.Handler), 41 | } 42 | } 43 | 44 | // Handle adds a handler for an "emitter/..." request 45 | func (s *Service) Handle(request string, handler service.Handler) { 46 | s.handlers[hash.OfString(request)] = handler 47 | } 48 | -------------------------------------------------------------------------------- /internal/provider/monitor/stats.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package monitor 16 | 17 | import ( 18 | "io" 19 | "time" 20 | 21 | "github.com/emitter-io/config" 22 | ) 23 | 24 | var defaultInterval = 5 * time.Second 25 | 26 | // Storage represents a message storage contract that message storage provides 27 | // must fulfill. 28 | type Storage interface { 29 | config.Provider 30 | io.Closer 31 | } 32 | 33 | // ------------------------------------------------------------------------------------ 34 | 35 | // Noop implements Storage contract. 36 | var _ Storage = new(Noop) 37 | 38 | // Noop represents a storage which does nothing. 39 | type Noop struct{} 40 | 41 | // NewNoop creates a new no-op storage. 42 | func NewNoop() *Noop { 43 | return new(Noop) 44 | } 45 | 46 | // Name returns the name of the provider. 47 | func (s *Noop) Name() string { 48 | return "noop" 49 | } 50 | 51 | // Configure configures the storage. The config parameter provided is 52 | // loosely typed, since various storage mechanisms will require different 53 | // configurations. 54 | func (s *Noop) Configure(config map[string]interface{}) error { 55 | return nil 56 | } 57 | 58 | // Close gracefully terminates the storage and ensures that every related 59 | // resource is properly disposed. 60 | func (s *Noop) Close() error { 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /internal/security/license/v3_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package license 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestNewV3(t *testing.T) { 24 | l := NewV3() 25 | l.User = 0x11248139 26 | l.Sign = 0x10062b50 27 | l.Index = 0x1 28 | assert.NotEqual(t, "", l.EncryptionKey) 29 | assert.Len(t, l.EncryptionKey, 32) 30 | 31 | c, err := l.Cipher() 32 | assert.NotNil(t, c) 33 | assert.NoError(t, err) 34 | 35 | text := l.String() 36 | assert.NotEqual(t, "", text) 37 | assert.Equal(t, ":3", text[len(text)-2:]) 38 | 39 | out, err := parseV3(text[:len(text)-2]) 40 | assert.NoError(t, err) 41 | assert.Equal(t, l, out) 42 | 43 | master, err := l.NewMasterKey(9) 44 | assert.NoError(t, err) 45 | assert.Equal(t, 9, int(master.Master())) 46 | } 47 | 48 | func TestParseV3(t *testing.T) { 49 | l, err := Parse("PfA8IPA43P8Jm4LfESjJYjzPIUG71uFkvKriYQjI4ZO2ABfHEBE36u13MmpHU5AumxGZKXG5gpKJAdDWmIABAQ:3") 50 | assert.NoError(t, err) 51 | assert.Equal(t, uint32(0x11248139), l.Contract()) 52 | assert.Equal(t, uint32(0x10062b50), l.Signature()) 53 | assert.Equal(t, uint32(0x1), l.Master()) 54 | } 55 | 56 | func TestParseV3_Invalid(t *testing.T) { 57 | _, err := Parse("``````````:2") 58 | assert.Error(t, err) 59 | 60 | _, err = Parse("xxxxxx:2") 61 | assert.Error(t, err) 62 | } 63 | -------------------------------------------------------------------------------- /internal/network/mock/noop.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package mock 16 | 17 | import ( 18 | "net" 19 | "time" 20 | ) 21 | 22 | // NewNoop returns a new noop. 23 | func NewNoop() *Noop { 24 | return new(Noop) 25 | } 26 | 27 | // Noop is a fake connection. 28 | type Noop struct{} 29 | 30 | // Close closes the End. 31 | func (e Noop) Close() (err error) { 32 | return 33 | } 34 | 35 | // LocalAddr gets the local address. 36 | func (e Noop) LocalAddr() net.Addr { 37 | return Addr{ 38 | NetworkString: "tcp", 39 | AddrString: "127.0.0.1", 40 | } 41 | } 42 | 43 | // RemoteAddr gets the local address. 44 | func (e Noop) RemoteAddr() net.Addr { 45 | return Addr{ 46 | NetworkString: "tcp", 47 | AddrString: "127.0.0.1", 48 | } 49 | } 50 | 51 | // Read implements the interface. 52 | func (e Noop) Read(data []byte) (n int, err error) { return 0, nil } 53 | 54 | // Write implements the interface. 55 | func (e Noop) Write(data []byte) (n int, err error) { return len(data), nil } 56 | 57 | // SetDeadline implements the interface. 58 | func (e Noop) SetDeadline(t time.Time) error { return nil } 59 | 60 | // SetReadDeadline implements the interface. 61 | func (e Noop) SetReadDeadline(t time.Time) error { return nil } 62 | 63 | // SetWriteDeadline implements the interface. 64 | func (e Noop) SetWriteDeadline(t time.Time) error { return nil } 65 | -------------------------------------------------------------------------------- /internal/service/cluster/peer_test.go: -------------------------------------------------------------------------------- 1 | package cluster 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/emitter-io/emitter/internal/message" 7 | "github.com/stretchr/testify/assert" 8 | "github.com/weaveworks/mesh" 9 | ) 10 | 11 | type stubGossip struct{} 12 | 13 | func (s *stubGossip) GossipNeighbourSubset(update mesh.GossipData) {} 14 | func (s *stubGossip) GossipBroadcast(update mesh.GossipData) {} 15 | func (s *stubGossip) GossipUnicast(dst mesh.PeerName, msg []byte) error { 16 | return nil 17 | } 18 | 19 | // Benchmark_ProcessQueue-8 3000 515299 ns/op 407117 B/op 18 allocs/op 20 | // Benchmark_ProcessQueue-8 5000 296610 ns/op 359599 B/op 10 allocs/op 21 | // Benchmark_ProcessQueue-8 5000 272677 ns/op 215082 B/op 7 allocs/op 22 | func Benchmark_ProcessQueue(b *testing.B) { 23 | msg := newTestMessage(message.Ssid{1, 2, 3}, "a/b/c/", "hello abc") 24 | s := new(Swarm) 25 | p := s.newPeer(123) 26 | p.sender = new(stubGossip) 27 | defer p.Close() 28 | 29 | b.ReportAllocs() 30 | b.ResetTimer() 31 | for i := 0; i < b.N; i++ { 32 | for m := 0; m < 1000; m++ { 33 | p.Send(&msg) 34 | } 35 | 36 | p.processSendQueue() 37 | } 38 | } 39 | 40 | func TestPeer_Multiple(t *testing.T) { 41 | s := new(Swarm) 42 | p := s.newPeer(123) 43 | defer p.Close() 44 | 45 | // Make sure we have a peer 46 | assert.NotNil(t, p) 47 | assert.Empty(t, p.frame) 48 | assert.NotNil(t, p.cancel) 49 | assert.Equal(t, "00:00:00:00:00:7b", p.ID()) 50 | assert.Equal(t, message.SubscriberRemote, p.Type()) 51 | assert.True(t, p.IsActive()) 52 | 53 | // Test the counters 54 | assert.True(t, p.onSubscribe("A", []uint32{1, 2, 3})) 55 | assert.False(t, p.onSubscribe("A", []uint32{1, 2, 3})) 56 | assert.False(t, p.onUnsubscribe("A", []uint32{1, 2, 3})) 57 | assert.True(t, p.onUnsubscribe("A", []uint32{1, 2, 3})) 58 | } 59 | 60 | func TestPeer_Send(t *testing.T) { 61 | s := new(Swarm) 62 | p := s.newPeer(123) 63 | defer p.Close() 64 | 65 | // Attach fake sender 66 | p.sender = new(stubGossip) 67 | 68 | // Make sure we have a peer 69 | p.Send(&message.Message{}) 70 | assert.Equal(t, 1, len(p.frame)) 71 | 72 | // Flush 73 | p.processSendQueue() 74 | assert.Equal(t, 0, len(p.frame)) 75 | } 76 | -------------------------------------------------------------------------------- /internal/network/listener/matcher_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | * 14 | * This file was originally developed by The CMux Authors and released under Apache 15 | * License, Version 2.0 in 2016. 16 | ************************************************************************************/ 17 | 18 | package listener 19 | 20 | import ( 21 | "strings" 22 | "testing" 23 | ) 24 | 25 | func testPTree(t *testing.T, strs ...string) { 26 | pt := newPatriciaTreeString(strs...) 27 | for _, s := range strs { 28 | if !pt.match(strings.NewReader(s)) { 29 | t.Errorf("%s is not matched by %s", s, s) 30 | } 31 | 32 | if !pt.matchPrefix(strings.NewReader(s + s)) { 33 | t.Errorf("%s is not matched as a prefix by %s", s+s, s) 34 | } 35 | 36 | if pt.match(strings.NewReader(s + s)) { 37 | t.Errorf("%s matches %s", s+s, s) 38 | } 39 | 40 | // The following tests are just to catch index out of 41 | // range and off-by-one errors and not the functionality. 42 | pt.matchPrefix(strings.NewReader(s[:len(s)-1])) 43 | pt.match(strings.NewReader(s[:len(s)-1])) 44 | pt.matchPrefix(strings.NewReader(s + "$")) 45 | pt.match(strings.NewReader(s + "$")) 46 | } 47 | } 48 | 49 | func TestPatriciaOnePrefix(t *testing.T) { 50 | testPTree(t, "prefix") 51 | } 52 | 53 | func TestPatriciaNonOverlapping(t *testing.T) { 54 | testPTree(t, "foo", "bar", "dummy") 55 | } 56 | 57 | func TestPatriciaOverlapping(t *testing.T) { 58 | testPTree(t, "foo", "far", "farther", "boo", "ba", "bar") 59 | } 60 | -------------------------------------------------------------------------------- /internal/provider/logging/logging_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package logging 16 | 17 | import ( 18 | "bytes" 19 | "errors" 20 | "log" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | // newTestLogger creates a new default stderr logger. 27 | func newTestLogger(buffer *bytes.Buffer) Logging { 28 | return (*stderrLogger)(log.New(buffer, "", 0)) 29 | } 30 | 31 | func TestLogAction(t *testing.T) { 32 | defer func(l Logging) { Logger = l }(Logger) 33 | 34 | buffer := bytes.NewBuffer(nil) 35 | Logger = newTestLogger(buffer) 36 | 37 | LogAction("a", "b") 38 | assert.Equal(t, "[a] b\n", string(buffer.Bytes())) 39 | } 40 | 41 | func TestLogError(t *testing.T) { 42 | defer func(l Logging) { Logger = l }(Logger) 43 | 44 | buffer := bytes.NewBuffer(nil) 45 | Logger = newTestLogger(buffer) 46 | 47 | LogError("a", "b", errors.New("err")) 48 | assert.Equal(t, "[a] error during b (err)\n", string(buffer.Bytes())) 49 | } 50 | 51 | func TestLogTarget(t *testing.T) { 52 | defer func(l Logging) { Logger = l }(Logger) 53 | 54 | buffer := bytes.NewBuffer(nil) 55 | Logger = newTestLogger(buffer) 56 | 57 | LogTarget("a", "b", 123) 58 | assert.Equal(t, "[a] b (123)\n", string(buffer.Bytes())) 59 | } 60 | 61 | func TestStdErrLogger(t *testing.T) { 62 | l := NewStdErr() 63 | assert.NoError(t, l.Configure(nil)) 64 | assert.Equal(t, "stderr", l.Name()) 65 | } 66 | -------------------------------------------------------------------------------- /internal/security/id.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package security 16 | 17 | import ( 18 | "crypto/sha1" 19 | "encoding/base32" 20 | "encoding/binary" 21 | "encoding/hex" 22 | "strings" 23 | "sync/atomic" 24 | "time" 25 | 26 | "golang.org/x/crypto/pbkdf2" 27 | ) 28 | 29 | // ID represents a process-wide unique ID. 30 | type ID uint64 31 | 32 | // next is the next identifier. We seed it with the time in seconds 33 | // to avoid collisions of ids between process restarts. 34 | var next = uint64( 35 | time.Now().Sub(time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)).Seconds(), 36 | ) 37 | 38 | // NewID generates a new, process-wide unique ID. 39 | func NewID() ID { 40 | return ID(atomic.AddUint64(&next, 1)) 41 | } 42 | 43 | // Unique generates unique id based on the current id with a prefix and salt. 44 | func (id ID) Unique(prefix uint64, salt string) string { 45 | buffer := [16]byte{} 46 | binary.BigEndian.PutUint64(buffer[:8], prefix) 47 | binary.BigEndian.PutUint64(buffer[8:], uint64(id)) 48 | 49 | enc := pbkdf2.Key(buffer[:], []byte(salt), 4096, 16, sha1.New) 50 | return strings.Trim(base32.StdEncoding.EncodeToString(enc), "=") 51 | } 52 | 53 | // String converts the ID to a string representation. 54 | func (id ID) String() string { 55 | buf := make([]byte, 10) // This will never be more than 9 bytes. 56 | l := binary.PutUvarint(buf, uint64(id)) 57 | return strings.ToUpper(hex.EncodeToString(buf[:l])) 58 | } 59 | -------------------------------------------------------------------------------- /internal/network/mock/end.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package mock 16 | 17 | import ( 18 | "io" 19 | "net" 20 | "time" 21 | ) 22 | 23 | // End is one 'end' of a simulated connection. 24 | type End struct { 25 | Reader *io.PipeReader 26 | Writer *io.PipeWriter 27 | } 28 | 29 | // Close closes the End. 30 | func (e End) Close() (err error) { 31 | if err = e.Writer.Close(); err == nil { 32 | err = e.Reader.Close() 33 | } 34 | return 35 | } 36 | 37 | // LocalAddr gets the local address. 38 | func (e End) LocalAddr() net.Addr { 39 | return Addr{ 40 | NetworkString: "tcp", 41 | AddrString: "127.0.0.1", 42 | } 43 | } 44 | 45 | // RemoteAddr gets the local address. 46 | func (e End) RemoteAddr() net.Addr { 47 | return Addr{ 48 | NetworkString: "tcp", 49 | AddrString: "127.0.0.1", 50 | } 51 | } 52 | 53 | // Read implements the interface. 54 | func (e End) Read(data []byte) (n int, err error) { return e.Reader.Read(data) } 55 | 56 | // Write implements the interface. 57 | func (e End) Write(data []byte) (n int, err error) { return e.Writer.Write(data) } 58 | 59 | // SetDeadline implements the interface. 60 | func (e End) SetDeadline(t time.Time) error { return nil } 61 | 62 | // SetReadDeadline implements the interface. 63 | func (e End) SetReadDeadline(t time.Time) error { return nil } 64 | 65 | // SetWriteDeadline implements the interface. 66 | func (e End) SetWriteDeadline(t time.Time) error { return nil } 67 | -------------------------------------------------------------------------------- /internal/broker/status.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package broker 16 | 17 | import ( 18 | "sync/atomic" 19 | 20 | "github.com/emitter-io/address" 21 | "github.com/emitter-io/stats" 22 | ) 23 | 24 | // sampler reads statistics of the service and creates a snapshot 25 | type sampler struct { 26 | service *Service // The service to use for stats collection. 27 | measurer stats.Measurer // The measurer to use for snapshotting. 28 | } 29 | 30 | // newSampler creates a stats sampler. 31 | func newSampler(s *Service, m stats.Measurer) stats.Snapshotter { 32 | return &sampler{ 33 | service: s, 34 | measurer: m, 35 | } 36 | } 37 | 38 | // Snapshot creates the stats snapshot. 39 | func (s *sampler) Snapshot() (snapshot []byte) { 40 | stat := s.service.measurer 41 | serv := s.service 42 | node := address.Fingerprint(serv.ID()) 43 | addr := serv.Config.Addr() 44 | 45 | // Track runtime information 46 | stat.MeasureRuntime() 47 | 48 | // Track node specific information 49 | stat.Measure("node.id", int32(node)) 50 | stat.Measure("node.peers", int32(serv.NumPeers())) 51 | stat.Measure("node.conns", int32(atomic.LoadInt64(&serv.connections))) 52 | stat.Measure("node.subs", int32(serv.subscriptions.Count())) 53 | 54 | // Add node tags 55 | stat.Tag("node.id", node.String()) 56 | stat.Tag("node.addr", addr.String()) 57 | 58 | // Create a snaphshot of all stats 59 | if m, ok := stat.(stats.Snapshotter); ok { 60 | snapshot = m.Snapshot() 61 | } 62 | return 63 | } 64 | -------------------------------------------------------------------------------- /internal/provider/storage/memory.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package storage 16 | 17 | import ( 18 | "context" 19 | "time" 20 | 21 | "github.com/dgraph-io/badger/v3" 22 | "github.com/emitter-io/emitter/internal/async" 23 | "github.com/emitter-io/emitter/internal/service" 24 | ) 25 | 26 | // InMemory implements Storage contract. 27 | var _ Storage = new(InMemory) 28 | 29 | // InMemory represents a storage which does nothing. 30 | type InMemory struct { 31 | SSD // Badger with in-memory mode 32 | } 33 | 34 | // NewInMemory creates a new in-memory storage. 35 | func NewInMemory(survey service.Surveyor) *InMemory { 36 | return &InMemory{SSD{ 37 | survey: survey, 38 | }} 39 | } 40 | 41 | // Name returns the name of the provider. 42 | func (s *InMemory) Name() string { 43 | return "inmemory" 44 | } 45 | 46 | // Configure configures the storage. The config parameter provided is 47 | // loosely typed, since various storage mechanisms will require different 48 | // configurations. 49 | func (s *InMemory) Configure(config map[string]interface{}) error { 50 | opts := badger.DefaultOptions("") 51 | opts.SyncWrites = true 52 | opts.InMemory = true 53 | 54 | // Attempt to open the database 55 | db, err := badger.Open(opts) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | // Setup the database and start GC 61 | s.db = db 62 | s.retain = configUint32(config, "retain", defaultRetain) 63 | s.cancel = async.Repeat(context.Background(), 30*time.Minute, s.GC) 64 | return err 65 | } 66 | -------------------------------------------------------------------------------- /internal/security/hash/murmur.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package hash 16 | 17 | import ( 18 | "math/bits" 19 | 20 | "github.com/kelindar/binary" 21 | ) 22 | 23 | const ( 24 | c1_32 uint32 = 0xcc9e2d51 25 | c2_32 uint32 = 0x1b873593 26 | ) 27 | 28 | // OfString returns a murmur32 hash for the string 29 | func OfString(value string) uint32 { 30 | return Of(binary.ToBytes(value)) 31 | } 32 | 33 | // Of returns a murmur32 hash for the data slice. 34 | func Of(data []byte) uint32 { 35 | // Seed is set to 37, same as C# version of emitter 36 | var h1 uint32 = 37 37 | 38 | clen := uint32(len(data)) 39 | for len(data) >= 4 { 40 | k1 := uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16 | uint32(data[3])<<24 41 | data = data[4:] 42 | 43 | k1 *= c1_32 44 | k1 = bits.RotateLeft32(k1, 15) 45 | k1 *= c2_32 46 | 47 | h1 ^= k1 48 | h1 = bits.RotateLeft32(h1, 13) 49 | h1 = h1*5 + 0xe6546b64 50 | } 51 | 52 | var k1 uint32 53 | switch len(data) { 54 | case 3: 55 | k1 ^= uint32(data[2]) << 16 56 | fallthrough 57 | case 2: 58 | k1 ^= uint32(data[1]) << 8 59 | fallthrough 60 | case 1: 61 | k1 ^= uint32(data[0]) 62 | k1 *= c1_32 63 | k1 = bits.RotateLeft32(k1, 15) 64 | k1 *= c2_32 65 | h1 ^= k1 66 | } 67 | 68 | h1 ^= uint32(clen) 69 | h1 ^= h1 >> 16 70 | h1 *= 0x85ebca6b 71 | h1 ^= h1 >> 13 72 | h1 *= 0xc2b2ae35 73 | h1 ^= h1 >> 16 74 | 75 | return (h1 << 24) | (((h1 >> 8) << 16) & 0xFF0000) | (((h1 >> 16) << 8) & 0xFF00) | (h1 >> 24) 76 | } 77 | -------------------------------------------------------------------------------- /internal/message/codec_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package message 16 | 17 | import ( 18 | "fmt" 19 | "testing" 20 | 21 | "github.com/golang/snappy" 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestCodec_Message(t *testing.T) { 26 | 27 | for i := 0; i < 100; i++ { 28 | t.Run("codec", func(t *testing.T) { 29 | t.Parallel() 30 | msg := newTestMessage(Ssid{1, 2, 3}, "a/b/c/", fmt.Sprintf("message number %v", i)) 31 | buffer := msg.Encode() 32 | //assert.True(t, len(buffer) >= 57) 33 | //assert.True(t, len(buffer) <= 58) 34 | 35 | // Decode 36 | output, err := DecodeMessage(buffer) 37 | assert.NoError(t, err) 38 | assert.Equal(t, msg, output) 39 | }) 40 | } 41 | } 42 | 43 | func TestCodec_HappyPath(t *testing.T) { 44 | frame := Frame{ 45 | newTestMessage(Ssid{1, 2, 3}, "a/b/c/", "hello abc"), 46 | newTestMessage(Ssid{1, 2, 3}, "a/b/", "hello ab"), 47 | } 48 | 49 | // Encode 50 | buffer := frame.Encode() 51 | assert.True(t, len(buffer) >= 65) 52 | 53 | // Decode 54 | output, err := DecodeFrame(buffer) 55 | assert.NoError(t, err) 56 | assert.Equal(t, frame, output) 57 | } 58 | 59 | func TestCodec_Corrupt(t *testing.T) { 60 | _, err := DecodeFrame([]byte{121, 4, 3, 2, 2, 1, 5, 3, 2}) 61 | assert.Equal(t, "snappy: corrupt input", err.Error()) 62 | } 63 | 64 | func TestCodec_Invalid(t *testing.T) { 65 | var out []byte 66 | out = snappy.Encode(out, []byte{121, 4, 3, 2, 2, 1, 5, 3, 2}) 67 | 68 | _, err := DecodeFrame(out) 69 | assert.Equal(t, "EOF", err.Error()) 70 | } 71 | -------------------------------------------------------------------------------- /internal/security/hash/murmur_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package hash 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | // BenchmarkOf-8 99755598 12.1 ns/op 0 B/op 0 allocs/op 24 | func BenchmarkOf(b *testing.B) { 25 | v := []byte("a/b/c/d/e/f/g/h/this/is/emitter") 26 | 27 | b.ReportAllocs() 28 | b.ResetTimer() 29 | for i := 0; i < b.N; i++ { 30 | _ = Of(v) 31 | } 32 | } 33 | 34 | // BenchmarkOfString-8 79873267 15.0 ns/op 0 B/op 0 allocs/op 35 | func BenchmarkOfString(b *testing.B) { 36 | v := "a/b/c/d/e/f/g/h/this/is/emitter" 37 | 38 | b.ReportAllocs() 39 | b.ResetTimer() 40 | for i := 0; i < b.N; i++ { 41 | _ = OfString(v) 42 | } 43 | } 44 | 45 | func TestKeyBanHash(t *testing.T) { 46 | h := OfString("keyban") 47 | assert.Equal(t, uint32(861724010), h) 48 | } 49 | 50 | func TestMeHash(t *testing.T) { 51 | h := OfString("me") 52 | assert.Equal(t, uint32(2539734036), h) 53 | } 54 | 55 | func TestShareHash(t *testing.T) { 56 | h := Of([]byte("$share")) 57 | assert.Equal(t, uint32(1480642916), h) 58 | } 59 | 60 | func TestLinkHash(t *testing.T) { 61 | h := Of([]byte("link")) 62 | assert.Equal(t, uint32(2667034312), h) 63 | } 64 | 65 | func TestGetHash(t *testing.T) { 66 | h := Of([]byte("+")) 67 | if h != 1815237614 { 68 | t.Errorf("Hash %d is not equal to %d", h, 1815237614) 69 | } 70 | } 71 | 72 | func TestGetHash2(t *testing.T) { 73 | h := Of([]byte("hello world")) 74 | if h != 4008393376 { 75 | t.Errorf("Hash %d is not equal to %d", h, 1815237614) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /internal/service/pubsub/lastwill.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package pubsub 16 | 17 | import ( 18 | "github.com/emitter-io/emitter/internal/event" 19 | "github.com/emitter-io/emitter/internal/message" 20 | "github.com/emitter-io/emitter/internal/security" 21 | ) 22 | 23 | // OnLastWill publishes a last will event of the subscriber. 24 | func (s *Service) OnLastWill(sub message.Subscriber, ev *event.Connection) bool { 25 | if ev == nil || !ev.WillFlag { 26 | return false 27 | } 28 | 29 | // Make sure we have a valid channel 30 | channel := security.ParseChannel(ev.WillTopic) 31 | if channel.ChannelType != security.ChannelStatic { 32 | return false 33 | } 34 | 35 | // Check the authorization and permissions 36 | contract, key, allowed := s.auth.Authorize(channel, security.AllowWrite) 37 | if !allowed || key.HasPermission(security.AllowExtend) { 38 | return false 39 | } 40 | 41 | // Create a new message 42 | msg := message.New( 43 | message.NewSsid(key.Contract(), channel.Query), 44 | channel.Channel, 45 | ev.WillMessage, 46 | ) 47 | 48 | // If a user have specified a retain flag, retain with a default TTL 49 | if ev.WillRetain { 50 | msg.TTL = message.RetainedTTL 51 | } 52 | 53 | // Store the message if needed 54 | if msg.Stored() && key.HasPermission(security.AllowStore) { 55 | s.store.Store(msg) 56 | } 57 | 58 | // Iterate through all subscribers and send them the message 59 | size := s.Publish(msg, nil) 60 | 61 | // Write the monitoring information 62 | contract.Stats().AddIngress(int64(len(ev.WillMessage))) 63 | contract.Stats().AddEgress(size) 64 | return true 65 | } 66 | -------------------------------------------------------------------------------- /internal/message/codec.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package message 16 | 17 | import ( 18 | "bytes" 19 | "reflect" 20 | "sync" 21 | 22 | "github.com/kelindar/binary" 23 | ) 24 | 25 | // Reusable long-lived encoder pool. 26 | var encoders = &sync.Pool{New: func() interface{} { 27 | return binary.NewEncoder( 28 | bytes.NewBuffer(make([]byte, 0, 8*1024)), 29 | ) 30 | }} 31 | 32 | type messageCodec struct{} 33 | 34 | // Encode encodes a value into the encoder. 35 | func (c *messageCodec) EncodeTo(e *binary.Encoder, rv reflect.Value) (err error) { 36 | id := rv.Field(0).Bytes() 37 | channel := rv.Field(1).Bytes() 38 | payload := rv.Field(2).Bytes() 39 | ttl := rv.Field(3).Uint() 40 | 41 | e.WriteUvarint(uint64(len(id))) 42 | e.Write(id) 43 | e.WriteUvarint(uint64(len(channel))) 44 | e.Write(channel) 45 | e.WriteUvarint(uint64(len(payload))) 46 | e.Write(payload) 47 | e.WriteUvarint(ttl) 48 | return 49 | } 50 | 51 | // Decode decodes into a reflect value from the decoder. 52 | func (c *messageCodec) DecodeTo(d *binary.Decoder, rv reflect.Value) (err error) { 53 | var v Message 54 | if v.ID, err = readBytes(d); err == nil { 55 | if v.Channel, err = readBytes(d); err == nil { 56 | if v.Payload, err = readBytes(d); err == nil { 57 | if ttl, err := d.ReadUvarint(); err == nil { 58 | v.TTL = uint32(ttl) 59 | rv.Set(reflect.ValueOf(v)) 60 | return nil 61 | } 62 | } 63 | } 64 | } 65 | return 66 | } 67 | 68 | func readBytes(d *binary.Decoder) (buffer []byte, err error) { 69 | var l uint64 70 | if l, err = d.ReadUvarint(); err == nil && l > 0 { 71 | buffer, err = d.Slice(int(l)) 72 | } 73 | return 74 | } 75 | -------------------------------------------------------------------------------- /internal/network/listener/conn_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package listener 16 | 17 | import ( 18 | "net" 19 | "testing" 20 | "time" 21 | 22 | "github.com/kelindar/rate" 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func TestConn(t *testing.T) { 27 | conn := newConn(new(fakeConn), 0) 28 | defer conn.Close() 29 | 30 | assert.Equal(t, 0, conn.Len()) 31 | assert.Nil(t, conn.LocalAddr()) 32 | assert.Nil(t, conn.RemoteAddr()) 33 | assert.Nil(t, conn.SetDeadline(time.Now())) 34 | assert.Nil(t, conn.SetReadDeadline(time.Now())) 35 | assert.Nil(t, conn.SetWriteDeadline(time.Now())) 36 | 37 | conn.limit = rate.New(1, time.Millisecond) 38 | for i := 0; i < 100; i++ { 39 | _, err := conn.Write([]byte{1, 2, 3}) 40 | assert.NoError(t, err) 41 | } 42 | time.Sleep(10 * time.Millisecond) 43 | _, err := conn.Write([]byte{1, 2, 3}) 44 | assert.NoError(t, err) 45 | 46 | } 47 | 48 | // ------------------------------------------------------------------------------------ 49 | 50 | type fakeConn struct{} 51 | 52 | func (m *fakeConn) Read(p []byte) (int, error) { 53 | return 0, nil 54 | } 55 | 56 | func (m *fakeConn) Write(p []byte) (int, error) { 57 | return 0, nil 58 | } 59 | 60 | func (m *fakeConn) Close() error { 61 | return nil 62 | } 63 | 64 | func (m *fakeConn) LocalAddr() net.Addr { 65 | return nil 66 | } 67 | 68 | func (m *fakeConn) RemoteAddr() net.Addr { 69 | return nil 70 | } 71 | 72 | func (m *fakeConn) SetDeadline(t time.Time) error { 73 | return nil 74 | } 75 | 76 | func (m *fakeConn) SetReadDeadline(t time.Time) error { 77 | return nil 78 | } 79 | 80 | func (m *fakeConn) SetWriteDeadline(t time.Time) error { 81 | return nil 82 | } 83 | -------------------------------------------------------------------------------- /deploy/k8s/minikube/broker_test.yaml: -------------------------------------------------------------------------------- 1 | #********************************************************************************** 2 | # Copyright (c) 2009-2018 Misakai Ltd. 3 | # This program is free software: you can redistribute it and/or modify it under the 4 | # terms of the GNU Affero General Public License as published by the Free Software 5 | # Foundation, either version 3 of the License, or(at your option) any later version. 6 | # 7 | # This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | # PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU Affero General Public License along 12 | # with this program. If not, see. 13 | #*********************************************************************************** 14 | --- 15 | apiVersion: v1 16 | kind: Service 17 | metadata: 18 | name: broker 19 | labels: 20 | app: broker 21 | spec: 22 | clusterIP: None 23 | ports: 24 | - port: 4000 25 | targetPort: 4000 26 | selector: 27 | app: broker 28 | --- 29 | apiVersion: apps/v1beta1 30 | kind: StatefulSet 31 | metadata: 32 | name: broker 33 | spec: 34 | serviceName: "broker" 35 | replicas: 3 36 | template: 37 | metadata: 38 | labels: 39 | app: broker 40 | role: broker 41 | spec: 42 | terminationGracePeriodSeconds: 10 43 | containers: 44 | - env: 45 | - name: EMITTER_LICENSE 46 | value: "zT83oDV0DWY5_JysbSTPTDr8KB0AAAAAAAAAAAAAAAI" # This is a test license, DO NOT USE IN PRODUCTION! 47 | - name: EMITTER_CLUSTER_SEED 48 | value: "broker" 49 | - name: EMITTER_CLUSTER_ADVERTISE 50 | value: "private:4000" 51 | - name: EMITTER_STORAGE_PROVIDER 52 | value: "ssd" 53 | name: broker 54 | image: emitter/server:latest 55 | ports: 56 | - containerPort: 8080 57 | - containerPort: 443 58 | - containerPort: 4000 59 | volumeMounts: 60 | - name: broker-volume 61 | mountPath: /data 62 | restartPolicy: Always 63 | terminationGracePeriodSeconds: 30 64 | volumeClaimTemplates: 65 | - metadata: 66 | name: broker-volume 67 | annotations: 68 | volume.beta.kubernetes.io/storage-class: "fast" 69 | spec: 70 | accessModes: [ "ReadWriteOnce" ] 71 | resources: 72 | requests: 73 | storage: 5Gi 74 | -------------------------------------------------------------------------------- /internal/network/mqtt/buffer.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package mqtt 16 | 17 | import ( 18 | "sync" 19 | ) 20 | 21 | // smallBufferSize is an initial allocation minimal capacity. 22 | const smallBufferSize = 64 23 | const maxInt = int(^uint(0) >> 1) 24 | 25 | // buffers are reusable fixed-side buffers for faster encoding. 26 | var buffers = newBufferPool(MaxMessageSize) 27 | 28 | // bufferPool represents a thread safe buffer pool 29 | type bufferPool struct { 30 | sync.Pool 31 | } 32 | 33 | // newBufferPool creates a new BufferPool bounded to the given size. 34 | func newBufferPool(bufferSize int) (bp *bufferPool) { 35 | return &bufferPool{ 36 | sync.Pool{ 37 | New: func() interface{} { 38 | return &byteBuffer{buf: make([]byte, bufferSize)} 39 | }, 40 | }, 41 | } 42 | } 43 | 44 | // Get gets a Buffer from the SizedBufferPool, or creates a new one if none are 45 | // available in the pool. Buffers have a pre-allocated capacity. 46 | func (bp *bufferPool) Get() *byteBuffer { 47 | return bp.Pool.Get().(*byteBuffer) 48 | } 49 | 50 | // Put returns the given Buffer to the SizedBufferPool. 51 | func (bp *bufferPool) Put(b *byteBuffer) { 52 | bp.Pool.Put(b) 53 | } 54 | 55 | type byteBuffer struct { 56 | buf []byte 57 | } 58 | 59 | // Bytes gets a byte slice of a specified size. 60 | func (b *byteBuffer) Bytes(n int) []byte { 61 | if n == 0 { // Return max size 62 | return b.buf 63 | } 64 | 65 | return b.buf[:n] 66 | } 67 | 68 | // Slice returns a slice at an offset. 69 | func (b *byteBuffer) Slice(from, until int) []byte { 70 | return b.buf[from:until] 71 | } 72 | 73 | // Split splits the bufer in two. 74 | func (b *byteBuffer) Split(n int) ([]byte, []byte) { 75 | buffer := b.Bytes(0) 76 | return buffer[:n], buffer[n:] 77 | } 78 | -------------------------------------------------------------------------------- /internal/security/cipher/base64_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package cipher 16 | 17 | import ( 18 | "encoding/base64" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func Test_decodeKey(t *testing.T) { 25 | // Just an error test since everything is already covered by other tests 26 | defer func(m [256]byte) { decodeMap = m }(decodeMap) 27 | for i := 0; i < len(decodeMap); i++ { 28 | decodeMap[i] = 1 29 | } 30 | 31 | in1 := []byte("#") 32 | _, err1 := decodeKey(make([]byte, 32), in1) 33 | assert.Error(t, err1) 34 | assert.NotEmpty(t, err1.Error()) 35 | 36 | for i := 0; i < len(decodeMap); i++ { 37 | decodeMap[i] = byte(i) 38 | } 39 | 40 | for i := 0; i < 255; i++ { 41 | assert.NotPanics(t, func() { 42 | in2 := []byte{0, byte(i)} 43 | decodeKey(make([]byte, 32), in2) 44 | }) 45 | } 46 | 47 | } 48 | 49 | func BenchmarkBase64(b *testing.B) { 50 | v := []byte("0TJnt4yZPL73zt35h1UTIFsYBLetyD_g") 51 | o := make([]byte, 24) 52 | 53 | b.ReportAllocs() 54 | b.ResetTimer() 55 | for i := 0; i < b.N; i++ { 56 | _, _ = decodeKey(o, v) 57 | } 58 | } 59 | 60 | func TestBase64Decode(t *testing.T) { 61 | v := []byte("0TJnt4yZPL73zt35h1UTIFsYBLetyD_g") 62 | o1 := make([]byte, 24) 63 | o2 := make([]byte, 24) 64 | 65 | n1, e1 := decodeKey(o1, v) 66 | n2, e2 := base64.RawURLEncoding.Decode(o2, v) 67 | 68 | assert.Equal(t, o1, o2) 69 | assert.Equal(t, n1, n2) 70 | assert.Equal(t, e1, e2) 71 | } 72 | 73 | func TestBase64SelfDecode(t *testing.T) { 74 | v := []byte("0TJnt4yZPL73zt35h1UTIFsYBLetyD_g") 75 | o1 := []byte("0TJnt4yZPL73zt35h1UTIFsYBLetyD_g") 76 | o2 := make([]byte, 24) 77 | 78 | n1, e1 := decodeKey(o1, o1) 79 | n2, e2 := base64.RawURLEncoding.Decode(o2, v) 80 | 81 | assert.Equal(t, o1[:24], o2) 82 | assert.Equal(t, n1, n2) 83 | assert.Equal(t, e1, e2) 84 | } 85 | -------------------------------------------------------------------------------- /internal/service/link/link_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package link 16 | 17 | import ( 18 | "encoding/json" 19 | "testing" 20 | 21 | "github.com/emitter-io/emitter/internal/service/fake" 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestLink(t *testing.T) { 26 | tests := []struct { 27 | contract int 28 | request *Request 29 | success bool 30 | }{ 31 | {request: nil}, 32 | {request: &Request{}}, 33 | { 34 | contract: 1, 35 | success: false, 36 | request: &Request{ 37 | Name: "a", 38 | Channel: "a/b/c/", 39 | }, 40 | }, 41 | { 42 | contract: 1, 43 | success: true, 44 | request: &Request{ 45 | Name: "a", 46 | Key: "key", 47 | Channel: "a/b/c/", 48 | }, 49 | }, 50 | { 51 | contract: 1, 52 | success: true, 53 | request: &Request{ 54 | Name: "a", 55 | Subscribe: true, 56 | Key: "key", 57 | Channel: "a/b/c/", 58 | }, 59 | }, 60 | } 61 | 62 | for _, tc := range tests { 63 | pubsub := new(fake.PubSub) 64 | auth := &fake.Authorizer{ 65 | Contract: uint32(tc.contract), 66 | Success: tc.contract != 0, 67 | } 68 | 69 | // Prepare the request 70 | b, _ := json.Marshal(tc.request) 71 | if tc.request == nil { 72 | b = []byte("invalid") 73 | } else { 74 | auth.Target = tc.request.Channel 75 | } 76 | 77 | // Issue a request 78 | s := New(auth, pubsub) 79 | c := new(fake.Conn) 80 | _, ok := s.OnRequest(c, b) 81 | assert.Equal(t, tc.success, ok) 82 | 83 | // Assert the successful result 84 | if tc.request != nil && tc.success { 85 | assert.LessOrEqual(t, 5, len(c.GetLink([]byte(tc.request.Name)))) 86 | 87 | // If requested to subscribe, make sure we have it 88 | if tc.request.Subscribe { 89 | assert.Equal(t, 1, pubsub.Trie.Count()) 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /internal/provider/contract/mock/mock.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package mock 16 | 17 | import ( 18 | "github.com/emitter-io/emitter/internal/provider/contract" 19 | "github.com/emitter-io/emitter/internal/provider/usage" 20 | "github.com/emitter-io/emitter/internal/security" 21 | "github.com/stretchr/testify/mock" 22 | ) 23 | 24 | // Contract represents a contract (user account). 25 | type Contract struct { 26 | mock.Mock 27 | } 28 | 29 | // Validate validates the contract data against a key. 30 | func (mock *Contract) Validate(key security.Key) bool { 31 | mockArgs := mock.Called(key) 32 | return mockArgs.Get(0).(bool) 33 | } 34 | 35 | // Stats returns the stats. 36 | func (mock *Contract) Stats() usage.Meter { 37 | mockArgs := mock.Called() 38 | return mockArgs.Get(0).(usage.Meter) 39 | } 40 | 41 | // ContractProvider is the mock provider for contracts 42 | type ContractProvider struct { 43 | mock.Mock 44 | } 45 | 46 | // NewContractProvider creates a new mock client provider. 47 | func NewContractProvider() *ContractProvider { 48 | return new(ContractProvider) 49 | } 50 | 51 | // Name returns the name of the provider. 52 | func (mock *ContractProvider) Name() string { 53 | return "mock" 54 | } 55 | 56 | // Configure configures the provider. 57 | func (mock *ContractProvider) Configure(config map[string]interface{}) error { 58 | mockArgs := mock.Called(config) 59 | return mockArgs.Error(0) 60 | } 61 | 62 | // Create creates a contract. 63 | func (mock *ContractProvider) Create() (contract.Contract, error) { 64 | mockArgs := mock.Called() 65 | return mockArgs.Get(0).(contract.Contract), mockArgs.Error(1) 66 | } 67 | 68 | // Get returns a ContractData fetched by its id. 69 | func (mock *ContractProvider) Get(id uint32) (contract.Contract, bool) { 70 | mockArgs := mock.Called(id) 71 | return mockArgs.Get(0).(contract.Contract), mockArgs.Bool(1) 72 | } 73 | -------------------------------------------------------------------------------- /internal/provider/monitor/http_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package monitor 16 | 17 | import ( 18 | "io" 19 | netHttp "net/http" 20 | "net/http/httptest" 21 | "testing" 22 | 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | type handler func(netHttp.ResponseWriter, *netHttp.Request) 27 | 28 | func (f handler) ServeHTTP(w netHttp.ResponseWriter, r *netHttp.Request) { 29 | f(w, r) 30 | } 31 | 32 | func TestHTTP_HappyPath(t *testing.T) { 33 | r := snapshot("test") 34 | server := httptest.NewServer(handler(func(w netHttp.ResponseWriter, r *netHttp.Request) { 35 | b, err := io.ReadAll(r.Body) 36 | assert.Equal(t, "test", string(b)) 37 | assert.NoError(t, err) 38 | w.WriteHeader(204) 39 | })) 40 | defer server.Close() 41 | 42 | s := NewHTTP(r) 43 | err := s.Configure(map[string]interface{}{ 44 | "interval": float64(100), 45 | "url": server.URL, 46 | "authorization": "123", 47 | }) 48 | 49 | assert.NoError(t, err) 50 | assert.Equal(t, "http", s.Name()) 51 | 52 | errClose := s.Close() 53 | assert.NoError(t, errClose) 54 | } 55 | 56 | func TestHTTP_ErrorPost(t *testing.T) { 57 | r := snapshot("test") 58 | server := httptest.NewServer(handler(func(w netHttp.ResponseWriter, r *netHttp.Request) { 59 | w.WriteHeader(500) 60 | })) 61 | defer server.Close() 62 | 63 | s := NewHTTP(r) 64 | err := s.Configure(map[string]interface{}{ 65 | "interval": float64(100), 66 | "url": server.URL, 67 | }) 68 | 69 | assert.NoError(t, err) 70 | assert.Equal(t, "http", s.Name()) 71 | 72 | errClose := s.Close() 73 | assert.NoError(t, errClose) 74 | } 75 | 76 | func TestHTTP_ErrorConfig(t *testing.T) { 77 | { 78 | s := NewHTTP(nil) 79 | err := s.Configure(nil) 80 | assert.Error(t, err) 81 | } 82 | { 83 | s := NewHTTP(nil) 84 | err := s.Configure(map[string]interface{}{}) 85 | assert.Error(t, err) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /internal/security/cipher/base64.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package cipher 16 | 17 | import "strconv" 18 | 19 | // The map used for base64 decode. 20 | var decodeMap [256]byte 21 | 22 | type corruptInputError int64 23 | 24 | func (e corruptInputError) Error() string { 25 | return "illegal base64 data at input byte " + strconv.FormatInt(int64(e), 10) 26 | } 27 | 28 | // Init prepares the lookup table. 29 | func init() { 30 | encoder := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" 31 | for i := 0; i < len(decodeMap); i++ { 32 | decodeMap[i] = 0xFF 33 | } 34 | for i := 0; i < len(encoder); i++ { 35 | decodeMap[encoder[i]] = byte(i) 36 | } 37 | } 38 | 39 | // decodeKey decodes the key from base64 string, url-encoded with no 40 | // padding. This is 2x faster than the built-in function as we trimmed 41 | // it significantly. 42 | func decodeKey(dst, src []byte) (n int, err error) { 43 | var idx int 44 | for idx < len(src) { 45 | var dbuf [4]byte 46 | dinc, dlen := 3, 4 47 | 48 | for j := range dbuf { 49 | if len(src) == idx { 50 | if j < 2 { 51 | return n, corruptInputError(idx - j) 52 | } 53 | dinc, dlen = j-1, j 54 | break 55 | } 56 | 57 | in := src[idx] 58 | idx++ 59 | 60 | dbuf[j] = decodeMap[in] 61 | if dbuf[j] == 0xFF { 62 | return n, corruptInputError(idx - 1) 63 | } 64 | } 65 | 66 | // Convert 4x 6bit source bytes into 3 bytes 67 | val := uint(dbuf[0])<<18 | uint(dbuf[1])<<12 | uint(dbuf[2])<<6 | uint(dbuf[3]) 68 | dbuf[2], dbuf[1], dbuf[0] = byte(val>>0), byte(val>>8), byte(val>>16) 69 | switch dlen { 70 | case 4: 71 | dst[2] = dbuf[2] 72 | dbuf[2] = 0 73 | fallthrough 74 | case 3: 75 | dst[1] = dbuf[1] 76 | dbuf[1] = 0 77 | fallthrough 78 | case 2: 79 | dst[0] = dbuf[0] 80 | } 81 | 82 | dst = dst[dinc:] 83 | n += dlen - 1 84 | } 85 | 86 | return n, err 87 | } 88 | -------------------------------------------------------------------------------- /internal/service/keygen/requests.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package keygen 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/emitter-io/emitter/internal/security" 21 | ) 22 | 23 | // Request represents a key generation request. 24 | type Request struct { 25 | Key string `json:"key"` // The master key to use. 26 | Channel string `json:"channel"` // The channel to create a key for. 27 | Type string `json:"type"` // The permission set. 28 | TTL int32 `json:"ttl"` // The TTL of the key. 29 | } 30 | 31 | // expires returns the requested expiration time 32 | func (m *Request) expires() time.Time { 33 | if m.TTL == 0 { 34 | return time.Unix(0, 0) 35 | } 36 | 37 | return time.Now().Add(time.Duration(m.TTL) * time.Second).UTC() 38 | } 39 | 40 | // access returns the requested level of access 41 | func (m *Request) access() uint8 { 42 | required := security.AllowNone 43 | for i := 0; i < len(m.Type); i++ { 44 | switch c := m.Type[i]; c { 45 | case 'r': 46 | required |= security.AllowRead 47 | case 'w': 48 | required |= security.AllowWrite 49 | case 's': 50 | required |= security.AllowStore 51 | case 'l': 52 | required |= security.AllowLoad 53 | case 'p': 54 | required |= security.AllowPresence 55 | case 'e': 56 | required |= security.AllowExtend 57 | case 'x': 58 | required |= security.AllowExecute 59 | } 60 | } 61 | 62 | return required 63 | } 64 | 65 | // ------------------------------------------------------------------------------------ 66 | 67 | // Response represents a key generation response 68 | type Response struct { 69 | Request uint16 `json:"req,omitempty"` 70 | Status int `json:"status"` 71 | Key string `json:"key"` 72 | Channel string `json:"channel"` 73 | } 74 | 75 | // ForRequest sets the request ID in the response for matching 76 | func (r *Response) ForRequest(id uint16) { 77 | r.Request = id 78 | } 79 | -------------------------------------------------------------------------------- /internal/service/pubsub/unsubscribe.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package pubsub 16 | 17 | import ( 18 | "github.com/emitter-io/emitter/internal/errors" 19 | "github.com/emitter-io/emitter/internal/event" 20 | "github.com/emitter-io/emitter/internal/message" 21 | "github.com/emitter-io/emitter/internal/security" 22 | "github.com/emitter-io/emitter/internal/service" 23 | "github.com/kelindar/binary/nocopy" 24 | ) 25 | 26 | // Unsubscribe unsubscribes from a channel 27 | func (s *Service) Unsubscribe(sub message.Subscriber, ev *event.Subscription) (ok bool) { 28 | if conn, ok := sub.(service.Conn); ok && !conn.CanUnsubscribe(ev.Ssid, ev.Channel) { 29 | return false 30 | } 31 | 32 | subscribers := s.trie.Lookup(ev.Ssid, nil) 33 | if ok = subscribers.Contains(sub); ok { 34 | s.trie.Unsubscribe(ev.Ssid, sub) 35 | } 36 | 37 | s.notifier.NotifyUnsubscribe(sub, ev) 38 | return 39 | } 40 | 41 | // OnUnsubscribe is a handler for MQTT Unsubscribe events. 42 | func (s *Service) OnUnsubscribe(c service.Conn, mqttTopic []byte) *errors.Error { 43 | 44 | // Parse the channel 45 | channel := security.ParseChannel(mqttTopic) 46 | if channel.ChannelType == security.ChannelInvalid { 47 | return errors.ErrBadRequest 48 | } 49 | 50 | // Check the authorization and permissions 51 | contract, key, allowed := s.auth.Authorize(channel, security.AllowRead) 52 | if !allowed { 53 | return errors.ErrUnauthorized 54 | } 55 | 56 | // Keys which are supposed to be extended should not be used for subscribing 57 | if key.HasPermission(security.AllowExtend) { 58 | return errors.ErrUnauthorizedExt 59 | } 60 | 61 | // Unsubscribe the client from the channel 62 | ssid := message.NewSsid(key.Contract(), channel.Query) 63 | s.Unsubscribe(c, &event.Subscription{ 64 | Conn: c.LocalID(), 65 | User: nocopy.String(c.Username()), 66 | Ssid: ssid, 67 | Channel: channel.Channel, 68 | }) 69 | 70 | c.Track(contract) 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /internal/service/presence/service_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package presence 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/emitter-io/emitter/internal/event" 21 | "github.com/emitter-io/emitter/internal/message" 22 | "github.com/emitter-io/emitter/internal/service/fake" 23 | "github.com/kelindar/binary" 24 | "github.com/kelindar/binary/nocopy" 25 | "github.com/stretchr/testify/assert" 26 | ) 27 | 28 | func TestPresence_OnSurvey(t *testing.T) { 29 | ssid := message.Ssid{1, 3238259379, 500706888, 1027807523} 30 | pubsub := new(fake.PubSub) 31 | pubsub.Subscribe(new(fake.Conn), &event.Subscription{ 32 | Peer: 2, 33 | Conn: 5, 34 | Ssid: ssid, 35 | Channel: nocopy.Bytes("a/b/c/"), 36 | }) 37 | 38 | s := New(&fake.Authorizer{ 39 | Contract: 1, 40 | Success: true, 41 | }, pubsub, new(fake.Surveyor), pubsub.Trie) 42 | 43 | // Bad query 44 | { 45 | _, ok := s.OnSurvey("xxx", nil) 46 | assert.False(t, ok) 47 | } 48 | 49 | // Bad request 50 | { 51 | _, ok := s.OnSurvey("presence", nil) 52 | assert.False(t, ok) 53 | } 54 | 55 | { 56 | b, _ := binary.Marshal(ssid) 57 | r, ok := s.OnSurvey("presence", b) 58 | 59 | var out []Info 60 | err := binary.Unmarshal(r, &out) 61 | assert.NoError(t, err) 62 | assert.True(t, ok) 63 | assert.Equal(t, 1, len(out)) 64 | } 65 | } 66 | 67 | func TestPresence_Notify(t *testing.T) { 68 | ssid := message.Ssid{1, 3238259379, 500706888, 1027807523} 69 | pubsub := new(fake.PubSub) 70 | pubsub.Subscribe(new(fake.Conn), &event.Subscription{ 71 | Peer: 2, 72 | Conn: 5, 73 | Ssid: ssid, 74 | Channel: nocopy.Bytes("a/b/c/"), 75 | }) 76 | 77 | s := New(&fake.Authorizer{ 78 | Contract: 1, 79 | Success: true, 80 | }, pubsub, new(fake.Surveyor), pubsub.Trie) 81 | 82 | s.Notify(EventTypeSubscribe, &event.Subscription{ 83 | Ssid: ssid, 84 | }, nil) 85 | s.pollPresenceChange() 86 | assert.Equal(t, 1, s.trie.Count()) 87 | 88 | } 89 | -------------------------------------------------------------------------------- /internal/provider/usage/usage_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package usage 16 | 17 | import ( 18 | "sync" 19 | "testing" 20 | 21 | "github.com/axiomhq/hyperloglog" 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestNewUsageMeter(t *testing.T) { 26 | meter := NewMeter(123) 27 | assert.Equal(t, uint32(123), meter.GetContract()) 28 | } 29 | 30 | func TestMeterAdd(t *testing.T) { 31 | meter := &usage{Contract: 123, Lock: new(sync.Mutex), Devices: hyperloglog.New()} 32 | meter.AddIngress(100) 33 | meter.AddEgress(200) 34 | meter.AddDevice("123") 35 | meter.AddDevice("456") 36 | 37 | assert.Equal(t, uint32(123), meter.GetContract()) 38 | 39 | assert.Equal(t, int64(1), meter.MessageIn) 40 | assert.Equal(t, int64(100), meter.TrafficIn) 41 | 42 | assert.Equal(t, int64(1), meter.MessageEg) 43 | assert.Equal(t, int64(200), meter.TrafficEg) 44 | 45 | assert.Equal(t, uint64(2), meter.Devices.Estimate()) 46 | } 47 | 48 | func TestMeterReset(t *testing.T) { 49 | meter := &usage{Lock: new(sync.Mutex), Devices: hyperloglog.New()} 50 | 51 | // Add a device and some traffic 52 | meter.AddIngress(1000) 53 | meter.AddDevice("123") 54 | meter.AddDevice("153") 55 | old1 := meter.reset().toUsage() 56 | 57 | // Assert 58 | assert.Equal(t, int64(1000), old1.TrafficIn) 59 | assert.Equal(t, int64(0), meter.TrafficIn) 60 | assert.Equal(t, 0, meter.DeviceCount()) 61 | assert.Equal(t, 2, old1.DeviceCount()) 62 | 63 | // Add a device and some traffic and reset again 64 | meter.AddIngress(1000) 65 | meter.AddDevice("123") 66 | meter.AddDevice("345") 67 | old2 := meter.reset().toUsage() 68 | 69 | // Assert 70 | assert.Equal(t, int64(1000), old2.TrafficIn) 71 | assert.Equal(t, int64(0), meter.TrafficIn) 72 | assert.Equal(t, 0, meter.DeviceCount()) 73 | assert.Equal(t, 2, old2.DeviceCount()) 74 | 75 | // Merge in 76 | old1.merge(&old2) 77 | assert.Equal(t, int64(2000), old1.TrafficIn) 78 | assert.Equal(t, 3, old1.DeviceCount()) 79 | } 80 | -------------------------------------------------------------------------------- /internal/provider/logging/logging.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package logging 16 | 17 | import ( 18 | "io" 19 | "log" 20 | "os" 21 | 22 | "github.com/emitter-io/config" 23 | ) 24 | 25 | // Discard is the discard logger. 26 | var Discard = log.New(io.Discard, "", 0) 27 | 28 | // Logger is the logger we use. 29 | var Logger = NewStdErr() 30 | 31 | // LogError logs the error as a string. 32 | func LogError(context string, action string, err error) { 33 | Logger.Printf("[%s] error during %s (%s)", context, action, err.Error()) 34 | } 35 | 36 | // LogAction logs The action with a tag. 37 | func LogAction(context string, action string) { 38 | Logger.Printf("[%s] %s", context, action) 39 | } 40 | 41 | // LogTarget logs The action with a tag. 42 | func LogTarget(context, action string, target interface{}) { 43 | Logger.Printf("[%s] %s (%v)", context, action, target) 44 | } 45 | 46 | // ------------------------------------------------------------------------------------ 47 | 48 | // Logging represents a logging contract. 49 | type Logging interface { 50 | config.Provider 51 | 52 | // Printf logs a formatted log line. 53 | Printf(format string, v ...interface{}) 54 | } 55 | 56 | // ------------------------------------------------------------------------------------ 57 | 58 | // stderrLogger implements Logging contract. 59 | var _ Logging = new(stderrLogger) 60 | 61 | // stderrLogger represents a simple golang logger. 62 | type stderrLogger log.Logger 63 | 64 | // NewStdErr creates a new default stderr logger. 65 | func NewStdErr() Logging { 66 | return (*stderrLogger)(log.New(os.Stderr, "", log.LstdFlags)) 67 | } 68 | 69 | // Name returns the name of the provider. 70 | func (s *stderrLogger) Name() string { 71 | return "stderr" 72 | } 73 | 74 | // Configure configures the provider 75 | func (s *stderrLogger) Configure(config map[string]interface{}) error { 76 | return nil 77 | } 78 | 79 | // Printf prints a log line. 80 | func (s *stderrLogger) Printf(format string, v ...interface{}) { 81 | (*log.Logger)(s).Printf(format+"\n", v...) 82 | } 83 | -------------------------------------------------------------------------------- /internal/security/license/license.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package license 16 | 17 | import ( 18 | "crypto/rand" 19 | "encoding/binary" 20 | "fmt" 21 | "strings" 22 | 23 | "github.com/emitter-io/emitter/internal/security" 24 | ) 25 | 26 | var be = binary.BigEndian 27 | 28 | // Cipher represents a cipher used by the license type. 29 | type Cipher interface { 30 | DecryptKey(buffer []byte) (security.Key, error) 31 | EncryptKey(k security.Key) (string, error) 32 | } 33 | 34 | // License represents an abstract license. 35 | type License interface { 36 | fmt.Stringer 37 | NewMasterKey(id uint16) (security.Key, error) 38 | Cipher() (Cipher, error) 39 | Contract() uint32 40 | Signature() uint32 41 | Master() uint32 42 | } 43 | 44 | // New generates a new license and master key. This uses the most up-to-date version 45 | // of the license to generate a new one. 46 | func New() (string, string) { 47 | license := NewV3() 48 | if secret, err := license.NewMasterKey(1); err != nil { 49 | panic(err) 50 | } else if cipher, err := license.Cipher(); err != nil { 51 | panic(err) 52 | } else if master, err := cipher.EncryptKey(secret); err != nil { 53 | panic(err) 54 | } else { 55 | return license.String(), master 56 | } 57 | } 58 | 59 | // Parse parses a valid license of any version. 60 | func Parse(data string) (License, error) { 61 | if len(data) < 5 { 62 | return nil, fmt.Errorf("No license was found, please provide a valid license key through the configuration file, an EMITTER_LICENSE environment variable or a valid vault key 'secrets/emitter/license'") 63 | } 64 | 65 | switch { 66 | case strings.HasSuffix(data, ":1"): 67 | return parseV1(data[:len(data)-2]) 68 | case strings.HasSuffix(data, ":2"): 69 | return parseV2(data[:len(data)-2]) 70 | case strings.HasSuffix(data, ":3"): 71 | return parseV3(data[:len(data)-2]) 72 | default: 73 | return parseV1(data) 74 | } 75 | } 76 | 77 | // RandN generates a crypto-random N bytes. 78 | func randN(n int) []byte { 79 | raw := make([]byte, n) 80 | rand.Read(raw) 81 | return raw 82 | } 83 | -------------------------------------------------------------------------------- /internal/provider/monitor/statsd_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package monitor 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | 21 | "github.com/emitter-io/stats" 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestStatsd_HappyPath(t *testing.T) { 26 | m := stats.New() 27 | for i := int32(0); i < 100; i++ { 28 | m.Measure("proc.test", i) 29 | m.Measure("node.test", i) 30 | m.Measure("rcv.test", i) 31 | } 32 | 33 | s := NewStatsd(m, "") 34 | defer s.Close() 35 | 36 | s.Configure(map[string]interface{}{ 37 | "interval": 1000000.00, 38 | "url": ":8125", 39 | }) 40 | assert.NotPanics(t, func() { 41 | s.write() 42 | }) 43 | } 44 | 45 | func TestStatsd_BadSnapshot(t *testing.T) { 46 | if os.Getenv("GITHUB_WORKSPACE") != "" { 47 | t.Skip("Skipping the test in CI environment") 48 | return 49 | } 50 | 51 | r := snapshot("test") 52 | s := NewStatsd(r, "") 53 | defer s.Close() 54 | 55 | err := s.Configure(map[string]interface{}{ 56 | "interval": 1000000.00, 57 | "url": ":8125", 58 | }) 59 | assert.NoError(t, err) 60 | assert.NotPanics(t, func() { 61 | s.write() 62 | }) 63 | } 64 | 65 | func TestStatsd_Configure(t *testing.T) { 66 | if os.Getenv("GITHUB_WORKSPACE") != "" { 67 | t.Skip("Skipping the test in CI environment") 68 | return 69 | } 70 | 71 | { 72 | s := NewStatsd(nil, "") 73 | defer s.Close() 74 | assert.Equal(t, "statsd", s.Name()) 75 | 76 | err := s.Configure(nil) 77 | assert.NoError(t, err) 78 | } 79 | 80 | { 81 | s := NewStatsd(nil, "") 82 | defer s.Close() 83 | assert.Equal(t, "statsd", s.Name()) 84 | 85 | err := s.Configure(map[string]interface{}{}) 86 | assert.NoError(t, err) 87 | } 88 | 89 | { 90 | s := NewStatsd(nil, "") 91 | defer s.Close() 92 | assert.Equal(t, "statsd", s.Name()) 93 | 94 | err := s.Configure(map[string]interface{}{ 95 | "interval": 100.00, 96 | "url": ":8125", 97 | }) 98 | assert.NoError(t, err) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /internal/service/keyban/keyban.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package keyban 16 | 17 | import ( 18 | "encoding/json" 19 | "regexp" 20 | 21 | "github.com/emitter-io/emitter/internal/errors" 22 | "github.com/emitter-io/emitter/internal/event" 23 | "github.com/emitter-io/emitter/internal/service" 24 | ) 25 | 26 | var ( 27 | shortcut = regexp.MustCompile("^[a-zA-Z0-9]{1,2}$") 28 | ) 29 | 30 | // Service represents a key blacklisting service. 31 | type Service struct { 32 | auth service.Authorizer // The authorizer to use. 33 | keygen service.Decryptor // The key generator to use. 34 | cluster service.Replicator // The cluster service to use. 35 | } 36 | 37 | // New creates a new key blacklisting service. 38 | func New(auth service.Authorizer, keygen service.Decryptor, cluster service.Replicator) *Service { 39 | return &Service{ 40 | auth: auth, 41 | keygen: keygen, 42 | cluster: cluster, 43 | } 44 | } 45 | 46 | // OnRequest handles a request to create a link. 47 | func (s *Service) OnRequest(c service.Conn, payload []byte) (service.Response, bool) { 48 | var message Request 49 | if err := json.Unmarshal(payload, &message); err != nil { 50 | return errors.ErrBadRequest, false 51 | } 52 | 53 | // Decrypt the secret/master key and make sure it's not expired 54 | secretKey, err := s.keygen.DecryptKey(message.Secret) 55 | if err != nil || secretKey.IsExpired() || !secretKey.IsMaster() { 56 | return errors.ErrUnauthorized, false 57 | } 58 | 59 | // Make sure the target key is for the same contract 60 | targetKey, err := s.keygen.DecryptKey(message.Target) 61 | if err != nil || targetKey.Contract() != secretKey.Contract() { 62 | return errors.ErrUnauthorized, false 63 | } 64 | 65 | // Depending on the flag, ban or unban the key 66 | bannedKey := event.Ban(message.Target) 67 | switch { 68 | case message.Banned && !s.cluster.Contains(&bannedKey): 69 | s.cluster.Notify(&bannedKey, true) 70 | case !message.Banned && s.cluster.Contains(&bannedKey): 71 | s.cluster.Notify(&bannedKey, false) 72 | } 73 | 74 | // Success, return the response 75 | return &Response{ 76 | Status: 200, 77 | Banned: message.Banned, 78 | }, true 79 | } 80 | -------------------------------------------------------------------------------- /internal/service/cluster/memberlist.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package cluster 16 | 17 | import ( 18 | "sort" 19 | "sync" 20 | "sync/atomic" 21 | "time" 22 | 23 | "github.com/weaveworks/mesh" 24 | ) 25 | 26 | // memberlist represents a peer cache 27 | type memberlist struct { 28 | list sync.Map 29 | ctor func(mesh.PeerName) *Peer 30 | } 31 | 32 | // newMemberlist creates a new memberlist 33 | func newMemberlist(ctor func(mesh.PeerName) *Peer) *memberlist { 34 | return &memberlist{ 35 | ctor: ctor, 36 | } 37 | } 38 | 39 | // GetOrAdd gets or adds a peer, returns a peer and whether a new peer was added or not 40 | func (m *memberlist) GetOrAdd(name mesh.PeerName) (*Peer, bool) { 41 | if p, ok := m.list.Load(name); ok { 42 | return p.(*Peer), false 43 | } 44 | 45 | // Create new peer and store it 46 | peer := m.ctor(name) 47 | v, loaded := m.list.LoadOrStore(name, peer) 48 | return v.(*Peer), !loaded 49 | } 50 | 51 | // Fallback gets a fallback peer for a given peer. 52 | func (m *memberlist) Fallback(name mesh.PeerName) (*Peer, bool) { 53 | peers := make([]*Peer, 0, 8) 54 | m.list.Range(func(k, v interface{}) bool { 55 | if peer := v.(*Peer); peer.IsActive() && peer.name != name { 56 | peers = append(peers, v.(*Peer)) 57 | } 58 | return true 59 | }) 60 | 61 | sort.Slice(peers, func(i, j int) bool { return name-peers[i].name > name-peers[j].name }) 62 | if len(peers) > 0 { 63 | return peers[0], true 64 | } 65 | return nil, false 66 | } 67 | 68 | // Touch updates the last activity time 69 | func (m *memberlist) Touch(name mesh.PeerName) { 70 | peer, _ := m.GetOrAdd(name) 71 | atomic.StoreInt64(&peer.activity, time.Now().Unix()) 72 | } 73 | 74 | // Contains checks if a peer is in the memberlist 75 | func (m *memberlist) Contains(name mesh.PeerName) bool { 76 | _, ok := m.list.Load(name) 77 | return ok 78 | } 79 | 80 | // Remove removes the peer from the memberlist 81 | func (m *memberlist) Remove(name mesh.PeerName) (*Peer, bool) { 82 | if p, ok := m.list.Load(name); ok { 83 | peer := p.(*Peer) 84 | m.list.Delete(peer.name) 85 | atomic.StoreInt64(&peer.activity, 0) 86 | return peer, true 87 | } 88 | 89 | return nil, false 90 | } 91 | -------------------------------------------------------------------------------- /internal/provider/monitor/prometheus_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package monitor 16 | 17 | import ( 18 | "io" 19 | "log" 20 | "net/http" 21 | "net/http/httptest" 22 | "testing" 23 | 24 | "github.com/emitter-io/stats" 25 | "github.com/stretchr/testify/assert" 26 | ) 27 | 28 | func TestPrometheus_HappyPath(t *testing.T) { 29 | m := stats.New() 30 | for i := int32(0); i < 100; i++ { 31 | m.Measure("proc.test", i) 32 | m.Measure("node.test", i) 33 | m.Measure("rcv.test", i) 34 | } 35 | 36 | mux := http.NewServeMux() 37 | 38 | s := NewPrometheus(m, mux) 39 | defer s.Close() 40 | 41 | err := s.Configure(map[string]interface{}{ 42 | "interval": 1000000.00, 43 | }) 44 | assert.NoError(t, err) 45 | assert.NotPanics(t, func() { 46 | s.write() 47 | }) 48 | } 49 | 50 | func TestPrometheus_Request(t *testing.T) { 51 | 52 | m := stats.New() 53 | for i := int32(0); i < 100; i++ { 54 | m.Measure("proc.test", i) 55 | m.Measure("node.test", i) 56 | m.Measure("rcv.test", i/10) 57 | m.Measure("node.peers", 2) 58 | m.Measure("node.conns", i) 59 | m.Measure("node.subs", i) 60 | } 61 | 62 | mux := http.NewServeMux() 63 | s := NewPrometheus(m, mux) 64 | defer s.Close() 65 | 66 | err := s.Configure(map[string]interface{}{ 67 | "interval": 1000000.00, 68 | }) 69 | 70 | ts := httptest.NewServer(mux) 71 | defer ts.Close() 72 | 73 | res, err := http.Get(ts.URL + "/metrics") 74 | if err != nil { 75 | log.Fatal(err) 76 | } 77 | content, err := io.ReadAll(res.Body) 78 | res.Body.Close() 79 | if err != nil { 80 | log.Fatal(err) 81 | } 82 | 83 | // assert gauges 84 | assert.Contains(t, string(content), "node_peers 2") 85 | assert.Contains(t, string(content), "node_subs 99") 86 | assert.Contains(t, string(content), "node_conns 99") 87 | 88 | // assert histograms 89 | assert.Contains(t, string(content), "rcv_test_bucket{le=\"0.01\"} 10") 90 | assert.Contains(t, string(content), "rcv_test_sum 450") 91 | assert.Contains(t, string(content), "rcv_test_count 100") 92 | 93 | // from InstrumentMetricHandler 94 | assert.Contains(t, string(content), "promhttp_metric_handler_requests_total") 95 | 96 | // from the NewGoCollector 97 | assert.Contains(t, string(content), "go_threads") 98 | } 99 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/emitter-io/emitter 2 | 3 | go 1.24 4 | 5 | toolchain go1.24.0 6 | 7 | require ( 8 | github.com/aws/aws-sdk-go v1.55.6 // indirect 9 | github.com/axiomhq/hyperloglog v0.2.3 10 | github.com/coocood/freecache v1.2.4 11 | github.com/dgraph-io/badger/v3 v3.2103.5 12 | github.com/dgryski/go-metro v0.0.0-20250106013310-edb8663e5e33 // indirect 13 | github.com/emitter-io/address v1.0.1 14 | github.com/emitter-io/config v1.0.0 15 | github.com/emitter-io/stats v1.0.3 16 | github.com/golang/snappy v0.0.4 17 | github.com/gorilla/websocket v1.5.3 18 | github.com/jawher/mow.cli v1.2.0 19 | github.com/kelindar/binary v1.0.19 20 | github.com/kelindar/rate v1.0.0 21 | github.com/kelindar/tcp v1.0.0 22 | github.com/klauspost/compress v1.17.11 // indirect 23 | github.com/prometheus/client_golang v1.20.5 24 | github.com/stretchr/testify v1.10.0 25 | github.com/tidwall/buntdb v1.3.2 26 | github.com/tidwall/pretty v1.2.1 // indirect 27 | github.com/valyala/fasthttp v1.58.0 28 | github.com/weaveworks/mesh v0.0.0-20191105120815-58dbcc3e8e63 29 | golang.org/x/crypto v0.33.0 30 | golang.org/x/net v0.35.0 // indirect 31 | gopkg.in/alexcesaro/statsd.v2 v2.0.0 32 | ) 33 | 34 | require github.com/eclipse/paho.mqtt.golang v1.5.0 35 | 36 | require ( 37 | github.com/andybalholm/brotli v1.1.1 // indirect 38 | github.com/beorn7/perks v1.0.1 // indirect 39 | github.com/cespare/xxhash v1.1.0 // indirect 40 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 41 | github.com/davecgh/go-spew v1.1.1 // indirect 42 | github.com/dgraph-io/ristretto v0.2.0 // indirect 43 | github.com/dustin/go-humanize v1.0.1 // indirect 44 | github.com/gogo/protobuf v1.3.2 // indirect 45 | github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect 46 | github.com/golang/protobuf v1.5.4 // indirect 47 | github.com/google/flatbuffers v25.2.10+incompatible // indirect 48 | github.com/jmespath/go-jmespath v0.4.0 // indirect 49 | github.com/kamstrup/intmap v0.5.1 // indirect 50 | github.com/kelindar/process v0.0.0-20170730150328-69a29e249ec3 // indirect 51 | github.com/kr/text v0.2.0 // indirect 52 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 53 | github.com/pkg/errors v0.9.1 // indirect 54 | github.com/pmezard/go-difflib v1.0.0 // indirect 55 | github.com/prometheus/client_model v0.6.1 // indirect 56 | github.com/prometheus/common v0.62.0 // indirect 57 | github.com/prometheus/procfs v0.15.1 // indirect 58 | github.com/stretchr/objx v0.5.2 // indirect 59 | github.com/tidwall/btree v1.7.0 // indirect 60 | github.com/tidwall/gjson v1.18.0 // indirect 61 | github.com/tidwall/grect v0.1.4 // indirect 62 | github.com/tidwall/match v1.1.1 // indirect 63 | github.com/tidwall/rtred v0.1.2 // indirect 64 | github.com/tidwall/tinyqueue v0.1.1 // indirect 65 | github.com/valyala/bytebufferpool v1.0.0 // indirect 66 | go.opencensus.io v0.24.0 // indirect 67 | golang.org/x/sync v0.11.0 // indirect 68 | golang.org/x/sys v0.30.0 // indirect 69 | golang.org/x/text v0.22.0 // indirect 70 | google.golang.org/protobuf v1.36.5 // indirect 71 | gopkg.in/yaml.v3 v3.0.1 // indirect 72 | ) 73 | -------------------------------------------------------------------------------- /internal/service/interfaces.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package service 16 | 17 | import ( 18 | "io" 19 | 20 | "github.com/emitter-io/emitter/internal/event" 21 | "github.com/emitter-io/emitter/internal/message" 22 | "github.com/emitter-io/emitter/internal/provider/contract" 23 | "github.com/emitter-io/emitter/internal/security" 24 | ) 25 | 26 | // Authorizer service performs authorization checks. 27 | type Authorizer interface { 28 | Authorize(*security.Channel, uint8) (contract.Contract, security.Key, bool) 29 | } 30 | 31 | // PubSub represents a pubsub service. 32 | type PubSub interface { 33 | Publish(*message.Message, func(message.Subscriber) bool) int64 34 | Subscribe(message.Subscriber, *event.Subscription) bool 35 | Unsubscribe(message.Subscriber, *event.Subscription) bool 36 | Handle(string, Handler) 37 | } 38 | 39 | // Handler represents a generic emitter request handler 40 | type Handler func(Conn, []byte) (Response, bool) 41 | 42 | // Response represents an emitter response. 43 | type Response interface { 44 | ForRequest(uint16) 45 | } 46 | 47 | // Surveyee handles the surveys. 48 | type Surveyee interface { 49 | OnSurvey(string, []byte) ([]byte, bool) 50 | } 51 | 52 | //Surveyor issues the surveys. 53 | type Surveyor interface { 54 | Query(string, []byte) (message.Awaiter, error) 55 | } 56 | 57 | // Conn represents a connection interface. 58 | type Conn interface { 59 | io.Closer 60 | message.Subscriber 61 | CanSubscribe(message.Ssid, []byte) bool 62 | CanUnsubscribe(message.Ssid, []byte) bool 63 | LocalID() security.ID 64 | Username() string 65 | Track(contract.Contract) 66 | Links() map[string]string 67 | GetLink([]byte) []byte 68 | AddLink(string, *security.Channel) 69 | } 70 | 71 | // Replicator replicates an event withih the cluster 72 | type Replicator interface { 73 | Notify(event.Event, bool) 74 | Contains(event.Event) bool 75 | } 76 | 77 | // Decryptor decrypts security keys. 78 | type Decryptor interface { 79 | DecryptKey(string) (security.Key, error) 80 | } 81 | 82 | // Notifier notifies the cluster about publish/subscribe events. 83 | type Notifier interface { 84 | NotifySubscribe(message.Subscriber, *event.Subscription) 85 | NotifyUnsubscribe(message.Subscriber, *event.Subscription) 86 | } 87 | -------------------------------------------------------------------------------- /internal/errors/error.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package errors 16 | 17 | // New creates a new error 18 | func New(msg string) *Error { 19 | return &Error{ 20 | Status: 500, 21 | Message: msg, 22 | } 23 | } 24 | 25 | // Error represents an event code which provides a more details. 26 | type Error struct { 27 | Request uint16 `json:"req,omitempty"` 28 | Status int `json:"status"` 29 | Message string `json:"message"` 30 | } 31 | 32 | // Error implements error interface. 33 | func (e *Error) Error() string { return e.Message } 34 | 35 | // Copy clones the error object. 36 | func (e *Error) Copy() *Error { 37 | copyErr := *e 38 | return ©Err 39 | } 40 | 41 | // ForRequest returns an error for a specific request. 42 | func (e *Error) ForRequest(requestID uint16) { 43 | e.Request = requestID 44 | } 45 | 46 | // Represents a set of errors used in the handlers. 47 | var ( 48 | ErrBadRequest = &Error{Status: 400, Message: "the request was invalid or cannot be otherwise served"} 49 | ErrUnauthorized = &Error{Status: 401, Message: "the security key provided is not authorized to perform this operation"} 50 | ErrPaymentRequired = &Error{Status: 402, Message: "the request can not be served, as the payment is required to proceed"} 51 | ErrForbidden = &Error{Status: 403, Message: "the request is understood, but it has been refused or access is not allowed"} 52 | ErrNotFound = &Error{Status: 404, Message: "the resource requested does not exist"} 53 | ErrServerError = &Error{Status: 500, Message: "an unexpected condition was encountered and no more specific message is suitable"} 54 | ErrNotImplemented = &Error{Status: 501, Message: "the server either does not recognize the request method, or it lacks the ability to fulfill the request"} 55 | ErrTargetInvalid = &Error{Status: 400, Message: "channel should end with `/` for strict types or `/#/` for wildcards"} 56 | ErrTargetTooLong = &Error{Status: 400, Message: "channel can not have more than 23 parts"} 57 | ErrLinkInvalid = &Error{Status: 400, Message: "the link must be an alphanumeric string of 1 or 2 characters"} 58 | ErrUnauthorizedExt = &Error{Status: 401, Message: "the security key with extend permission can only be used for private links"} 59 | ) 60 | -------------------------------------------------------------------------------- /internal/service/keyban/keyban_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package keyban 16 | 17 | import ( 18 | "encoding/json" 19 | "testing" 20 | 21 | "github.com/emitter-io/emitter/internal/event" 22 | "github.com/emitter-io/emitter/internal/security" 23 | "github.com/emitter-io/emitter/internal/service/fake" 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | func TestKeyBan(t *testing.T) { 28 | tests := []struct { 29 | contract1 int 30 | contract2 int 31 | perms uint8 32 | request *Request 33 | initial string 34 | expected string 35 | success bool 36 | }{ 37 | {request: nil}, 38 | {request: &Request{}}, 39 | { 40 | contract1: 1, 41 | contract2: 1, 42 | perms: security.AllowMaster, 43 | success: true, 44 | expected: "b", 45 | request: &Request{ 46 | Secret: "a", 47 | Target: "b", 48 | Banned: true, 49 | }, 50 | }, 51 | { 52 | contract1: 1, 53 | contract2: 1, 54 | perms: security.AllowMaster, 55 | success: true, 56 | initial: "b", 57 | request: &Request{ 58 | Secret: "a", 59 | Target: "b", 60 | Banned: false, 61 | }, 62 | }, 63 | { 64 | contract1: 1, 65 | contract2: 2, 66 | request: &Request{ 67 | Secret: "a", 68 | Target: "b", 69 | Banned: true, 70 | }, 71 | }, 72 | } 73 | 74 | for _, tc := range tests { 75 | repl := new(fake.Replicator) 76 | s := New(&fake.Authorizer{ 77 | Contract: uint32(tc.contract1), 78 | Success: tc.contract1 != 0, 79 | }, &fake.Decryptor{ 80 | Contract: uint32(tc.contract2), 81 | Permissions: tc.perms, 82 | }, repl) 83 | 84 | // Fill the replicator 85 | initial := event.Ban(tc.initial) 86 | repl.Notify(&initial, tc.initial != "") 87 | 88 | // Prepare the request 89 | b, _ := json.Marshal(tc.request) 90 | if tc.request == nil { 91 | b = []byte("invalid") 92 | } 93 | 94 | // Issue a request 95 | _, ok := s.OnRequest(nil, b) 96 | assert.Equal(t, tc.success, ok) 97 | 98 | // Make sure we have the key if expected 99 | expected := event.Ban(tc.expected) 100 | assert.Equal(t, tc.expected != "", repl.Contains(&expected)) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "os" 20 | 21 | "github.com/emitter-io/config/dynamo" 22 | "github.com/emitter-io/config/vault" 23 | "github.com/emitter-io/emitter/internal/broker" 24 | "github.com/emitter-io/emitter/internal/command/keygen" 25 | "github.com/emitter-io/emitter/internal/command/license" 26 | "github.com/emitter-io/emitter/internal/command/load" 27 | "github.com/emitter-io/emitter/internal/command/version" 28 | "github.com/emitter-io/emitter/internal/config" 29 | "github.com/emitter-io/emitter/internal/provider/logging" 30 | cli "github.com/jawher/mow.cli" 31 | ) 32 | 33 | //go:generate go run internal/broker/generate/assets_gen.go 34 | 35 | func main() { 36 | app := cli.App("emitter", "Runs the Emitter broker.") 37 | app.Spec = "[ -c= ] " 38 | confPath := app.StringOpt("c config", "emitter.conf", "Specifies the configuration path (file) to use for the broker.") 39 | app.Action = func() { listen(app, confPath) } 40 | 41 | // Register sub-commands 42 | app.Command("version", "Prints the version of the executable.", version.Print) 43 | app.Command("load", "Runs the load testing client for emitter.", load.Run) 44 | app.Command("license", "Manipulates licenses and secret keys.", func(cmd *cli.Cmd) { 45 | cmd.Command("new", "Generates a new license and secret key pair.", license.New) 46 | // TODO: add more sub-commands for license 47 | }) 48 | app.Command("keygen", "Generates a new key for a channel.", keygen.NewKey) 49 | 50 | app.Run(os.Args) 51 | } 52 | 53 | // Listen starts the service. 54 | func listen(app *cli.Cli, conf *string) { 55 | 56 | // Generate a new license if none was provided 57 | cfg := config.New(*conf, dynamo.NewProvider(), vault.NewProvider(config.VaultUser)) 58 | if cfg.License == "" { 59 | logging.LogAction("service", "unable to find a license, make sure 'license' "+ 60 | "value is set in the config file or EMITTER_LICENSE environment variable") 61 | app.Run([]string{"emitter", "license", "new"}) 62 | return 63 | } 64 | 65 | // Start new service 66 | svc, err := broker.NewService(context.Background(), cfg) 67 | if err != nil { 68 | logging.LogError("service", "startup", err) 69 | return 70 | } 71 | 72 | // Listen and serve 73 | svc.Listen() 74 | } 75 | -------------------------------------------------------------------------------- /internal/service/history/history_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package history 16 | 17 | import ( 18 | "encoding/json" 19 | "testing" 20 | 21 | "github.com/emitter-io/emitter/internal/message" 22 | "github.com/emitter-io/emitter/internal/provider/storage" 23 | "github.com/emitter-io/emitter/internal/security" 24 | "github.com/emitter-io/emitter/internal/service/fake" 25 | "github.com/stretchr/testify/assert" 26 | ) 27 | 28 | // TestHistory tests the history service. 29 | func TestHistory(t *testing.T) { 30 | ssid := message.Ssid{1, 3238259379, 500706888, 1027807523} 31 | store := storage.NewInMemory(nil) 32 | store.Configure(nil) 33 | auth := &fake.Authorizer{ 34 | Success: true, 35 | Contract: uint32(1), 36 | ExtraPerm: security.AllowLoad, 37 | } 38 | // Create new service 39 | service := New(auth, store) 40 | connection := &fake.Conn{} 41 | 42 | // The most basic request, on an empty store. 43 | request := &Request{ 44 | Key: "key", 45 | Channel: "key/a/b/c/", 46 | } 47 | 48 | // Store 2 messages 49 | firstSSID := message.NewID(ssid) 50 | store.Store(&message.Message{ 51 | ID: firstSSID, 52 | Channel: []byte("a/b/c/"), 53 | Payload: []byte("hello"), 54 | TTL: 30, 55 | }) 56 | store.Store(&message.Message{ 57 | ID: message.NewID(ssid), 58 | Channel: []byte("a/b/c/"), 59 | Payload: []byte("hello"), 60 | TTL: 30, 61 | }) 62 | reqBytes, _ := json.Marshal(request) 63 | 64 | // Issue the same request 65 | response, ok := service.OnRequest(connection, reqBytes) 66 | // The request should have succeeded and returned a response. 67 | assert.Equal(t, true, ok) 68 | // The response should have returned the last message as per MQTT spec. 69 | assert.Equal(t, 1, len(response.(*Response).Messages)) 70 | 71 | store.Store(&message.Message{ 72 | ID: message.NewID(ssid), 73 | Channel: []byte("a/b/c/"), 74 | Payload: []byte("hello"), 75 | TTL: 30, 76 | }) 77 | request.Channel = "key/a/b/c/?last=2" 78 | reqBytes, _ = json.Marshal(request) 79 | response, ok = service.OnRequest(connection, reqBytes) 80 | // The request should have succeeded and returned a response. 81 | assert.Equal(t, true, ok) 82 | // The response should have returned the last 2 messages. 83 | assert.Equal(t, 2, len(response.(*Response).Messages)) 84 | } 85 | -------------------------------------------------------------------------------- /internal/security/cipher/shuffle.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package cipher 16 | 17 | import ( 18 | "encoding/base64" 19 | "errors" 20 | 21 | "github.com/emitter-io/emitter/internal/security" 22 | "golang.org/x/crypto/salsa20/salsa" 23 | ) 24 | 25 | // Shuffle represents a security cipher which can encrypt/decrypt security keys. 26 | type Shuffle struct { 27 | key [32]byte 28 | nonce [16]byte 29 | } 30 | 31 | // NewShuffle creates a new shuffled salsa cipher. 32 | func NewShuffle(key, nonce []byte) (*Shuffle, error) { 33 | if len(key) != 32 || len(nonce) != 16 { 34 | return nil, errors.New("shuffled: invalid cryptographic key") 35 | } 36 | 37 | cipher := new(Shuffle) 38 | copy(cipher.key[:], key) 39 | copy(cipher.nonce[:], nonce) 40 | return cipher, nil 41 | } 42 | 43 | // EncryptKey encrypts the key and return a base-64 encoded string. 44 | func (c *Shuffle) EncryptKey(k security.Key) (string, error) { 45 | buffer := make([]byte, 24) 46 | copy(buffer[:], k) 47 | 48 | err := c.crypt(buffer) 49 | return base64.RawURLEncoding.EncodeToString(buffer), err 50 | } 51 | 52 | // DecryptKey decrypts the security key from a base64 encoded string. 53 | func (c *Shuffle) DecryptKey(buffer []byte) (security.Key, error) { 54 | if len(buffer) != 32 { 55 | return nil, errors.New("cipher: the key provided is not valid") 56 | } 57 | 58 | // Warning: we do a base64 decode in the same underlying buffer, to save up 59 | // on memory allocations. Keep in mind that the previous data will be lost. 60 | n, err := decodeKey(buffer, buffer) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | // We now need to resize the slice, since we changed it. 66 | buffer = buffer[:n] 67 | c.crypt(buffer) 68 | 69 | // Return the key on the decrypted buffer. 70 | return security.Key(buffer), nil 71 | } 72 | 73 | // crypt encrypts or decrypts the data and shuffles (recommended). 74 | func (c *Shuffle) crypt(data []byte) error { 75 | buffer := data[2:] 76 | salt := data[0:2] 77 | 78 | // Apply the salt to nonce 79 | var nonce [16]byte 80 | for i := 0; i < 16; i += 2 { 81 | nonce[i] = salt[0] ^ c.nonce[i] 82 | nonce[i+1] = salt[1] ^ c.nonce[i+1] 83 | } 84 | 85 | var subKey [32]byte 86 | salsa.HSalsa20(&subKey, &nonce, &c.key, &salsa.Sigma) 87 | salsa.XORKeyStream(buffer, buffer, &nonce, &subKey) 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /internal/service/link/link.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package link 16 | 17 | import ( 18 | "encoding/json" 19 | "regexp" 20 | 21 | "github.com/emitter-io/emitter/internal/errors" 22 | "github.com/emitter-io/emitter/internal/event" 23 | "github.com/emitter-io/emitter/internal/message" 24 | "github.com/emitter-io/emitter/internal/security" 25 | "github.com/emitter-io/emitter/internal/service" 26 | "github.com/kelindar/binary/nocopy" 27 | ) 28 | 29 | var ( 30 | shortcut = regexp.MustCompile("^[a-zA-Z0-9]{1,2}$") 31 | ) 32 | 33 | // Service represents a link generation service. 34 | type Service struct { 35 | auth service.Authorizer // The authorizer to use. 36 | pubsub service.PubSub // The pub/sub service to use. 37 | } 38 | 39 | // New creates a new link generation service. 40 | func New(auth service.Authorizer, pubsub service.PubSub) *Service { 41 | return &Service{ 42 | auth: auth, 43 | pubsub: pubsub, 44 | } 45 | } 46 | 47 | // OnRequest handles a request to create a link. 48 | func (s *Service) OnRequest(c service.Conn, payload []byte) (service.Response, bool) { 49 | var request Request 50 | if err := json.Unmarshal(payload, &request); err != nil { 51 | return errors.ErrBadRequest, false 52 | } 53 | 54 | // Check whether the name is a valid shortcut name 55 | if !shortcut.Match([]byte(request.Name)) { 56 | return errors.ErrLinkInvalid, false 57 | } 58 | 59 | // Ensures that the channel requested is valid 60 | channel := security.MakeChannel(request.Key, request.Channel) 61 | if channel == nil || channel.ChannelType == security.ChannelInvalid { 62 | return errors.ErrBadRequest, false 63 | } 64 | 65 | // Create the link with the name and set the full channel to it 66 | c.AddLink(request.Name, channel) 67 | 68 | // If an auto-subscribe was requested and the key has read permissions, subscribe 69 | if _, key, allowed := s.auth.Authorize(channel, security.AllowRead); allowed && request.Subscribe { 70 | ssid := message.NewSsid(key.Contract(), channel.Query) 71 | s.pubsub.Subscribe(c, &event.Subscription{ 72 | Conn: c.LocalID(), 73 | User: nocopy.String(c.Username()), 74 | Ssid: ssid, 75 | Channel: channel.Channel, 76 | }) 77 | } 78 | 79 | return &Response{ 80 | Status: 200, 81 | Name: request.Name, 82 | Channel: channel.SafeString(), 83 | }, true 84 | } 85 | -------------------------------------------------------------------------------- /internal/message/id_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package message 16 | 17 | import ( 18 | "math" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestID_NewID(t *testing.T) { 25 | next = math.MaxUint32 26 | id := NewID(Ssid{1, share, 3}) 27 | 28 | assert.Zero(t, next) 29 | assert.True(t, id.Time() > 1527819700) 30 | 31 | id.SetTime(offset + 1) 32 | assert.Equal(t, int64(offset+1), id.Time()) 33 | } 34 | 35 | func TestID_NewPrefix(t *testing.T) { 36 | id1 := NewID(Ssid{1, 2, 3}) 37 | id2 := NewPrefix(Ssid{1, 2, 3}, 123) 38 | 39 | assert.Equal(t, id1[:4], id2[:4]) 40 | } 41 | 42 | func TestID_HasPrefix(t *testing.T) { 43 | next = 0 44 | id := NewID(Ssid{1, 2, 3}) 45 | 46 | assert.True(t, id.HasPrefix(Ssid{1, 2}, 0)) 47 | assert.False(t, id.HasPrefix(Ssid{1, 2}, 2527784701635600500)) // After Sunday, February 6, 2050 6:25:01.636 PM this test will fail :-) 48 | assert.False(t, id.HasPrefix(Ssid{1, 3}, 0)) 49 | } 50 | 51 | func TestID_Match(t *testing.T) { 52 | id := NewID(Ssid{1, 2, 3, 4}) 53 | 54 | assert.NotEmpty(t, id) 55 | assert.True(t, id.Match(Ssid{1, 2, 3, 4}, 0, math.MaxInt64)) 56 | assert.True(t, id.Match(Ssid{1, 2, 3}, 0, math.MaxInt64)) 57 | assert.True(t, id.Match(Ssid{1, 2}, 0, math.MaxInt64)) 58 | assert.False(t, id.Match(Ssid{1, 2, 3, 5}, 0, math.MaxInt64)) 59 | assert.False(t, id.Match(Ssid{1, 5}, 0, math.MaxInt64)) 60 | assert.False(t, id.Match(Ssid{1, 5}, 0, math.MaxInt64)) 61 | assert.True(t, id.Match(Ssid{1, 2}, 0, 2527784701635600500)) 62 | assert.False(t, id.Match(Ssid{1, 2}, 2527784701635600500, math.MaxInt64)) 63 | assert.False(t, id.Match(Ssid{2, 2, 3, 4}, 0, math.MaxInt64)) 64 | assert.False(t, id.Match(Ssid{2, 2, 3, 4, 5}, 0, math.MaxInt64)) 65 | } 66 | 67 | func TestID_Ssid(t *testing.T) { 68 | in := Ssid{1, 2, 3, 4, 5, 6} 69 | id := NewID(in) 70 | 71 | assert.Equal(t, in[0], id.Contract()) 72 | assert.Equal(t, in, id.Ssid()) 73 | } 74 | 75 | func BenchmarkID_New(b *testing.B) { 76 | b.ReportAllocs() 77 | b.ResetTimer() 78 | 79 | ssid := Ssid{1, 2} 80 | for n := 0; n < b.N; n++ { 81 | NewID(ssid) 82 | } 83 | } 84 | 85 | func BenchmarkID_Match(b *testing.B) { 86 | id := NewID(Ssid{1, 2, 3, 4}) 87 | ssid := Ssid{1, 2, 3, 4} 88 | 89 | b.ReportAllocs() 90 | b.ResetTimer() 91 | for n := 0; n < b.N; n++ { 92 | id.Match(ssid, 0, math.MaxInt64) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /internal/provider/monitor/self.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package monitor 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "time" 21 | 22 | "github.com/emitter-io/address" 23 | "github.com/emitter-io/emitter/internal/async" 24 | "github.com/emitter-io/stats" 25 | ) 26 | 27 | // Noop implements Storage contract. 28 | var _ Storage = new(Self) 29 | 30 | // Self represents a storage which self-publishes stats. 31 | type Self struct { 32 | reader stats.Snapshotter // The reader which reads the snapshot of stats. 33 | channel string // The channel name to publish into. 34 | publish func(string, []byte) // The publish function to use. 35 | cancel context.CancelFunc // The cancellation function. 36 | } 37 | 38 | // NewSelf creates a new self-publishing stats sink. 39 | func NewSelf(snapshotter stats.Snapshotter, selfPublish func(string, []byte)) *Self { 40 | return &Self{ 41 | publish: selfPublish, 42 | channel: "stats", 43 | reader: snapshotter, 44 | } 45 | } 46 | 47 | // Name returns the name of the provider. 48 | func (s *Self) Name() string { 49 | return "self" 50 | } 51 | 52 | // Configure configures the storage. The config parameter provided is 53 | // loosely typed, since various storage mechanisms will require different 54 | // configurations. 55 | func (s *Self) Configure(config map[string]interface{}) error { 56 | 57 | // Get the interval from the provider configuration 58 | interval := time.Second 59 | if v, ok := config["interval"]; ok { 60 | if i, ok := v.(float64); ok { 61 | interval = time.Duration(i) * time.Millisecond 62 | } 63 | } 64 | 65 | // Get the url from the provider configuration 66 | if c, ok := config["channel"]; ok { 67 | s.channel = c.(string) 68 | } 69 | 70 | // Set channel name 71 | s.channel = fmt.Sprintf("%s/%s/", s.channel, address.GetHardware().Hex()) 72 | 73 | // Setup a repeat flush 74 | s.cancel = async.Repeat(context.Background(), interval, s.write) 75 | return nil 76 | } 77 | 78 | // Flush reads and writes stats into this stats sink. 79 | func (s *Self) write() { 80 | if snapshot := s.reader.Snapshot(); len(snapshot) > 0 { 81 | s.publish(s.channel, snapshot) 82 | } 83 | } 84 | 85 | // Close gracefully terminates the storage and ensures that every related 86 | // resource is properly disposed. 87 | func (s *Self) Close() error { 88 | if s.cancel != nil { 89 | s.cancel() 90 | } 91 | 92 | return nil 93 | } 94 | -------------------------------------------------------------------------------- /internal/service/keygen/assets/keygen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 15 |
16 |
17 |
18 |
19 |

Key Generation

20 |
21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 |
29 |
30 | 31 | 32 |
33 |
34 | 35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 43 |
44 | 45 |
46 |
{{.Response}}
47 |
48 |
49 | 50 |
51 |
52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /internal/security/cipher/salsa_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package cipher 16 | 17 | import ( 18 | "testing" 19 | "time" 20 | 21 | "github.com/emitter-io/emitter/internal/security" 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | // BenchmarkEncryptKey2-8 5000000 356 ns/op 64 B/op 2 allocs/op 26 | func Benchmark_Salsa_EncryptKey(b *testing.B) { 27 | cipher := new(Salsa) 28 | key := "um4m30suos9k0tNjZiO19FyGNtmZjRlN" 29 | 30 | b.ReportAllocs() 31 | b.ResetTimer() 32 | for i := 0; i < b.N; i++ { 33 | _, _ = cipher.EncryptKey([]byte(key)) 34 | } 35 | } 36 | 37 | func Test_Salsa(t *testing.T) { 38 | cipher := new(Salsa) 39 | key := security.Key(make([]byte, 24)) 40 | key.SetSalt(999) 41 | key.SetMaster(2) 42 | key.SetContract(123) 43 | key.SetSignature(777) 44 | key.SetPermissions(security.AllowReadWrite) 45 | key.SetTarget("a/b/c/") 46 | key.SetExpires(time.Unix(1497683272, 0).UTC()) 47 | 48 | encoded, err := cipher.EncryptKey(key) 49 | assert.NoError(t, err) 50 | assert.Equal(t, "uYkm3UsuorRk0tBqliO18gs5xXmXioMF", encoded) 51 | 52 | decoded, err := cipher.DecryptKey([]byte(encoded)) 53 | assert.NoError(t, err) 54 | assert.Equal(t, key, decoded) 55 | } 56 | 57 | // Benchmark_Salsa_DecryptKey-8 5000000 309 ns/op 0 B/op 0 allocs/op 58 | func Benchmark_Salsa_DecryptKey(b *testing.B) { 59 | cipher := new(Salsa) 60 | key := "um4m30suos9k0tNjZiO19FyGNtmZjRlN" 61 | 62 | b.ReportAllocs() 63 | b.ResetTimer() 64 | for i := 0; i < b.N; i++ { 65 | _, _ = cipher.DecryptKey([]byte(key)) 66 | } 67 | } 68 | 69 | func Test_Salsa_Errors(t *testing.T) { 70 | cipher := new(Salsa) 71 | tests := []struct { 72 | key string 73 | err bool 74 | }{ 75 | 76 | { 77 | key: "", 78 | err: true, 79 | }, 80 | { 81 | key: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*", 82 | err: true, 83 | }, 84 | } 85 | 86 | for _, tc := range tests { 87 | _, err := cipher.DecryptKey([]byte(tc.key)) 88 | assert.Equal(t, tc.err, err != nil, tc.key) 89 | 90 | } 91 | } 92 | 93 | func TestNewSalsa(t *testing.T) { 94 | 95 | // Happy path 96 | { 97 | c, err := NewSalsa(make([]byte, 32), make([]byte, 24)) 98 | assert.NoError(t, err) 99 | assert.NotNil(t, c) 100 | } 101 | 102 | // Error case 103 | { 104 | c, err := NewSalsa(nil, nil) 105 | assert.Error(t, err) 106 | assert.Nil(t, c) 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /internal/event/crdt/volatile_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package crdt 16 | 17 | import ( 18 | "fmt" 19 | "sync" 20 | "testing" 21 | 22 | "github.com/kelindar/binary" 23 | "github.com/stretchr/testify/assert" 24 | ) 25 | 26 | func TestVolatile_Concurrent(t *testing.T) { 27 | i := 0 28 | lww := NewVolatile() 29 | for ; i < 100; i++ { 30 | setClock(int64(i)) 31 | lww.Add(fmt.Sprintf("%v", i), nil) 32 | } 33 | 34 | go func() { 35 | binary.Marshal(lww) 36 | }() 37 | 38 | var start, stop sync.WaitGroup 39 | start.Add(1) 40 | 41 | for x := 2; x < 10; x++ { 42 | other := NewVolatile() 43 | gi := i 44 | gu := x * 100 45 | 46 | for ; gi < gu; gi++ { 47 | setClock(int64(100000 + gi)) 48 | other.Del(fmt.Sprintf("%v", i)) 49 | } 50 | 51 | stop.Add(1) 52 | go func() { 53 | start.Wait() 54 | lww.Merge(other) 55 | other.Merge(lww) 56 | stop.Done() 57 | }() 58 | } 59 | start.Done() 60 | stop.Wait() 61 | } 62 | 63 | // ------------------------------------------------------------------------------------ 64 | 65 | func TestRange(t *testing.T) { 66 | state := newVolatileWith( 67 | map[string]Value{ 68 | "AC": newTime(60, 50, nil), 69 | "AB": newTime(60, 50, nil), 70 | "AA": newTime(10, 50, nil), // Deleted 71 | "BA": newTime(60, 50, nil), 72 | "BB": newTime(60, 50, nil), 73 | "BC": newTime(60, 50, nil), 74 | }) 75 | 76 | var count int 77 | state.Range([]byte("A"), false, func(_ string, v Value) bool { 78 | count++ 79 | 80 | return true 81 | }) 82 | assert.Equal(t, 2, count) 83 | 84 | count = 0 85 | state.Range(nil, false, func(_ string, v Value) bool { 86 | count++ 87 | return true 88 | }) 89 | assert.Equal(t, 5, count) 90 | assert.Equal(t, 6, state.Count()) 91 | } 92 | 93 | // ------------------------------------------------------------------------------------ 94 | 95 | func TestTempMarshal(t *testing.T) { 96 | defer restoreClock(Now) 97 | 98 | setClock(0) 99 | state := newVolatileWith(map[string]Value{"A": newTime(10, 50, nil)}) 100 | 101 | // Encode 102 | enc, err := binary.Marshal(state) 103 | assert.NoError(t, err) 104 | assert.Equal(t, []byte{0x1, 0x1, 0x41, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x32}, enc) 105 | 106 | // Decode 107 | dec := NewVolatile() 108 | err = binary.Unmarshal(enc, dec) 109 | assert.NoError(t, err) 110 | assert.Equal(t, state, dec) 111 | } 112 | -------------------------------------------------------------------------------- /internal/command/load/load_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2019 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package load 16 | 17 | import ( 18 | "bytes" 19 | "io" 20 | "net" 21 | "testing" 22 | "time" 23 | 24 | "github.com/emitter-io/emitter/internal/network/mqtt" 25 | cli "github.com/jawher/mow.cli" 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestRun(t *testing.T) { 30 | dial = func(string, string) (net.Conn, error) { 31 | conn := new(fakeConn) 32 | 33 | // Push connack 34 | connack := new(mqtt.Connack) 35 | connack.EncodeTo(&conn.read) 36 | 37 | // Push suback 38 | suback := new(mqtt.Suback) 39 | suback.EncodeTo(&conn.read) 40 | return conn, nil 41 | } 42 | 43 | runCommand("emitter", "test", "key") 44 | } 45 | 46 | func Test_newConn_Error(t *testing.T) { 47 | dial = func(string, string) (net.Conn, error) { 48 | return nil, io.EOF 49 | } 50 | 51 | conn, _ := newConn("127.0.0.1:8080", "", "") 52 | assert.Nil(t, conn) 53 | assert.NotPanics(t, func() { 54 | runCommand("emitter", "test", "key") 55 | }) 56 | } 57 | 58 | func Test_newConn(t *testing.T) { 59 | dial = func(string, string) (net.Conn, error) { 60 | return new(fakeConn), nil 61 | } 62 | 63 | conn, _ := newConn("127.0.0.1:8080", "", "") 64 | assert.NotNil(t, conn) 65 | 66 | _, err := conn.ReadByte() 67 | assert.Equal(t, io.EOF, err) 68 | } 69 | 70 | func Test_newMessage(t *testing.T) { 71 | msg := newMessage("a/", -1) 72 | assert.Equal(t, "a/", string(msg.Topic)) 73 | } 74 | 75 | func runCommand(args ...string) { 76 | app := cli.App("emitter", "") 77 | app.Command("test", "", Run) 78 | app.Run(args) 79 | } 80 | 81 | // ------------------------------------------------------------------------------------ 82 | 83 | type fakeConn struct { 84 | read bytes.Buffer 85 | write int 86 | } 87 | 88 | func (m *fakeConn) Read(p []byte) (int, error) { 89 | return m.read.Read(p) 90 | } 91 | 92 | func (m *fakeConn) Write(p []byte) (int, error) { 93 | if m.write++; m.write < 100 { 94 | return len(p), nil 95 | } 96 | 97 | return 0, io.EOF 98 | } 99 | 100 | func (m *fakeConn) Close() error { 101 | return nil 102 | } 103 | 104 | func (m *fakeConn) LocalAddr() net.Addr { 105 | return nil 106 | } 107 | 108 | func (m *fakeConn) RemoteAddr() net.Addr { 109 | return nil 110 | } 111 | 112 | func (m *fakeConn) SetDeadline(t time.Time) error { 113 | return nil 114 | } 115 | 116 | func (m *fakeConn) SetReadDeadline(t time.Time) error { 117 | return nil 118 | } 119 | 120 | func (m *fakeConn) SetWriteDeadline(t time.Time) error { 121 | return nil 122 | } 123 | -------------------------------------------------------------------------------- /internal/service/pubsub/unsubscribe_test.go: -------------------------------------------------------------------------------- 1 | /********************************************************************************** 2 | * Copyright (c) 2009-2020 Misakai Ltd. 3 | * This program is free software: you can redistribute it and/or modify it under the 4 | * terms of the GNU Affero General Public License as published by the Free Software 5 | * Foundation, either version 3 of the License, or(at your option) any later version. 6 | * 7 | * This program is distributed in the hope that it will be useful, but WITHOUT ANY 8 | * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 9 | * PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 10 | * 11 | * You should have received a copy of the GNU Affero General Public License along 12 | * with this program. If not, see. 13 | ************************************************************************************/ 14 | 15 | package pubsub 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/emitter-io/emitter/internal/event" 21 | "github.com/emitter-io/emitter/internal/message" 22 | "github.com/emitter-io/emitter/internal/provider/storage" 23 | "github.com/emitter-io/emitter/internal/security" 24 | "github.com/emitter-io/emitter/internal/service/fake" 25 | "github.com/kelindar/binary/nocopy" 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestPubSub_Unsubscribe(t *testing.T) { 30 | ssid := message.Ssid{1, 3238259379, 500706888, 1027807523} 31 | tests := []struct { 32 | contract int // The contract ID 33 | topic string // The topic 34 | extraPerm uint8 // Extra key permission 35 | disabled bool // Is the connection disabled? 36 | expectCount int // How many subscribers now? 37 | success bool // Success or failure? 38 | }{ 39 | { // Bad request 40 | success: false, 41 | expectCount: 10, 42 | topic: "", 43 | }, 44 | { // Unauthorized 45 | success: false, 46 | expectCount: 10, 47 | topic: "key/a/b/c/", 48 | }, 49 | { // Unauthorized, extend 50 | success: false, 51 | contract: 1, 52 | expectCount: 10, 53 | extraPerm: security.AllowExtend, 54 | topic: "key/a/b/c/", 55 | }, 56 | { // Happy Path, simple 57 | success: true, 58 | contract: 1, 59 | expectCount: 9, 60 | topic: "key/a/b/c/", 61 | }, 62 | { // Disabled 63 | success: true, 64 | contract: 1, 65 | disabled: true, 66 | expectCount: 10, 67 | topic: "key/a/b/c/", 68 | }, 69 | } 70 | 71 | for _, tc := range tests { 72 | trie := message.NewTrie() 73 | auth := &fake.Authorizer{ 74 | Contract: uint32(tc.contract), 75 | Success: tc.contract != 0, 76 | ExtraPerm: tc.extraPerm, 77 | } 78 | 79 | // Create new service 80 | s := New(auth, storage.NewNoop(), new(fake.Notifier), trie) 81 | 82 | // Register few subscribers 83 | for i := 0; i < 10; i++ { 84 | assert.True(t, s.Subscribe(&fake.Conn{ 85 | ConnID: i, 86 | }, &event.Subscription{ 87 | Peer: 2, 88 | Conn: security.ID(i), 89 | Ssid: ssid, 90 | Channel: nocopy.Bytes("a/b/c/"), 91 | })) 92 | } 93 | 94 | err := s.OnUnsubscribe(&fake.Conn{ 95 | ConnID: 5, 96 | Disabled: tc.disabled, 97 | }, []byte(tc.topic)) 98 | assert.Equal(t, tc.success, err == nil) 99 | assert.Equal(t, tc.expectCount, trie.Count()) 100 | } 101 | } 102 | --------------------------------------------------------------------------------