diff --git a/config/get.go b/config/get.go new file mode 100644 index 0000000..1210544 --- /dev/null +++ b/config/get.go @@ -0,0 +1,57 @@ +package config + +import ( + "sync" + + "github.com/tevino/abool" +) + +var ( + validityFlag = abool.NewBool(true) + validityFlagLock sync.RWMutex + + tableLock sync.RWMutex + + stringTable map[string]string + intTable map[string]int + boolTable map[string]bool +) + +func getValidityFlag() *abool.AtomicBool { + validityFlagLock.RLock() + defer validityFlagLock.RUnlock() + return validityFlag +} + +func resetValidityFlag() { + validityFlagLock.Lock() + defer validityFlagLock.Unlock() + validityFlag.SetTo(false) + validityFlag = abool.NewBool(true) +} + +// GetAsString returns a function that returns the wanted string with high performance. +func GetAsString(name string, fallback string) func() string { + valid := getValidityFlag() + value := findStringValue(name, fallback) + return func() string { + if !valid.IsSet() { + valid = getValidityFlag() + value = findStringValue(name, fallback) + } + return value + } +} + +// GetAsInt returns a function that returns the wanted int with high performance. +func GetAsInt(name string, fallback int64) func() int64 { + valid := getValidityFlag() + value := findIntValue(name, fallback) + return func() int64 { + if !valid.IsSet() { + valid = getValidityFlag() + value = findIntValue(name, fallback) + } + return value + } +} diff --git a/config/get_test.go b/config/get_test.go new file mode 100644 index 0000000..3e39e9f --- /dev/null +++ b/config/get_test.go @@ -0,0 +1,137 @@ +package config + +import ( + "testing" +) + +func TestGet(t *testing.T) { + + err := SetConfig(` + { + "monkey": "1", + "elephant": 2 + } + `) + if err != nil { + t.Fatal(err) + } + + err = SetDefaultConfig(` + { + "monkey": "0", + "snake": "0", + "elephant": 0 + } + `) + if err != nil { + t.Fatal(err) + } + + monkey := GetAsString("monkey", "none") + elephant := GetAsInt("elephant", -1) + if monkey() != "1" { + t.Fatalf("monkey should be 1, is %s", monkey()) + } + if elephant() != 2 { + t.Fatalf("elephant should be 2, is %d", elephant()) + } + + err = SetConfig(` + { + "monkey": "3" + } + `) + if err != nil { + t.Fatal(err) + } + + if monkey() != "3" { + t.Fatalf("monkey should be 0, is %s", monkey()) + } + if elephant() != 0 { + t.Fatalf("elephant should be 0, is %d", elephant()) + } + +} + +func BenchmarkGetAsStringCached(b *testing.B) { + // Setup + err := SetConfig(` + { + "monkey": "banana" + } + `) + if err != nil { + b.Fatal(err) + } + monkey := GetAsString("monkey", "no banana") + + // Reset timer for precise results + b.ResetTimer() + + // Start benchmark + for i := 0; i < b.N; i++ { + monkey() + } +} + +func BenchmarkGetAsStringRefetch(b *testing.B) { + // Setup + err := SetConfig(` + { + "monkey": "banana" + } + `) + if err != nil { + b.Fatal(err) + } + + // Reset timer for precise results + b.ResetTimer() + + // Start benchmark + for i := 0; i < b.N; i++ { + findStringValue("monkey", "no banana") + } +} + +func BenchmarkGetAsIntCached(b *testing.B) { + // Setup + err := SetConfig(` + { + "monkey": 1 + } + `) + if err != nil { + b.Fatal(err) + } + monkey := GetAsInt("monkey", -1) + + // Reset timer for precise results + b.ResetTimer() + + // Start benchmark + for i := 0; i < b.N; i++ { + monkey() + } +} + +func BenchmarkGetAsIntRefetch(b *testing.B) { + // Setup + err := SetConfig(` + { + "monkey": 1 + } + `) + if err != nil { + b.Fatal(err) + } + + // Reset timer for precise results + b.ResetTimer() + + // Start benchmark + for i := 0; i < b.N; i++ { + findIntValue("monkey", 1) + } +} diff --git a/config/layers.go b/config/layers.go new file mode 100644 index 0000000..cb84407 --- /dev/null +++ b/config/layers.go @@ -0,0 +1,82 @@ +package config + +import ( + "errors" + "sync" + + "github.com/tidwall/gjson" +) + +var ( + configLock sync.RWMutex + + userConfig = "" + defaultConfig = "" + + // ErrInvalidJSON is returned by SetConfig and SetDefaultConfig if they receive invalid json. + ErrInvalidJSON = errors.New("json string invalid") +) + +// SetConfig sets the (prioritized) user defined config. +func SetConfig(json string) error { + if !gjson.Valid(json) { + return ErrInvalidJSON + } + + configLock.Lock() + defer configLock.Unlock() + userConfig = json + resetValidityFlag() + + return nil +} + +// SetDefaultConfig sets the (fallback) default config. +func SetDefaultConfig(json string) error { + if !gjson.Valid(json) { + return ErrInvalidJSON + } + + configLock.Lock() + defer configLock.Unlock() + defaultConfig = json + resetValidityFlag() + + return nil +} + +// findValue find the correct value in the user or default config +func findValue(name string) (result gjson.Result) { + configLock.RLock() + defer configLock.RUnlock() + + result = gjson.Get(userConfig, name) + if !result.Exists() { + result = gjson.Get(defaultConfig, name) + } + return result +} + +// findStringValue validates and return the value with the given name +func findStringValue(name string, fallback string) (value string) { + result := findValue(name) + if !result.Exists() { + return fallback + } + if result.Type != gjson.String { + return fallback + } + return result.String() +} + +// findIntValue validates and return the value with the given name +func findIntValue(name string, fallback int64) (value int64) { + result := findValue(name) + if !result.Exists() { + return fallback + } + if result.Type != gjson.Number { + return fallback + } + return result.Int() +} diff --git a/config/layers_test.go b/config/layers_test.go new file mode 100644 index 0000000..90a3ec5 --- /dev/null +++ b/config/layers_test.go @@ -0,0 +1,51 @@ +package config + +import "testing" + +func TestLayers(t *testing.T) { + + err := SetConfig("{invalid json") + if err == nil { + t.Fatal("expected error") + } + + err = SetDefaultConfig("{invalid json") + if err == nil { + t.Fatal("expected error") + } + + err = SetConfig(` + { + "monkey": "banana", + "elephant": 3 + } + `) + if err != nil { + t.Fatal(err) + } + + // Test missing values + + missingString := GetAsString("missing", "fallback") + if missingString() != "fallback" { + t.Fatal("expected fallback value: fallback") + } + + missingInt := GetAsInt("missing", -1) + if missingInt() != -1 { + t.Fatal("expected fallback value: -1") + } + + // Test value mismatch + + notString := GetAsString("elephant", "fallback") + if notString() != "fallback" { + t.Fatal("expected fallback value: fallback") + } + + notInt := GetAsInt("monkey", -1) + if notInt() != -1 { + t.Fatal("expected fallback value: -1") + } + +} diff --git a/configuration/configuration.go b/configuration/configuration.go deleted file mode 100644 index 64af2cf..0000000 --- a/configuration/configuration.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file. - -package configuration - -import ( - "sync/atomic" - - "github.com/Safing/safing-core/database" - - datastore "github.com/ipfs/go-datastore" -) - -type SecurityLevelBoolean int8 - -func (slb SecurityLevelBoolean) IsSet() bool { - return int8(atomic.LoadInt32(securityLevel)) >= int8(slb) -} - -func (slb SecurityLevelBoolean) IsSetWithLevel(customSecurityLevel int8) bool { - return customSecurityLevel >= int8(slb) || int8(atomic.LoadInt32(securityLevel)) >= int8(slb) -} - -func (slb SecurityLevelBoolean) Level() int8 { - return int8(slb) -} - -type Configuration struct { - database.Base - - // Security Config - EnforceCT SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Hardfail on Certificate Transparency - EnforceRevocation SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Hardfail on Certificate Revokation - DenyInsecureTLS SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Block TLS connections, that use insecure TLS versions, cipher suites, ... - DenyTLSWithoutSNI SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Block TLS connections that do not use SNI, connections without SNI cannot be verified as well as connections with SNI. - DoNotUseAssignedDNS SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Do not use DNS Servers assigned by DHCP - DoNotUseMDNS SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Do not use mDNS - DoNotForwardSpecialDomains SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Do not resolve special domains with assigned DNS Servers - AlwaysPromptAtNewProfile SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Always prompt user to review new profiles - DenyNetworkUntilProfileApproved SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Deny network communication until a new profile is actively approved by the user - - // Generic Config - CompetenceLevel int8 `json:",omitempty bson:",omitempty"` // Select CompetenceLevel - Beta bool `json:",omitempty bson:",omitempty"` // Take part in Beta - PermanentVerdicts bool `json:",omitempty bson:",omitempty"` // As soon as work on a link is finished, leave it to the system for performance and stability - DNSServers []string `json:",omitempty bson:",omitempty"` // DNS Servers to use for name resolution. Please refer to the user guide for further help. - // regex: ^(DoH|DNS|TDNS)\|[A-Za-z0-9\.:\[\]]+(\|[A-Za-z0-9\.:]+)?$ - DNSServerRetryRate int64 `json:",omitempty bson:",omitempty"` // Amount of seconds to wait until failing DNS Servers may be retried. - CountryBlacklist []string `json:",omitempty bson:",omitempty"` // Do not connect to servers in these countries - ASBlacklist []uint32 `json:",omitempty bson:",omitempty"` // Do not connect to server in these AS - - LocalPort17Node bool `json:",omitempty bson:",omitempty"` // Serve as local Port17 Node - PublicPort17Node bool `json:",omitempty bson:",omitempty"` // Serve as public Port17 Node -} - -var ( - configurationModel *Configuration // only use this as parameter for database.EnsureModel-like functions - configurationInstanceName = "config" - defaultConfigurationInstanceName = "default" -) - -func initConfigurationModel() { - database.RegisterModel(configurationModel, func() database.Model { return new(Configuration) }) -} - -// Create saves Configuration with the provided name in the default namespace. -func (m *Configuration) Create(name string) error { - return m.CreateObject(&database.Me, name, m) -} - -// CreateInNamespace saves Configuration with the provided name in the provided namespace. -func (m *Configuration) CreateInNamespace(namespace *datastore.Key, name string) error { - return m.CreateObject(namespace, name, m) -} - -// Save saves Configuration. -func (m *Configuration) Save() error { - return m.SaveObject(m) -} - -// GetConfiguration fetches Configuration with the provided name in the default namespace. -func GetConfiguration(name string) (*Configuration, error) { - return GetConfigurationFromNamespace(&database.Me, name) -} - -// GetConfigurationFromNamespace fetches Configuration with the provided name in the provided namespace. -func GetConfigurationFromNamespace(namespace *datastore.Key, name string) (*Configuration, error) { - object, err := database.GetAndEnsureModel(namespace, name, configurationModel) - if err != nil { - return nil, err - } - model, ok := object.(*Configuration) - if !ok { - return nil, database.NewMismatchError(object, configurationModel) - } - return model, nil -} diff --git a/configuration/core.go b/configuration/core.go deleted file mode 100644 index f05b46e..0000000 --- a/configuration/core.go +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file. - -package configuration - -import ( - "fmt" - "sync" - "sync/atomic" - "time" - - "github.com/Safing/safing-core/database" - "github.com/Safing/safing-core/log" - "github.com/Safing/safing-core/modules" -) - -// think about: -// config changes validation (e.g. if on in secure mode, must be on in fortress mode) -// config switches -// small codebase -// nice api -// be static as much as possible - -const ( - SecurityLevelOff int8 = 0 - SecurityLevelDynamic int8 = 1 - SecurityLevelSecure int8 = 2 - SecurityLevelFortress int8 = 3 - - CompetenceLevelNone int8 = 0 - CompetenceLevelBasic int8 = 1 - CompetenceLevelPowerUser int8 = 2 - CompetenceLevelExpert int8 = 3 - - StatusOk int8 = 0 - StatusWarning int8 = 1 - StatusError int8 = 2 -) - -var ( - configurationModule *modules.Module - - lastChange *int64 - securityLevel *int32 - - lock sync.RWMutex - status *SystemStatus - currentConfig *Configuration -) - -func init() { - configurationModule = modules.Register("Configuration", 128) - - initDefaultConfig() - initSystemStatusModel() - initConfigurationModel() - - lastChangeValue := time.Now().Unix() - lastChange = &lastChangeValue - - var securityLevelValue int32 - securityLevel = &securityLevelValue - - var err error - config, err := GetConfiguration(configurationInstanceName) - if err != nil { - log.Warningf("configuration: could not load configuration: %s", err) - loadedConfig := defaultConfig - config = &loadedConfig - err = config.Create(configurationInstanceName) - if err != nil { - log.Warningf("configuration: could not save new configuration: %s", err) - } - } - - status, err = GetSystemStatus() - if err != nil { - log.Warningf("configuration: could not load status: %s", err) - status = &SystemStatus{ - CurrentSecurityLevel: 1, - SelectedSecurityLevel: 1, - } - err = status.Create() - if err != nil { - log.Warningf("configuration: could not save new status: %s", err) - } - } - - log.Infof("configuration: initial security level is [%s]", status.FmtSecurityLevel()) - // atomic.StoreInt32(securityLevel, int32(status.CurrentSecurityLevel)) - - updateConfig(config) - - go configChangeListener() - go statusChangeListener() -} - -func configChangeListener() { - sub := database.NewSubscription() - sub.Subscribe(fmt.Sprintf("%s/Configuration:%s", database.Me.String(), configurationInstanceName)) - for { - var receivedModel database.Model - - select { - case <-configurationModule.Stop: - configurationModule.StopComplete() - return - case receivedModel = <-sub.Updated: - case receivedModel = <-sub.Created: - } - - config, ok := database.SilentEnsureModel(receivedModel, configurationModel).(*Configuration) - if !ok { - log.Warning("configuration: received config update, but was not of type *Configuration") - continue - } - - updateConfig(config) - - } -} - -func updateConfig(update *Configuration) { - new := &Configuration{} - - if update.EnforceCT > 0 && update.EnforceCT < 4 { - new.EnforceCT = update.EnforceCT - } else { - new.EnforceCT = defaultConfig.EnforceCT - } - if update.EnforceRevocation > 0 && update.EnforceRevocation < 4 { - new.EnforceRevocation = update.EnforceRevocation - } else { - new.EnforceRevocation = defaultConfig.EnforceRevocation - } - if update.DenyInsecureTLS > 0 && update.DenyInsecureTLS < 4 { - new.DenyInsecureTLS = update.DenyInsecureTLS - } else { - new.DenyInsecureTLS = defaultConfig.DenyInsecureTLS - } - if update.DenyTLSWithoutSNI > 0 && update.DenyTLSWithoutSNI < 4 { - new.DenyTLSWithoutSNI = update.DenyTLSWithoutSNI - } else { - new.DenyTLSWithoutSNI = defaultConfig.DenyTLSWithoutSNI - } - if update.DoNotUseAssignedDNS > 0 && update.DoNotUseAssignedDNS < 4 { - new.DoNotUseAssignedDNS = update.DoNotUseAssignedDNS - } else { - new.DoNotUseAssignedDNS = defaultConfig.DoNotUseAssignedDNS - } - if update.DoNotUseMDNS > 0 && update.DoNotUseMDNS < 4 { - new.DoNotUseMDNS = update.DoNotUseMDNS - } else { - new.DoNotUseMDNS = defaultConfig.DoNotUseMDNS - } - if update.DoNotForwardSpecialDomains > 0 && update.DoNotForwardSpecialDomains < 4 { - new.DoNotForwardSpecialDomains = update.DoNotForwardSpecialDomains - } else { - new.DoNotForwardSpecialDomains = defaultConfig.DoNotForwardSpecialDomains - } - if update.AlwaysPromptAtNewProfile > 0 && update.AlwaysPromptAtNewProfile < 4 { - new.AlwaysPromptAtNewProfile = update.AlwaysPromptAtNewProfile - } else { - new.AlwaysPromptAtNewProfile = defaultConfig.AlwaysPromptAtNewProfile - } - if update.DenyNetworkUntilProfileApproved > 0 && update.DenyNetworkUntilProfileApproved < 4 { - new.DenyNetworkUntilProfileApproved = update.DenyNetworkUntilProfileApproved - } else { - new.DenyNetworkUntilProfileApproved = defaultConfig.DenyNetworkUntilProfileApproved - } - - // generic configuration - if update.CompetenceLevel >= 0 && update.CompetenceLevel <= 3 { - new.CompetenceLevel = update.CompetenceLevel - } else { - new.CompetenceLevel = 3 - // TODO: maybe notify user? - } - - if len(update.DNSServers) != 0 { - new.DNSServers = update.DNSServers - } else { - new.DNSServers = defaultConfig.DNSServers - } - - if update.DNSServerRetryRate != 0 { - new.DNSServerRetryRate = update.DNSServerRetryRate - } else { - new.DNSServerRetryRate = defaultConfig.DNSServerRetryRate - } - if len(update.CountryBlacklist) != 0 { - new.CountryBlacklist = update.CountryBlacklist - } else { - new.CountryBlacklist = defaultConfig.CountryBlacklist - } - if len(update.ASBlacklist) != 0 { - new.ASBlacklist = update.ASBlacklist - } else { - new.ASBlacklist = defaultConfig.ASBlacklist - } - - lock.Lock() - defer lock.Unlock() - - // set new config and update timestamp - currentConfig = new - atomic.StoreInt64(lastChange, time.Now().UnixNano()) - - // update status with new values - // status.CurrentSecurityLevel = currentConfig.SecurityLevel - // status.Save() - - // update atomic securityLevel - // atomic.StoreInt32(securityLevel, int32(currentConfig.SecurityLevel)) -} - -func statusChangeListener() { - sub := database.NewSubscription() - sub.Subscribe(fmt.Sprintf("%s/SystemStatus:%s", database.Me.String(), systemStatusInstanceName)) - for { - var receivedModel database.Model - - select { - case <-configurationModule.Stop: - configurationModule.StopComplete() - return - case receivedModel = <-sub.Updated: - case receivedModel = <-sub.Created: - } - - status, ok := database.SilentEnsureModel(receivedModel, systemStatusModel).(*SystemStatus) - if !ok { - log.Warning("configuration: received system status update, but was not of type *SystemStatus") - continue - } - - atomic.StoreInt32(securityLevel, int32(status.CurrentSecurityLevel)) - } -} diff --git a/configuration/core_test.go b/configuration/core_test.go deleted file mode 100644 index 8b248d2..0000000 --- a/configuration/core_test.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file. - -package configuration - -import ( - "fmt" - "testing" - "time" -) - -func TestConfiguration(t *testing.T) { - - config1 := Get() - fmt.Printf("%v", config1) - time.Sleep(1 * time.Millisecond) - config1.Changed() - time.Sleep(1 * time.Millisecond) - config1.Save() - time.Sleep(1 * time.Millisecond) - config1.Changed() - time.Sleep(1 * time.Millisecond) - -} diff --git a/configuration/defaults.go b/configuration/defaults.go deleted file mode 100644 index 0c32a67..0000000 --- a/configuration/defaults.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file. - -package configuration - -import ( - "github.com/Safing/safing-core/log" -) - -var ( - defaultConfig Configuration -) - -func initDefaultConfig() { - defaultConfig = Configuration{ - // based on security level - EnforceCT: 3, - EnforceRevocation: 3, - DenyInsecureTLS: 2, - DenyTLSWithoutSNI: 2, - DoNotUseAssignedDNS: 3, - DoNotUseMDNS: 2, - DoNotForwardSpecialDomains: 2, - AlwaysPromptAtNewProfile: 3, - DenyNetworkUntilProfileApproved: 3, - - // generic configuration - CompetenceLevel: 0, - PermanentVerdicts: true, - // Possible values: DNS, DoH (DNS over HTTPS - using Google's syntax: https://developers.google.com/speed/public-dns/docs/dns-over-https) - // DNSServers: []string{"DoH|dns.google.com:443|df:www.google.com"}, - DNSServers: []string{"DNS|1.1.1.1:53", "DNS|1.0.0.1:53", "DNS|[2606:4700:4700::1111]:53", "DNS|[2606:4700:4700::1001]:53", "DNS|8.8.8.8:53", "DNS|8.8.4.4:53", "DNS|[2001:4860:4860::8888]:53", "DNS|[2001:4860:4860::8844]:53", "DNS|208.67.222.222:53", "DNS|208.67.220.220:53"}, - // DNSServers: []string{"DNS|[2001:4860:4860::8888]:53", "DNS|[2001:4860:4860::8844]:53"}, - // DNSServers: []string{"DoH|dns.google.com:443|df:www.google.com", "DNS|8.8.8.8:53", "DNS|8.8.4.4:53", "DNS|172.30.30.1:53", "DNS|172.20.30.2:53"}, - // DNSServers: []string{"DNS|208.67.222.222:53", "DNS|208.67.220.220:53", "DNS|8.8.8.8:53", "DNS|8.8.4.4:53"}, - // Amount of seconds to wait until failing DNS Servers may be retried. - DNSServerRetryRate: 120, - // CountryBlacklist []string - // ASBlacklist []uint32 - LocalPort17Node: false, - PublicPort17Node: true, - } - err := defaultConfig.Create(defaultConfigurationInstanceName) - if err != nil { - log.Warningf("configuration: could not save default configuration: %s", err) - } -} diff --git a/configuration/interface.go b/configuration/interface.go deleted file mode 100644 index ff3a1cb..0000000 --- a/configuration/interface.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file. - -package configuration - -import ( - "sync" - "sync/atomic" -) - -type Interface struct { - *Configuration - - LastChange int64 - ConfigLock sync.RWMutex -} - -func Get() *Interface { - lock.RLock() - defer lock.RUnlock() - return &Interface{ - Configuration: currentConfig, - LastChange: atomic.LoadInt64(lastChange), - } -} - -func (lc *Interface) RLock() { - lc.ConfigLock.RLock() -} - -func (lc *Interface) RUnlock() { - lc.ConfigLock.RUnlock() -} - -func (lc *Interface) Changed() bool { - lastGlobalChange := atomic.LoadInt64(lastChange) - if lc.LastChange != lastGlobalChange { - lc.ConfigLock.Lock() - lock.RLock() - lc.Configuration = currentConfig - lc.LastChange = lastGlobalChange - lock.RUnlock() - lc.ConfigLock.Unlock() - return true - } - return false -} - -func (lc *Interface) SecurityLevel() int8 { - return int8(atomic.LoadInt32(securityLevel)) -} diff --git a/configuration/status.go b/configuration/status.go deleted file mode 100644 index 242f1fc..0000000 --- a/configuration/status.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file. - -package configuration - -import ( - "github.com/Safing/safing-core/database" - - datastore "github.com/ipfs/go-datastore" -) - -// SystemStatus saves basic information about the current system status. -type SystemStatus struct { - database.Base - CurrentSecurityLevel int8 - SelectedSecurityLevel int8 - - ThreatLevel int8 `json:",omitempty" bson:",omitempty"` - ThreatReason string `json:",omitempty" bson:",omitempty"` - - PortmasterStatus int8 `json:",omitempty" bson:",omitempty"` - PortmasterStatusMsg string `json:",omitempty" bson:",omitempty"` - - Port17Status int8 `json:",omitempty" bson:",omitempty"` - Port17StatusMsg string `json:",omitempty" bson:",omitempty"` -} - -var ( - systemStatusModel *SystemStatus // only use this as parameter for database.EnsureModel-like functions - systemStatusInstanceName = "status" -) - -func initSystemStatusModel() { - database.RegisterModel(systemStatusModel, func() database.Model { return new(SystemStatus) }) -} - -// Create saves SystemStatus with the provided name in the default namespace. -func (m *SystemStatus) Create() error { - return m.CreateObject(&database.Me, systemStatusInstanceName, m) -} - -// CreateInNamespace saves SystemStatus with the provided name in the provided namespace. -func (m *SystemStatus) CreateInNamespace(namespace *datastore.Key) error { - return m.CreateObject(namespace, systemStatusInstanceName, m) -} - -// Save saves SystemStatus. -func (m *SystemStatus) Save() error { - return m.SaveObject(m) -} - -// FmtSecurityLevel returns the current security level as a string. -func (m *SystemStatus) FmtSecurityLevel() string { - var s string - switch m.CurrentSecurityLevel { - case SecurityLevelOff: - s = "Off" - case SecurityLevelDynamic: - s = "Dynamic" - case SecurityLevelSecure: - s = "Secure" - case SecurityLevelFortress: - s = "Fortress" - } - if m.CurrentSecurityLevel != m.SelectedSecurityLevel { - s += "*" - } - return s -} - -// GetSystemStatus fetches SystemStatus with the provided name in the default namespace. -func GetSystemStatus() (*SystemStatus, error) { - return GetSystemStatusFromNamespace(&database.Me) -} - -// GetSystemStatusFromNamespace fetches SystemStatus with the provided name in the provided namespace. -func GetSystemStatusFromNamespace(namespace *datastore.Key) (*SystemStatus, error) { - object, err := database.GetAndEnsureModel(namespace, systemStatusInstanceName, systemStatusModel) - if err != nil { - return nil, err - } - model, ok := object.(*SystemStatus) - if !ok { - return nil, database.NewMismatchError(object, systemStatusModel) - } - return model, nil -}