102 lines
2.9 KiB
Go
102 lines
2.9 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package controlclient
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"tailscale.com/types/empty"
|
|
"tailscale.com/types/netmap"
|
|
"tailscale.com/types/persist"
|
|
"tailscale.com/types/structs"
|
|
)
|
|
|
|
// State is the high-level state of the client. It is used only in
|
|
// unit tests for proper sequencing, don't depend on it anywhere else.
|
|
//
|
|
// TODO(apenwarr): eliminate the state, as it's now obsolete.
|
|
//
|
|
// apenwarr: Historical note: controlclient.Auto was originally
|
|
// intended to be the state machine for the whole tailscale client, but that
|
|
// turned out to not be the right abstraction layer, and it moved to
|
|
// ipn.Backend. Since ipn.Backend now has a state machine, it would be
|
|
// much better if controlclient could be a simple stateless API. But the
|
|
// current server-side API (two interlocking polling https calls) makes that
|
|
// very hard to implement. A server side API change could untangle this and
|
|
// remove all the statefulness.
|
|
type State int
|
|
|
|
const (
|
|
StateNew = State(iota)
|
|
StateNotAuthenticated
|
|
StateAuthenticating
|
|
StateURLVisitRequired
|
|
StateAuthenticated
|
|
StateSynchronized // connected and received map update
|
|
)
|
|
|
|
func (s State) MarshalText() ([]byte, error) {
|
|
return []byte(s.String()), nil
|
|
}
|
|
|
|
func (s State) String() string {
|
|
switch s {
|
|
case StateNew:
|
|
return "state:new"
|
|
case StateNotAuthenticated:
|
|
return "state:not-authenticated"
|
|
case StateAuthenticating:
|
|
return "state:authenticating"
|
|
case StateURLVisitRequired:
|
|
return "state:url-visit-required"
|
|
case StateAuthenticated:
|
|
return "state:authenticated"
|
|
case StateSynchronized:
|
|
return "state:synchronized"
|
|
default:
|
|
return fmt.Sprintf("state:unknown:%d", int(s))
|
|
}
|
|
}
|
|
|
|
type Status struct {
|
|
_ structs.Incomparable
|
|
LoginFinished *empty.Message // nonempty when login finishes
|
|
LogoutFinished *empty.Message // nonempty when logout finishes
|
|
Err error
|
|
URL string // interactive URL to visit to finish logging in
|
|
NetMap *netmap.NetworkMap // server-pushed configuration
|
|
|
|
// The internal state should not be exposed outside this
|
|
// package, but we have some automated tests elsewhere that need to
|
|
// use them. Please don't use these fields.
|
|
// TODO(apenwarr): Unexport or remove these.
|
|
State State
|
|
Persist *persist.PersistView // locally persisted configuration
|
|
}
|
|
|
|
// Equal reports whether s and s2 are equal.
|
|
func (s *Status) Equal(s2 *Status) bool {
|
|
if s == nil && s2 == nil {
|
|
return true
|
|
}
|
|
return s != nil && s2 != nil &&
|
|
(s.LoginFinished == nil) == (s2.LoginFinished == nil) &&
|
|
(s.LogoutFinished == nil) == (s2.LogoutFinished == nil) &&
|
|
s.Err == s2.Err &&
|
|
s.URL == s2.URL &&
|
|
reflect.DeepEqual(s.Persist, s2.Persist) &&
|
|
reflect.DeepEqual(s.NetMap, s2.NetMap) &&
|
|
s.State == s2.State
|
|
}
|
|
|
|
func (s Status) String() string {
|
|
b, err := json.MarshalIndent(s, "", "\t")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return s.State.String() + " " + string(b)
|
|
}
|