summaryrefslogtreecommitdiff
path: root/pkg/backend/status.go
blob: f6b195ce3d64c0f9b1cc55c55c4d7aad263ed22b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package backend

import (
	"bytes"
	"encoding/json"
	"log"
	"sync"

	"0xacab.org/leap/bitmask-vpn/pkg/bitmask"
	"0xacab.org/leap/bitmask-vpn/pkg/config"
	"0xacab.org/leap/bitmask-vpn/pkg/snowflake"
)

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

// these mutexes protect setting and updating the global status in this go backend
var statusMutex sync.Mutex
var updateMutex sync.Mutex

// 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"`
	TosURL            string              `json:"tosURL"`
	HelpURL           string              `json:"helpURL"`
	AskForDonations   bool                `json:"askForDonations"`
	DonateDialog      bool                `json:"donateDialog"`
	DonateURL         string              `json:"donateURL"`
	LoginDialog       bool                `json:"loginDialog"`
	LoginOk           bool                `json:"loginOk"`
	Version           string              `json:"version"`
	Errors            string              `json:"errors"`
	Status            status              `json:"status"`
	Locations         map[string]float64  `json:"locations"`
	LocationLabels    map[string][]string `json:"locationLabels"`
	CurrentGateway    string              `json:"currentGateway"`
	CurrentLocation   string              `json:"currentLocation"`
	CurrentCountry    string              `json:"currentCountry"`
	BestLocation      string              `json:"bestLocation"`
	Transport         string              `json:"transport"`
	UseUDP            bool                `json:"udp"`
	OffersUDP         bool                `json:"offersUdp"`
	ManualLocation    bool                `json:"manualLocation"`
	IsReady           bool                `json:"isReady"`
	CanUpgrade        bool                `json:"canUpgrade"`
	Motd              string              `json:"motd"`
	HasTor            bool                `json:"hasTor"`
	UseSnowflake      bool                `json:"snowflake"`
	SnowflakeProgress int                 `json:"snowflakeProgress"`
	SnowflakeTag      string              `json:"snowflakeTag"`
	bm                bitmask.Bitmask
	autostart         bitmask.Autostart
	cfg               *config.Config
}

func (c *connectionCtx) toJson() ([]byte, error) {
	statusMutex.Lock()
	if c.bm != nil {
		transport := c.bm.GetTransport()
		c.Locations = c.bm.ListLocationFullness(transport)
		c.LocationLabels = c.bm.ListLocationLabels(transport)
		c.CurrentGateway = c.bm.GetCurrentGateway()
		c.CurrentLocation = c.bm.GetCurrentLocation()
		c.CurrentCountry = c.bm.GetCurrentCountry()
		c.BestLocation = c.bm.GetBestLocation(transport)
		c.Transport = transport
		c.UseUDP = c.cfg.UDP // TODO initialize bitmask param?
		c.OffersUDP = c.bm.OffersUDP()
		c.UseSnowflake = c.cfg.Snowflake // TODO initialize bitmask
		c.ManualLocation = c.bm.IsManualLocation()
		c.CanUpgrade = c.bm.CanUpgrade()
		c.Motd = c.bm.GetMotd()
		c.HasTor = snowflake.HasTor()
	}
	defer statusMutex.Unlock()
	b, err := json.Marshal(c)
	if err != nil {
		log.Println(err)
		return nil, err
	}
	return b, nil
}

func (c connectionCtx) updateStatus() {
	updateMutex.Lock()
	defer updateMutex.Unlock()
	if stStr, err := c.bm.GetStatus(); err != nil {
		log.Printf("Error getting status: %v", err)
	} else {
		setStatusFromStr(stStr)
	}

	go func() {
		snowflakeCh := c.bm.GetSnowflakeCh()
		for {
			select {
			case event := <-snowflakeCh:
				setSnowflakeStatus(event)
			}
		}
	}()

	statusCh := c.bm.GetStatusCh()
	for {
		select {
		case stStr := <-statusCh:
			setStatusFromStr(stStr)
		}
	}
}

func setSnowflakeStatus(event *snowflake.StatusEvent) {
	statusMutex.Lock()
	defer statusMutex.Unlock()
	ctx.SnowflakeProgress = event.Progress
	ctx.SnowflakeTag = event.Tag
	go trigger(OnStatusChanged)
}

func setStatus(st status) {
	statusMutex.Lock()
	defer statusMutex.Unlock()
	ctx.Status = st
	go trigger(OnStatusChanged)
}

// 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
	}
}

func setStatusFromStr(stStr string) {
	setStatus(unknown.fromString(stStr))
}