diff options
| author | jwijenbergh <jeroenwijenbergh@protonmail.com> | 2022-06-17 14:00:40 +0200 |
|---|---|---|
| committer | jwijenbergh <jeroenwijenbergh@protonmail.com> | 2022-09-20 20:31:23 +0200 |
| commit | 7af07c596166bf93b79a9d0816b1950dde360fb9 (patch) | |
| tree | 08b5374c34d6c33b3c596ed981bfb069cca37ade /internal/fsm | |
| parent | 6dc7b64f634f6dcbeedea24c741382366a3c7b8c (diff) | |
Server: Implement function for checking renewal button visibility
Diffstat (limited to 'internal/fsm')
| -rw-r--r-- | internal/fsm/fsm.go | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/internal/fsm/fsm.go b/internal/fsm/fsm.go new file mode 100644 index 0000000..bb7f330 --- /dev/null +++ b/internal/fsm/fsm.go @@ -0,0 +1,228 @@ +package fsm + +import ( + "fmt" + "os" + "os/exec" + "path" + "sort" + "github.com/jwijenbergh/eduvpn-common/internal/log" +) + +type ( + FSMStateID int8 + FSMStateIDSlice []FSMStateID +) + +func (v FSMStateIDSlice) Len() int { + return len(v) +} + +func (v FSMStateIDSlice) Less(i, j int) bool { + return v[i] < v[j] +} + +func (v FSMStateIDSlice) Swap(i, j int) { + v[i], v[j] = v[j], v[i] +} + +const ( + // Deregistered means the app is not registered with the wrapper + DEREGISTERED FSMStateID = iota + + // No Server means the user has not chosen a server yet + NO_SERVER + + // Chosen Server means the user has chosen a server to connect to + CHOSEN_SERVER + + // OAuth Started means the OAuth process has started + OAUTH_STARTED + + // Authorized means the OAuth process has finished and the user is now authorized with the server + AUTHORIZED + + // Requested config means the user has requested a config for connecting + REQUEST_CONFIG + + // Has config means the user has gotten a config + HAS_CONFIG + + // Ask profile means the go code is asking for a profile selection from the ui + ASK_PROFILE + + // Connected means the user has been connected to the server + CONNECTED +) + +func (s FSMStateID) String() string { + switch s { + case DEREGISTERED: + return "Deregistered" + case NO_SERVER: + return "No_Server" + case CHOSEN_SERVER: + return "Chosen_Server" + case OAUTH_STARTED: + return "OAuth_Started" + case HAS_CONFIG: + return "Has_Config" + case REQUEST_CONFIG: + return "Request_Config" + case ASK_PROFILE: + return "Ask_Profile" + case AUTHORIZED: + return "Authorized" + case CONNECTED: + return "Connected" + default: + panic("unknown conversion of state to string") + } +} + +type FSMTransition struct { + To FSMStateID + Description string +} + +type ( + FSMTransitions []FSMTransition + FSMStates map[FSMStateID]FSMTransitions +) + +type FSM struct { + States FSMStates + Current FSMStateID + + // Info to be passed from the parent state + Name string + StateCallback func(string, string, string) + Logger *log.FileLogger + Directory string + Debug bool +} + +func (fsm *FSM) Init(name string, callback func(string, string, string), logger *log.FileLogger, directory string, debug bool) { + fsm.States = FSMStates{ + DEREGISTERED: {{NO_SERVER, "Client registers"}}, + NO_SERVER: {{CHOSEN_SERVER, "User chooses a server"}}, + CHOSEN_SERVER: {{AUTHORIZED, "Found tokens in config"}, {OAUTH_STARTED, "No tokens found in config"}}, + OAUTH_STARTED: {{AUTHORIZED, "User authorizes with browser"}, {NO_SERVER, "Cancel or Error"}}, + AUTHORIZED: {{OAUTH_STARTED, "Re-authorize with OAuth"}, {REQUEST_CONFIG, "Client requests a config"}}, + REQUEST_CONFIG: {{ASK_PROFILE, "Multiple profiles found and no profile chosen"}, {HAS_CONFIG, "Only one profile or profile already chosen"}, {NO_SERVER, "Cancel or Error"}, {OAUTH_STARTED, "Re-authorize"}}, + ASK_PROFILE: {{HAS_CONFIG, "User chooses profile"}, {NO_SERVER, "Done but no profile selected"}}, + HAS_CONFIG: {{CONNECTED, "OS reports connected"}, {REQUEST_CONFIG, "User chooses a new profile"}, {NO_SERVER, "User wants to choose a new server"}}, + CONNECTED: {{HAS_CONFIG, "OS reports disconnected"}}, + } + fsm.Current = DEREGISTERED + fsm.Name = name + fsm.StateCallback = callback + fsm.Logger = logger + fsm.Directory = directory + fsm.Debug = debug +} + +func (fsm *FSM) InState(check FSMStateID) bool { + return check == fsm.Current +} + +func (fsm *FSM) HasTransition(check FSMStateID) bool { + for _, transition_state := range fsm.States[fsm.Current] { + if transition_state.To == check { + return true + } + } + + return false +} + +func (fsm *FSM) getGraphFilename(extension string) string { + debugPath := path.Join(fsm.Directory, fsm.Name) + return fmt.Sprintf("%s%s", debugPath, extension) +} + +func (fsm *FSM) writeGraph() { + graph := fsm.GenerateGraph() + graphFile := fsm.getGraphFilename(".graph") + graphImgFile := fsm.getGraphFilename(".png") + f, err := os.Create(graphFile) + if err != nil { + fsm.Logger.Log(log.LOG_INFO, fmt.Sprintf("Failed to write debug fsm graph with error %v", err)) + return + } + + f.WriteString(graph) + f.Close() + cmd := exec.Command("mmdc", "-i", graphFile, "-o", graphImgFile) + + cmd.Start() +} + +func (fsm *FSM) GoTransitionWithData(newState FSMStateID, data string, background bool) bool { + ok := fsm.HasTransition(newState) + + if ok { + oldState := fsm.Current + fsm.Current = newState + if fsm.Debug { + fsm.writeGraph() + } + + fsm.Logger.Log(log.LOG_INFO, fmt.Sprintf("State: %s -> State: %s with data %s\n", oldState.String(), newState.String(), data)) + + if background { + go fsm.StateCallback(oldState.String(), newState.String(), data) + } else { + fsm.StateCallback(oldState.String(), newState.String(), data) + } + } + + return ok +} + +func (fsm *FSM) GoTransition(newState FSMStateID) bool { + return fsm.GoTransitionWithData(newState, "", false) +} + +func (fsm *FSM) generateMermaidGraph() string { + graph := "graph TD\n" + sorted_fsm := make(FSMStateIDSlice, 0, len(fsm.States)) + for state_id := range fsm.States { + sorted_fsm = append(sorted_fsm, state_id) + } + sort.Sort(sorted_fsm) + for _, state := range sorted_fsm { + transitions := fsm.States[state] + for _, transition := range transitions { + if state == fsm.Current { + graph += "\nstyle " + state.String() + " fill:cyan\n" + } else { + graph += "\nstyle " + state.String() + " fill:white\n" + } + graph += state.String() + "(" + state.String() + ") " + "-->|" + transition.Description + "| " + transition.To.String() + "\n" + } + } + return graph +} + +func (fsm *FSM) GenerateGraph() string { + return fsm.generateMermaidGraph() +} + +type FSMWrongStateTransitionError struct { + Got FSMStateID + Want FSMStateID +} + +func (e *FSMWrongStateTransitionError) Error() string { + return fmt.Sprintf("wrong FSM state, got: %s, want a state with a transition to: %s", e.Got.String(), e.Want.String()) +} + +type FSMWrongStateError struct { + Got FSMStateID + Want FSMStateID +} + +func (e *FSMWrongStateError) Error() string { + return fmt.Sprintf("wrong FSM state, got: %s, want: %s", e.Got.String(), e.Want.String()) +} |
