open sourcing agent

This commit is contained in:
dencoded 2020-02-24 23:40:55 -05:00
parent aeb77bdba5
commit 50972f5737
20 changed files with 3154 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
.idea
.indihub-agent
indihub.json
bin/

44
Makefile Normal file
View file

@ -0,0 +1,44 @@
build-macos64:
mkdir -p ./bin/indihub-agent-macos64
GOOS=darwin GOARCH=amd64 go build -v -o ./bin/indihub-agent-macos64/indihub-agent ./
build-linux64:
mkdir -p ./bin/indihub-agent-linux64
GOOS=linux GOARCH=amd64 go build -v -o ./bin/indihub-agent-linux64/indihub-agent ./
build-unix64:
mkdir -p ./bin/indihub-agent-unix64
GOOS=freebsd GOARCH=amd64 go build -v -o ./bin/indihub-agent-unix64/indihub-agent ./
build-win64:
mkdir -p ./bin/indihub-agent-win64
GOOS=windows GOARCH=amd64 go build -v -o ./bin/indihub-agent-win64/indihub-agent.exe ./
build-win32:
mkdir -p ./bin/indihub-agent-win32
GOOS=windows GOARCH=386 go build -v -o ./bin/indihub-agent-win32/indihub-agent.exe ./
build-raspberrypi:
mkdir -p ./bin/indihub-agent-raspberrypi
GOOS=linux GOARCH=arm GOARM=5 go build -v -o ./bin/indihub-agent-raspberrypi/indihub-agent ./
build-all: build-macos64 build-linux64 build-unix64 build-win64 build-win32 build-raspberrypi
release: build-all
zip -r ./bin/indihub-agent-macos64.zip ./bin/indihub-agent-macos64
openssl dgst -sha256 ./bin/indihub-agent-macos64.zip > ./bin/indihub-agent-macos64.sha256
tar czf ./bin/indihub-agent-linux64.tar.gz ./bin/indihub-agent-linux64
openssl dgst -sha256 ./bin/indihub-agent-linux64.tar.gz > ./bin/indihub-agent-linux64.sha256
tar czf ./bin/indihub-agent-unix64.tar.gz ./bin/indihub-agent-unix64
openssl dgst -sha256 ./bin/indihub-agent-unix64.tar.gz > ./bin/indihub-agent-unix64.sha256
zip -r ./bin/indihub-agent-win64.zip ./bin/indihub-agent-win64
openssl dgst -sha256 ./bin/indihub-agent-win64.zip > ./bin/indihub-agent-win64.sha256
zip -r ./bin/indihub-agent-win32.zip ./bin/indihub-agent-win32
openssl dgst -sha256 ./bin/indihub-agent-win32.zip > ./bin/indihub-agent-win32.sha256
tar czf ./bin/indihub-agent-raspberrypi.tar.gz ./bin/indihub-agent-raspberrypi
openssl dgst -sha256 ./bin/indihub-agent-raspberrypi.tar.gz > ./bin/indihub-agent-raspberrypi.sha256

68
README.md Normal file
View file

