From 96a892f8400bc60a3a4021d7783776e2315036ba Mon Sep 17 00:00:00 2001
From: Ruben Pollan <meskio@sindominio.net>
Date: Fri, 19 Jan 2018 19:51:03 +0100
Subject: [PATCH] [feat] implement bitmask.core zmq comunication

---
 README.md         |  4 +++
 bitmask/events.go | 40 ++++++++++++++++++++++++
 bitmask/main.go   | 77 +++++++++++++++++++++++++++++++++++++++++++++++
 bitmask/vpn.go    | 27 +++++++++++++++++
 main.go           | 41 +++++++------------------
 systray.go        | 48 +++++++++++++++++------------
 6 files changed, 188 insertions(+), 49 deletions(-)
 create mode 100644 README.md
 create mode 100644 bitmask/events.go
 create mode 100644 bitmask/main.go
 create mode 100644 bitmask/vpn.go

diff --git a/README.md b/README.md
new file mode 100644
index 00000000..f4255bb3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+Dependencies
+------------
+
+  # apt install libzmq3-dev libgtk-3-dev libappindicator3-dev
diff --git a/bitmask/events.go b/bitmask/events.go
new file mode 100644
index 00000000..3fed26a3
--- /dev/null
+++ b/bitmask/events.go
@@ -0,0 +1,40 @@
+package bitmask
+
+import (
+	"github.com/pebbe/zmq4"
+)
+
+const (
+	eventsEndpoint = "tcp://127.0.0.1:9001"
+	//serverKeyPath  = "/home/user/.config/leap/events/zmq_certificates/public_keys/server.key" // FIXME
+)
+
+func initEvents() (*zmq4.Socket, error) {
+	socket, err := zmq4.NewSocket(zmq4.SUB)
+	if err != nil {
+		return nil, err
+	}
+
+	if zmq4.HasCurve() {
+		// TODO
+	}
+
+	err = socket.Connect(eventsEndpoint)
+	if err != nil {
+		return nil, err
+	}
+	return socket, nil
+}
+
+func (b *Bitmask) fetchStatus() {
+	// TODO: this should be a subscription to the event
+	for {
+		time.Sleep(time.Second)
+		status, err := b.GetStatus()
+		if err != nil {
+			log.Printf("Error receiving status: %v", err)
+			continue
+		}
+		b.statusCh <- status
+	}
+}
diff --git a/bitmask/main.go b/bitmask/main.go
new file mode 100644
index 00000000..b4607b3d
--- /dev/null
+++ b/bitmask/main.go
@@ -0,0 +1,77 @@
+package bitmask
+
+import (
+	"encoding/json"
+	"errors"
+
+	"github.com/pebbe/zmq4"
+)
+
+const (
+	// On win should be: tcp://127.0.0.1:5001
+	coreEndpoint = "ipc:///tmp/bitmask.core.sock"
+)
+
+// Bitmask holds the bitmask client data
+type Bitmask struct {
+	//eventsoc *zmq4.Socket
+	coresoc  *zmq4.Socket
+	statusCh chan string
+}
+
+// Init the connection to bitmask
+func Init() (*Bitmask, error) {
+	statusCh := make(chan string)
+	socket, err := initCore()
+	if err != nil {
+		return nil, err
+	}
+
+	b := Bitmask{socket, statusCh}
+	go b.fetchStatus()
+	return &b, nil
+}
+
+// GetStatusCh returns a channel that will recieve VPN status changes
+func (b *Bitmask) GetStatusCh() chan string {
+	return b.statusCh
+}
+
+// Close the connection to bitmask
+func (b *Bitmask) Close() {
+	b.coresoc.Close()
+}
+
+func (b *Bitmask) send(parts ...interface{}) (map[string]interface{}, error) {
+	_, err := b.coresoc.SendMessage(parts...)
+	if err != nil {
+		return nil, err
+	}
+	resJSON, err := b.coresoc.RecvBytes(0)
+	if err != nil {
+		return nil, err
+	}
+	return parseResponse(resJSON)
+}
+
+func parseResponse(resJSON []byte) (map[string]interface{}, error) {
+	var response struct {
+		Result map[string]interface{}
+		Error  string
+	}
+	err := json.Unmarshal(resJSON, &response)
+	if response.Error != "" {
+		return nil, errors.New(response.Error)
+	}
+	return response.Result, err
+}
+
+func initCore() (*zmq4.Socket, error) {
+	socket, err := zmq4.NewSocket(zmq4.REQ)
+	if err != nil {
+		return nil, err
+	}
+
+	err = socket.Connect(coreEndpoint)
+	return socket, err
+}
diff --git a/bitmask/vpn.go b/bitmask/vpn.go
new file mode 100644
index 00000000..025e2e36
--- /dev/null
+++ b/bitmask/vpn.go
@@ -0,0 +1,27 @@
+package bitmask
+
+import (
+	"log"
+	"time"
+)
+
+// StartVPN for provider
+func (b *Bitmask) StartVPN(provider string) error {
+	_, err := b.send("vpn", "start", provider)
+	return err
+}
+
+// StopVPN or cancel
+func (b *Bitmask) StopVPN() error {
+	_, err := b.send("vpn", "stop")
+	return err
+}
+
+// GetStatus returns the VPN status
+func (b *Bitmask) GetStatus() (string, error) {
+	res, err := b.send("vpn", "status")
+	if err != nil {
+		return "", err
+	}
+	return res["status"].(string), nil
+}
diff --git a/main.go b/main.go
index b47df4c8..ddf7172c 100644
--- a/main.go
+++ b/main.go
@@ -1,40 +1,21 @@
 package main
 
 import (
-	"time"
+	"log"
+
+	"0xacab.org/meskio/bitmask-systray/bitmask"
 )
 
