summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gui/backend.go302
-rw-r--r--pkg/backend/api.go74
-rw-r--r--pkg/backend/bitmask.go52
-rw-r--r--pkg/backend/callbacks.go63
-rw-r--r--pkg/backend/mocks.go32
-rw-r--r--pkg/backend/status.go122
6 files changed, 357 insertions, 288 deletions
diff --git a/gui/backend.go b/gui/backend.go
index cf7c0fb..9c65025 100644
--- a/gui/backend.go
+++ b/gui/backend.go
@@ -1,333 +1,59 @@
package main
-/* a wrapper around bitmask that exposes status to a QtQml gui */
+/* a wrapper around bitmask that exposes status to a QtQml gui.
+ Have a look at the pkg/backend module for further enlightment. */
import (
- "bytes"
- "encoding/json"
- "fmt"
- "log"
- "net/http"
- "os"
- "reflect"
- "sync"
- //"time"
+ "C"
"unsafe"
- "0xacab.org/leap/bitmask-vpn/pkg/bitmask"
- "0xacab.org/leap/bitmask-vpn/pkg/pickle"
+ "0xacab.org/leap/bitmask-vpn/pkg/backend"
)
-// typedef void (*cb)();
-// inline void _do_callback(cb f) {
-// f();
-// }
-import "C"
-
-/* callbacks into C-land */
-
-var mut sync.Mutex
-var stmut sync.Mutex
-var cbs = make(map[string](*[0]byte))
-var initOnce sync.Once
-
-// Events are just a enumeration of all the posible events that C functions can
-// be interested in subscribing to. You cannot subscribe to an event that is
-// not listed here.
-type Events struct {
- OnStatusChanged string
-}
-
-const OnStatusChanged string = "OnStatusChanged"
-
-// subscribe registers a callback from C-land.
-// This callback needs to be passed as a void* C function pointer.
-func subscribe(event string, fp unsafe.Pointer) {
- mut.Lock()
- defer mut.Unlock()
- e := &Events{}
- v := reflect.Indirect(reflect.ValueOf(&e))
- hf := v.Elem().FieldByName(event)
- if reflect.ValueOf(hf).IsZero() {
- fmt.Println("ERROR: not a valid event:", event)
- } else {
- cbs[event] = (*[0]byte)(fp)
- }
-}
-
-// trigger fires a callback from C-land.
-func trigger(event string) {
- mut.Lock()
- defer mut.Unlock()
- cb := cbs[event]
- if cb != nil {
- C._do_callback(cb)
- } else {
- fmt.Println("ERROR: this event does not have subscribers:", event)
- }
-}
-
-/* connection status */
-
-const (
- offStr = "off"
- startingStr = "starting"
- onStr = "on"
- stoppingStr = "stopping"
- failedStr = "failed"
-)
-
-// status reflects the current VPN status. Go code is responsible for updating
-// it; C-land just watches its changes and pulls its updates via the serialized
-// context object.
-type status int
-
-const (
- off status = iota
- starting
- on
- stopping
- failed
- unknown
-)
-
-func (s status) String() string {
- return [...]string{offStr, startingStr, onStr, stoppingStr, failedStr}[s]
-}
-
-func (s status) MarshalJSON() ([]byte, error) {
- b := bytes.NewBufferString(`"`)
- b.WriteString(s.String())
- b.WriteString(`"`)
- return b.Bytes(), nil
-}
-
-func (s status) fromString(st string) status {
- switch st {
- case offStr:
- return off
- case startingStr:
- return starting
- case onStr:
- return on
- case stoppingStr:
- return stopping
- case failedStr:
- return failed
- default:
- return unknown
- }
-}
-
-// The connectionCtx keeps the global state that is passed around to C-land. It
-// also serves as the primary way of passing requests from the frontend to the
-// Go-core, by letting the UI write some of these variables and processing
-// them.
-type connectionCtx struct {
- AppName string `json:"appName"`
- Provider string `json:"provider"`
- Donate bool `json:"donate"`
- Status status `json:"status"`
- bm bitmask.Bitmask
-}
-
-func (c connectionCtx) toJson() ([]byte, error) {
- stmut.Lock()
- defer stmut.Unlock()
- b, err := json.Marshal(c)
- if err != nil {
- log.Println(err)
- return nil, err
- }
- return b, nil
-}
-
-func (c connectionCtx) updateStatus() {
- if stStr, err := c.bm.GetStatus(); err != nil {
- log.Printf("Error getting status: %v", err)
- } else {
- setStatusFromStr(stStr)
- }
-
- statusCh := c.bm.GetStatusCh()
- for {
- select {
- case stStr := <-statusCh:
- setStatusFromStr(stStr)
- }
- }
-}
-
-var ctx *connectionCtx
-
-func setStatus(st status) {
- stmut.Lock()
- defer stmut.Unlock()
- ctx.Status = st
- go trigger(OnStatusChanged)
-}
-
-func toggleDonate() {
- stmut.Lock()
- defer stmut.Unlock()
- ctx.Donate = !ctx.Donate
- go trigger(OnStatusChanged)
-}
-
-func setStatusFromStr(stStr string) {
- setStatus(unknown.fromString(stStr))
-}
-
-// initializeBitmask instantiates a bitmask connection
-func initializeBitmask() {
- if ctx == nil {
- log.Println("error: cannot initialize bitmask, ctx is nil")
- os.Exit(1)
- }
- bitmask.InitializeLogger()
-
- b, err := bitmask.InitializeBitmask()
- if err != nil {
- log.Println("error: cannot initialize bitmask")
- }
- ctx.bm = b
-}
-
-func startVPN() {
- err := ctx.bm.StartVPN(ctx.Provider)
- if err != nil {
- log.Println(err)
- os.Exit(1)
- }
-}
-
-func stopVPN() {
- err := ctx.bm.StopVPN()
- if err != nil {
- log.Println(err)
- }
-}
-
-// initializeContext initializes an empty connStatus and assigns it to the
-// global ctx holder. This is expected to be called only once, so the public
-// api uses the sync.Once primitive to call this.
-func initializeContext(provider, appName string) {
- var st status = off
- ctx = &connectionCtx{
- AppName: appName,
- Provider: provider,
- Donate: false,
- Status: st,
- }
- go trigger(OnStatusChanged)
- initializeBitmask()
-}
-
-/* mock http server: easy way to mocking vpn behavior on ui interaction. This
-* should also show a good way of writing functionality tests just for the Qml
-* layer */
-
-func mockUIOn(w http.ResponseWriter, r *http.Request) {
- log.Println("changing status: on")
- setStatus(on)
-}
-
-func mockUIOff(w http.ResponseWriter, r *http.Request) {
- log.Println("changing status: off")
- setStatus(off)
-}
-
-func mockUIFailed(w http.ResponseWriter, r *http.Request) {
- log.Println("changing status: failed")
- setStatus(failed)
-}
-
-func mockUI() {
- http.HandleFunc("/on", mockUIOn)
- http.HandleFunc("/off", mockUIOff)
- http.HandleFunc("/failed", mockUIFailed)
- http.ListenAndServe(":8080", nil)
-}
-
-/*
-
- exported C api
-
-*/
-
//export SwitchOn
func SwitchOn() {
- go setStatus(starting)
- go startVPN()
+ backend.SwitchOn()
}
//export SwitchOff
func SwitchOff() {
- go setStatus(stopping)
- go stopVPN()
+ backend.SwitchOff()
}
//export Unblock
func Unblock() {
- fmt.Println("unblock... [not implemented]")
+ backend.Unblock()
}
//export Quit
func Quit() {
- if ctx.Status != off {
- go setStatus(stopping)
- stopVPN()
- }
+ backend.Quit()
+
}
//export ToggleDonate
func ToggleDonate() {
- toggleDonate()
+ backend.ToggleDonate()
}
//export SubscribeToEvent
func SubscribeToEvent(event string, f unsafe.Pointer) {
- subscribe(event, f)
+ backend.SubscribeToEvent(event, f)
}
//export InitializeBitmaskContext
func InitializeBitmaskContext() {
- pi := bitmask.GetConfiguredProvider()
-
- initOnce.Do(func() {
- initializeContext(pi.Provider, pi.AppName)
- })
- go ctx.updateStatus()
-
- /* DEBUG
- timer := time.NewTimer(time.Second * 3)
- go func() {
- <-timer.C
- fmt.Println("donate timer fired")
- toggleDonate()
- }()
- */
+ backend.InitializeBitmaskContext()
}
//export RefreshContext
func RefreshContext() *C.char {
- c, _ := ctx.toJson()
- return C.CString(string(c))
+ return (*C.char)(backend.RefreshContext())
}
//export InstallHelpers
func InstallHelpers() {
- pickle.InstallHelpers()
-}
-
-/* end of the exposed api */
-
-/* we could enable this one optionally for the qt tests */
-
-/* uncomment: export MockUIInteraction */
-func MockUIInteraction() {
- log.Println("mocking ui interaction on port 8080. \nTry 'curl localhost:8080/{on|off|failed}' to toggle status.")
- go mockUI()
+ backend.InstallHelpers()
}
func main() {}
diff --git a/pkg/backend/api.go b/pkg/backend/api.go
new file mode 100644
index 0000000..f924cbd
--- /dev/null
+++ b/pkg/backend/api.go
@@ -0,0 +1,74 @@
+/* All the exported functions live here */
+
+package backend
+
+import (
+ "C"
+ "fmt"
+ "log"
+ "unsafe"
+
+ "0xacab.org/leap/bitmask-vpn/pkg/bitmask"
+ "0xacab.org/leap/bitmask-vpn/pkg/pickle"
+)
+
+func SwitchOn() {
+ go setStatus(starting)
+ go startVPN()
+}
+
+func SwitchOff() {
+ go setStatus(stopping)
+ go stopVPN()
+}
+
+func Unblock() {
+ fmt.Println("unblock... [not implemented]")
+}
+
+func Quit() {
+ if ctx.Status != off {
+ go setStatus(stopping)
+ stopVPN()
+ }
+}
+
+func ToggleDonate() {
+ toggleDonate()
+}
+
+func SubscribeToEvent(event string, f unsafe.Pointer) {
+ subscribe(event, f)
+}
+
+func InitializeBitmaskContext() {
+ pi := bitmask.GetConfiguredProvider()
+
+ initOnce.Do(func() {
+ initializeContext(pi.Provider, pi.AppName)
+ })
+ go ctx.updateStatus()
+
+ /* DEBUG
+ timer := time.NewTimer(time.Second * 3)
+ go func() {
+ <-timer.C
+ fmt.Println("donate timer fired")
+ toggleDonate()
+ }()
+ */
+}
+
+func RefreshContext() *C.char {
+ c, _ := ctx.toJson()
+ return C.CString(string(c))
+}
+
+func InstallHelpers() {
+ pickle.InstallHelpers()
+}
+
+func MockUIInteraction() {
+ log.Println("mocking ui interaction on port 8080. \nTry 'curl localhost:8080/{on|off|failed}' to toggle status.")
+ go mockUI()
+}
diff --git a/pkg/backend/bitmask.go b/pkg/backend/bitmask.go
new file mode 100644
index 0000000..07d27ea
--- /dev/null
+++ b/pkg/backend/bitmask.go
@@ -0,0 +1,52 @@
+package backend
+
+import (
+ "log"
+ "os"
+
+ "0xacab.org/leap/bitmask-vpn/pkg/bitmask"
+)
+
+func initializeBitmask() {
+ if ctx == nil {
+ log.Println("error: cannot initialize bitmask, ctx is nil")
+ os.Exit(1)
+ }
+ bitmask.InitializeLogger()
+
+ b, err := bitmask.InitializeBitmask()
+ if err != nil {
+ log.Println("error: cannot initialize bitmask")
+ }
+ ctx.bm = b
+}
+
+func startVPN() {
+ err := ctx.bm.StartVPN(ctx.Provider)
+ if err != nil {
+ log.Println(err)
+ os.Exit(1)
+ }
+}
+
+func stopVPN() {
+ err := ctx.bm.StopVPN()
+ if err != nil {
+ log.Println(err)
+ }
+}
+
+// initializeContext initializes an empty connStatus and assigns it to the
+// global ctx holder. This is expected to be called only once, so the public
+// api uses the sync.Once primitive to call this.
+func initializeContext(provider, appName string) {
+ var st status = off
+ ctx = &connectionCtx{
+ AppName: appName,
+ Provider: provider,
+ Donate: false,
+ Status: st,
+ }
+ go trigger(OnStatusChanged)
+ initializeBitmask()
+}
diff --git a/pkg/backend/callbacks.go b/pkg/backend/callbacks.go
new file mode 100644
index 0000000..5ea3b04
--- /dev/null
+++ b/pkg/backend/callbacks.go
@@ -0,0 +1,63 @@
+package backend
+
+import (
+ "fmt"
+ "reflect"
+ "sync"
+ "unsafe"
+)
+
+/* NOTE! ATCHUNG! what follow are not silly comments. Well, *this one* is, but
+ the lines after this are not.
+ Those are inline C functions, that are invoked by CGO later on.
+ it's also crucial that you don't any extra space between the function
+ block and the 'import "C"' line. */
+
+// typedef void (*cb)();
+// inline void _do_callback(cb f) {
+// f();
+// }
+import "C"
+
+/* callbacks into C-land */
+
+var mut sync.Mutex
+var stmut sync.Mutex
+var cbs = make(map[string](*[0]byte))
+var initOnce sync.Once
+
+// Events are just a enumeration of all the posible events that C functions can
+// be interested in subscribing to. You cannot subscribe to an event that is
+// not listed here.
+type Events struct {
+ OnStatusChanged string
+}
+
+const OnStatusChanged string = "OnStatusChanged"
+
+// subscribe registers a callback from C-land.
+// This callback needs to be passed as a void* C function pointer.
+func subscribe(event string, fp unsafe.Pointer) {
+ mut.Lock()
+ defer mut.Unlock()
+ e := &Events{}
+ v := reflect.Indirect(reflect.ValueOf(&e))
+ hf := v.Elem().FieldByName(event)
+ if reflect.ValueOf(hf).IsZero() {
+ fmt.Println("ERROR: not a valid event:", event)
+ } else {
+ cbs[event] = (*[0]byte)(fp)
+ }
+}
+
+// trigger fires a callback from C-land.
+func trigger(event string) {
+ mut.Lock()
+ defer mut.Unlock()
+ cb := cbs[event]
+ if cb != nil {
+ C._do_callback(cb)
+ } else {
+ fmt.Println("ERROR: this event does not have subscribers:", event)
+ }
+}
diff --git a/pkg/backend/mocks.go b/pkg/backend/mocks.go
new file mode 100644
index 0000000..a8ede73
--- /dev/null
+++ b/pkg/backend/mocks.go
@@ -0,0 +1,32 @@
+package backend
+
+import (
+ "log"
+ "net/http"
+)
+
+/* mock http server: easy way to mocking vpn behavior on ui interaction. This
+* should also show a good way of writing functionality tests just for the Qml
+* layer */
+
+func mockUIOn(w http.ResponseWriter, r *http.Request) {
+ log.Println("changing status: on")
+ setStatus(on)
+}
+
+func mockUIOff(w http.ResponseWriter, r *http.Request) {
+ log.Println("changing status: off")
+ setStatus(off)
+}
+
+func mockUIFailed(w http.ResponseWriter, r *http.Request) {
+ log.Println("changing status: failed")
+ setStatus(failed)
+}
+
+func mockUI() {
+ http.HandleFunc("/on", mockUIOn)
+ http.HandleFunc("/off", mockUIOff)
+ http.HandleFunc("/failed", mockUIFailed)
+ http.ListenAndServe(":8080", nil)
+}
diff --git a/pkg/backend/status.go b/pkg/backend/status.go
new file mode 100644
index 0000000..e2d31db
--- /dev/null
+++ b/pkg/backend/status.go
@@ -0,0 +1,122 @@
+package backend
+
+import (
+ "bytes"
+ "encoding/json"
+ "log"
+
+ "0xacab.org/leap/bitmask-vpn/pkg/bitmask"
+)
+
+const (
+ offStr = "off"
+ startingStr = "starting"
+ onStr = "on"
+ stoppingStr = "stopping"
+ failedStr = "failed"
+)
+
+// ctx will be our glorious global object.
+// if we ever switch again to a provider-agnostic app, we should keep a map here.
+var ctx *connectionCtx
+
+// the status type reflects the current VPN status. Go code is responsible for updating
+// it; the C gui just watches its changes and pulls its updates via the serialized
+// context object.
+
+type status int
+
+const (
+ off status = iota
+ starting
+ on
+ stopping
+ failed
+ unknown
+)
+
+func (s status) String() string {
+ return [...]string{offStr, startingStr, onStr, stoppingStr, failedStr}[s]
+}
+
+func (s status) MarshalJSON() ([]byte, error) {
+ b := bytes.NewBufferString(`"`)
+ b.WriteString(s.String())
+ b.WriteString(`"`)
+ return b.Bytes(), nil
+}
+
+func (s status) fromString(st string) status {
+ switch st {
+ case offStr:
+ return off
+ case startingStr:
+ return starting
+ case onStr:
+ return on
+ case stoppingStr:
+ return stopping
+ case failedStr:
+ return failed
+ default:
+ return unknown
+ }
+}
+
+// The connectionCtx keeps the global state that is passed around to C-land. It
+// also serves as the primary way of passing requests from the frontend to the
+// Go-core, by letting the UI write some of these variables and processing
+// them.
+
+type connectionCtx struct {
+ AppName string `json:"appName"`
+ Provider string `json:"provider"`
+ Donate bool `json:"donate"`
+ Status status `json:"status"`
+ bm bitmask.Bitmask
+}
+
+func (c connectionCtx) toJson() ([]byte, error) {
+ stmut.Lock()
+ defer stmut.Unlock()
+ b, err := json.Marshal(c)
+ if err != nil {
+ log.Println(err)
+ return nil, err
+ }
+ return b, nil
+}
+
+func (c connectionCtx) updateStatus() {
+ if stStr, err := c.bm.GetStatus(); err != nil {
+ log.Printf("Error getting status: %v", err)
+ } else {
+ setStatusFromStr(stStr)
+ }
+
+ statusCh := c.bm.GetStatusCh()
+ for {
+ select {
+ case stStr := <-statusCh:
+ setStatusFromStr(stStr)
+ }
+ }
+}
+
+func setStatus(st status) {
+ stmut.Lock()
+ defer stmut.Unlock()
+ ctx.Status = st
+ go trigger(OnStatusChanged)
+}
+
+func toggleDonate() {
+ stmut.Lock()
+ defer stmut.Unlock()
+ ctx.Donate = !ctx.Donate
+ go trigger(OnStatusChanged)
+}
+
+func setStatusFromStr(stStr string) {
+ setStatus(unknown.fromString(stStr))
+}