@ -0,0 +1,68 @@
# indihub-agent
The `indihub-agent` is a command line interface tool to connect your astro-photography equipment to [INDIHUB](https://indihub.space) network. The network can be used to share you equipment with others, use others' equipment remotely or just use your equipment without sharing but still contribute your images to INDIHUB-network.
NOTE: all astro-photos taken via INDIHUB-network (with auto-guiding or main imaging cameras) will be processed by INDIHUB cloud pipeline and used for scientific purposes.
## Prerequisites
0. You have a desire to contribute to Space exploration and sustainability projects.
1. You have motorized astro-photography equipment connected to your home network.
2. Your equipment is controlled by [IND-server](https://github.com/indilib/indi), manuals and docs can be found on [INDI-lib](http://indilib.org) Web-site.
3. INDI-server is controlled by [INDI Web Manager](https://github.com/knro/indiwebmanager).
4. You have ready to use INDI-profile created with INDI Web Manager.
5. Raspberry PI (or computer) where you run `indihub-agent` is connected to Internet so it can register your equipment on INDIHUB-network.
## Registration on INDIHUB-network as a host
Registration is very easy - you don't have to do anything.
The `indihub-agent` doesn't require any signup or token to join INDIHUB.
When you first run `indihub-agent` and it is connected to INDIHUB-network successfully - it receives token from network and saves in the same folder in the file `indihub.json`. Please keep this file there and don't loose it as it identifies you as a host on INDIHUB-network. Also, this file will be read and used automatically for all next runs of `indihub-agent`.
## indihub-agent modes
There are four modes available at the moment:
1. `share` - open remote access to your equipment via INDIHUB-network of telescopes, so you can provide remote imaging sessions to your guests.
2. `solo` - use you equipment without opening remote access but equipment is still connected to INDIHUB-network and all images taken are contributed for scientific purposes.
3. `broadcast` - broadcast you imaging session to observers watching it via INDI-clients, in this case without any equipment remote access and sharing (experimental).
4. `robotic` - open remote access to your equipment to be controlled by scheduler running in INDIHUB-cloud (experimental).
The mode is specified via `-mode` parameter, i.e. to run indihub-agent in a share-mode you will need run command:
```bash
./indihub-agent -indi-profile=my-profile -mode=solo
```
The only mandatory parameter is `-indi-profile` where you specify profile name created with [INDI Web Manager](https://github.com/knro/indiwebmanager). All other parameters have default valyes. I.e. `-mode` default value is `solo`.
To get usage of all parameters just run `indihub-agent -help`.
The latest `indihub-agent` release can be downloaded from [releases](https://github.com/indihub-space/agent/releases) or [indihub.space](https://indihub.space) Web-site.
## Building indihub-agent
You will need to install [Golang](https://golang.org/dl/).
There are `make` build-commands for different platforms available:
- `make build-macos64` - build for macOS (64 bit)
- `make build-linux64` - build for Linux (64 bit)
- `make build-unix64` - build for Unix (64 bit)
- `make build-win64` - build for Windows (64 bit)
- `make build-win32` - build for Windows (32 bit)
- `make build-raspberrypi` - build for Raspberry Pi (ARM5)
## Contributing
PRs and issues are highly appreciated. TODO: `indihub-agent` speaks to the INDIHUB-cloud so special dev-server will be added for development purposes soon.
## What is next
The INDIHUB-network is in its beta release at the moment. We board new host on the network, collect data to most importantly - feedback from our first hosts.
Also we work on partnerships. If you are interested please send us email at [info@indihub.space](mailto:info@indihub.space).
And last but not least - don't forget to signup to our mailing list on [indihub.space](https://indihub.space) so you will know all the news first!

189
broadcast/broadcast.go Normal file
View file

@ -0,0 +1,189 @@
package broadcast
import (
"bytes"
"io"
"log"
"net"
"sync/atomic"
"github.com/fatih/color"
"github.com/indihub-space/agent/lib"
"github.com/indihub-space/agent/proto/indihub"
)
var (
getPropertiesStart = []byte("<getProperties")
)
type INDIHubBroadcastTunnel interface {
Send(*indihub.Response) error
Recv() (*indihub.Request, error)
CloseSend() error
}
type BroadcastTcpProxy struct {
Name string
Addr string
listener net.Listener
Tunnel INDIHubBroadcastTunnel
connInMap map[uint32]net.Conn
connOutMap map[uint32]net.Conn
shouldExit int32
}
func New(name string, addr string, tunnel INDIHubBroadcastTunnel) *BroadcastTcpProxy {
return &BroadcastTcpProxy{
Name: name,
Addr: addr,
Tunnel: tunnel,
connInMap: map[uint32]net.Conn{},
connOutMap: map[uint32]net.Conn{},
}
}
func (p *BroadcastTcpProxy) Start(sessionID uint64, sessionToken string, addr string) {
log.Printf("Starting INDI-server for INDIHUB in broadcast mode on %s ...", addr)
var err error
p.listener, err = net.Listen("tcp", addr)
if err != nil {
log.Printf("Could not start INDI-server for INDIHUB in broadcast mode: %v\n", err)
return
}
log.Println("...OK")
// receive public address from tunnel
in, err := p.Tunnel.Recv()
if err == io.EOF {
// read done, server closed connection
log.Printf("Exiting. Got EOF from %s tunnel.\n", p.Name)
return
}
if err != nil {
log.Printf("Exiting. Failed to receive a request from %s tunnel: %v\n", p.Name, err)
return
}
c := color.New(color.FgCyan)
gc := color.New(color.FgGreen)
c.Println()
c.Println(" ************************************************************")
c.Println(" * INDIHUB broadcast address !! *")
c.Println(" ************************************************************")
c.Println(" ")
gc.Printf(" %s\n", string(in.Data))
c.Println(" ")
c.Println(" ************************************************************")
c.Println()
var connCnt uint32
for {
if atomic.LoadInt32(&p.shouldExit) == 1 {
break
}
// Wait for a connection from INDI-client
connIn, err := p.listener.Accept()
if err != nil {
break
}
// connection to real INDI-server
connOut, err := net.Dial("tcp", p.Addr)
if err != nil {
log.Printf("%s - could not connect to INDI-server: %v\n", p.Name, err)
connIn.Close()
continue
}
connCnt += 1
p.connInMap[connCnt] = connIn
p.connOutMap[connCnt] = connOut
// copy requests
go func(cNum uint32) {
buf := make([]byte, lib.INDIServerMaxRecvMsgSize, lib.INDIServerMaxRecvMsgSize)
for {
// read from client
n, err := connIn.Read(buf)
if err != nil {
log.Printf("%s - could not read from INDI-client: %v\n", p.Name, err)
connIn.Close()
connOut.Close()
break
}
// we want to let server know about getProperties and connection number
if bytes.HasPrefix(buf[:n], getPropertiesStart) {
// broadcast
resp := &indihub.Response{
Data: buf[:n],
Conn: cNum,
SessionID: sessionID,
SessionToken: sessionToken,
}
if err := p.Tunnel.Send(resp); err != nil {
log.Printf("Failed broadcast request to %s tunnel: %v", p.Name, err)
}
}
// send to server
if _, err := connOut.Write(buf[:n]); err != nil {
log.Printf("%s - could not write to INDI-server: %v\n", p.Name, err)
connIn.Close()
connOut.Close()
break
}
}
delete(p.connInMap, cNum)
delete(p.connOutMap, cNum)
}(connCnt)
// copy and broadcast responses
go func(cNum uint32, sessID uint64) {
buf := make([]byte, lib.INDIServerMaxSendMsgSize, lib.INDIServerMaxSendMsgSize)
for {
n, err := connOut.Read(buf)
if err != nil {
log.Printf("%s - could not read from INDI-server: %v\n", p.Name, err)
connIn.Close()
return
}
// send to client
if _, err := connIn.Write(buf[:n]); err != nil {
log.Printf("%s - could not write to INDI-client: %v\n", p.Name, err)
connOut.Close()
return
}
// broadcast
resp := &indihub.Response{
Data: buf[:n],
Conn: cNum,
SessionID: sessID,
SessionToken: sessionToken,
}
if err := p.Tunnel.Send(resp); err != nil {
log.Printf("Failed broadcast response to %s tunnel: %v", p.Name, err)
}
}
}(connCnt, sessionID)
}
// close connections to tunnel
}
func (p *BroadcastTcpProxy) Close() {
atomic.SwapInt32(&p.shouldExit, 1)
for _, c := range p.connInMap {
c.Close()
}
p.listener.Close()
for _, c := range p.connOutMap {
c.Close()
}
}

37
config/config.go Normal file
View file

@ -0,0 +1,37 @@
package config
import (
"encoding/json"
"io/ioutil"
)
type Config struct {
Token string `json:"token"`
}
func Read(fileName string) (*Config, error) {
jsonData, err := ioutil.ReadFile(fileName)
if err != nil {
return nil, err
}
config := &Config{}
if err := json.Unmarshal(jsonData, config); err != nil {
return nil, err
}
return config, nil
}
func Write(fileName string, config *Config) error {
jsonData, err := json.Marshal(config)
if err != nil {
return err
}
if err := ioutil.WriteFile(fileName, jsonData, 0600); err != nil {
return err
}
return nil
}

14
go.mod Normal file
View file

@ -0,0 +1,14 @@
module github.com/indihub-space/agent
go 1.13
require (
github.com/clbanning/mxj v1.8.4
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/fatih/color v1.9.0
github.com/golang/protobuf v1.3.3
github.com/gorilla/websocket v1.4.1
github.com/labstack/echo v3.3.10+incompatible
github.com/labstack/gommon v0.3.0
google.golang.org/grpc v1.27.1
)

81
go.sum Normal file
View file

@ -0,0 +1,81 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

30
hostutils/indifilter.go Normal file
View file

@ -0,0 +1,30 @@
package hostutils
// INDIFilterConfig contains config for incoming and outgoinf traffic rules
type INDIFilterConfig struct {
IncomingRules map[string]interface{} // TODO: design format for rules
OutgoingRules map[string]interface{} // TODO: design format for rules
}
// INDIFilter provides logic for incoming/outgoing traffic
type INDIFilter struct {
config *INDIFilterConfig
}
func NewINDIFilter(config *INDIFilterConfig) *INDIFilter {
return &INDIFilter{
config: config,
}
}
// FilterOutgoing filters out outgoing traffic
func (f *INDIFilter) FilterOutgoing(data [][]byte) [][]byte {
// TODO: apply f.OutgoingRules
return data
}
// FilterIncoming filters out outgoing traffic
func (f *INDIFilter) FilterIncoming(data [][]byte) [][]byte {
// TODO: apply f.IncomingRules
return data
}

9
lib/const.go Normal file
View file

@ -0,0 +1,9 @@
package lib
const (
GRPCMaxRecvMsgSize = 10 * 1024 * 1024
GRPCMaxSendMsgSize = 10 * 1024 * 1024
INDIServerMaxRecvMsgSize = 49152
INDIServerMaxSendMsgSize = 2048
)

28
lib/type.go Normal file
View file

@ -0,0 +1,28 @@
package lib
//{"id": 1, "name": "Simulators", "port": 7624, "autostart": 0, "autoconnect": 0}
type INDIProfile struct {
ID uint32 `json:"id"`
Name string `json:"name"`
Port uint32 `json:"port"`
AutoStart uint32 `json:"autostart"`
AutoConnect uint32 `json:"autoconnect"`
}
/*
[
{"binary": "indi_asi_ccd", "skeleton": null, "family": "CCDs", "label": "ZWO CCD", "version": "1.5", "role": "", "custom": false, "name": "ZWO CCD"},
{"binary": "indi_ieq_telescope", "skeleton": null, "family": "Telescopes", "label": "iOptron CEM25", "version": "1.8", "role": "", "custom": false, "name": "iEQ"},
{"binary": "indi_asi_focuser", "skeleton": null, "family": "Focusers", "label": "ASI EAF", "version": "1.5", "role": "", "custom": false, "name": "ASI EAF"}
]
*/
type INDIDriver struct {
Binary string `json:"binary"`
Skeleton interface{} `json:"skeleton"`
Family string `json:"family"`
Label string `json:"label"`
Version string `json:"version"`
Role string `json:"role"`
Custom bool `json:"custom"`
Name string `json:"name"`
}

136
lib/xml.go Normal file
View file

@ -0,0 +1,136 @@
package lib
import (
"bytes"
"log"
"github.com/clbanning/mxj"
)
var (
elementMessage = []byte("<message")
elementDelProperty = []byte("<delProperty")
elementGetProperties = []byte("<getProperties")
elementSingleTagEnd = []byte("/>")
)
// XmlFlattener reads XML from INDI-server by chunks and returns elements
type XmlFlattener struct {
buffer []byte
nextEndElement []byte
}
func init() {
mxj.PrependAttrWithHyphen(false)
mxj.SetAttrPrefix("attr_")
mxj.DecodeSimpleValuesAsMap(false)
}
// NewXMLReader returns XmlReader
func NewXmlFlattener() *XmlFlattener {
return &XmlFlattener{
buffer: make([]byte, 0, INDIServerMaxRecvMsgSize),
}
}
func (r *XmlFlattener) FeedChunk(chunk []byte) [][]byte {
if len(chunk) == 0 {
return nil
}
elements := make([][]byte, 0, 10)
r.buffer = append(r.buffer, chunk...)
for {
if len(r.nextEndElement) == 0 {
// look for start of new entity
if n := bytes.IndexByte(r.buffer, '<'); n > 0 {
r.buffer = r.buffer[n:]
}
if r.buffer[0] != '<' {
// something went wrong
return elements
}
// special case of message-element without closing tag
if bytes.HasPrefix(r.buffer, elementMessage) || bytes.HasPrefix(r.buffer, elementDelProperty) ||
bytes.HasPrefix(r.buffer, elementGetProperties) {
r.nextEndElement = elementSingleTagEnd
} else {
// get next end element value
if n := bytes.IndexByte(r.buffer, ' '); n < 1 {
// something went wrong
return elements
} else {
r.nextEndElement = []byte{'<', '/'}
if r.buffer[n-1] == '\n' {
n = n - 1
}
r.nextEndElement = append(r.nextEndElement, r.buffer[1:n]...)
r.nextEndElement = append(r.nextEndElement, '>')
}
}
}
if n := bytes.Index(r.buffer, r.nextEndElement); n == -1 {
// should read next chunk
return elements
} else {
// element closed, add it to return
end := n + len(r.nextEndElement) + 1
if end > len(r.buffer) {
end = len(r.buffer)
}
elements = append(elements, r.buffer[:end])
r.nextEndElement = r.nextEndElement[:0]
if len(r.buffer) == end {
r.buffer = r.buffer[:0]
break
}
r.buffer = r.buffer[end:]
}
}
return elements
}
func (r *XmlFlattener) ConvertChunkToJSON(chunk []byte) [][]byte {
elements := r.FeedChunk(chunk)
if len(elements) == 0 {
return [][]byte{}
}
jsonElements := make([][]byte, 0, len(elements))
for _, el := range elements {
mapVal, err := mxj.NewMapXml(el, true)
if err != nil {
log.Println("could not parse XML chunk:", err)
continue
}
jsonEl, err := mapVal.Json()
if err != nil {
log.Println("could not convert map to JSON:", err)
continue
}
jsonElements = append(jsonElements, jsonEl)
}
return jsonElements
}
func (r *XmlFlattener) ConvertJSONToXML(jsonData []byte) ([]byte, error) {
mapVal, err := mxj.NewMapJson(jsonData)
if err != nil {
return nil, err
}
xmlData, err := mapVal.Xml()
if err != nil {
return nil, err
}
return xmlData, nil
}

20
logutil/logutil.go Normal file
View file

@ -0,0 +1,20 @@
package logutil
import (
"log"
"os"
)
var IsDev = false
func init() {
IsDev = os.Getenv("INDIHUB_DEV") != ""
}
func LogError(format string, args ...interface{}) {
if !IsDev {
return
}
log.Printf(format, args...)
}

578
main.go Normal file
View file

@ -0,0 +1,578 @@
package main
import (
"context"
"crypto/tls"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"runtime"
"sync"
"time"
"github.com/fatih/color"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
_ "google.golang.org/grpc/encoding/gzip"
"github.com/indihub-space/agent/broadcast"
"github.com/indihub-space/agent/config"
"github.com/indihub-space/agent/hostutils"
"github.com/indihub-space/agent/lib"
"github.com/indihub-space/agent/logutil"
"github.com/indihub-space/agent/manager"
"github.com/indihub-space/agent/proto/indihub"
"github.com/indihub-space/agent/proxy"
"github.com/indihub-space/agent/solo"
"github.com/indihub-space/agent/version"
"github.com/indihub-space/agent/websockets"
)
const (
defaultWSPort uint64 = 2020
modeSolo = "solo"
modeBroadcast = "broadcast"
modeShare = "share"
modeRobotic = "robotic"
)
var (
flagINDIServerManagerAddr string
flagPHD2ServerAddr string
flagINDIProfile string
flagToken string
flagConfFile string
flagSoloINDIServerAddr string
flagBroadcastINDIServerAddr string
flagCompress bool
flagWSServer bool
flagWSIsTLS bool
flagWSPort uint64
flagWSOrigins string
flagMode string
indiServerAddr string
httpClientSM = http.Client{}
)
func init() {
flag.StringVar(
&flagINDIServerManagerAddr,
"indi-server-manager",
"raspberrypi.local:8624",
"INDI-server Manager address (host:port)",
)
flag.StringVar(
&flagMode,
"mode",
modeSolo,
`indihub-agent mode (deafult value is "solo"), there four modes:\n
solo - equipment sharing is not possible, you are connected to INDIHUB and contributing images
sharing - you are sharing equipment with another INDIHUB user (agent will output connection info)
broadcast - equipment sharing is not possible, you are broadcasting your experience to any number of INDIHUB users
robotic - equipment sharing is not possible, your equipment is controlled by INDIHUB AI (you can still watch what it is doing!)
`,
)
flag.BoolVar(
&flagCompress,
"compress",
true,
"Enable gzip-compression",
)
flag.StringVar(
&flagSoloINDIServerAddr,
"solo-indi-server",
"localhost:7624",
"agent INDI-server address (host:port) for solo-mode",
)
flag.StringVar(
&flagBroadcastINDIServerAddr,
"broadcast-indi-server",
"localhost:7624",
"agent INDI-server address (host:port) for broadcast-mode",
)
flag.StringVar(
&flagPHD2ServerAddr,
"phd2-server",
"",
"PHD2-server address (host:port)",
)
flag.StringVar(
&flagToken,
"token",
"",
"token - can be requested at https://indihub.space/token",
)
flag.StringVar(
&flagConfFile,
"conf",
"indihub.json",
"INDIHub Agent config file path",
)
flag.StringVar(
&flagINDIProfile,
"indi-profile",
"",
"Name of INDI-profile to share via indihub",
)
flag.BoolVar(
&flagWSServer,
"ws-server",
true,
"launch Websocket server to control equipment via Websocket API",
)
flag.BoolVar(
&flagWSIsTLS,
"ws-tls",
false,
"serve web-socket over TLS with self-signed certificate",
)
flag.Uint64Var(
&flagWSPort,
"ws-port",
defaultWSPort,
"port to start web socket-server on",
)
flag.StringVar(
&flagWSOrigins,
"ws-origins",
"",
"comma-separated list of origins allowed to connect to WS-server",
)
}
func main() {
flag.Parse()
if flagMode != modeSolo && flagMode != modeShare && flagMode != modeBroadcast && flagMode != modeRobotic {
log.Fatalf("Unknown mode '%s' provided\n", flagMode)
}
indiHubAddr := "relay.indihub.io:7668" // tls one
if logutil.IsDev {
indiHubAddr = "localhost:7667" // TODO: change this to optional DEV server
}
if flagINDIServerManagerAddr == "" {
log.Fatal("'indi-server-manager' parameter is missing, the 'host:port' format is expected")
}
indiHost, _, err := net.SplitHostPort(flagINDIServerManagerAddr)
if err != nil {
log.Fatal("Bad syntax for 'indi-server-manager' parameter, the 'host:port' format is expected")
}
if flagINDIProfile == "" {
log.Fatal("'indi-profile' parameter is required")
}
// read token from flag or from config file if exists
if flagToken == "" {
conf, err := config.Read(flagConfFile)
if err == nil {
flagToken = conf.Token
}
}
// connect to INDI-server Manager
log.Printf("Connection to local INDI-Server Manager on %s...\n", flagINDIServerManagerAddr)
managerClient := manager.NewClient(flagINDIServerManagerAddr)
running, currINDIProfile, err := managerClient.GetStatus()
if err != nil {
log.Fatal(err)
}
log.Println("...OK")
// start required profile if it is not active and running
if !running || currINDIProfile != flagINDIProfile {
log.Printf("Setting active INDI-profile to '%s'\n", flagINDIProfile)
if err := managerClient.StopServer(); err != nil {
log.Fatal(err)
}
if err := managerClient.StartProfile(flagINDIProfile); err != nil {
log.Fatal(err)
}
} else {
log.Printf("INDI-server is running with active INDI-profile '%s'\n", flagINDIProfile)
}
// get profile connect data
indiProfile, err := managerClient.GetProfile(flagINDIProfile)
if err != nil {
log.Fatalf("could not get INDI-profile from INDI-server manager: %s", err)
}
indiServerAddr = fmt.Sprintf("%s:%d", indiHost, indiProfile.Port)
// get profile drivers data
indiDrivers, err := managerClient.GetDrivers()
if err != nil {
log.Fatalf("could not get INDI-drivers info from INDI-server manager: %s", err)
}
log.Println("INDIDrivers:")
for _, d := range indiDrivers {
log.Printf("%+v", *d)
}
// test connect to local INDI-server
log.Printf("Test connection to local INDI-Server on %s...\n", indiServerAddr)
indiConn, err := net.Dial("tcp", indiServerAddr)
if err != nil {
log.Fatal(err)
}
indiConn.Close()
log.Println("...OK")
if flagPHD2ServerAddr != "" {
log.Printf("Test connection to local PHD2-Server on %s...\n", flagPHD2ServerAddr)
phd2Conn, err := net.Dial("tcp", flagPHD2ServerAddr)
if err != nil {
log.Fatal(err)
}
phd2Conn.Close()
log.Println("...OK")
}
// prepare indihub-host data
indiHubHost := &indihub.INDIHubHost{
Token: flagToken,
Profile: &indihub.INDIProfile{
Id: indiProfile.ID,
Name: indiProfile.Name,
Port: indiProfile.Port,
Autostart: indiProfile.AutoStart,
Autoconnect: indiProfile.AutoConnect,
},
Drivers: make([]*indihub.INDIDriver, len(indiDrivers)),
SoloMode: flagMode == modeSolo,
IsPHD2: flagPHD2ServerAddr != "",
IsRobotic: flagMode == modeRobotic,
IsBroadcast: flagMode == modeBroadcast,
AgentVersion: version.AgentVersion,
Os: runtime.GOOS,
Arch: runtime.GOARCH,
}
for i, driver := range indiDrivers {
indiHubHost.Drivers[i] = &indihub.INDIDriver{
Binary: driver.Binary,
Family: driver.Family,
Label: driver.Label,
Version: driver.Version,
Role: driver.Role,
Custom: driver.Custom,
Name: driver.Name,
}
}
log.Println("Connecting to the indihub.space cloud...")
opts := []grpc.DialOption{
grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(lib.GRPCMaxSendMsgSize)),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(lib.GRPCMaxRecvMsgSize)),
}
if flagCompress {
opts = append(opts, grpc.WithDefaultCallOptions(grpc.UseCompressor("gzip")))
}
if logutil.IsDev {
opts = append(opts, grpc.WithInsecure())
} else {
tlsConfig := &tls.Config{}
opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
}
conn, err := grpc.Dial(
indiHubAddr,
opts...,
)
if err != nil {
log.Fatal(err)
}
log.Println("...OK")
indiHubClient := indihub.NewINDIHubClient(conn)
// register host
regInfo, err := indiHubClient.RegisterHost(context.Background(), indiHubHost)
if err != nil {
log.Fatal(err)
}
log.Println("Current agent version:", version.AgentVersion)
log.Println("Latest agent version:", regInfo.AgentVersion)
if version.AgentVersion < regInfo.AgentVersion {
yc := color.New(color.FgYellow)
yc.Println()
yc.Println(" ************************************************************")
yc.Println(" * WARNING: you version of agent is outdated! *")
yc.Println(" * *")
yc.Println(" * Please download the latest version from: *")
yc.Println(" * https://indihub.space/downloads *")
yc.Println(" * *")
yc.Println(" ************************************************************")
yc.Println(" ")
}
log.Printf("Access token: %s\n", regInfo.Token)
log.Printf("Host session token: %s\n", regInfo.SessionIDPublic)
// create config for new host if flag wasn't provided
if flagToken == "" {
conf := &config.Config{
Token: regInfo.Token,
}
if err := config.Write(flagConfFile, conf); err != nil {
log.Printf("Could not create config file %s: %s", flagConfFile, err)
}
}
// start WS-server
wsServer := websockets.NewWsServer(
regInfo.Token,
indiServerAddr,
flagPHD2ServerAddr,
flagWSPort,
flagWSIsTLS,
flagWSOrigins,
)
go wsServer.Start()
// start session
switch flagMode {
case modeSolo:
// solo mode - equipment sharing is not available but host still sends all images to INDIHUB
log.Println("'solo' parameter was provided. Your session is in solo-mode: equipment sharing is not available")
log.Println("Starting INDIHUB agent in solo mode!")
soloClient, err := indiHubClient.SoloMode(context.Background())
if err != nil {
log.Fatalf("Could not start agent in solo mode: %v", err)
}
soloProxy := solo.New(
"INDI-Server Solo-mode",
indiServerAddr,
soloClient,
)
go func() {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt, os.Kill)
<-sigint
// stop WS-server
wsServer.Stop()
log.Println("Closing INDIHUB solo-session")
// close connections to local INDI-server and to INDI client
soloProxy.Close()
time.Sleep(1 * time.Second)
// close grpc client connection
conn.Close()
}()
// start solo mode INDI-server tcp-proxy
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
soloProxy.Start(flagSoloINDIServerAddr, regInfo.SessionID, regInfo.SessionIDPublic)
}()
wg.Wait()
case modeShare, modeRobotic:
// main equipment sharing mode
if flagMode == modeRobotic {
log.Println("'robotic' parameter was provided. Your session is in robotic-mode: equipment sharing is not available")
}
// open INDI server tunnel
log.Println("Starting INDI-Server in the cloud...")
indiServTunnel, err := indiHubClient.INDIServer(
context.Background(),
)
if err != nil {
log.Fatal(err)
}
log.Println("...OK")
indiFilterConf := &hostutils.INDIFilterConfig{} // TODO: add reading config
indiFilter := hostutils.NewINDIFilter(indiFilterConf)
indiServerProxy := proxy.New("INDI-Server", indiServerAddr, indiServTunnel, indiFilter)
// start PHD2 server proxy if specified
var phd2ServerProxy *proxy.TcpProxy
if flagPHD2ServerAddr != "" {
// open PHD2 server tunnel
log.Println("Starting PHD2-Server in the cloud...")
phd2ServTunnel, err := indiHubClient.PHD2Server(
context.Background(),
)
if err != nil {
log.Fatal(err)
}
log.Println("...OK")
phd2ServerProxy = proxy.New("PHD2-Server", flagPHD2ServerAddr, phd2ServTunnel, nil)
}
go func() {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt, os.Kill)
<-sigint
// stop WS-server
wsServer.Stop()
// close connections to tunnels
indiServTunnel.CloseSend()
if phd2ServerProxy != nil {
phd2ServerProxy.Tunnel.CloseSend()
}
// close grpc client connection
conn.Close()
// close connections to local INDI-server and PHD2-Server
indiServerProxy.Close()
if phd2ServerProxy != nil {
phd2ServerProxy.Close()
}
}()
serverAddrChan := make(chan proxy.PublicServerAddr, 3)
wg := sync.WaitGroup{}
// INDI Server Proxy start
waitNum := 1
wg.Add(1)
go func() {
defer wg.Done()
indiServerProxy.Start(serverAddrChan, regInfo.SessionID, regInfo.SessionIDPublic)
}()
if flagPHD2ServerAddr != "" {
waitNum = 2
wg.Add(1)
go func() {
defer wg.Done()
phd2ServerProxy.Start(serverAddrChan, regInfo.SessionID, regInfo.SessionIDPublic)
}()
}
addrData := []proxy.PublicServerAddr{}
for i := 0; i < waitNum; i++ {
sAddr := <-serverAddrChan
addrData = append(addrData, sAddr)
}
c := color.New(color.FgCyan)
gc := color.New(color.FgGreen)
yc := color.New(color.FgYellow)
rc := color.New(color.FgMagenta)
if flagMode != modeRobotic {
c.Println()
c.Println(" ************************************************************")
c.Println(" * INDIHUB public address list!! *")
c.Println(" ************************************************************")
c.Println(" ")
for _, sAddr := range addrData {
gc.Printf(" %s: %s\n", sAddr.Name, sAddr.Addr)
}
c.Println(" ")
c.Println(" ************************************************************")
c.Println()
c.Println(" Please provide your guest with this information:")
c.Println()
c.Println(" 1. Public address list from the above")
c.Println(" 2. Focal length and aperture of your main telescope")
c.Println(" 3. Focal length and aperture of your guiding telescope")
c.Println(" 4. Type of guiding you use: PHD2 or guiding via camera")
c.Println(" 5. Names of your imaging camera and guiding cameras")
c.Println()
yc.Println(" NOTE: These public addresses will be available ONLY until")
yc.Println(" agent is running! (Ctrl+C will stop the session)")
c.Println()
} else {
c.Println()
c.Println(" ************************************************************")
c.Println(" * INDIHUB robotic-session started!! *")
c.Println(" ************************************************************")
c.Println(" ")
}
wg.Wait()
c.Println()
c.Println(" ************************************************************")
c.Println(" * INDIHUB session finished!! *")
c.Println(" ************************************************************")
c.Println(" ")
if flagMode != modeRobotic {
for _, sAddr := range addrData {
rc.Printf(" %s: %s - CLOSED!!\n", sAddr.Name, sAddr.Addr)
}
} else {
c.Println(" * INDIHUB robotic-session finished. *")
c.Println(" * Thank you for your contribution! *")
}
c.Println(" ")
c.Println(" ************************************************************")
case modeBroadcast:
// broadcast - broadcasting all replies from INDI-server to INDIHUB, equipment sharing is not available
log.Println("Starting INDIHUB agent in broadcast mode!")
broadcastClient, err := indiHubClient.BroadcastINDIServer(context.Background())
if err != nil {
log.Fatalf("Could not start agent in broadcast mode: %v", err)
}
broadcastProxy := broadcast.New(
"INDI-Server Solo-mode",
indiServerAddr,
broadcastClient,
)
go func() {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt, os.Kill)
<-sigint
// stop WS-server
wsServer.Stop()
log.Println("Closing INDIHUB solo-session")
// close connections to local INDI-server and to INDI client
broadcastProxy.Close()
time.Sleep(1 * time.Second)
// close grpc client connection
conn.Close()
}()
// start broadcast mode INDI-server tcp-proxy
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
broadcastProxy.Start(regInfo.SessionID, regInfo.SessionIDPublic, flagBroadcastINDIServerAddr)
}()
wg.Wait()
}
}

