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
187
188
189
190
191
192
193
194
195
196
197
198
|
package client
import (
"fmt"
"github.com/eduvpn/eduvpn-common/i18nerr"
"github.com/eduvpn/eduvpn-common/internal/fsm"
"github.com/eduvpn/eduvpn-common/internal/log"
)
type (
// FSMStateID is an alias to the fsm state ID type
FSMStateID = fsm.StateID
// FSMStates is an alias to the fsm states type
FSMStates = fsm.States
// FSMState is an alias to the fsm state type
FSMState = fsm.State
// FSMTransition is an alias to the fsm transition type
FSMTransition = fsm.Transition
)
const (
// StateDeregistered is the state where we are deregistered
StateDeregistered FSMStateID = iota
// StateMain is the main state
StateMain
// StateAddingServer is the state where a server is being added
StateAddingServer
// StateOAuthStarted means the state where the OAuth procedure is triggered
StateOAuthStarted
// StateGettingConfig is the state a VPN config is being obtained
StateGettingConfig
// StateAskLocation is the state where a secure internet location is being asked
StateAskLocation
// StateAskProfile is the state where a profile is being asked for
StateAskProfile
// StateGotConfig is the state where a config is obtained
StateGotConfig
// StateConnecting is the state where the VPN is connecting
StateConnecting
// StateConnected is the state where the VPN is connected
StateConnected
// StateDisconnecting is the state where the VPN is disconnecting
StateDisconnecting
// StateDisconnected is the state where the VPN is disconnected
StateDisconnected
)
// GetStateName gets the State name for state `s`
func GetStateName(s FSMStateID) string {
switch s {
case StateDeregistered:
return "Deregistered"
case StateMain:
return "Main"
case StateAddingServer:
return "AddingServer"
case StateOAuthStarted:
return "OAuthStarted"
case StateGettingConfig:
return "GettingConfig"
case StateAskLocation:
return "AskLocation"
case StateAskProfile:
return "AskProfile"
case StateGotConfig:
return "GotConfig"
case StateConnecting:
return "Connecting"
case StateConnected:
return "Connected"
case StateDisconnecting:
return "Disconnecting"
case StateDisconnected:
return "Disconnected"
default:
panic(fmt.Sprintf("unknown conversion of state: %d to string", s))
}
}
func newFSM(
callback func(FSMStateID, FSMStateID, interface{}) bool,
directory string,
) fsm.FSM {
states := FSMStates{
StateDeregistered: FSMState{
Transitions: []FSMTransition{
{To: StateMain, Description: "Register"},
},
},
StateMain: FSMState{
Transitions: []FSMTransition{
{To: StateDeregistered, Description: "Deregister"},
{To: StateAddingServer, Description: "Add a server"},
{To: StateGettingConfig, Description: "Get a VPN config"},
{To: StateConnected, Description: "Already connected"},
},
},
StateAddingServer: FSMState{
Transitions: []FSMTransition{
{To: StateOAuthStarted, Description: "Authorize"},
},
},
StateOAuthStarted: FSMState{
Transitions: []FSMTransition{
{To: StateMain, Description: "Authorized"},
{To: StateDisconnected, Description: "Cancel, was disconnected"},
},
},
StateGettingConfig: FSMState{
Transitions: []FSMTransition{
{To: StateAskLocation, Description: "Invalid location"},
{To: StateAskProfile, Description: "Invalid or no profile"},
{To: StateDisconnected, Description: "Go back to disconnected"},
{To: StateGotConfig, Description: "Successfully got a configuration"},
{To: StateOAuthStarted, Description: "Authorize"},
},
},
StateAskLocation: FSMState{
Transitions: []FSMTransition{
{To: StateGettingConfig, Description: "Location chosen"},
},
},
StateAskProfile: FSMState{
Transitions: []FSMTransition{
{To: StateGettingConfig, Description: "Profile chosen"},
},
},
StateGotConfig: FSMState{
Transitions: []FSMTransition{
{To: StateGettingConfig, Description: "Get a VPN config again"},
{To: StateConnecting, Description: "VPN is connecting"},
},
},
StateConnecting: FSMState{
Transitions: []FSMTransition{
{To: StateConnected, Description: "VPN is connected"},
{To: StateDisconnecting, Description: "Cancel connecting"},
},
},
StateConnected: FSMState{
Transitions: []FSMTransition{
{To: StateDisconnecting, Description: "VPN is disconnecting"},
},
},
StateDisconnecting: FSMState{
Transitions: []FSMTransition{
{To: StateDisconnected, Description: "VPN is disconnected"},
{To: StateConnected, Description: "Cancel disconnecting"},
},
},
StateDisconnected: FSMState{
Transitions: []FSMTransition{
{To: StateConnecting, Description: "Connect with existing config"},
{To: StateGettingConfig, Description: "Connect with a new config"},
{To: StateOAuthStarted, Description: "Renew"},
},
},
}
returnedFSM := fsm.FSM{}
returnedFSM.Init(StateMain, states, callback, directory, GetStateName)
return returnedFSM
}
// SetState sets the state for the client FSM to `state`
func (c *Client) SetState(state FSMStateID) error {
c.mu.Lock()
defer c.mu.Unlock()
curr := c.FSM.Current
_, err := c.FSM.GoTransition(state)
if err != nil {
// self-transitions are only debug errors
if c.FSM.InState(state) {
log.Logger.Debugf("attempt an invalid self-transition: %s", c.FSM.GetStateName(state))
return nil
}
return i18nerr.WrapInternalf(err, "Failed internal state transition requested by the client from: '%s' to '%s'", GetStateName(curr), GetStateName(state))
}
return nil
}
// InState returns whether or not the client is in state `state`
func (c *Client) InState(state FSMStateID) bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.FSM.InState(state)
}
|