summaryrefslogtreecommitdiff
path: root/src/fsm.go
blob: 9978fded6d80cbe2a39aaaf3f9a94a9446e7c209 (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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package eduvpn

import (
	"errors"
)

type FSMStateID int8

const (
	// Registered means the app is registered with the wrapper
	APP_REGISTERED FSMStateID = iota

	// Deregistered means the app is not registered with the wrapper
	APP_DEREGISTERED

	// We have the states where a server is chosen or not
	// When no server is chosen, we have no substate
	CONFIG_NOSERVER

	// When a server is chosen we have the remaining states as substates
	CONFIG_CHOSENSERVER

	// The states for when the server is authenticated
	// The SERVER_AUTHENTICATED is the parent state
	// While SERVER_CONNECTED and SERVER_DISCONNECTED are substatse
	SERVER_AUTHENTICATED
	SERVER_CONNECTED
	SERVER_DISCONNECTED

	// The states for when the server is not authenticated
	// The SERVER_NOT_AUTHENTICATED is the parent state
	// While SERVER_INITIALIZED, SERVER_OAUTH_STARTED and SERVER_OAUTH_FINISHED are substates
	SERVER_NOT_AUTHENTICATED
	SERVER_INITIALIZED
	SERVER_OAUTH_STARTED
	SERVER_OAUTH_FINISHED
)

func (s FSMStateID) String() string {
	switch s {
	case APP_REGISTERED:
		return "APP_REGISTERED"
	case APP_DEREGISTERED:
		return "APP_DEREGISTERED"
	case CONFIG_NOSERVER:
		return "CONFIG_NOSERVER"
	case CONFIG_CHOSENSERVER:
		return "CONFIG_CHOSENSERVER"
	case SERVER_AUTHENTICATED:
		return "SERVER_AUTHENTICATED"
	case SERVER_CONNECTED:
		return "SERVER_CONNECTED"
	case SERVER_DISCONNECTED:
		return "SERVER_DISCONNECTED"
	case SERVER_NOT_AUTHENTICATED:
		return "SERVER_NOT_AUTHENTICATED"
	case SERVER_INITIALIZED:
		return "SERVER_INITIALIZED"
	case SERVER_OAUTH_STARTED:
		return "SERVER_OAUTH_STARTED"
	case SERVER_OAUTH_FINISHED:
		return "SERVER_OAUTH_FINISHED"
	default:
		panic("unknown conversion of state to string")
	}
}

type (
	FSMStates      map[FSMStateID]*FSMState
	FSMTransitions []FSMStateID
)

type FSMState struct {
	Sub        *FSM
	Transition FSMTransitions

	// When Locked=True it cannot go to the parent state and transition away
	Locked bool
}

type FSM struct {
	States  FSMStates
	Current FSMStateID
}

func (fsmState *FSMState) hasTransition(check FSMStateID) bool {
	for _, state := range fsmState.Transition {
		if state == check {
			return true
		}
	}
	return false
}

func (eduvpn *VPNState) getCurrentState() (*FSMState, error) {
	state, hasState := eduvpn.FSM.States[eduvpn.FSM.Current]

	if !hasState {
		return nil, errors.New("Cannot get current state")
	}

	return state, nil
}

func FindFSMState(state FSMStateID, fsm *FSM) *FSM {
	if fsm == nil {
		return nil
	}

	// Check if the state is in the current fsm
	retrievedState, hasState := fsm.States[state]

	// Otherwise we need to go to the sub states
	if !hasState || retrievedState == nil {
		return FindFSMState(state, fsm.States[fsm.Current].Sub)
	} else {
		return fsm
	}
}

func (eduvpn *VPNState) IsInFSMState(check FSMStateID) bool {
	return eduvpn.FSM.Current == check
}

func (eduvpn *VPNState) findTransition(check FSMStateID) (*FSM, bool) {
	fsm := FindFSMState(check, eduvpn.FSM)

	if fsm == nil {
		return nil, false
	}

	subStates := fsm.States[fsm.Current].Sub

	if subStates != nil {
		if subStates.States[subStates.Current].Locked {
			return nil, false
		}
	}

	for _, val := range fsm.States[fsm.Current].Transition {
		if val == check {
			return fsm, true
		}
	}

	return nil, false
}

func (eduvpn *VPNState) HasTransition(check FSMStateID) bool {
	fsm, ok := eduvpn.findTransition(check)

	return ok && fsm != nil
}

func (eduvpn *VPNState) GoTransition(newState FSMStateID, data string) bool {
	fsm, ok := eduvpn.findTransition(newState)

	if ok {
		oldState := fsm.Current
		fsm.Current = newState
		eduvpn.StateCallback(oldState.String(), newState.String(), data)
	}

	return ok
}

func (eduvpn *VPNState) InitializeFSM() {
	// The states when a server is authenticated
	serverAuthenticated := &FSMState{Sub: &FSM{States: FSMStates{
		SERVER_DISCONNECTED: {Transition: FSMTransitions{SERVER_CONNECTED}},
		SERVER_CONNECTED:    {Transition: FSMTransitions{SERVER_DISCONNECTED}},
	}, Current: SERVER_DISCONNECTED}, Transition: FSMTransitions{SERVER_NOT_AUTHENTICATED}}

	// The states when a server is not authenticated
	serverNotAuthenticated := &FSMState{Sub: &FSM{States: FSMStates{
		// In this state we cannot exit to the parent state
		// As the parent state can go to authenticated
		SERVER_INITIALIZED: {Transition: FSMTransitions{SERVER_OAUTH_STARTED}, Locked: true},

		// The state that indicates oauth is in progress
		SERVER_OAUTH_STARTED:  {Transition: FSMTransitions{SERVER_OAUTH_FINISHED}, Locked: true},
		SERVER_OAUTH_FINISHED: {Transition: FSMTransitions{SERVER_OAUTH_STARTED}},
	}, Current: SERVER_INITIALIZED}, Transition: FSMTransitions{SERVER_AUTHENTICATED}}

	// The states of the server, it has authenticated and not authenticated ass sub states
	serverStates := &FSMState{Sub: &FSM{States: FSMStates{
		SERVER_AUTHENTICATED:     serverAuthenticated,
		SERVER_NOT_AUTHENTICATED: serverNotAuthenticated,
	}, Current: SERVER_NOT_AUTHENTICATED}, Transition: FSMTransitions{CONFIG_NOSERVER}}

	// The state when a server is registered
	registeredState := &FSMState{Sub: &FSM{States: FSMStates{
		// When no server has been chosen, we have no sub states
		CONFIG_NOSERVER: {Transition: FSMTransitions{CONFIG_CHOSENSERVER}},
		// A server has been chosen, it has substates such as oauth, connected and disconnected
		CONFIG_CHOSENSERVER: serverStates,
	}, Current: CONFIG_NOSERVER}, Transition: FSMTransitions{APP_DEREGISTERED}}

	deregisteredState := &FSMState{Transition: FSMTransitions{APP_REGISTERED}}

	eduvpn.FSM = &FSM{
		States: FSMStates{
			APP_REGISTERED: registeredState, APP_DEREGISTERED: deregisteredState,
		}, Current: APP_DEREGISTERED,
	}
}