124
manager/client.go Normal file
View file

@ -0,0 +1,124 @@
package manager
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/indihub-space/agent/lib"
)
const (
False = "False"
True = "True"
)
type client struct {
httpClient http.Client
addr string
}
func NewClient(managerServerAddr string) *client {
return &client{
httpClient: http.Client{},
addr: managerServerAddr,
}
}
func (c *client) GetStatus() (bool, string, error) {
resp, err := c.httpClient.Get(fmt.Sprintf("http://%s/api/server/status", c.addr))
if err != nil {
return false, "", err
}
defer resp.Body.Close()
respJson, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, "", err
}
respData := []map[string]string{}
if err := json.Unmarshal(respJson, &respData); err != nil {
return false, "", err
}
if len(respData) != 1 {
return false, "", fmt.Errorf("wrong slice length in INDI-server manager reply %v", respData)
}
return respData[0]["status"] == True, respData[0]["active_profile"], nil
}
func (c *client) StopServer() error {
resp, err := c.httpClient.Post(fmt.Sprintf("http://%s/api/server/stop", c.addr),
"application/json", nil)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("could not stop INDI-server, response code %d", resp.StatusCode)
}
return nil
}
func (c *client) GetProfile(profile string) (*lib.INDIProfile, error) {
resp, err := c.httpClient.Get(fmt.Sprintf("http://%s/api/profiles/%s", c.addr, profile))
if err != nil {
return nil, err
}
defer resp.Body.Close()
respJson, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
indiProfile := &lib.INDIProfile{}
if err := json.Unmarshal(respJson, indiProfile); err != nil {
return nil, err
}
return indiProfile, nil
}
func (c *client) StartProfile(profile string) error {
resp, err := c.httpClient.Post(fmt.Sprintf("http://%s/api/server/start/%s", c.addr, profile),
"application/json", nil)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("could not start INDI-server with profile %s, response code %d",
profile,
resp.StatusCode,
)
}
return nil
}
func (c *client) GetDrivers() ([]*lib.INDIDriver, error) {
resp, err := c.httpClient.Get(fmt.Sprintf("http://%s/api/server/drivers", c.addr))
if err != nil {
return nil, err
}
defer resp.Body.Close()
respJson, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
drivers := []*lib.INDIDriver{}
if err := json.Unmarshal(respJson, &drivers); err != nil {
return nil, err
}
return drivers, nil
}