-var ch chan string
+const (
+	provider = "demo.bitmask.net"
+)
 
 func main() {
 	go notificate()
 
-	ch = make(chan string)
-	run(ch)
-}
-
-func startVPN() {
-	go func() {
-		ch <- "starting"
-		time.Sleep(time.Second * 5)
-		ch <- "on"
-	}()
-}
-
-func cancelVPN() {
-	go func() {
-		ch <- "stopping"
-		time.Sleep(time.Second * 5)
-		ch <- "off"
-	}()
-}
-
-func stopVPN() {
-	go func() {
-		ch <- "failed"
-	}()
-}
-
-func getVPNStatus() string {
-	return "off"
+	b, err := bitmask.Init()
+	if err != nil {
+		log.Fatal(err)
+	}
+	run(b)
 }
diff --git a/systray.go b/systray.go
index 0efaa1b7..e520f0ad 100644
--- a/systray.go
+++ b/systray.go
@@ -1,17 +1,18 @@
 package main
 
 import (
-	"fmt"
+	"log"
 	"os"
 	"time"
 
+	"0xacab.org/meskio/bitmask-systray/bitmask"
 	"0xacab.org/meskio/bitmask-systray/icon"
 	"github.com/getlantern/systray"
 	"github.com/skratchdot/open-golang/open"
 )
 
 type bmTray struct {
-	ch        chan string
+	bm        *bitmask.Bitmask
 	waitCh    chan bool
 	mStatus   *systray.MenuItem
 	mTurnOn   *systray.MenuItem
@@ -20,14 +21,14 @@ type bmTray struct {
 	mGateways []*systray.MenuItem
 }
 
-func run(ch chan string) {
-	bt := bmTray{ch: ch}
+func run(bm *bitmask.Bitmask) {
+	bt := bmTray{bm: bm}
 	systray.Run(bt.onReady, bt.onExit)
 }
 
 func (bt bmTray) onExit() {
 	// TODO: this doesn't get executed :(
-	fmt.Println("Finished onExit")
+	log.Println("Finished onExit")
 }
 
 func (bt *bmTray) onReady() {
@@ -51,31 +52,37 @@ func (bt *bmTray) onReady() {
 	bt.mGateways[2].Uncheck()
 	systray.AddSeparator()
 
-	mHelp := systray.AddMenuItem("Help", "")
-	mDonate := systray.AddMenuItem("Donate", "")
-	mAbout := systray.AddMenuItem("About", "")
+	mHelp := systray.AddMenuItem("Help ...", "")
+	mDonate := systray.AddMenuItem("Donate ...)", "")
+	mAbout := systray.AddMenuItem("About ...", "")
 	systray.AddSeparator()
 
 	mQuit := systray.AddMenuItem("Quit", "Quit BitmaskVPN")
 
 	go func() {
-		bt.changeStatus(getVPNStatus())
+		ch := bt.bm.GetStatusCh()
+		status, err := bt.bm.GetStatus()
+		if err != nil {
+			log.Printf("Error getting status: %v", err)
+		} else {
+			bt.changeStatus(status)
+		}
 
 		for {
 			select {
 			case status := <-ch:
-				fmt.Println("status: " + status)
+				log.Println("status: " + status)
 				bt.changeStatus(status)
 
 			case <-bt.mTurnOn.ClickedCh:
-				fmt.Println("on")
-				startVPN()
+				log.Println("on")
+				bt.bm.StartVPN(provider)
 			case <-bt.mTurnOff.ClickedCh:
-				fmt.Println("off")
-				stopVPN()
+				log.Println("off")
+				bt.bm.StopVPN()
 			case <-bt.mCancel.ClickedCh:
-				fmt.Println("cancel")
-				cancelVPN()
+				log.Println("cancel")
+				bt.bm.StopVPN()
 
 			case <-mHelp.ClickedCh:
 				open.Run("https://riseup.net/en/vpn/vpn-black")
@@ -86,7 +93,8 @@ func (bt *bmTray) onReady() {
 
 			case <-mQuit.ClickedCh:
 				systray.Quit()
-				fmt.Println("Quit now...")
+				bt.bm.Close()
+				log.Println("Quit now...")
 				os.Exit(0)
 			}
 		}
@@ -95,6 +103,7 @@ func (bt *bmTray) onReady() {
 
 func (bt *bmTray) changeStatus(status string) {
 	statusStr := status
+	bt.mTurnOn.SetTitle("Turn on")
 	if bt.waitCh != nil {
 		bt.waitCh <- true
 		bt.waitCh = nil
@@ -129,9 +138,10 @@ func (bt *bmTray) changeStatus(status string) {
 
 	case "failed":
 		systray.SetIcon(icon.Error)
+		bt.mTurnOn.SetTitle("Retry")
 		go bt.mTurnOn.Show()
-		go bt.mTurnOff.Hide()
-		go bt.mCancel.Show()
+		go bt.mTurnOff.Show()
+		go bt.mCancel.Hide()
 		statusStr = "blocking internet"
 	}
 
-- 
GitLab