999
proto/indihub/indihub.pb.go Normal file
View file

@ -0,0 +1,999 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: indihub.proto
package indihub
import (
context "context"
fmt "fmt"
proto "github.com/golang/protobuf/proto"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type Request struct {
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
Conn uint32 `protobuf:"varint,2,opt,name=conn,proto3" json:"conn,omitempty"`
Closed bool `protobuf:"varint,3,opt,name=closed,proto3" json:"closed,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Request) Reset() { *m = Request{} }
func (m *Request) String() string { return proto.CompactTextString(m) }
func (*Request) ProtoMessage() {}
func (*Request) Descriptor() ([]byte, []int) {
return fileDescriptor_84cfc05744c754a5, []int{0}
}
func (m *Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Request.Unmarshal(m, b)
}
func (m *Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Request.Marshal(b, m, deterministic)
}
func (m *Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_Request.Merge(m, src)
}
func (m *Request) XXX_Size() int {
return xxx_messageInfo_Request.Size(m)
}
func (m *Request) XXX_DiscardUnknown() {
xxx_messageInfo_Request.DiscardUnknown(m)
}
var xxx_messageInfo_Request proto.InternalMessageInfo
func (m *Request) GetData() []byte {
if m != nil {
return m.Data
}
return nil
}
func (m *Request) GetConn() uint32 {
if m != nil {
return m.Conn
}
return 0
}
func (m *Request) GetClosed() bool {
if m != nil {
return m.Closed
}
return false
}
type Response struct {
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
Conn uint32 `protobuf:"varint,2,opt,name=conn,proto3" json:"conn,omitempty"`
SessionID uint64 `protobuf:"varint,3,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
SessionToken string `protobuf:"bytes,4,opt,name=sessionToken,proto3" json:"sessionToken,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) {
return fileDescriptor_84cfc05744c754a5, []int{1}
}
func (m *Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Response.Unmarshal(m, b)
}
func (m *Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Response.Marshal(b, m, deterministic)
}
func (m *Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_Response.Merge(m, src)
}
func (m *Response) XXX_Size() int {
return xxx_messageInfo_Response.Size(m)
}
func (m *Response) XXX_DiscardUnknown() {
xxx_messageInfo_Response.DiscardUnknown(m)
}
var xxx_messageInfo_Response proto.InternalMessageInfo
func (m *Response) GetData() []byte {
if m != nil {
return m.Data
}
return nil
}
func (m *Response) GetConn() uint32 {
if m != nil {
return m.Conn
}
return 0
}
func (m *Response) GetSessionID() uint64 {
if m != nil {
return m.SessionID
}
return 0
}
func (m *Response) GetSessionToken() string {
if m != nil {
return m.SessionToken
}
return ""
}
type INDIProfile struct {
Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Port uint32 `protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"`
Autostart uint32 `protobuf:"varint,4,opt,name=autostart,proto3" json:"autostart,omitempty"`
Autoconnect uint32 `protobuf:"varint,5,opt,name=autoconnect,proto3" json:"autoconnect,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *INDIProfile) Reset() { *m = INDIProfile{} }
func (m *INDIProfile) String() string { return proto.CompactTextString(m) }
func (*INDIProfile) ProtoMessage() {}
func (*INDIProfile) Descriptor() ([]byte, []int) {
return fileDescriptor_84cfc05744c754a5, []int{2}
}
func (m *INDIProfile) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_INDIProfile.Unmarshal(m, b)
}
func (m *INDIProfile) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_INDIProfile.Marshal(b, m, deterministic)
}
func (m *INDIProfile) XXX_Merge(src proto.Message) {
xxx_messageInfo_INDIProfile.Merge(m, src)
}
func (m *INDIProfile) XXX_Size() int {
return xxx_messageInfo_INDIProfile.Size(m)
}
func (m *INDIProfile) XXX_DiscardUnknown() {
xxx_messageInfo_INDIProfile.DiscardUnknown(m)
}
var xxx_messageInfo_INDIProfile proto.InternalMessageInfo
func (m *INDIProfile) GetId() uint32 {
if m != nil {
return m.Id
}
return 0
}
func (m *INDIProfile) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *INDIProfile) GetPort() uint32 {
if m != nil {
return m.Port
}
return 0
}
func (m *INDIProfile) GetAutostart() uint32 {
if m != nil {
return m.Autostart
}
return 0
}
func (m *INDIProfile) GetAutoconnect() uint32 {
if m != nil {
return m.Autoconnect
}
return 0
}
type INDIDriver struct {
Binary string `protobuf:"bytes,1,opt,name=binary,proto3" json:"binary,omitempty"`
Family string `protobuf:"bytes,2,opt,name=family,proto3" json:"family,omitempty"`
Label string `protobuf:"bytes,3,opt,name=label,proto3" json:"label,omitempty"`
Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"`
Role string `protobuf:"bytes,5,opt,name=role,proto3" json:"role,omitempty"`
Custom bool `protobuf:"varint,6,opt,name=custom,proto3" json:"custom,omitempty"`
Name string `protobuf:"bytes,7,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *INDIDriver) Reset() { *m = INDIDriver{} }
func (m *INDIDriver) String() string { return proto.CompactTextString(m) }
func (*INDIDriver) ProtoMessage() {}
func (*INDIDriver) Descriptor() ([]byte, []int) {
return fileDescriptor_84cfc05744c754a5, []int{3}
}
func (m *INDIDriver) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_INDIDriver.Unmarshal(m, b)
}
func (m *INDIDriver) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_INDIDriver.Marshal(b, m, deterministic)
}
func (m *INDIDriver) XXX_Merge(src proto.Message) {
xxx_messageInfo_INDIDriver.Merge(m, src)
}
func (m *INDIDriver) XXX_Size() int {
return xxx_messageInfo_INDIDriver.Size(m)
}
func (m *INDIDriver) XXX_DiscardUnknown() {
xxx_messageInfo_INDIDriver.DiscardUnknown(m)
}
var xxx_messageInfo_INDIDriver proto.InternalMessageInfo
func (m *INDIDriver) GetBinary() string {
if m != nil {
return m.Binary
}
return ""
}
func (m *INDIDriver) GetFamily() string {
if m != nil {
return m.Family
}
return ""
}
func (m *INDIDriver) GetLabel() string {
if m != nil {
return m.Label
}
return ""
}
func (m *INDIDriver) GetVersion() string {
if m != nil {
return m.Version
}
return ""
}
func (m *INDIDriver) GetRole() string {
if m != nil {
return m.Role
}
return ""
}
func (m *INDIDriver) GetCustom() bool {
if m != nil {
return m.Custom
}
return false
}
func (m *INDIDriver) GetName() string {
if m != nil {
return m.Name
}
return ""
}
type INDIHubHost struct {
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
Profile *INDIProfile `protobuf:"bytes,2,opt,name=profile,proto3" json:"profile,omitempty"`
Drivers []*INDIDriver `protobuf:"bytes,3,rep,name=drivers,proto3" json:"drivers,omitempty"`
SoloMode bool `protobuf:"varint,4,opt,name=soloMode,proto3" json:"soloMode,omitempty"`
IsPHD2 bool `protobuf:"varint,5,opt,name=isPHD2,proto3" json:"isPHD2,omitempty"`
IsRobotic bool `protobuf:"varint,6,opt,name=isRobotic,proto3" json:"isRobotic,omitempty"`
AgentVersion string `protobuf:"bytes,7,opt,name=agentVersion,proto3" json:"agentVersion,omitempty"`
Os string `protobuf:"bytes,8,opt,name=os,proto3" json:"os,omitempty"`
Arch string `protobuf:"bytes,9,opt,name=arch,proto3" json:"arch,omitempty"`
IsBroadcast bool `protobuf:"varint,10,opt,name=isBroadcast,proto3" json:"isBroadcast,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *INDIHubHost) Reset() { *m = INDIHubHost{} }
func (m *INDIHubHost) String() string { return proto.CompactTextString(m) }
func (*INDIHubHost) ProtoMessage() {}
func (*INDIHubHost) Descriptor() ([]byte, []int) {
return fileDescriptor_84cfc05744c754a5, []int{4}
}
func (m *INDIHubHost) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_INDIHubHost.Unmarshal(m, b)
}
func (m *INDIHubHost) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_INDIHubHost.Marshal(b, m, deterministic)
}
func (m *INDIHubHost) XXX_Merge(src proto.Message) {
xxx_messageInfo_INDIHubHost.Merge(m, src)
}
func (m *INDIHubHost) XXX_Size() int {
return xxx_messageInfo_INDIHubHost.Size(m)
}
func (m *INDIHubHost) XXX_DiscardUnknown() {
xxx_messageInfo_INDIHubHost.DiscardUnknown(m)
}
var xxx_messageInfo_INDIHubHost proto.InternalMessageInfo
func (m *INDIHubHost) GetToken() string {
if m != nil {
return m.Token
}
return ""
}
func (m *INDIHubHost) GetProfile() *INDIProfile {
if m != nil {
return m.Profile
}
return nil
}
func (m *INDIHubHost) GetDrivers() []*INDIDriver {
if m != nil {
return m.Drivers
}
return nil
}
func (m *INDIHubHost) GetSoloMode() bool {
if m != nil {
return m.SoloMode
}
return false
}
func (m *INDIHubHost) GetIsPHD2() bool {
if m != nil {
return m.IsPHD2
}
return false
}
func (m *INDIHubHost) GetIsRobotic() bool {
if m != nil {
return m.IsRobotic
}
return false
}
func (m *INDIHubHost) GetAgentVersion() string {
if m != nil {
return m.AgentVersion
}
return ""
}
func (m *INDIHubHost) GetOs() string {
if m != nil {
return m.Os
}
return ""
}
func (m *INDIHubHost) GetArch() string {
if m != nil {
return m.Arch
}
return ""
}
func (m *INDIHubHost) GetIsBroadcast() bool {
if m != nil {
return m.IsBroadcast
}
return false
}
type RegisterInfo struct {
Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"`
SessionID uint64 `protobuf:"varint,2,opt,name=sessionID,proto3" json:"sessionID,omitempty"`
SessionIDPublic string `protobuf:"bytes,3,opt,name=sessionIDPublic,proto3" json:"sessionIDPublic,omitempty"`
AgentVersion string `protobuf:"bytes,4,opt,name=agentVersion,proto3" json:"agentVersion,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *RegisterInfo) Reset() { *m = RegisterInfo{} }
func (m *RegisterInfo) String() string { return proto.CompactTextString(m) }
func (*RegisterInfo) ProtoMessage() {}
func (*RegisterInfo) Descriptor() ([]byte, []int) {
return fileDescriptor_84cfc05744c754a5, []int{5}
}
func (m *RegisterInfo) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_RegisterInfo.Unmarshal(m, b)
}
func (m *RegisterInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_RegisterInfo.Marshal(b, m, deterministic)
}
func (m *RegisterInfo) XXX_Merge(src proto.Message) {
xxx_messageInfo_RegisterInfo.Merge(m, src)
}
func (m *RegisterInfo) XXX_Size() int {
return xxx_messageInfo_RegisterInfo.Size(m)
}
func (m *RegisterInfo) XXX_DiscardUnknown() {
xxx_messageInfo_RegisterInfo.DiscardUnknown(m)
}
var xxx_messageInfo_RegisterInfo proto.InternalMessageInfo
func (m *RegisterInfo) GetToken() string {
if m != nil {
return m.Token
}
return ""
}
func (m *RegisterInfo) GetSessionID() uint64 {
if m != nil {
return m.SessionID
}
return 0
}
func (m *RegisterInfo) GetSessionIDPublic() string {
if m != nil {
return m.SessionIDPublic
}
return ""
}
func (m *RegisterInfo) GetAgentVersion() string {
if m != nil {
return m.AgentVersion
}
return ""
}
type SoloSummary struct {
ImagesNum uint64 `protobuf:"varint,1,opt,name=imagesNum,proto3" json:"imagesNum,omitempty"`
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *SoloSummary) Reset() { *m = SoloSummary{} }
func (m *SoloSummary) String() string { return proto.CompactTextString(m) }
func (*SoloSummary) ProtoMessage() {}
func (*SoloSummary) Descriptor() ([]byte, []int) {
return fileDescriptor_84cfc05744c754a5, []int{6}
}
func (m *SoloSummary) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SoloSummary.Unmarshal(m, b)
}
func (m *SoloSummary) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SoloSummary.Marshal(b, m, deterministic)
}
func (m *SoloSummary) XXX_Merge(src proto.Message) {
xxx_messageInfo_SoloSummary.Merge(m, src)
}
func (m *SoloSummary) XXX_Size() int {
return xxx_messageInfo_SoloSummary.Size(m)
}
func (m *SoloSummary) XXX_DiscardUnknown() {
xxx_messageInfo_SoloSummary.DiscardUnknown(m)
}
var xxx_messageInfo_SoloSummary proto.InternalMessageInfo
func (m *SoloSummary) GetImagesNum() uint64 {
if m != nil {
return m.ImagesNum
}
return 0
}
func (m *SoloSummary) GetData() []byte {
if m != nil {
return m.Data
}
return nil
}
func init() {
proto.RegisterType((*Request)(nil), "Request")
proto.RegisterType((*Response)(nil), "Response")
proto.RegisterType((*INDIProfile)(nil), "INDIProfile")
proto.RegisterType((*INDIDriver)(nil), "INDIDriver")
proto.RegisterType((*INDIHubHost)(nil), "INDIHubHost")
proto.RegisterType((*RegisterInfo)(nil), "RegisterInfo")
proto.RegisterType((*SoloSummary)(nil), "SoloSummary")
}
func init() { proto.RegisterFile("indihub.proto", fileDescriptor_84cfc05744c754a5) }
var fileDescriptor_84cfc05744c754a5 = []byte{
// 606 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x51, 0x6b, 0xdb, 0x3c,
0x14, 0xad, 0xdd, 0xb4, 0xb1, 0xe5, 0xf8, 0xfb, 0x40, 0x1b, 0xc3, 0x94, 0x3d, 0x18, 0xc3, 0x36,
0xbf, 0xcc, 0x94, 0xec, 0x07, 0x0c, 0x46, 0x1e, 0x9a, 0x87, 0x95, 0xa2, 0x8c, 0xbd, 0xcb, 0xb6,
0xda, 0x8a, 0xd9, 0x56, 0x26, 0xc9, 0x85, 0xfe, 0x80, 0xbd, 0xef, 0x6d, 0x7f, 0x62, 0xec, 0x37,
0x8e, 0x7b, 0xad, 0xc4, 0x4e, 0xcb, 0x20, 0x6f, 0xf7, 0x1c, 0x29, 0xb9, 0xe7, 0x9c, 0x7b, 0x65,
0x12, 0xcb, 0xae, 0x96, 0xf7, 0x7d, 0x59, 0x6c, 0xb5, 0xb2, 0x2a, 0x5b, 0x93, 0x39, 0x13, 0xdf,
0x7b, 0x61, 0x2c, 0xa5, 0x64, 0x56, 0x73, 0xcb, 0x13, 0x2f, 0xf5, 0xf2, 0x05, 0xc3, 0x1a, 0xb8,
0x4a, 0x75, 0x5d, 0xe2, 0xa7, 0x5e, 0x1e, 0x33, 0xac, 0xe9, 0x2b, 0x72, 0x5e, 0x35, 0xca, 0x88,
0x3a, 0x39, 0x4d, 0xbd, 0x3c, 0x60, 0x0e, 0x65, 0x96, 0x04, 0x4c, 0x98, 0xad, 0xea, 0x8c, 0x38,
0xfa, 0xbf, 0x5e, 0x93, 0xd0, 0x08, 0x63, 0xa4, 0xea, 0xd6, 0x2b, 0xfc, 0xbb, 0x19, 0x1b, 0x09,
0x9a, 0x91, 0x85, 0x03, 0x5f, 0xd4, 0x37, 0xd1, 0x25, 0xb3, 0xd4, 0xcb, 0x43, 0x76, 0xc0, 0x65,
0x3f, 0x3c, 0x12, 0xad, 0xaf, 0x57, 0xeb, 0x1b, 0xad, 0x6e, 0x65, 0x23, 0xe8, 0x7f, 0xc4, 0x97,
0x35, 0xf6, 0x8d, 0x99, 0x2f, 0x6b, 0xe8, 0xda, 0xf1, 0x56, 0x60, 0xd7, 0x90, 0x61, 0x0d, 0xdc,
0x56, 0x69, 0x8b, 0x0d, 0x63, 0x86, 0x35, 0x28, 0xe1, 0xbd, 0x55, 0xc6, 0x72, 0x6d, 0xb1, 0x51,
0xcc, 0x46, 0x82, 0xa6, 0x24, 0x02, 0x00, 0x9a, 0x45, 0x65, 0x93, 0x33, 0x3c, 0x9f, 0x52, 0xd9,
0x6f, 0x8f, 0x10, 0xd0, 0xb1, 0xd2, 0xf2, 0x41, 0x68, 0x08, 0xa9, 0x94, 0x1d, 0xd7, 0x8f, 0x28,
0x25, 0x64, 0x0e, 0x01, 0x7f, 0xcb, 0x5b, 0xd9, 0x3c, 0x3a, 0x41, 0x0e, 0xd1, 0x97, 0xe4, 0xac,
0xe1, 0xa5, 0x68, 0x50, 0x53, 0xc8, 0x06, 0x40, 0x13, 0x32, 0x7f, 0x10, 0x1a, 0xcc, 0x3a, 0xef,
0x3b, 0x08, 0x16, 0xb4, 0x6a, 0x04, 0x2a, 0x09, 0x19, 0xd6, 0x38, 0x98, 0xde, 0x58, 0xd5, 0x26,
0xe7, 0x6e, 0x30, 0x88, 0xf6, 0x11, 0xcc, 0xc7, 0x08, 0xb2, 0x3f, 0xfe, 0x10, 0xdb, 0x55, 0x5f,
0x5e, 0x29, 0x63, 0xa1, 0xbf, 0xc5, 0x8c, 0x07, 0xb9, 0x03, 0xa0, 0x6f, 0xc9, 0x7c, 0x3b, 0xe4,
0x8a, 0x72, 0xa3, 0xe5, 0xa2, 0x98, 0x64, 0xcd, 0x76, 0x87, 0xf4, 0x0d, 0x99, 0xd7, 0xe8, 0xdb,
0x24, 0xa7, 0xe9, 0x69, 0x1e, 0x2d, 0xa3, 0x62, 0xcc, 0x82, 0xed, 0xce, 0xe8, 0x05, 0x09, 0x8c,
0x6a, 0xd4, 0x67, 0x55, 0x0b, 0xf4, 0x13, 0xb0, 0x3d, 0x06, 0xf1, 0xd2, 0xdc, 0x5c, 0xad, 0x96,
0x68, 0x29, 0x60, 0x0e, 0xc1, 0x5c, 0xa4, 0x61, 0xaa, 0x54, 0x56, 0x56, 0xce, 0xd7, 0x48, 0xc0,
0x86, 0xf0, 0x3b, 0xd1, 0xd9, 0xaf, 0x2e, 0xa5, 0xc1, 0xe2, 0x01, 0x07, 0x1b, 0xa1, 0x4c, 0x12,
0xe0, 0x89, 0xaf, 0x0c, 0xc4, 0xc1, 0x75, 0x75, 0x9f, 0x84, 0x43, 0x1c, 0x50, 0xc3, 0x7c, 0xa5,
0xf9, 0xa4, 0x15, 0xaf, 0x2b, 0x6e, 0x6c, 0x42, 0xb0, 0xcf, 0x94, 0xca, 0x7e, 0x7a, 0x64, 0xc1,
0xc4, 0x9d, 0x34, 0x56, 0xe8, 0x75, 0x77, 0xab, 0xfe, 0x91, 0xd8, 0xc1, 0x42, 0xfb, 0x4f, 0x17,
0x3a, 0x27, 0xff, 0xef, 0xc1, 0x4d, 0x5f, 0x36, 0xb2, 0x72, 0xf3, 0x7e, 0x4a, 0x3f, 0x33, 0x36,
0x7b, 0x6e, 0x2c, 0xfb, 0x48, 0xa2, 0x8d, 0x6a, 0xd4, 0xa6, 0x6f, 0x5b, 0x58, 0x2d, 0x48, 0xaa,
0xe5, 0x77, 0xc2, 0x5c, 0xf7, 0x2d, 0x8a, 0x9a, 0xb1, 0x91, 0xd8, 0xbf, 0x48, 0x7f, 0x7c, 0x91,
0xcb, 0x5f, 0x3e, 0x99, 0xbb, 0x25, 0xa0, 0xef, 0x47, 0x7b, 0xb8, 0x10, 0xc3, 0xa4, 0xdd, 0x7a,
0x5c, 0xc4, 0xc5, 0xd4, 0x7b, 0x76, 0x42, 0xdf, 0x0d, 0xdb, 0xbe, 0x11, 0x1a, 0xb6, 0x3d, 0x2c,
0x76, 0x2f, 0xff, 0x22, 0x28, 0xdc, 0xf7, 0x24, 0x3b, 0xc9, 0xbd, 0x4b, 0x0f, 0x2e, 0xc2, 0x1c,
0x8f, 0xb9, 0x18, 0x6c, 0x76, 0xcb, 0x30, 0xb9, 0xb6, 0x28, 0x26, 0x1e, 0xe1, 0x2a, 0xbd, 0x24,
0x2f, 0xf6, 0x63, 0x39, 0x4e, 0xc3, 0xf4, 0x17, 0x47, 0x89, 0x29, 0xcf, 0xf1, 0xeb, 0xf8, 0xe1,
0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xfb, 0x67, 0x14, 0x2e, 0x05, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// INDIHubClient is the client API for INDIHub service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type INDIHubClient interface {
RegisterHost(ctx context.Context, in *INDIHubHost, opts ...grpc.CallOption) (*RegisterInfo, error)
INDIServer(ctx context.Context, opts ...grpc.CallOption) (INDIHub_INDIServerClient, error)
PHD2Server(ctx context.Context, opts ...grpc.CallOption) (INDIHub_PHD2ServerClient, error)
SoloMode(ctx context.Context, opts ...grpc.CallOption) (INDIHub_SoloModeClient, error)
BroadcastINDIServer(ctx context.Context, opts ...grpc.CallOption) (INDIHub_BroadcastINDIServerClient, error)
BroadcastPHD2Server(ctx context.Context, opts ...grpc.CallOption) (INDIHub_BroadcastPHD2ServerClient, error)
}
type iNDIHubClient struct {
cc *grpc.ClientConn
}
func NewINDIHubClient(cc *grpc.ClientConn) INDIHubClient {
return &iNDIHubClient{cc}
}
func (c *iNDIHubClient) RegisterHost(ctx context.Context, in *INDIHubHost, opts ...grpc.CallOption) (*RegisterInfo, error) {
out := new(RegisterInfo)
err := c.cc.Invoke(ctx, "/INDIHub/RegisterHost", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *iNDIHubClient) INDIServer(ctx context.Context, opts ...grpc.CallOption) (INDIHub_INDIServerClient, error) {
stream, err := c.cc.NewStream(ctx, &_INDIHub_serviceDesc.Streams[0], "/INDIHub/INDIServer", opts...)
if err != nil {
return nil, err
}
x := &iNDIHubINDIServerClient{stream}
return x, nil
}
type INDIHub_INDIServerClient interface {
Send(*Response) error
Recv() (*Request, error)
grpc.ClientStream
}
type iNDIHubINDIServerClient struct {
grpc.ClientStream
}
func (x *iNDIHubINDIServerClient) Send(m *Response) error {
return x.ClientStream.SendMsg(m)
}
func (x *iNDIHubINDIServerClient) Recv() (*Request, error) {
m := new(Request)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *iNDIHubClient) PHD2Server(ctx context.Context, opts ...grpc.CallOption) (INDIHub_PHD2ServerClient, error) {
stream, err := c.cc.NewStream(ctx, &_INDIHub_serviceDesc.Streams[1], "/INDIHub/PHD2Server", opts...)
if err != nil {
return nil, err
}
x := &iNDIHubPHD2ServerClient{stream}
return x, nil
}
type INDIHub_PHD2ServerClient interface {
Send(*Response) error
Recv() (*Request, error)
grpc.ClientStream
}
type iNDIHubPHD2ServerClient struct {
grpc.ClientStream
}
func (x *iNDIHubPHD2ServerClient) Send(m *Response) error {
return x.ClientStream.SendMsg(m)
}
func (x *iNDIHubPHD2ServerClient) Recv() (*Request, error) {
m := new(Request)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *iNDIHubClient) SoloMode(ctx context.Context, opts ...grpc.CallOption) (INDIHub_SoloModeClient, error) {
stream, err := c.cc.NewStream(ctx, &_INDIHub_serviceDesc.Streams[2], "/INDIHub/SoloMode", opts...)
if err != nil {
return nil, err
}
x := &iNDIHubSoloModeClient{stream}
return x, nil
}
type INDIHub_SoloModeClient interface {
Send(*Response) error
CloseAndRecv() (*SoloSummary, error)
grpc.ClientStream
}
type iNDIHubSoloModeClient struct {
grpc.ClientStream
}
func (x *iNDIHubSoloModeClient) Send(m *Response) error {
return x.ClientStream.SendMsg(m)
}
func (x *iNDIHubSoloModeClient) CloseAndRecv() (*SoloSummary, error) {
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
m := new(SoloSummary)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *iNDIHubClient) BroadcastINDIServer(ctx context.Context, opts ...grpc.CallOption) (INDIHub_BroadcastINDIServerClient, error) {
stream, err := c.cc.NewStream(ctx, &_INDIHub_serviceDesc.Streams[3], "/INDIHub/BroadcastINDIServer", opts...)
if err != nil {
return nil, err
}
x := &iNDIHubBroadcastINDIServerClient{stream}
return x, nil
}
type INDIHub_BroadcastINDIServerClient interface {
Send(*Response) error
Recv() (*Request, error)
grpc.ClientStream
}
type iNDIHubBroadcastINDIServerClient struct {
grpc.ClientStream
}
func (x *iNDIHubBroadcastINDIServerClient) Send(m *Response) error {
return x.ClientStream.SendMsg(m)
}
func (x *iNDIHubBroadcastINDIServerClient) Recv() (*Request, error) {
m := new(Request)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *iNDIHubClient) BroadcastPHD2Server(ctx context.Context, opts ...grpc.CallOption) (INDIHub_BroadcastPHD2ServerClient, error) {
stream, err := c.cc.NewStream(ctx, &_INDIHub_serviceDesc.Streams[4], "/INDIHub/BroadcastPHD2Server", opts...)
if err != nil {
return nil, err
}
x := &iNDIHubBroadcastPHD2ServerClient{stream}
return x, nil
}
type INDIHub_BroadcastPHD2ServerClient interface {
Send(*Response) error
Recv() (*Request, error)
grpc.ClientStream
}
type iNDIHubBroadcastPHD2ServerClient struct {
grpc.ClientStream
}
func (x *iNDIHubBroadcastPHD2ServerClient) Send(m *Response) error {
return x.ClientStream.SendMsg(m)
}
func (x *iNDIHubBroadcastPHD2ServerClient) Recv() (*Request, error) {
m := new(Request)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// INDIHubServer is the server API for INDIHub service.
type INDIHubServer interface {
RegisterHost(context.Context, *INDIHubHost) (*RegisterInfo, error)
INDIServer(INDIHub_INDIServerServer) error
PHD2Server(INDIHub_PHD2ServerServer) error
SoloMode(INDIHub_SoloModeServer) error
BroadcastINDIServer(INDIHub_BroadcastINDIServerServer) error
BroadcastPHD2Server(INDIHub_BroadcastPHD2ServerServer) error
}
// UnimplementedINDIHubServer can be embedded to have forward compatible implementations.
type UnimplementedINDIHubServer struct {
}
func (*UnimplementedINDIHubServer) RegisterHost(ctx context.Context, req *INDIHubHost) (*RegisterInfo, error) {
return nil, status.Errorf(codes.Unimplemented, "method RegisterHost not implemented")
}
func (*UnimplementedINDIHubServer) INDIServer(srv INDIHub_INDIServerServer) error {
return status.Errorf(codes.Unimplemented, "method INDIServer not implemented")
}
func (*UnimplementedINDIHubServer) PHD2Server(srv INDIHub_PHD2ServerServer) error {
return status.Errorf(codes.Unimplemented, "method PHD2Server not implemented")
}
func (*UnimplementedINDIHubServer) SoloMode(srv INDIHub_SoloModeServer) error {
return status.Errorf(codes.Unimplemented, "method SoloMode not implemented")
}
func (*UnimplementedINDIHubServer) BroadcastINDIServer(srv INDIHub_BroadcastINDIServerServer) error {
return status.Errorf(codes.Unimplemented, "method BroadcastINDIServer not implemented")
}
func (*UnimplementedINDIHubServer) BroadcastPHD2Server(srv INDIHub_BroadcastPHD2ServerServer) error {
return status.Errorf(codes.Unimplemented, "method BroadcastPHD2Server not implemented")
}
func RegisterINDIHubServer(s *grpc.Server, srv INDIHubServer) {
s.RegisterService(&_INDIHub_serviceDesc, srv)
}
func _INDIHub_RegisterHost_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(INDIHubHost)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(INDIHubServer).RegisterHost(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/INDIHub/RegisterHost",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(INDIHubServer).RegisterHost(ctx, req.(*INDIHubHost))
}
return interceptor(ctx, in, info, handler)
}
func _INDIHub_INDIServer_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(INDIHubServer).INDIServer(&iNDIHubINDIServerServer{stream})
}
type INDIHub_INDIServerServer interface {
Send(*Request) error
Recv() (*Response, error)
grpc.ServerStream
}
type iNDIHubINDIServerServer struct {
grpc.ServerStream
}
func (x *iNDIHubINDIServerServer) Send(m *Request) error {
return x.ServerStream.SendMsg(m)
}
func (x *iNDIHubINDIServerServer) Recv() (*Response, error) {
m := new(Response)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func _INDIHub_PHD2Server_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(INDIHubServer).PHD2Server(&iNDIHubPHD2ServerServer{stream})
}
type INDIHub_PHD2ServerServer interface {
Send(*Request) error
Recv() (*Response, error)
grpc.ServerStream
}
type iNDIHubPHD2ServerServer struct {
grpc.ServerStream
}
func (x *iNDIHubPHD2ServerServer) Send(m *Request) error {
return x.ServerStream.SendMsg(m)
}
func (x *iNDIHubPHD2ServerServer) Recv() (*Response, error) {
m := new(Response)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func _INDIHub_SoloMode_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(INDIHubServer).SoloMode(&iNDIHubSoloModeServer{stream})
}
type INDIHub_SoloModeServer interface {
SendAndClose(*SoloSummary) error
Recv() (*Response, error)
grpc.ServerStream
}
type iNDIHubSoloModeServer struct {
grpc.ServerStream
}
func (x *iNDIHubSoloModeServer) SendAndClose(m *SoloSummary) error {
return x.ServerStream.SendMsg(m)
}
func (x *iNDIHubSoloModeServer) Recv() (*Response, error) {
m := new(Response)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func _INDIHub_BroadcastINDIServer_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(INDIHubServer).BroadcastINDIServer(&iNDIHubBroadcastINDIServerServer{stream})
}
type INDIHub_BroadcastINDIServerServer interface {
Send(*Request) error
Recv() (*Response, error)
grpc.ServerStream
}
type iNDIHubBroadcastINDIServerServer struct {
grpc.ServerStream
}
func (x *iNDIHubBroadcastINDIServerServer) Send(m *Request) error {
return x.ServerStream.SendMsg(m)
}
func (x *iNDIHubBroadcastINDIServerServer) Recv() (*Response, error) {
m := new(Response)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func _INDIHub_BroadcastPHD2Server_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(INDIHubServer).BroadcastPHD2Server(&iNDIHubBroadcastPHD2ServerServer{stream})
}
type INDIHub_BroadcastPHD2ServerServer interface {
Send(*Request) error
Recv() (*Response, error)
grpc.ServerStream
}
type iNDIHubBroadcastPHD2ServerServer struct {
grpc.ServerStream
}
func (x *iNDIHubBroadcastPHD2ServerServer) Send(m *Request) error {
return x.ServerStream.SendMsg(m)
}
func (x *iNDIHubBroadcastPHD2ServerServer) Recv() (*Response, error) {
m := new(Response)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
var _INDIHub_serviceDesc = grpc.ServiceDesc{
ServiceName: "INDIHub",
HandlerType: (*INDIHubServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "RegisterHost",
Handler: _INDIHub_RegisterHost_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "INDIServer",
Handler: _INDIHub_INDIServer_Handler,
ServerStreams: true,
ClientStreams: true,
},
{
StreamName: "PHD2Server",
Handler: _INDIHub_PHD2Server_Handler,
ServerStreams: true,
ClientStreams: true,
},
{
StreamName: "SoloMode",
Handler: _INDIHub_SoloMode_Handler,
ClientStreams: true,
},
{
StreamName: "BroadcastINDIServer",
Handler: _INDIHub_BroadcastINDIServer_Handler,
ServerStreams: true,
ClientStreams: true,
},
{
StreamName: "BroadcastPHD2Server",
Handler: _INDIHub_BroadcastPHD2Server_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "indihub.proto",
}

218
proxy/proxy.go Normal file
View file

@ -0,0 +1,218 @@
package proxy
import (
"io"
"log"
"net"
"sync"
"time"
"github.com/indihub-space/agent/hostutils"
"github.com/indihub-space/agent/lib"
"github.com/indihub-space/agent/proto/indihub"
)
type INDIHubTunnel interface {
Send(*indihub.Response) error
Recv() (*indihub.Request, error)
CloseSend() error
}
type TcpProxy struct {
Name string
Addr string
Tunnel INDIHubTunnel
connMu sync.Mutex
connMap map[uint32]net.Conn
filter *hostutils.INDIFilter
}
type PublicServerAddr struct {
Name string
Addr string
}
func New(name string, addr string, tunnel INDIHubTunnel, filter *hostutils.INDIFilter) *TcpProxy {
return &TcpProxy{
Name: name,
Addr: addr,
Tunnel: tunnel,
connMap: map[uint32]net.Conn{},
filter: filter,
}
}
func (p *TcpProxy) Close() {
p.connMu.Lock()
defer p.connMu.Unlock()
for num, c := range p.connMap {
c.Close()
delete(p.connMap, num)
}
}
func (p *TcpProxy) connect(cNum uint32) (net.Conn, bool, error) {
p.connMu.Lock()
defer p.connMu.Unlock()
if c, ok := p.connMap[cNum]; ok {
return c, false, nil
}
log.Printf("Connecting to local %s... on %s\n", p.Name, p.Addr)
c, err := net.Dial("tcp", p.Addr)
if err != nil {
log.Printf("Could not connect to %s: %s\n", p.Name, err)
return nil, false, err
}
log.Println("...OK")
p.connMap[cNum] = c
return c, true, err
}
func (p *TcpProxy) reConnect(cNum uint32) (net.Conn, error) {
p.connMu.Lock()
defer p.connMu.Unlock()
log.Println("Re-connecting to local %s... on %s\n", p.Name, p.Addr)
c, err := net.Dial("tcp", p.Addr)
if err != nil {
log.Printf("Could not connect to %s: %s\n", p.Name, err)
return nil, err
}
log.Println("...OK")
p.connMap[cNum] = c
return c, err
}
func (p *TcpProxy) close(cNum uint32) {
p.connMu.Lock()
defer p.connMu.Unlock()
if c, ok := p.connMap[cNum]; ok {
c.Close()
delete(p.connMap, cNum)
}
}
func (p *TcpProxy) Start(pubAddrChan chan PublicServerAddr, sessionID uint64, sessionToken string) {
wg := sync.WaitGroup{}
addrReceived := false
xmlFlattener := map[uint32]*lib.XmlFlattener{}
for {
// receive request from tunnel
in, err := p.Tunnel.Recv()
if err == io.EOF {
// read done, server closed connection
log.Printf("Exiting. Got EOF from %s tunnel.\n", p.Name)
break
}
if err != nil {
log.Printf("Exiting. Failed to receive a request from %s tunnel: %v\n", p.Name, err)
break
}
// 1st message always with server address
if !addrReceived && in.Conn == 0 {
pubAddrChan <- PublicServerAddr{
Name: p.Name,
Addr: string(in.Data),
}
addrReceived = true
continue
}
// Flatten XML data stream into elements
if xmlFlattener[in.Conn] == nil {
xmlFlattener[in.Conn] = lib.NewXmlFlattener()
}
xmlCommands := xmlFlattener[in.Conn].FeedChunk(in.Data)
// check if we need to filter traffic
if p.filter != nil {
xmlCommands = p.filter.FilterIncoming(xmlCommands)
}
c, isNewConn, err := p.connect(in.Conn)
if err != nil {
if c, err = p.reConnect(in.Conn); err != nil {
log.Printf("Failed to send a request to %s: %v\n", p.Name, err)
time.Sleep(1 * time.Second)
continue
}
}
if in.Closed {
log.Printf("Client closed connection %d to the cloud, so closing it to local %s too\n",
in.Conn, p.Name)
p.close(in.Conn)
continue
}
if isNewConn {
// INDI Server responses
wg.Add(1)
go func(conn net.Conn, cNum uint32, sessID uint64) {
defer wg.Done()
readBuf := make([]byte, lib.INDIServerMaxRecvMsgSize)
for {
// receive response from server
n, err := conn.Read(readBuf)
if err == io.EOF {
if conn, err = p.reConnect(cNum); err != nil {
log.Printf("Failed to send a request to %s: %v", p.Name, err)
time.Sleep(1 * time.Second)
continue
} else {
n, err = conn.Read(readBuf)
}
}
if err != nil {
log.Printf("Failed to receive a response from %s: %v", p.Name, err)
return
}
// send request to tunnel
resp := &indihub.Response{
Data: readBuf[:n],
Conn: cNum,
SessionID: sessID,
}
if err := p.Tunnel.Send(resp); err != nil {
log.Printf("Failed to send a response to %s tunnel: %v", p.Name, err)
return
}
}
}(c, in.Conn, sessionID)
}
if len(xmlCommands) == 0 {
continue
}
for _, command := range xmlCommands {
if _, err = c.Write(command); err == io.EOF {
if c, err = p.reConnect(in.Conn); err != nil {
log.Printf("Failed to send a request to %s: %v", p.Name, err)
time.Sleep(1 * time.Second)
continue
} else {
_, err = c.Write(command)
}
}
if err != nil {
break
}
}
if err != nil {
log.Printf("Failed to send a request to %s: %v", p.Name, err)
time.Sleep(1 * time.Second)
continue
}
}
wg.Wait()
}

166
solo/solo.go Normal file
View file

@ -0,0 +1,166 @@
package solo
import (
"bytes"
"io"
"log"
"net"
"sync/atomic"
"github.com/fatih/color"
"github.com/indihub-space/agent/lib"
"github.com/indihub-space/agent/proto/indihub"
)
var (
setBLOBVector = []byte("<setBLOBVector")
)
type INDIHubSoloTunnel interface {
Send(response *indihub.Response) error
CloseAndRecv() (*indihub.SoloSummary, error)
}
type SoloTcpProxy struct {
Name string
Addr string
listener net.Listener
Tunnel INDIHubSoloTunnel
connInMap map[uint32]net.Conn
connOutMap map[uint32]net.Conn
shouldExit int32
}
func New(name string, addr string, tunnel INDIHubSoloTunnel) *SoloTcpProxy {
return &SoloTcpProxy{
Name: name,
Addr: addr,
Tunnel: tunnel,
connInMap: map[uint32]net.Conn{},
connOutMap: map[uint32]net.Conn{},
}
}
func (p *SoloTcpProxy) Start(addr string, sessionID uint64, sessionToken string) {
log.Printf("Starting INDI-server for INDIHUB in solo mode on %s ...", addr)
var err error
p.listener, err = net.Listen("tcp", addr)
if err != nil {
log.Printf("Could not start INDI-server for INDIHUB in solo mode: %v\n", err)
return
}
log.Println("...OK")
var connCnt uint32
for {
if atomic.LoadInt32(&p.shouldExit) == 1 {
break
}
// Wait for a connection from INDI-client
connIn, err := p.listener.Accept()
if err != nil {
break
}
// connection to real INDI-server
connOut, err := net.Dial("tcp", p.Addr)
if err != nil {
log.Printf("%s - could not connect to INDI-server: %v\n", p.Name, err)
connIn.Close()
continue
}
connCnt += 1
p.connInMap[connCnt] = connIn
p.connOutMap[connCnt] = connOut
// copy requests
go func(cNum uint32) {
_, err := io.Copy(connOut, connIn)
if err == nil {
// client closed connection so closing to INDI-server
connOut.Close()
} else {
connIn.Close()
connOut.Close()
}
delete(p.connInMap, cNum)
delete(p.connOutMap, cNum)
}(connCnt)
// copy responses
go func(cNum uint32, sessID uint64, sessToken string) {
buf := make([]byte, lib.INDIServerMaxSendMsgSize, lib.INDIServerMaxSendMsgSize)
xmlFlattener := lib.NewXmlFlattener()
for {
n, err := connOut.Read(buf)
if err != nil {
log.Printf("%s - could not read from INDI-server: %v\n", p.Name, err)
connIn.Close()
return
}
if _, err := connIn.Write(buf[:n]); err != nil {
log.Printf("%s - could not write to INDI-client: %v\n", p.Name, err)
connOut.Close()
return
}
// catch images
xmlCommands := xmlFlattener.FeedChunk(buf[:n])
for _, xmlComm := range xmlCommands {
if !bytes.HasPrefix(xmlComm, setBLOBVector) {
continue
}
err := p.sendImages(xmlComm, cNum, sessID, sessToken)
if err != nil {
log.Printf("%s - could not send image to INDIHUB: %v\n", p.Name, err)
}
}
}
}(connCnt, sessionID, sessionToken)
}
// close connections to tunnel
if summary, err := p.Tunnel.CloseAndRecv(); err == nil {
gc := color.New(color.FgGreen)
gc.Println()
gc.Println(" ************************************************************")
gc.Println(" * INDIHUB solo session finished!! *")
gc.Println(" ************************************************************")
gc.Println(" ")
gc.Printf(" "+
"Processed %d images. Thank you for your contribution!\n",
summary.ImagesNum,
)
gc.Println(" ************************************************************")
} else {
log.Printf("Error getting solo-session summary: %v", err)
}
}
func (p *SoloTcpProxy) Close() {
atomic.SwapInt32(&p.shouldExit, 1)
for _, c := range p.connInMap {
c.Close()
}
p.listener.Close()
for _, c := range p.connOutMap {
c.Close()
}
}
func (p *SoloTcpProxy) sendImages(imagesData []byte, cNum uint32, sessID uint64, sessToken string) error {
resp := &indihub.Response{
Data: imagesData,
Conn: cNum,
SessionID: sessID,
SessionToken: sessToken,
}
return p.Tunnel.Send(resp)
}

3
version/version.go Normal file
View file

@ -0,0 +1,3 @@
package version
var AgentVersion = "1.0.1"

221
websockets/server.go Normal file
View file

@ -0,0 +1,221 @@
package websockets
import (
"context"
"fmt"
"log"
"net"
"net/http"
"net/url"
"strings"
"github.com/gorilla/websocket"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
elog "github.com/labstack/gommon/log"
"github.com/indihub-space/agent/lib"
"github.com/indihub-space/agent/logutil"
)
var allowedOrigins = map[string]bool{
"indihub.space": true,
"app.indihub.space": true,
"kids.indihub.space": true,
}
type WsServer struct {
token string
indiServerAddr string
phd2ServerAddr string
wsPort uint64
isTLS bool
origins string
e *echo.Echo
upgrader websocket.Upgrader
connList []net.Conn
}
func NewWsServer(token string, indiServerAddr string, phd2ServerAddr string, wsPort uint64, isTLS bool, origins string) *WsServer {
wsServer := &WsServer{
token: token,
indiServerAddr: indiServerAddr,
phd2ServerAddr: phd2ServerAddr,
wsPort: wsPort,
isTLS: isTLS,
e: echo.New(),
upgrader: websocket.Upgrader{},
connList: []net.Conn{},
}
if logutil.IsDev {
allowedOrigins["localhost"] = true
}
// add optional additional origins
for _, orig := range strings.Split(origins, ",") {
allowedOrigins[strings.TrimSpace(orig)] = true
}
// allow WS connections only from number of domains
wsServer.upgrader.CheckOrigin = func(r *http.Request) bool {
origin := r.Header["Origin"]
if len(origin) == 0 {
return false
}
u, err := url.Parse(origin[0])
if err != nil {
return false
}
host, _, err := net.SplitHostPort(u.Host)
if err != nil {
return false
}
return allowedOrigins[host]
}
return wsServer
}
func (s *WsServer) newIndiConnection(c echo.Context) error {
// upgrade to WS connection
ws, err := s.upgrader.Upgrade(c.Response(), c.Request(), nil)
if err != nil {
return err
}
defer ws.Close()
// open connection to INDI-Server
conn, err := net.Dial("tcp", s.indiServerAddr)
if err != nil {
return err
}
// add to connection list
s.connList = append(s.connList, conn)
// read messages from INDI-server and write them to WS
go func(indiConn net.Conn, wsConn *websocket.Conn) {
buf := make([]byte, lib.INDIServerMaxSendMsgSize, lib.INDIServerMaxSendMsgSize)
xmlFlattener := lib.NewXmlFlattener()
for {
// read from INDI-server
n, err := indiConn.Read(buf)
if err != nil {
indiConn.Close()
return
}
jsonMessages := xmlFlattener.ConvertChunkToJSON(buf[:n])
// Write to WS
for _, m := range jsonMessages {
err = wsConn.WriteMessage(websocket.TextMessage, m)
if err != nil {
indiConn.Close()
return
}
}
}
}(conn, ws)
// read messages from WS and write them to INDI-server
xmlFlattener := lib.NewXmlFlattener()
for {
// Read from WS
_, msg, err := ws.ReadMessage()
if err != nil {
conn.Close()
return err
}
xmlMsg, err := xmlFlattener.ConvertJSONToXML(msg)
if err != nil {
log.Printf("could not convert json '%s' to xml: %s", string(msg), err)
continue
}
// write to INDI server
_, err = conn.Write(xmlMsg)
if err != nil {
conn.Close()
return err
}
}
}
func (s *WsServer) newPHD2Connection(c echo.Context) error {
return nil
}
func (s *WsServer) Start() {
s.e.HideBanner = true
s.e.HidePort = true
if !logutil.IsDev {
s.e.Logger.SetLevel(elog.OFF)
}
s.e.Use(middleware.Recover())
// set CORS in case browser decides to do pre-flight OPTIONS request
// default ones
allowOrigins := []string{}
for orig := range allowedOrigins {
allowOrigins = append(allowOrigins, "http://"+orig)
allowOrigins = append(allowOrigins, "https://"+orig)
}
// optional ones
for _, orig := range strings.Split(s.origins, ",") {
allowOrigins = append(allowOrigins, "http://"+strings.TrimSpace(orig))
allowOrigins = append(allowOrigins, "https://"+strings.TrimSpace(orig))
}
// localhost for dev-mode
if logutil.IsDev {
allowOrigins = append(allowOrigins, "http://localhost:5000")
}
s.e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: allowOrigins,
AllowMethods: []string{http.MethodGet},
}))
// set auth middleware
s.e.Use(middleware.KeyAuthWithConfig(middleware.KeyAuthConfig{
KeyLookup: "query:token",
Validator: func(token string, eCtx echo.Context) (b bool, err error) {
return token == s.token, nil
},
}))
s.e.GET("/indiserver", s.newIndiConnection)
s.e.GET("/phd2server", s.newPHD2Connection)
// check if we are running TLS
if s.isTLS {
// generate self-signed cert to serve WS over TLS
keyFile, certFile, err := getSelfSignedCert()
if err != nil {
log.Println("could not start WSS server, cert generation failed:", err)
return
}
err = s.e.StartTLS(fmt.Sprintf(":%d", s.wsPort), certFile, keyFile)
if err != nil {
log.Println("WSS server error:", err)
}
return
}
// run WS
err := s.e.Start(fmt.Sprintf(":%d", s.wsPort))
if err != nil {
log.Println("WSS server error:", err)
}
}
func (s *WsServer) Stop() {
for _, conn := range s.connList {
conn.Close()
}
s.e.Shutdown(context.Background())
}

185
websockets/tls.go Normal file
View file

@ -0,0 +1,185 @@
package websockets
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"os"
"time"
"github.com/indihub-space/agent/logutil"
)
const (
tlsDirName = "./.indihub-agent/tls"
rootKeyCA = tlsDirName + "/root_CA.key"
rootCertCA = tlsDirName + "/root_CA.pem"
serverKey = tlsDirName + "/server.key"
serverCert = tlsDirName + "/server.pem"
)
func getSelfSignedCert() (string, string, error) {
// create dir for tls keys and certificates
if _, err := os.Stat(tlsDirName); os.IsNotExist(err) {
if err := os.MkdirAll(tlsDirName, 0700); err != nil {
return "", "", err
}
}
// return if files are already there
if _, err := os.Stat(tlsDirName + "/" + rootKeyCA); !os.IsNotExist(err) {
return serverKey, serverCert, nil
}
// generate self-signed CA and cert for server
// NOTE: Web-UI user will have to set it as trusted on the desktop
// prepare hosts
hostName, err := os.Hostname()
if err != nil {
return "", "", err
}
hosts := []string{
hostName,
hostName + ".local",
}
// don't forget localhost for DEV mode
if logutil.IsDev {
hosts = append(hosts, "localhost")
}
// dates
notBefore := time.Now()
notAfter := notBefore.Add(10 * 365 * 24 * time.Hour) // 10 years
// serial number
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
// Root CA
rootKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return "", "", err
}
if err = savePrivateKey(rootKeyCA, rootKey); err != nil {
return "", "", err
}
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return "", "", err
}
rootCert := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"INDIHUB"},
CommonName: "Root CA",
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
}
rootCertBytes, err := x509.CreateCertificate(rand.Reader, &rootCert, &rootCert, &rootKey.PublicKey, rootKey)
if err != nil {
return "", "", err
}
if err = saveCertificate(rootCertCA, rootCertBytes); err != nil {
return "", "", err
}
// server certificate
serverPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return "", "", err
}
if err = savePrivateKey(serverKey, serverPrivateKey); err != nil {
return "", "", err
}
serialNumber, err = rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return "", "", err
}
serverCertKey := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"INDIHUB"},
CommonName: "indihub-agent",
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: false,
}
for _, h := range hosts {
if ip := net.ParseIP(h); ip != nil {
serverCertKey.IPAddresses = append(serverCertKey.IPAddresses, ip)
} else {
serverCertKey.DNSNames = append(serverCertKey.DNSNames, h)
}
}
serverCertBytes, err := x509.CreateCertificate(rand.Reader, &serverCertKey, &rootCert, &serverPrivateKey.PublicKey, rootKey)
if err != nil {
return "", "", err
}
if err = saveCertificate(serverCert, serverCertBytes); err != nil {
return "", "", err
}
return serverKey, serverCert, nil
}
func savePrivateKey(fileName string, key *ecdsa.PrivateKey) error {
file, err := os.Create(fileName)
if err != nil {
return err
}
defer file.Close()
data, err := x509.MarshalECPrivateKey(key)
if err != nil {
return err
}
err = pem.Encode(
file,
&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: data,
})
if err != nil {
return err
}
return nil
}
func saveCertificate(fileName string, data []byte) error {
file, err := os.Create(fileName)
if err != nil {
return err
}
defer file.Close()
err = pem.Encode(
file,
&pem.Block{
Type: "CERTIFICATE",
Bytes: data,
})
if err != nil {
return err
}
return nil
}